Global config dialog to assign icons to Sport / SubSport (#4695)
* New dialog Options > Data Fields > Icons * New IconManager to centrally assign icons (svg only) to Sport / SubSport * Removed all material symbols due to license incompatibility * Using IconManager in PlanningCalendarWindow and ManualActivityWizard
@@ -30,6 +30,7 @@
|
||||
#include "ManualActivityWizard.h"
|
||||
#include "RepeatScheduleWizard.h"
|
||||
#include "WorkoutFilter.h"
|
||||
#include "IconManager.h"
|
||||
|
||||
#define HLO "<h4>"
|
||||
#define HLC "</h4>"
|
||||
@@ -490,21 +491,7 @@ PlanningCalendarWindow::getActivities
|
||||
activity.secondaryMetric = "";
|
||||
}
|
||||
|
||||
if (sport == "Bike") {
|
||||
activity.iconFile = ":images/material/bike.svg";
|
||||
} else if (sport == "Run") {
|
||||
activity.iconFile = ":images/material/run.svg";
|
||||
} else if (sport == "Swim") {
|
||||
activity.iconFile = ":images/material/swim.svg";
|
||||
} else if (sport == "Row") {
|
||||
activity.iconFile = ":images/material/rowing.svg";
|
||||
} else if (sport == "Ski") {
|
||||
activity.iconFile = ":images/material/ski.svg";
|
||||
} else if (sport == "Gym") {
|
||||
activity.iconFile = ":images/material/weight-lifter.svg";
|
||||
} else {
|
||||
activity.iconFile = ":images/breeze/games-highscores.svg";
|
||||
}
|
||||
activity.iconFile = IconManager::instance().getFilepath(rideItem);
|
||||
if (rideItem->color.alpha() < 255 || rideItem->planned) {
|
||||
activity.color = QColor("#F79130");
|
||||
} else {
|
||||
|
||||
315
src/Gui/IconManager.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "IconManager.h"
|
||||
|
||||
#include <QSvgRenderer>
|
||||
|
||||
#include "../qzip/zipwriter.h"
|
||||
#include "../qzip/zipreader.h"
|
||||
|
||||
|
||||
IconManager&
|
||||
IconManager::instance
|
||||
()
|
||||
{
|
||||
static IconManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
IconManager::IconManager
|
||||
()
|
||||
{
|
||||
baseDir.mkpath(".");
|
||||
loadMapping();
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
IconManager::getFilepath
|
||||
(const QString &sport, const QString &subSport) const
|
||||
{
|
||||
QString ret;
|
||||
if ( ! sport.isEmpty()
|
||||
&& icons.contains("Sport")
|
||||
&& ! icons["Sport"].value(sport, "").isEmpty()) {
|
||||
ret = baseDir.absoluteFilePath(icons["Sport"].value(sport, ""));
|
||||
}
|
||||
if ( ! subSport.isEmpty()
|
||||
&& icons.contains("SubSport")
|
||||
&& ! icons["SubSport"].value(subSport, "").isEmpty()) {
|
||||
ret = baseDir.absoluteFilePath(icons["SubSport"].value(subSport, ""));
|
||||
}
|
||||
QFileInfo fileInfo(ret);
|
||||
if (! fileInfo.isFile() || ! fileInfo.isReadable()) {
|
||||
ret = defaultIcon;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
IconManager::getFilepath
|
||||
(RideItem const * const rideItem) const
|
||||
{
|
||||
if (rideItem != nullptr) {
|
||||
return getFilepath(rideItem->sport, rideItem->getText("SubSport", ""));
|
||||
}
|
||||
return defaultIcon;
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
IconManager::getDefault
|
||||
() const
|
||||
{
|
||||
return defaultIcon;
|
||||
}
|
||||
|
||||
|
||||
QStringList
|
||||
IconManager::listIconFiles
|
||||
() const
|
||||
{
|
||||
QStringList nameFilter { "*.svg" };
|
||||
return baseDir.entryList(nameFilter, QDir::Files | QDir::Readable);
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
IconManager::toFilepath
|
||||
(const QString &filename)
|
||||
{
|
||||
return baseDir.absoluteFilePath(filename);
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
IconManager::assignedIcon
|
||||
(const QString &field, const QString &value) const
|
||||
{
|
||||
if (icons.contains(field)) {
|
||||
return icons[field].value(value, "");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IconManager::assignIcon
|
||||
(const QString &field, const QString &value, const QString &filename)
|
||||
{
|
||||
if (! filename.isEmpty()) {
|
||||
icons[field][value] = filename;
|
||||
} else if (icons.contains(field)) {
|
||||
icons[field].remove(value);
|
||||
}
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconManager::addIconFile
|
||||
(const QFile &sourceFile)
|
||||
{
|
||||
QFileInfo fileInfo(sourceFile);
|
||||
if (fileInfo.suffix() != "svg") {
|
||||
return false;
|
||||
}
|
||||
QSvgRenderer renderer(sourceFile.fileName());
|
||||
if (! renderer.isValid()) {
|
||||
return false;
|
||||
}
|
||||
return QFile::copy(fileInfo.absoluteFilePath(), baseDir.absoluteFilePath(fileInfo.fileName()));
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconManager::deleteIconFile
|
||||
(const QString &filename)
|
||||
{
|
||||
QString filepath = toFilepath(filename);
|
||||
QFileInfo fileInfo(filepath);
|
||||
if (fileInfo.suffix() != "svg") {
|
||||
return false;
|
||||
}
|
||||
if (QFile::remove(filepath)) {
|
||||
bool save = false;
|
||||
for (QString &field : icons.keys()) {
|
||||
for (QString &value : icons[field].keys()) {
|
||||
if (icons[field].value(value, "") == filename) {
|
||||
save = true;
|
||||
icons[field].remove(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (save) {
|
||||
saveConfig();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconManager::exportBundle
|
||||
(const QString &filename)
|
||||
{
|
||||
QFile zipFile(filename);
|
||||
if (! zipFile.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
zipFile.close();
|
||||
ZipWriter writer(zipFile.fileName());
|
||||
QStringList files = listIconFiles();
|
||||
files << "LICENSE"
|
||||
<< "LICENSE.md"
|
||||
<< "LICENSE.txt"
|
||||
<< "README"
|
||||
<< "README.md"
|
||||
<< "README.txt"
|
||||
<< "COPYING"
|
||||
<< "mapping.json";
|
||||
for (QString file : files) {
|
||||
QFile sourceFile(toFilepath(file));
|
||||
if (sourceFile.open(QIODevice::ReadOnly)) {
|
||||
writer.addFile(file, sourceFile.readAll());
|
||||
sourceFile.close();
|
||||
}
|
||||
}
|
||||
writer.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconManager::importBundle
|
||||
(const QString &filename)
|
||||
{
|
||||
QFile zipFile(filename);
|
||||
if (! zipFile.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
zipFile.close();
|
||||
ZipReader reader(zipFile.fileName());
|
||||
for (ZipReader::FileInfo info : reader.fileInfoList()) {
|
||||
if (info.isFile) {
|
||||
QByteArray data = reader.fileData(info.filePath);
|
||||
QFile file(baseDir.absoluteFilePath(info.filePath));
|
||||
QFileInfo fileInfo(file);
|
||||
if (! file.open(QIODevice::WriteOnly)) {
|
||||
continue;
|
||||
}
|
||||
qint64 bytesWritten = file.write(data);
|
||||
if (bytesWritten == -1) {
|
||||
continue;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
return loadMapping();
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconManager::saveConfig
|
||||
() const
|
||||
{
|
||||
QJsonObject rootObj;
|
||||
for (const QString &field : icons.keys()) {
|
||||
writeGroup(rootObj, field, icons[field]);
|
||||
}
|
||||
|
||||
QJsonDocument jsonDoc(rootObj);
|
||||
|
||||
QFile file(baseDir.absoluteFilePath(configFile));
|
||||
if (! file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
qWarning() << "Could not open file for writing:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(jsonDoc.toJson(QJsonDocument::Indented));
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconManager::loadMapping
|
||||
()
|
||||
{
|
||||
QFile file(baseDir.absoluteFilePath(configFile));
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QByteArray jsonData = file.readAll();
|
||||
file.close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
if ( ! jsonDoc.isNull()
|
||||
&& jsonDoc.isObject()) {
|
||||
QJsonObject root = jsonDoc.object();
|
||||
icons["Sport"] = readGroup(root, "Sport");
|
||||
icons["SubSport"] = readGroup(root, "SubSport");
|
||||
}
|
||||
} else {
|
||||
qWarning().noquote().nospace()
|
||||
<< "Cannot read icon mappings ("
|
||||
<< file.fileName()
|
||||
<< "): "
|
||||
<< file.errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IconManager::writeGroup
|
||||
(QJsonObject &rootObj, const QString &group, const QHash<QString, QString> &data) const
|
||||
{
|
||||
QJsonObject groupObj;
|
||||
if (data.size() > 0) {
|
||||
for (auto it = data.constBegin(); it != data.constEnd(); ++it) {
|
||||
groupObj.insert(it.key(), it.value());
|
||||
}
|
||||
rootObj.insert(group, groupObj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QHash<QString, QString>
|
||||
IconManager::readGroup
|
||||
(const QJsonObject &root, const QString &group)
|
||||
{
|
||||
QHash<QString, QString> result;
|
||||
if (root.contains(group)) {
|
||||
QJsonObject groupObj = root.value(group).toObject();
|
||||
for (auto it = groupObj.constBegin(); it != groupObj.constEnd(); ++it) {
|
||||
QString filename = it.value().toString();
|
||||
if (filename.endsWith(".svg") && QFile::exists(toFilepath(filename))) {
|
||||
result.insert(it.key(), filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
73
src/Gui/IconManager.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _GC_IconManager_h
|
||||
#define _GC_IconManager_h
|
||||
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
#include <QDir>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "RideItem.h"
|
||||
|
||||
|
||||
class IconManager {
|
||||
public:
|
||||
static IconManager &instance();
|
||||
|
||||
IconManager(const IconManager&) = delete;
|
||||
IconManager &operator=(const IconManager&) = delete;
|
||||
|
||||
// Full path
|
||||
QString getFilepath(const QString &sport, const QString &subSport = "") const;
|
||||
QString getFilepath(RideItem const * const rideItem) const;
|
||||
QString getDefault() const;
|
||||
|
||||
// Filename only
|
||||
QStringList listIconFiles() const;
|
||||
QString toFilepath(const QString &filename);
|
||||
|
||||
QString assignedIcon(const QString &field, const QString &value) const;
|
||||
void assignIcon(const QString &field, const QString &value, const QString &filename);
|
||||
|
||||
bool addIconFile(const QFile &sourceFile);
|
||||
bool deleteIconFile(const QString &filename);
|
||||
|
||||
bool exportBundle(const QString &filepath);
|
||||
bool importBundle(const QString &filepath);
|
||||
|
||||
bool saveConfig() const;
|
||||
|
||||
private:
|
||||
IconManager();
|
||||
|
||||
const QString defaultIcon = ":images/breeze/games-highscores.svg";
|
||||
QHash<QString, QHash<QString, QString>> icons; // Key 1: Fieldname (Sport, SubSport, ...)
|
||||
// Key 2: Value (Bike, Run, ...)
|
||||
// Value: Icon filename (no path)
|
||||
|
||||
const QDir baseDir = QDir(gcroot + "/.icons");
|
||||
const QString configFile = "mapping.json";
|
||||
|
||||
bool loadMapping();
|
||||
void writeGroup(QJsonObject &rootObj, const QString &field, const QHash<QString, QString> &data) const;
|
||||
QHash<QString, QString> readGroup(const QJsonObject &rootObj, const QString &field);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "RideMetadata.h"
|
||||
#include "Units.h"
|
||||
#include "HelpWhatsThis.h"
|
||||
#include "IconManager.h"
|
||||
|
||||
#define MANDATORY " *"
|
||||
#define TRADEMARK "<sup>TM</sup>"
|
||||
@@ -81,7 +82,7 @@ ManualActivityWizard::ManualActivityWizard
|
||||
#else
|
||||
setWizardStyle(QWizard::ModernStyle);
|
||||
#endif
|
||||
setPixmap(ICON_TYPE, svgAsColoredPixmap(":images/breeze/games-highscores.svg", QSize(ICON_SIZE * dpiXFactor, ICON_SIZE * dpiYFactor), ICON_MARGIN * dpiXFactor, ICON_COLOR));
|
||||
setPixmap(ICON_TYPE, svgAsColoredPixmap(IconManager::instance().getDefault(), QSize(ICON_SIZE * dpiXFactor, ICON_SIZE * dpiYFactor), ICON_MARGIN * dpiXFactor, ICON_COLOR));
|
||||
|
||||
setPage(PageBasics, new ManualActivityPageBasics(context, plan, when));
|
||||
setPage(PageWorkout, new ManualActivityPageWorkout(context));
|
||||
@@ -331,6 +332,7 @@ ManualActivityPageBasics::ManualActivityPageBasics
|
||||
connect(timeEdit, &QTimeEdit::timeChanged, this, &ManualActivityPageBasics::checkDateTime);
|
||||
connect(sportEdit, &QLineEdit::editingFinished, this, &ManualActivityPageBasics::sportsChanged);
|
||||
connect(sportEdit, &QLineEdit::textChanged, this, [this]() { emit completeChanged(); });
|
||||
connect(subSportEdit, &QLineEdit::editingFinished, this, &ManualActivityPageBasics::sportsChanged);
|
||||
|
||||
registerField("activityDate", dateEdit);
|
||||
registerField("activityTime", timeEdit, "time", SIGNAL(timeChanged(QTime)));
|
||||
@@ -419,21 +421,9 @@ void
|
||||
ManualActivityPageBasics::sportsChanged
|
||||
()
|
||||
{
|
||||
QString path(":images/breeze/games-highscores.svg");
|
||||
QString sport = RideFile::sportTag(field("sport").toString().trimmed());
|
||||
if (sport == "Bike") {
|
||||
path = ":images/material/bike.svg";
|
||||
} else if (sport == "Run") {
|
||||
path = ":images/material/run.svg";
|
||||
} else if (sport == "Swim") {
|
||||
path = ":images/material/swim.svg";
|
||||
} else if (sport == "Row") {
|
||||
path = ":images/material/rowing.svg";
|
||||
} else if (sport == "Ski") {
|
||||
path = ":images/material/ski.svg";
|
||||
} else if (sport == "Gym") {
|
||||
path = ":images/material/weight-lifter.svg";
|
||||
}
|
||||
QString subSport = field("subSport").toString().trimmed();
|
||||
QString path = IconManager::instance().getFilepath(sport, subSport);
|
||||
wizard()->setPixmap(ICON_TYPE, svgAsColoredPixmap(path, QSize(ICON_SIZE * dpiXFactor, ICON_SIZE * dpiYFactor), ICON_MARGIN * dpiXFactor, ICON_COLOR));
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "LocalFileStore.h"
|
||||
#include "Secrets.h"
|
||||
#include "Utils.h"
|
||||
#include "IconManager.h"
|
||||
#ifdef GC_WANT_PYTHON
|
||||
#include "PythonEmbed.h"
|
||||
#include "FixPySettings.h"
|
||||
@@ -2152,12 +2153,14 @@ MetadataPage::MetadataPage(Context *context) : context(context)
|
||||
// setup maintenance pages using current config
|
||||
fieldsPage = new FieldsPage(this, fieldDefinitions);
|
||||
keywordsPage = new KeywordsPage(this, keywordDefinitions);
|
||||
iconsPage = new IconsPage(fieldDefinitions, this);
|
||||
defaultsPage = new DefaultsPage(this, defaultDefinitions);
|
||||
processorPage = new ProcessorPage(context);
|
||||
|
||||
tabs = new QTabWidget(this);
|
||||
tabs->addTab(fieldsPage, tr("Fields"));
|
||||
tabs->addTab(keywordsPage, tr("Colour Keywords"));
|
||||
tabs->addTab(iconsPage, tr("Icons"));
|
||||
tabs->addTab(defaultsPage, tr("Defaults"));
|
||||
tabs->addTab(processorPage, tr("Processors && Automation"));
|
||||
|
||||
@@ -2198,6 +2201,8 @@ MetadataPage::saveClicked()
|
||||
if (b4.fieldFingerprint != FieldDefinition::fingerprint(fieldDefinitions))
|
||||
state += CONFIG_FIELDS;
|
||||
|
||||
state |= iconsPage->saveClicked();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -2402,6 +2407,513 @@ KeywordsPage::getDefinitions(QList<KeywordDefinition> &keywordList)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Icons page
|
||||
//
|
||||
|
||||
#define ICONSPAGE_L_W 64 * dpiXFactor
|
||||
#define ICONSPAGE_L_H 64 * dpiXFactor
|
||||
#define ICONSPAGE_L QSize(ICONSPAGE_L_W, ICONSPAGE_L_H)
|
||||
#define ICONSPAGE_L_SPACE QSize(80 * dpiXFactor, 80 * dpiYFactor)
|
||||
#define ICONSPAGE_S_W 48 * dpiXFactor
|
||||
#define ICONSPAGE_S_H 48 * dpiXFactor
|
||||
#define ICONSPAGE_S QSize(ICONSPAGE_S_W, ICONSPAGE_S_H)
|
||||
#define ICONSPAGE_MARGIN 2 * dpiXFactor
|
||||
|
||||
IconsPage::IconsPage
|
||||
(const QList<FieldDefinition> &fieldDefinitions, QWidget *parent)
|
||||
: QWidget(parent), fieldDefinitions(fieldDefinitions)
|
||||
{
|
||||
QPalette palette;
|
||||
|
||||
sportTree = new QTreeWidget();
|
||||
sportTree->setColumnCount(3);
|
||||
basicTreeWidgetStyle(sportTree);
|
||||
sportTree->setHeaderLabels({ tr("Field"), tr("Value"), tr("Icon") });
|
||||
sportTree->setIconSize(ICONSPAGE_S);
|
||||
sportTree->setAcceptDrops(true);
|
||||
sportTree->installEventFilter(this);
|
||||
sportTree->viewport()->installEventFilter(this);
|
||||
initSportTree();
|
||||
|
||||
iconList = new QListWidget();
|
||||
iconList->setViewMode(QListView::IconMode);
|
||||
iconList->setIconSize(ICONSPAGE_L);
|
||||
iconList->setGridSize(ICONSPAGE_L_SPACE);
|
||||
iconList->setResizeMode(QListView::Adjust);
|
||||
iconList->setWrapping(true);
|
||||
iconList->setFlow(QListView::LeftToRight);
|
||||
iconList->setSpacing(10 * dpiXFactor);
|
||||
iconList->setMovement(QListView::Static);
|
||||
iconList->setUniformItemSizes(true);
|
||||
iconList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
iconList->setDragEnabled(false);
|
||||
iconList->setAcceptDrops(true);
|
||||
iconList->installEventFilter(this);
|
||||
iconList->viewport()->installEventFilter(this);
|
||||
updateIconList();
|
||||
|
||||
QPixmap trashPixmap = svgAsColoredPixmap(":images/breeze/trash-empty.svg", ICONSPAGE_L, ICONSPAGE_MARGIN, palette.color(QPalette::WindowText));
|
||||
QPixmap trashPixmapActive = svgAsColoredPixmap(":images/breeze/trash-empty.svg", ICONSPAGE_L, ICONSPAGE_MARGIN, QColor("#F79130"));
|
||||
trashIcon.addPixmap(trashPixmap, QIcon::Normal);
|
||||
trashIcon.addPixmap(trashPixmapActive, QIcon::Active);
|
||||
|
||||
trash = new QLabel();
|
||||
trash->setAcceptDrops(true);
|
||||
trash->setPixmap(trashIcon.pixmap(ICONSPAGE_L, QIcon::Normal));
|
||||
trash->installEventFilter(this);
|
||||
QPushButton *importButton = new QPushButton(tr("Import"));
|
||||
QPushButton *exportButton = new QPushButton(tr("Export"));
|
||||
|
||||
QHBoxLayout *contentLayout = new QHBoxLayout();
|
||||
contentLayout->addWidget(sportTree);
|
||||
contentLayout->addWidget(iconList);
|
||||
|
||||
QHBoxLayout *actionLayout = new QHBoxLayout();
|
||||
actionLayout->addWidget(trash);
|
||||
actionLayout->addStretch();
|
||||
actionLayout->addWidget(importButton);
|
||||
actionLayout->addWidget(exportButton);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addLayout(contentLayout);
|
||||
mainLayout->addLayout(actionLayout);
|
||||
|
||||
connect(importButton, &QPushButton::clicked, [=]() {
|
||||
QString zipFile = QFileDialog::getOpenFileName(this, tr("Import Icons"), "", tr("Zip Files (*.zip)"));
|
||||
if (zipFile.isEmpty() || ! IconManager::instance().importBundle(zipFile)) {
|
||||
QMessageBox::warning(nullptr, tr("Icons Bundle"), tr("Bundle file %1 cannot be imported.").arg(zipFile));
|
||||
} else {
|
||||
initSportTree();
|
||||
updateIconList();
|
||||
}
|
||||
});
|
||||
connect(exportButton, &QPushButton::clicked, [=]() {
|
||||
QString zipFile = QFileDialog::getSaveFileName(this, tr("Export Icons"), "", tr("Zip Files (*.zip)"));
|
||||
if (zipFile.isEmpty() || ! IconManager::instance().exportBundle(zipFile)) {
|
||||
QMessageBox::warning(nullptr, tr("Icons Bundle"), tr("Bundle file %1 cannot be created.").arg(zipFile));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
qint32
|
||||
IconsPage::saveClicked
|
||||
()
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
int rowCount = sportTree->topLevelItemCount();
|
||||
for (int i = 0; i < rowCount; ++i) {
|
||||
QTreeWidgetItem *item = sportTree->topLevelItem(i);
|
||||
if (! item) {
|
||||
continue;
|
||||
}
|
||||
QString originalIcon = item->data(0, Qt::UserRole + 1).toString();
|
||||
QString newIcon = item->data(0, Qt::UserRole + 2).toString();
|
||||
if (originalIcon != newIcon) {
|
||||
QString type = item->data(0, Qt::UserRole).toString();
|
||||
QString key = item->data(1, Qt::DisplayRole).toString();
|
||||
IconManager::instance().assignIcon(type, key, newIcon);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
return CONFIG_APPEARANCE;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconsPage::eventFilter
|
||||
(QObject *watched, QEvent *event)
|
||||
{
|
||||
bool handled = false;
|
||||
if (watched == trash) {
|
||||
handled = eventFilterTrash(event);
|
||||
} else if (watched == sportTree) {
|
||||
handled = eventFilterSportTree(event);
|
||||
} else if (watched == sportTree->viewport()) {
|
||||
handled = eventFilterSportTreeViewport(event);
|
||||
} else if (watched == iconList) {
|
||||
handled = eventFilterIconList(event);
|
||||
} else if (watched == iconList->viewport()) {
|
||||
handled = eventFilterIconListViewport(event);
|
||||
}
|
||||
return handled ? true : QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconsPage::eventFilterTrash
|
||||
(QEvent *event)
|
||||
{
|
||||
if ( event->type() == QEvent::DragEnter
|
||||
|| event->type() == QEvent::Drop) {
|
||||
QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
|
||||
if ( dropEvent->mimeData()->hasFormat("application/x-gc-icon")
|
||||
&& (dropEvent->possibleActions() & Qt::MoveAction)
|
||||
&& ( dropEvent->source() == sportTree
|
||||
|| dropEvent->source() == iconList)) {
|
||||
dropEvent->setDropAction(Qt::MoveAction);
|
||||
dropEvent->accept();
|
||||
trash->setPixmap(trashIcon.pixmap(ICONSPAGE_L, event->type() == QEvent::Drop ? QIcon::Normal : QIcon::Active));
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::DragLeave) {
|
||||
trash->setPixmap(trashIcon.pixmap(ICONSPAGE_L, QIcon::Normal));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconsPage::eventFilterSportTree
|
||||
(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::DragEnter) {
|
||||
QDragEnterEvent *dragEnterEvent = static_cast<QDragEnterEvent*>(event);
|
||||
if ( dragEnterEvent->mimeData()->hasFormat("application/x-gc-icon")
|
||||
&& (dragEnterEvent->possibleActions() & Qt::LinkAction)
|
||||
&& dragEnterEvent->source() == iconList) {
|
||||
sportTree->setStyleSheet("QTreeWidget { border: 2px solid #F79130; border-radius: 8px; }");
|
||||
dragEnterEvent->setDropAction(Qt::LinkAction);
|
||||
dragEnterEvent->accept();
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::DragMove) {
|
||||
QDragMoveEvent *dragMoveEvent = static_cast<QDragMoveEvent*>(event);
|
||||
if ( dragMoveEvent->mimeData()->hasFormat("application/x-gc-icon")
|
||||
&& (dragMoveEvent->possibleActions() & Qt::LinkAction)
|
||||
&& dragMoveEvent->source() == iconList) {
|
||||
QPoint globalCursor = QCursor::pos();
|
||||
QPoint pos = sportTree->viewport()->mapFromGlobal(globalCursor);
|
||||
QTreeWidgetItem* targetItem = sportTree->itemAt(pos);
|
||||
if (targetItem) {
|
||||
sportTree->setCurrentItem(targetItem);
|
||||
dragMoveEvent->setDropAction(Qt::LinkAction);
|
||||
dragMoveEvent->accept();
|
||||
} else {
|
||||
dragMoveEvent->ignore();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::DragLeave) {
|
||||
sportTree->setStyleSheet("");
|
||||
return true;
|
||||
} else if (event->type() == QEvent::Drop) {
|
||||
QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
|
||||
if ( dropEvent->mimeData()->hasFormat("application/x-gc-icon")
|
||||
&& (dropEvent->possibleActions() & Qt::LinkAction)
|
||||
&& dropEvent->source() == iconList) {
|
||||
QByteArray iconBytes = dropEvent->mimeData()->data("application/x-gc-icon");
|
||||
QString iconFile = QString::fromUtf8(iconBytes);
|
||||
QPoint globalCursor = QCursor::pos();
|
||||
QPoint pos = sportTree->viewport()->mapFromGlobal(globalCursor);
|
||||
QTreeWidgetItem* targetItem = sportTree->itemAt(pos);
|
||||
if (targetItem) {
|
||||
QPalette palette;
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
QPixmap pixmap = svgAsColoredPixmap(IconManager::instance().toFilepath(iconFile), QSize(1000, 1000), 0, palette.color(QPalette::Text));
|
||||
qint64 elapsed = timer.elapsed();
|
||||
if ( elapsed < 50
|
||||
|| QMessageBox::question(this, tr("Complex Icon"), tr("The selected icon %1 appears to be complex and could impact performance. Are you sure you want to use this icon?").arg(iconFile)) == QMessageBox::Yes) {
|
||||
QPixmap pixmap = svgAsColoredPixmap(IconManager::instance().toFilepath(iconFile), ICONSPAGE_S, ICONSPAGE_MARGIN, palette.color(QPalette::Text));
|
||||
targetItem->setData(0, Qt::UserRole + 2, iconFile);
|
||||
targetItem->setIcon(2, QIcon(pixmap));
|
||||
dropEvent->setDropAction(Qt::LinkAction);
|
||||
dropEvent->accept();
|
||||
} else {
|
||||
dropEvent->ignore();
|
||||
}
|
||||
}
|
||||
}
|
||||
sportTree->setStyleSheet("");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconsPage::eventFilterSportTreeViewport
|
||||
(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
#if QT_VERSION >= 0x060000
|
||||
QTreeWidgetItem *item = sportTree->itemAt(mouseEvent->position().toPoint());
|
||||
#else
|
||||
QTreeWidgetItem *item = sportTree->itemAt(mouseEvent->pos());
|
||||
#endif
|
||||
if (item && ! item->data(0, Qt::UserRole + 2).toString().isEmpty()) {
|
||||
#if QT_VERSION >= 0x060000
|
||||
sportTreeDragStartPos = mouseEvent->position().toPoint();
|
||||
#else
|
||||
sportTreeDragStartPos = mouseEvent->pos();
|
||||
#endif
|
||||
sportTreeDragWatch = true;
|
||||
} else {
|
||||
sportTreeDragWatch = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if ( ! (mouseEvent->buttons() & Qt::LeftButton)
|
||||
|| ! sportTreeDragWatch
|
||||
#if QT_VERSION >= 0x060000
|
||||
|| (mouseEvent->position().toPoint() - sportTreeDragStartPos).manhattanLength() < QApplication::startDragDistance()) {
|
||||
#else
|
||||
|| (mouseEvent->pos() - sportTreeDragStartPos).manhattanLength() < QApplication::startDragDistance()) {
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
sportTreeDragWatch = false;
|
||||
#if QT_VERSION >= 0x060000
|
||||
QTreeWidgetItem *item = sportTree->itemAt(mouseEvent->position().toPoint());
|
||||
#else
|
||||
QTreeWidgetItem *item = sportTree->itemAt(mouseEvent->pos());
|
||||
#endif
|
||||
if (! item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray entryBytes = item->data(0, Qt::UserRole).toString().toUtf8();
|
||||
QMimeData *mimeData = new QMimeData();
|
||||
mimeData->setData("application/x-gc-icon", entryBytes);
|
||||
|
||||
QDrag *drag = new QDrag(sportTree);
|
||||
drag->setMimeData(mimeData);
|
||||
drag->setPixmap(item->icon(2).pixmap(ICONSPAGE_S));
|
||||
drag->setHotSpot(QPoint(ICONSPAGE_S_W / 2, ICONSPAGE_S_H / 2));
|
||||
Qt::DropAction dropAction = drag->exec(Qt::MoveAction);
|
||||
if (dropAction == Qt::MoveAction) {
|
||||
QPixmap pixmap(ICONSPAGE_S);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QIcon icon(pixmap);
|
||||
item->setData(0, Qt::UserRole + 2, "");
|
||||
item->setIcon(2, icon);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconsPage::eventFilterIconList
|
||||
(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::DragEnter) {
|
||||
QDragEnterEvent *dragEnterEvent = static_cast<QDragEnterEvent*>(event);
|
||||
if ( dragEnterEvent->mimeData()->hasFormat("application/x-gc-icon")
|
||||
&& (dragEnterEvent->possibleActions() & Qt::MoveAction)
|
||||
&& dragEnterEvent->source() == sportTree) {
|
||||
dragEnterEvent->setDropAction(Qt::MoveAction);
|
||||
dragEnterEvent->accept();
|
||||
} else if ( dragEnterEvent->mimeData()->hasUrls()
|
||||
&& (dragEnterEvent->possibleActions() & Qt::CopyAction)) {
|
||||
const QList<QUrl> urls = dragEnterEvent->mimeData()->urls();
|
||||
int svgs = 0;
|
||||
for (const QUrl &url : urls) {
|
||||
QString path = url.toLocalFile();
|
||||
if (path.endsWith(".svg")) {
|
||||
++svgs;
|
||||
}
|
||||
}
|
||||
if (svgs > 0) {
|
||||
dragEnterEvent->setDropAction(Qt::CopyAction);
|
||||
dragEnterEvent->accept();
|
||||
}
|
||||
}
|
||||
if (dragEnterEvent->isAccepted()) {
|
||||
iconList->setStyleSheet("QListWidget { border: 2px solid #F79130; border-radius: 8px; }");
|
||||
}
|
||||
return true;
|
||||
} else if (event->type() == QEvent::DragLeave) {
|
||||
iconList->setStyleSheet("");
|
||||
return true;
|
||||
} else if (event->type() == QEvent::Drop) {
|
||||
QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
|
||||
if ( dropEvent->mimeData()->hasFormat("application/x-gc-icon")
|
||||
&& (dropEvent->possibleActions() & Qt::MoveAction)
|
||||
&& dropEvent->source() == sportTree) {
|
||||
dropEvent->setDropAction(Qt::MoveAction);
|
||||
dropEvent->accept();
|
||||
} else if ( dropEvent->mimeData()->hasUrls()
|
||||
&& (dropEvent->possibleActions() & Qt::CopyAction)) {
|
||||
const QList<QUrl> urls = dropEvent->mimeData()->urls();
|
||||
int added = 0;
|
||||
for (const QUrl &url : urls) {
|
||||
QString path = url.toLocalFile();
|
||||
if (IconManager::instance().addIconFile(path)) {
|
||||
++added;
|
||||
}
|
||||
}
|
||||
if (added > 0) {
|
||||
dropEvent->setDropAction(Qt::CopyAction);
|
||||
dropEvent->accept();
|
||||
updateIconList();
|
||||
}
|
||||
}
|
||||
iconList->setStyleSheet("");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IconsPage::eventFilterIconListViewport
|
||||
(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Paint) {
|
||||
if (iconList->count() == 0) {
|
||||
QPaintEvent *paintEvent = static_cast<QPaintEvent*>(event);
|
||||
QPainter painter(iconList->viewport());
|
||||
QPalette palette;
|
||||
QColor pixmapColor = palette.color(QPalette::Disabled, QPalette::Text);
|
||||
if (palette.color(QPalette::Base).lightness() < 127) {
|
||||
pixmapColor = pixmapColor.darker(135);
|
||||
} else {
|
||||
pixmapColor = pixmapColor.lighter(135);
|
||||
}
|
||||
QPixmap pixmap = svgAsColoredPixmap(":/images/breeze/edit-image-face-add.svg", QSize(256 * dpiXFactor, 256 * dpiYFactor), 0, pixmapColor);
|
||||
painter.drawPixmap((paintEvent->rect().width() - pixmap.width()) / 2, (paintEvent->rect().height() - pixmap.height()) / 2, pixmap);
|
||||
painter.drawText(paintEvent->rect(), Qt::AlignCenter | Qt::TextWordWrap, tr("No icons available.\nDrag and drop .svg files here to add icons."));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (event->type() == QEvent::MouseButtonPress) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
#if QT_VERSION >= 0x060000
|
||||
QListWidgetItem *item = iconList->itemAt(mouseEvent->position().toPoint());
|
||||
#else
|
||||
QListWidgetItem *item = iconList->itemAt(mouseEvent->pos());
|
||||
#endif
|
||||
if (item) {
|
||||
#if QT_VERSION >= 0x060000
|
||||
iconListDragStartPos = mouseEvent->position().toPoint();
|
||||
#else
|
||||
iconListDragStartPos = mouseEvent->pos();
|
||||
#endif
|
||||
iconListDragWatch = true;
|
||||
} else {
|
||||
iconListDragWatch = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
if ( ! (mouseEvent->buttons() & Qt::LeftButton)
|
||||
|| ! iconListDragWatch
|
||||
#if QT_VERSION >= 0x060000
|
||||
|| (mouseEvent->position().toPoint() - iconListDragStartPos).manhattanLength() < QApplication::startDragDistance()) {
|
||||
#else
|
||||
|| (mouseEvent->pos() - iconListDragStartPos).manhattanLength() < QApplication::startDragDistance()) {
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
iconListDragWatch = false;
|
||||
#if QT_VERSION >= 0x060000
|
||||
QListWidgetItem *item = iconList->itemAt(mouseEvent->position().toPoint());
|
||||
#else
|
||||
QListWidgetItem *item = iconList->itemAt(mouseEvent->pos());
|
||||
#endif
|
||||
if (! item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString iconFile = item->data(Qt::UserRole).toString();
|
||||
QMimeData *mimeData = new QMimeData();
|
||||
mimeData->setData("application/x-gc-icon", iconFile.toUtf8());
|
||||
|
||||
QDrag *drag = new QDrag(iconList);
|
||||
drag->setMimeData(mimeData);
|
||||
drag->setPixmap(item->icon().pixmap(ICONSPAGE_L));
|
||||
drag->setHotSpot(QPoint(ICONSPAGE_L_W / 2, ICONSPAGE_L_H / 2));
|
||||
Qt::DropAction dropAction = drag->exec(Qt::MoveAction | Qt::LinkAction);
|
||||
if (dropAction == Qt::MoveAction) {
|
||||
if (IconManager::instance().deleteIconFile(iconFile)) {
|
||||
updateIconList();
|
||||
int rowCount = sportTree->topLevelItemCount();
|
||||
for (int i = 0; i < rowCount; ++i) {
|
||||
QTreeWidgetItem *item = sportTree->topLevelItem(i);
|
||||
if (item && item->data(0, Qt::UserRole + 2).toString() == iconFile) {
|
||||
QPixmap pixmap(ICONSPAGE_S);
|
||||
pixmap.fill(Qt::transparent);
|
||||
item->setData(0, Qt::UserRole + 2, "");
|
||||
item->setIcon(2, QIcon(pixmap));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IconsPage::initSportTree
|
||||
()
|
||||
{
|
||||
sportTree->clear();
|
||||
QPalette palette;
|
||||
SpecialFields &specials = SpecialFields::getInstance();
|
||||
for (QString field : { "Sport", "SubSport" }) {
|
||||
for (const FieldDefinition &fieldDefinition : fieldDefinitions) {
|
||||
if (fieldDefinition.name == field) {
|
||||
for (const QString &fieldValue : fieldDefinition.values) {
|
||||
QIcon icon;
|
||||
QString assignedFile = IconManager::instance().assignedIcon(fieldDefinition.name, fieldValue);
|
||||
if (! assignedFile.isEmpty()) {
|
||||
QPixmap pixmap = svgAsColoredPixmap(IconManager::instance().toFilepath(assignedFile), ICONSPAGE_S, ICONSPAGE_MARGIN, palette.color(QPalette::Text));
|
||||
icon.addPixmap(pixmap);
|
||||
} else {
|
||||
QPixmap pixmap(ICONSPAGE_S);
|
||||
pixmap.fill(Qt::transparent);
|
||||
icon.addPixmap(pixmap);
|
||||
}
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem();
|
||||
item->setData(0, Qt::DisplayRole, specials.displayName(fieldDefinition.name));
|
||||
item->setData(0, Qt::UserRole, fieldDefinition.name);
|
||||
item->setData(0, Qt::UserRole + 1, assignedFile);
|
||||
item->setData(0, Qt::UserRole + 2, assignedFile);
|
||||
item->setData(1, Qt::DisplayRole, fieldValue);
|
||||
item->setIcon(2, icon);
|
||||
sportTree->addTopLevelItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IconsPage::updateIconList
|
||||
()
|
||||
{
|
||||
iconList->clear();
|
||||
QPalette palette;
|
||||
QStringList icons = IconManager::instance().listIconFiles();
|
||||
for (QString icon : icons) {
|
||||
QPixmap pixmap = svgAsColoredPixmap(IconManager::instance().toFilepath(icon), ICONSPAGE_L, ICONSPAGE_MARGIN, palette.color(QPalette::Text));
|
||||
QListWidgetItem *item = new QListWidgetItem(QIcon(pixmap), "");
|
||||
item->setData(Qt::UserRole, icon);
|
||||
iconList->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Ride metadata page
|
||||
//
|
||||
|
||||
@@ -423,6 +423,40 @@ class KeywordsPage : public QWidget
|
||||
MetadataPage *parent;
|
||||
};
|
||||
|
||||
class IconsPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
IconsPage(const QList<FieldDefinition> &fieldDefinitions, QWidget *parent = nullptr);
|
||||
qint32 saveClicked();
|
||||
|
||||
public slots:
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
QList<FieldDefinition> fieldDefinitions;
|
||||
QTreeWidget *sportTree;
|
||||
QListWidget *iconList;
|
||||
QLabel *trash;
|
||||
QIcon trashIcon;
|
||||
QPoint sportTreeDragStartPos;
|
||||
bool sportTreeDragWatch = false;
|
||||
QPoint iconListDragStartPos;
|
||||
bool iconListDragWatch = false;
|
||||
|
||||
bool eventFilterTrash(QEvent *event);
|
||||
bool eventFilterSportTree(QEvent *event);
|
||||
bool eventFilterSportTreeViewport(QEvent *event);
|
||||
bool eventFilterIconList(QEvent *event);
|
||||
bool eventFilterIconListViewport(QEvent *event);
|
||||
|
||||
void initSportTree();
|
||||
void updateIconList();
|
||||
};
|
||||
|
||||
class ColorsPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -594,6 +628,7 @@ class MetadataPage : public QWidget
|
||||
|
||||
QTabWidget *tabs;
|
||||
KeywordsPage *keywordsPage;
|
||||
IconsPage *iconsPage;
|
||||
FieldsPage *fieldsPage;
|
||||
DefaultsPage *defaultsPage;
|
||||
ProcessorPage *processorPage;
|
||||
|
||||
@@ -124,6 +124,8 @@
|
||||
<file>xml/home-perspectives.xml</file>
|
||||
<file>ini/measures.ini</file>
|
||||
<file>html/ltm-summary.html</file>
|
||||
<file>images/breeze/trash-empty.svg</file>
|
||||
<file>images/breeze/edit-image-face-add.svg</file>
|
||||
<file>images/breeze/media-playlist-repeat.svg</file>
|
||||
<file>images/breeze/games-highscores.svg</file>
|
||||
<file>images/breeze/network-mobile-0.svg</file>
|
||||
@@ -241,12 +243,6 @@
|
||||
<file>images/services/polarflow.png</file>
|
||||
<file>images/services/sporttracks.png</file>
|
||||
<file>images/services/nolio.png</file>
|
||||
<file>images/material/bike.svg</file>
|
||||
<file>images/material/run.svg</file>
|
||||
<file>images/material/swim.svg</file>
|
||||
<file>images/material/rowing.svg</file>
|
||||
<file>images/material/ski.svg</file>
|
||||
<file>images/material/weight-lifter.svg</file>
|
||||
<file>python/library.py</file>
|
||||
<file>images/devices/imagic.png</file>
|
||||
<file>data/powerprofile.csv</file>
|
||||
|
||||
13
src/Resources/images/breeze/edit-image-face-add.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 2 L 2 5 L 3 5 L 3 3 L 5 3 L 5 2 L 2 2 z M 6 2 L 6 3 L 10 3 L 10 2 L 6 2 z M 11 2 L 11 3 L 13 3 L 13 5 L 14 5 L 14 2 L 11 2 z M 8 4 A 1.5 1.5 0 0 0 6.5 5.5 A 1.5 1.5 0 0 0 8 7 A 1.5 1.5 0 0 0 9.5 5.5 A 1.5 1.5 0 0 0 8 4 z M 2 6 L 2 10 L 3 10 L 3 6 L 2 6 z M 13 6 L 13 9 L 14 9 L 14 6 L 13 6 z M 8 8 A 3 3 0 0 0 5 11 L 5 12 L 8 12 L 8 11 L 6 11 A 2 2 0 0 1 8 9 L 10.232422 9 A 3 3 0 0 0 8 8 z M 11 9 L 11 11 L 9 11 L 9 12 L 11 12 L 11 14 L 12 14 L 12 12 L 14 12 L 14 11 L 12 11 L 12 9 L 11 9 z M 2 11 L 2 14 L 5 14 L 5 13 L 3 13 L 3 11 L 2 11 z M 6 13 L 6 14 L 9 14 L 9 13 L 6 13 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 920 B |
13
src/Resources/images/breeze/trash-empty.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m5 2v2h1v-1h4v1h1v-2h-5zm-3 3v1h2v8h8v-8h2v-1zm3 1h6v7h-6z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 394 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="mdi-bike" viewBox="0 0 24 24"><path d="M5,20.5A3.5,3.5 0 0,1 1.5,17A3.5,3.5 0 0,1 5,13.5A3.5,3.5 0 0,1 8.5,17A3.5,3.5 0 0,1 5,20.5M5,12A5,5 0 0,0 0,17A5,5 0 0,0 5,22A5,5 0 0,0 10,17A5,5 0 0,0 5,12M14.8,10H19V8.2H15.8L13.86,4.93C13.57,4.43 13,4.1 12.4,4.1C11.93,4.1 11.5,4.29 11.2,4.6L7.5,8.29C7.19,8.6 7,9 7,9.5C7,10.13 7.33,10.66 7.85,10.97L11.2,13V18H13V11.5L10.75,9.85L13.07,7.5M19,20.5A3.5,3.5 0 0,1 15.5,17A3.5,3.5 0 0,1 19,13.5A3.5,3.5 0 0,1 22.5,17A3.5,3.5 0 0,1 19,20.5M19,12A5,5 0 0,0 14,17A5,5 0 0,0 19,22A5,5 0 0,0 24,17A5,5 0 0,0 19,12M16,4.8C17,4.8 17.8,4 17.8,3C17.8,2 17,1.2 16,1.2C15,1.2 14.2,2 14.2,3C14.2,4 15,4.8 16,4.8Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 693 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="mdi-rowing" viewBox="0 0 24 24"><path d="M8.5,14.5L4,19L5.5,20.5L9,17H11L8.5,14.5M15,1A2,2 0 0,0 13,3A2,2 0 0,0 15,5A2,2 0 0,0 17,3A2,2 0 0,0 15,1M21,21L18,24L15,21V19.5L7.91,12.41C7.6,12.46 7.3,12.5 7,12.5V10.32C8.66,10.35 10.61,9.45 11.67,8.28L13.07,6.73C13.26,6.5 13.5,6.35 13.76,6.23C14.05,6.09 14.38,6 14.72,6H14.75C16,6 17,7 17,8.26V14C17,14.85 16.65,15.62 16.08,16.17L12.5,12.59V10.32C11.87,10.84 11.07,11.34 10.21,11.71L16.5,18H18L21,21Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 499 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="mdi-run" viewBox="0 0 24 24"><path d="M13.5,5.5C14.59,5.5 15.5,4.58 15.5,3.5C15.5,2.38 14.59,1.5 13.5,1.5C12.39,1.5 11.5,2.38 11.5,3.5C11.5,4.58 12.39,5.5 13.5,5.5M9.89,19.38L10.89,15L13,17V23H15V15.5L12.89,13.5L13.5,10.5C14.79,12 16.79,13 19,13V11C17.09,11 15.5,10 14.69,8.58L13.69,7C13.29,6.38 12.69,6 12,6C11.69,6 11.5,6.08 11.19,6.08L6,8.28V13H8V9.58L9.79,8.88L8.19,17L3.29,16L2.89,18L9.89,19.38Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 454 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="mdi-ski" viewBox="0 0 24 24"><path d="M17.92 13.32C17.67 13.28 16.71 13 16.46 12.89L14.39 19.37L11.3 18.24L13.5 12.47L10.45 9L13 7.54C13.45 8.67 14.17 9.62 15.12 10.4S17.16 11.67 18.38 11.86L19.5 8.43L18.06 7.96L17.54 9.56C16.88 9.28 16.3 8.86 15.8 8.32C15.3 7.77 14.94 7.13 14.72 6.41L14.39 5.33C14.27 4.93 14.04 4.61 13.71 4.37C13.38 4.14 13 4 12.63 3.97C12.24 3.94 11.86 4 11.5 4.21L8 6.23C7.63 6.44 7.36 6.74 7.19 7.12C7 7.5 6.96 7.88 7 8.29S7.26 9.06 7.54 9.37L11.11 13.08L9.42 17.54L2.47 15.05L2 16.46L16.04 21.58C16.82 21.86 17.65 22 18.53 22C19.15 22 19.76 21.92 20.36 21.77C20.95 21.61 21.5 21.39 22 21.11L20.87 20C20.12 20.33 19.34 20.5 18.53 20.5C17.87 20.5 17.21 20.39 16.55 20.17L15.8 19.89L17.92 13.32M19 3C19 4.11 18.11 5 17 5S15 4.11 15 3 15.9 1 17 1 19 1.9 19 3Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 833 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="mdi-swim" viewBox="0 0 24 24"><path d="M2,18C4.22,17 6.44,16 8.67,16C10.89,16 13.11,18 15.33,18C17.56,18 19.78,16 22,16V19C19.78,19 17.56,21 15.33,21C13.11,21 10.89,19 8.67,19C6.44,19 4.22,20 2,21V18M8.67,13C7.89,13 7.12,13.12 6.35,13.32L11.27,9.88L10.23,8.64C10.09,8.47 10,8.24 10,8C10,7.66 10.17,7.35 10.44,7.17L16.16,3.17L17.31,4.8L12.47,8.19L17.7,14.42C16.91,14.75 16.12,15 15.33,15C13.11,15 10.89,13 8.67,13M18,7A2,2 0 0,1 20,9A2,2 0 0,1 18,11A2,2 0 0,1 16,9A2,2 0 0,1 18,7Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 533 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="mdi-weight-lifter" viewBox="0 0 24 24"><path d="M12 5C10.89 5 10 5.89 10 7S10.89 9 12 9 14 8.11 14 7 13.11 5 12 5M22 1V6H20V4H4V6H2V1H4V3H20V1H22M15 11.26V23H13V18H11V23H9V11.26C6.93 10.17 5.5 8 5.5 5.5L5.5 5H7.5L7.5 5.5C7.5 8 9.5 10 12 10S16.5 8 16.5 5.5L16.5 5H18.5L18.5 5.5C18.5 8 17.07 10.17 15 11.26Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 359 B |
@@ -651,7 +651,8 @@ HEADERS += Gui/AboutDialog.h Gui/AddIntervalDialog.h Gui/AnalysisSidebar.h Gui/C
|
||||
Gui/AddTileWizard.h Gui/NavigationModel.h Gui/AthleteView.h Gui/AthleteConfigDialog.h Gui/AthletePages.h Gui/Perspective.h \
|
||||
Gui/PerspectiveDialog.h Gui/SplashScreen.h Gui/StyledItemDelegates.h Gui/MetadataDialog.h Gui/ActionButtonBox.h \
|
||||
Gui/MetricOverrideDialog.h Gui/RepeatScheduleWizard.h \
|
||||
Gui/Calendar.h Gui/CalendarData.h Gui/CalendarItemDelegates.h
|
||||
Gui/Calendar.h Gui/CalendarData.h Gui/CalendarItemDelegates.h \
|
||||
Gui/IconManager.h
|
||||
|
||||
# metrics and models
|
||||
HEADERS += Metrics/Banister.h Metrics/CPSolver.h Metrics/Estimator.h Metrics/ExtendedCriticalPower.h Metrics/HrZones.h Metrics/PaceZones.h \
|
||||
@@ -763,7 +764,8 @@ SOURCES += Gui/AboutDialog.cpp Gui/AddIntervalDialog.cpp Gui/AnalysisSidebar.cpp
|
||||
Gui/AddTileWizard.cpp Gui/NavigationModel.cpp Gui/AthleteView.cpp Gui/AthleteConfigDialog.cpp Gui/AthletePages.cpp Gui/Perspective.cpp \
|
||||
Gui/PerspectiveDialog.cpp Gui/SplashScreen.cpp Gui/StyledItemDelegates.cpp Gui/MetadataDialog.cpp Gui/ActionButtonBox.cpp \
|
||||
Gui/MetricOverrideDialog.cpp Gui/RepeatScheduleWizard.cpp \
|
||||
Gui/Calendar.cpp Gui/CalendarItemDelegates.cpp
|
||||
Gui/Calendar.cpp Gui/CalendarItemDelegates.cpp \
|
||||
Gui/IconManager.cpp
|
||||
|
||||
## Models and Metrics
|
||||
SOURCES += Metrics/aBikeScore.cpp Metrics/aCoggan.cpp Metrics/AerobicDecoupling.cpp Metrics/Banister.cpp Metrics/BasicRideMetrics.cpp \
|
||||
|
||||