From 089be2b87c945a0044054d0e59c9c462243acd9f Mon Sep 17 00:00:00 2001 From: optizone <42874998+optizone@users.noreply.github.com> Date: Wed, 1 May 2019 19:15:33 +0300 Subject: [PATCH] refactor theme edit feature (#1461) Refactor Theme Editor --- src/Cutter.pro | 21 +- src/Main.cpp | 24 + src/common/ColorSchemeFileSaver.cpp | 186 ---- src/common/ColorSchemeFileSaver.h | 47 - src/common/ColorThemeWorker.cpp | 306 +++++++ src/common/ColorThemeWorker.h | 130 +++ src/common/Configuration.cpp | 87 +- src/common/Configuration.h | 22 +- src/core/MainWindow.cpp | 2 +- src/dialogs/WelcomeDialog.cpp | 4 +- src/dialogs/XrefsDialog.cpp | 2 +- .../preferences/AppearanceOptionsWidget.cpp | 251 ++++-- .../preferences/AppearanceOptionsWidget.h | 35 +- .../preferences/AppearanceOptionsWidget.ui | 153 +++- .../preferences/ColorThemeEditDialog.cpp | 146 ++++ .../preferences/ColorThemeEditDialog.h | 52 ++ .../preferences/ColorThemeEditDialog.ui | 158 ++++ src/dialogs/preferences/PreferencesDialog.cpp | 2 +- src/img/icons/download_black.svg | 101 +++ src/img/icons/pencil_thin.svg | 99 +++ src/img/icons/rename.svg | 126 +++ src/img/icons/reset.svg | 53 ++ src/img/icons/save_black.svg | 54 ++ src/img/icons/trash_bin.svg | 103 +++ src/img/icons/upload_black.svg | 101 +++ src/menus/DisassemblyContextMenu.cpp | 12 +- src/resources.qrc | 7 + src/widgets/ColorPicker.cpp | 333 +++++++ src/widgets/ColorPicker.h | 165 ++++ src/widgets/ColorPicker.ui | 349 ++++++++ src/widgets/ColorSchemePrefWidget.cpp | 562 ------------ src/widgets/ColorSchemePrefWidget.h | 130 --- src/widgets/ColorSchemePrefWidget.ui | 101 --- src/widgets/ColorThemeComboBox.cpp | 89 ++ src/widgets/ColorThemeComboBox.h | 37 + src/widgets/ColorThemeListView.cpp | 823 ++++++++++++++++++ src/widgets/ColorThemeListView.h | 101 +++ src/widgets/CutterDockWidget.cpp | 10 +- src/widgets/DisassemblerGraphView.cpp | 4 +- src/widgets/DisassemblyWidget.cpp | 81 +- src/widgets/DisassemblyWidget.h | 6 +- 41 files changed, 3812 insertions(+), 1263 deletions(-) delete mode 100644 src/common/ColorSchemeFileSaver.cpp delete mode 100644 src/common/ColorSchemeFileSaver.h create mode 100644 src/common/ColorThemeWorker.cpp create mode 100644 src/common/ColorThemeWorker.h create mode 100644 src/dialogs/preferences/ColorThemeEditDialog.cpp create mode 100644 src/dialogs/preferences/ColorThemeEditDialog.h create mode 100644 src/dialogs/preferences/ColorThemeEditDialog.ui create mode 100644 src/img/icons/download_black.svg create mode 100644 src/img/icons/pencil_thin.svg create mode 100644 src/img/icons/rename.svg create mode 100644 src/img/icons/reset.svg create mode 100644 src/img/icons/save_black.svg create mode 100644 src/img/icons/trash_bin.svg create mode 100644 src/img/icons/upload_black.svg create mode 100644 src/widgets/ColorPicker.cpp create mode 100644 src/widgets/ColorPicker.h create mode 100644 src/widgets/ColorPicker.ui delete mode 100644 src/widgets/ColorSchemePrefWidget.cpp delete mode 100644 src/widgets/ColorSchemePrefWidget.h delete mode 100644 src/widgets/ColorSchemePrefWidget.ui create mode 100644 src/widgets/ColorThemeComboBox.cpp create mode 100644 src/widgets/ColorThemeComboBox.h create mode 100644 src/widgets/ColorThemeListView.cpp create mode 100644 src/widgets/ColorThemeListView.h diff --git a/src/Cutter.pro b/src/Cutter.pro index 4bdef227..5af70774 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -322,8 +322,6 @@ SOURCES += \ widgets/RegisterRefsWidget.cpp \ dialogs/SetToDataDialog.cpp \ dialogs/EditVariablesDialog.cpp \ - widgets/ColorSchemePrefWidget.cpp \ - common/ColorSchemeFileSaver.cpp \ dialogs/EditFunctionDialog.cpp \ widgets/CutterTreeView.cpp \ widgets/ComboQuickFilterView.cpp \ @@ -340,6 +338,11 @@ SOURCES += \ plugins/PluginManager.cpp \ common/BasicBlockHighlighter.cpp \ dialogs/LinkTypeDialog.cpp \ + widgets/ColorPicker.cpp \ + common/ColorThemeWorker.cpp \ + widgets/ColorThemeComboBox.cpp \ + widgets/ColorThemeListView.cpp \ + dialogs/preferences/ColorThemeEditDialog.cpp \ common/UpdateWorker.cpp \ widgets/MemoryDockWidget.cpp \ common/CrashHandler.cpp \ @@ -442,8 +445,6 @@ HEADERS += \ dialogs/SetToDataDialog.h \ common/InitialOptions.h \ dialogs/EditVariablesDialog.h \ - common/ColorSchemeFileSaver.h \ - widgets/ColorSchemePrefWidget.h \ dialogs/EditFunctionDialog.h \ widgets/CutterTreeView.h \ widgets/ComboQuickFilterView.h \ @@ -462,8 +463,13 @@ HEADERS += \ plugins/PluginManager.h \ common/BasicBlockHighlighter.h \ common/UpdateWorker.h \ - dialogs/LinkTypeDialog.h \ + widgets/ColorPicker.h \ + common/ColorThemeWorker.h \ + widgets/ColorThemeComboBox.h \ widgets/MemoryDockWidget.h \ + widgets/ColorThemeListView.h \ + dialogs/preferences/ColorThemeEditDialog.h \ + dialogs/LinkTypeDialog.h \ common/BugReporting.h \ common/HighDpiPixmap.h \ widgets/GraphLayout.h \ @@ -520,7 +526,6 @@ FORMS += \ widgets/RegisterRefsWidget.ui \ dialogs/SetToDataDialog.ui \ dialogs/EditVariablesDialog.ui \ - widgets/ColorSchemePrefWidget.ui \ widgets/CutterTreeView.ui \ widgets/ComboQuickFilterView.ui \ dialogs/HexdumpRangeDialog.ui \ @@ -528,7 +533,9 @@ FORMS += \ dialogs/EditMethodDialog.ui \ dialogs/LoadNewTypesDialog.ui \ widgets/SdbWidget.ui \ - dialogs/LinkTypeDialog.ui + dialogs/LinkTypeDialog.ui \ + widgets/ColorPicker.ui \ + dialogs/preferences/ColorThemeEditDialog.ui RESOURCES += \ resources.qrc \ diff --git a/src/Main.cpp b/src/Main.cpp index f6496b08..c44f65e5 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -2,9 +2,12 @@ #include "CutterApplication.h" #include "core/MainWindow.h" #include "common/UpdateWorker.h" +#include "common/ColorThemeWorker.h" #include "CutterConfig.h" #include "common/CrashHandler.h" +#include + /** * @brief Migrate Settings used before Cutter 1.8 */ @@ -48,6 +51,27 @@ int main(int argc, char *argv[]) CutterApplication a(argc, argv); + // Removes obsolete color options (highlight and highlightWord) from custom theme files + if (!settings.value("updated_custom_themes", false).toBool()) { + const QStringList options = Core()->cmdj("ecj").object().keys() + << ColorThemeWorker::cutterSpecificOptions; + for (auto theme : Core()->cmdList("eco*")) { + theme = theme.trimmed(); + if (!ThemeWorker().isCustomTheme(theme)) { + continue; + } + QJsonObject updatedTheme; + auto sch = ThemeWorker().getTheme(theme); + for (auto key : sch.object().keys()) { + if (options.contains(key)) { + updatedTheme.insert(key, sch[key]); + } + } + ThemeWorker().save(QJsonDocument(updatedTheme), theme); + } + settings.setValue("updated_custom_themes", true); + } + if (Config()->getAutoUpdateEnabled()) { UpdateWorker *updateWorker = new UpdateWorker; QObject::connect(updateWorker, &UpdateWorker::checkComplete, diff --git a/src/common/ColorSchemeFileSaver.cpp b/src/common/ColorSchemeFileSaver.cpp deleted file mode 100644 index c29ae175..00000000 --- a/src/common/ColorSchemeFileSaver.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "common/ColorSchemeFileSaver.h" - -#include -#include -#include -#include -#include -#include -#include "common/Configuration.h" - -static const QStringList cutterSpecificOptions = { - "gui.main", - "highlight", - "gui.imports", - "highlightPC", - "highlightWord", - "gui.navbar.err", - "gui.navbar.seek", - "gui.navbar.pc", - "gui.navbar.sym", - "gui.dataoffset", - "gui.navbar.code", - "gui.navbar.empty", - "gui.navbar.str", - "gui.disass_selected", - "gui.breakpoint_background", - "gui.overview.node", - "gui.tooltip.background", - "gui.tooltip.foreground" - "gui.overview.node" -}; - -ColorSchemeFileSaver::ColorSchemeFileSaver(QObject *parent) : QObject (parent) -{ - char* szThemes = r_str_home(R2_HOME_THEMES); - customR2ThemesLocationPath = szThemes; - r_mem_free(szThemes); - if (!QDir(customR2ThemesLocationPath).exists()) { - QDir().mkpath(customR2ThemesLocationPath); - } - - QDir currDir { QStringLiteral("%1%2%3") - .arg(r_sys_prefix(nullptr)) - .arg(R_SYS_DIR) - .arg(R2_THEMES) - }; - if (currDir.exists()) { - standardR2ThemesLocationPath = currDir.absolutePath(); - } else { - QMessageBox::critical(nullptr, - tr("Standard themes not found!"), - tr("The radare2 standard themes could not be found! This probably means radare2 is not properly installed. If you think it is open an issue please.") - ); - } -} - -QFile::FileError ColorSchemeFileSaver::copy(const QString &srcThemeName, - const QString ©ThemeName) const -{ - QFile fIn(standardR2ThemesLocationPath + QDir::separator() + srcThemeName); - QFile fOut(customR2ThemesLocationPath + QDir::separator() + copyThemeName); - - if (srcThemeName != QStringLiteral("default") && !fIn.open(QFile::ReadOnly)) { - fIn.setFileName(customR2ThemesLocationPath + QDir::separator() + srcThemeName); - if (!fIn.open(QFile::ReadOnly)) { - return fIn.error(); - } - } - - const QString &srcTheme = fIn.readAll(); - fIn.close(); - - if (!fOut.open(QFile::WriteOnly | QFile::Truncate)) { - return fOut.error(); - } - - QStringList options = Core()->cmdj("ecj").object().keys(); - options << cutterSpecificOptions; - QStringList src; - - if (srcThemeName == "default") { - const QString &theme = Config()->getColorTheme(); - Core()->cmd("ecd"); - QJsonObject obj = Core()->cmdj("ecj").object(); - Core()->cmd(QStringLiteral("eco %1").arg(theme)); - QColor back = Config()->getColor(standardBackgroundOptionName); - obj[standardBackgroundOptionName] = QJsonArray({back.red(), back.green(), back.blue()}); - for (const QString &it : obj.keys()) { - QJsonArray rgb = obj[it].toArray(); - if (rgb.size() != 3) { - continue; - } - src.push_back(QStringLiteral("ec %1 rgb:%2") - .arg(it) - .arg(QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt()).name().remove('#'))); - } - } else { - src = srcTheme.split('\n'); - } - - QStringList tmp; - for (const QString &it : src) { - if (it.isEmpty()) { - continue; - } - fOut.write(it.toUtf8() + '\n'); - - tmp = it.split(' '); - if (it.length() > 2 && it.left(2) == "#~") { - options.removeOne(tmp[0].remove("#~").toUtf8()); - } else if (tmp.size() > 1) { - options.removeOne(tmp.at(1)); - } - } - - for (const QString &it : options) { - if (cutterSpecificOptions.contains(it)) { - fOut.write("#~"); - } else { - fOut.write("ec "); - } - fOut.write(QStringLiteral("%1 rgb:%2\n") - .arg(it) - .arg(Config()->getColor(it).name().remove('#')).toUtf8()); - } - - fOut.close(); - - return QFile::FileError::NoError; -} - -QFile::FileError ColorSchemeFileSaver::save(const QString &scheme, const QString &schemeName) const -{ - QFile fOut(customR2ThemesLocationPath + QDir::separator() + schemeName); - if (!fOut.open(QFile::WriteOnly | QFile::Truncate)) - return fOut.error(); - - fOut.write(scheme.toUtf8()); - fOut.close(); - return QFile::FileError::NoError; -} - -bool ColorSchemeFileSaver::isCustomScheme(const QString &schemeName) const -{ - return QFile::exists(QDir(customR2ThemesLocationPath).filePath(schemeName)); -} - -bool ColorSchemeFileSaver::isNameEngaged(const QString &name) const -{ - return (!standardR2ThemesLocationPath.isEmpty() && QFile::exists(standardR2ThemesLocationPath + QDir::separator() + name)) || - QFile::exists(customR2ThemesLocationPath + QDir::separator() + name); -} - -QMap ColorSchemeFileSaver::getCutterSpecific() const -{ - QFile f(customR2ThemesLocationPath + QDir::separator() + Config()->getColorTheme()); - if (!f.open(QFile::ReadOnly)) - return QMap(); - - const QStringList &data = QString(f.readAll()).split('\n'); - f.close(); - - QMap ret; - for (const QString &it : data) { - if (it.length() > 2 && it.left(2) == "#~") { - QStringList currLine = it.split(' '); - if (currLine.size() > 1) { - ret.insert(currLine[0].remove("#~"), currLine[1].replace("rgb:", "#")); - } - } - } - - return ret; -} - -QStringList ColorSchemeFileSaver::getCustomSchemes() const -{ - return QDir(customR2ThemesLocationPath).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name); -} - -void ColorSchemeFileSaver::deleteScheme(const QString &schemeName) const -{ - if (!isCustomScheme(schemeName)) - return; - QFile::remove(customR2ThemesLocationPath + QDir::separator() + schemeName); -} diff --git a/src/common/ColorSchemeFileSaver.h b/src/common/ColorSchemeFileSaver.h deleted file mode 100644 index cd19048a..00000000 --- a/src/common/ColorSchemeFileSaver.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef COLORSCHEMEFILESAVER_H -#define COLORSCHEMEFILESAVER_H - -#include -#include -#include - -constexpr const char *standardBackgroundOptionName = "gui.background"; -constexpr const char *standardTextOptionName = "btext"; -#define ColorSchemeFileWorker() (ColorSchemeFileSaver::instance()) - -class ColorSchemeFileSaver : public QObject -{ - Q_OBJECT -public: - static ColorSchemeFileSaver &instance() - { - static ColorSchemeFileSaver ex; - return ex; - } - - virtual ~ColorSchemeFileSaver() {} - - QFile::FileError copy(const QString &srcSchemeName, const QString ©SchemeName) const; - - QFile::FileError save(const QString &scheme, const QString &schemeName) const; - - bool isCustomScheme(const QString &schemeName) const; - - bool isNameEngaged(const QString &name) const; - - QMap getCutterSpecific() const; - - QStringList getCustomSchemes() const; - - void deleteScheme(const QString &schemeName) const; - -private: - QString standardR2ThemesLocationPath; - QString customR2ThemesLocationPath; - - ColorSchemeFileSaver(QObject *parent = nullptr); - ColorSchemeFileSaver(const ColorSchemeFileSaver &root) = delete; - ColorSchemeFileSaver &operator=(const ColorSchemeFileSaver &) = delete; -}; - -#endif // COLORSCHEMEFILESAVER_H diff --git a/src/common/ColorThemeWorker.cpp b/src/common/ColorThemeWorker.cpp new file mode 100644 index 00000000..a78179d0 --- /dev/null +++ b/src/common/ColorThemeWorker.cpp @@ -0,0 +1,306 @@ +#include "ColorThemeWorker.h" + +#include +#include +#include +#include +#include +#include "common/Configuration.h" + +const QStringList ColorThemeWorker::cutterSpecificOptions = { + "linehl", + "wordhl", + "gui.main", + "gui.imports", + "highlightPC", + "gui.navbar.err", + "gui.navbar.seek", + "gui.navbar.pc", + "gui.navbar.sym", + "gui.dataoffset", + "gui.navbar.code", + "gui.navbar.empty", + "angui.navbar.str", + "gui.disass_selected", + "gui.breakpoint_background", + "gui.overview.node", + "gui.border", + "gui.background", + "gui.alt_background", + "gui.disass_selected" +}; + +const QStringList ColorThemeWorker::radare2UnusedOptions = { + "graph.box", + "graph.box2", + "graph.box3", + "graph.box4", + "graph.current", + "graph.box2", + "widget_sel", + "widget_bg", + "label", + "ai.write", + "invalid", + "ai.seq", + "args", + "ai.read", + "ai.exec", + "ai.ascii", + "prompt", + "graph.traced" +}; + +ColorThemeWorker::ColorThemeWorker(QObject *parent) : QObject (parent) +{ + char* szThemes = r_str_home(R2_HOME_THEMES); + customR2ThemesLocationPath = szThemes; + r_mem_free(szThemes); + if (!QDir(customR2ThemesLocationPath).exists()) { + QDir().mkpath(customR2ThemesLocationPath); + } + + QDir currDir { QStringLiteral("%1%2%3") + .arg(r_sys_prefix(nullptr)) + .arg(R_SYS_DIR) + .arg(R2_THEMES) + }; + if (currDir.exists()) { + standardR2ThemesLocationPath = currDir.absolutePath(); + } else { + QMessageBox::critical(nullptr, + tr("Standard themes not found"), + tr("The radare2 standard themes could not be found. " + "Most likely, radare2 is not properly installed.") + ); + } +} + +QColor ColorThemeWorker::mergeColors(const QColor& upper, const QColor& lower) const +{ + qreal r1, g1, b1, a1; + qreal r2, g2, b2, a2; + qreal r, g, b, a; + + upper.getRgbF(&r1, &g1, &b1, &a1); + lower.getRgbF(&r2, &g2, &b2, &a2); + + a = (1.0 - a1) * a2 + a1; + r = ((1.0 - a1) * a2 * r2 + a1 * r1) / a; + g = ((1.0 - a1) * a2 * g2 + a1 * g1) / a; + b = ((1.0 - a1) * a2 * b2 + a1 * b1) / a; + + QColor res; + res.setRgbF(r, g, b, a); + return res; +} + +QString ColorThemeWorker::copy(const QString &srcThemeName, + const QString ©ThemeName) const +{ + if (!isThemeExist(srcThemeName)) { + return tr("Theme %1 does not exist.") + .arg(srcThemeName); + } + + return save(getTheme(srcThemeName), copyThemeName); +} + +QString ColorThemeWorker::save(const QJsonDocument &theme, const QString &themeName) const +{ + QFile fOut(QDir(customR2ThemesLocationPath).filePath(themeName)); + if (!fOut.open(QFile::WriteOnly | QFile::Truncate)) { + return tr("The file %1 cannot be opened.") + .arg(QFileInfo(fOut).filePath()); + } + + QJsonObject obj = theme.object(); + for (auto it = obj.constBegin(); it != obj.constEnd(); it++) { + QString line; + if (cutterSpecificOptions.contains(it.key())) { + line = "#~%1 %2\n"; + } else { + line = "ec %1 %2\n"; + } + QJsonArray arr = it.value().toArray(); + if (arr.isEmpty()) { + fOut.write(line.arg(it.key()) + .arg(it.value().toVariant().value().name()).toUtf8()); + } else if (arr.size() == 3) { + fOut.write(line.arg(it.key()) + .arg(QColor(arr[0].toInt(), arr[1].toInt(), arr[2].toInt()).name()).toUtf8()); + } + } + + fOut.close(); + return ""; +} + +bool ColorThemeWorker::isCustomTheme(const QString &themeName) const +{ + return QFile::exists(QDir(customR2ThemesLocationPath).filePath(themeName)); +} + +bool ColorThemeWorker::isThemeExist(const QString &name) const +{ + QStringList themes = Core()->getColorThemes(); + return themes.contains(name); +} + +QJsonDocument ColorThemeWorker::getTheme(const QString& themeName) const +{ + int r, g, b; + QJsonObject theme; + QString curr = Config()->getColorTheme(); + + if (themeName != curr) { + Core()->cmd(QString("eco %1").arg(themeName)); + theme = Core()->cmdj("ecj").object(); + Core()->cmd(QString("eco %1").arg(curr)); + } else { + theme = Core()->cmdj("ecj").object(); + } + + if (isCustomTheme(themeName)) { + QFile src(QDir(customR2ThemesLocationPath).filePath(themeName)); + if (!src.open(QFile::ReadOnly)) { + return QJsonDocument(); + } + QStringList sl; + for (auto &line : QString(src.readAll()).split('\n', QString::SkipEmptyParts)) { + sl = line.replace("#~", "ec ").replace("rgb:", "#").split(' ', QString::SkipEmptyParts); + if (sl.size() != 3 || sl[0][0] == '#') { + continue; + } + QColor(sl[2]).getRgb(&r, &g, &b); + theme.insert(sl[1], QJsonArray({r, g, b})); + } + } else { + for (auto &it : cutterSpecificOptions) { + mergeColors(Config()->getColor(it), + Config()->getColor("gui.background")).getRgb(&r, &g, &b); + theme.insert(it, QJsonArray({r, g, b})); + } + } + + for (auto &key : radare2UnusedOptions) { + theme.remove(key); + } + return QJsonDocument(theme); +} + +QString ColorThemeWorker::deleteTheme(const QString &themeName) const +{ + if (!isCustomTheme(themeName)) { + return tr("You can not delete standard radare2 color themes."); + } + if (!isThemeExist(themeName)) { + return tr("Theme %1 does not exist.").arg(themeName); + } + + QFile file(QDir(customR2ThemesLocationPath).filePath(themeName)); + if (file.isWritable()) { + return tr("You have no permission to write to %1") + .arg(QFileInfo(file).filePath()); + } + if (!file.open(QFile::ReadOnly)) { + return tr("File %1 can not be opened.") + .arg(QFileInfo(file).filePath()); + } + if (!file.remove()) { + return tr("File %1 can not be removed.") + .arg(QFileInfo(file).filePath()); + } + return ""; +} + +QString ColorThemeWorker::importTheme(const QString& file) const +{ + QFileInfo src(file); + if (!src.exists()) { + return tr("File %1 does not exist.").arg(file); + } + + bool ok; + bool isTheme = isFileTheme(file, &ok); + if (!ok) { + return tr("File %1 could not be opened. " + "Please make sure you have access to it and try again.") + .arg(file); + } else if (!isTheme) { + return tr("File %1 is not a Cutter color theme").arg(file); + } + + QString name = src.fileName(); + if (isThemeExist(name)) { + return tr("A color theme named %1 already exists.").arg(name); + } + + if (QFile::copy(file, QDir(customR2ThemesLocationPath).filePath(name))) { + return ""; + } else { + return tr("Error occurred during importing. " + "Please make sure you have an access to " + "the directory %1 and try again.") + .arg(src.dir().path()); + } +} + +QString ColorThemeWorker::renameTheme(const QString& themeName, const QString& newName) const +{ + if (isThemeExist(newName)) { + return tr("A color theme named \"%1\" already exists.").arg(newName); + } + + if (!isCustomTheme(themeName)) { + return tr("You can not rename standard radare2 themes."); + } + + QDir dir = customR2ThemesLocationPath; + bool ok = QFile::rename(dir.filePath(themeName), dir.filePath(newName)); + if (!ok) { + return tr("Something went wrong during renaming. " + "Please make sure you have access to the directory \"%1\".").arg(dir.path()); + } + return ""; +} + +bool ColorThemeWorker::isFileTheme(const QString& filePath, bool* ok) const +{ + QFile f(filePath); + if (!f.open(QFile::ReadOnly)) { + *ok = false; + return false; + } + + const QString colors = "black|red|white|green|magenta|yellow|cyan|blue|gray|none"; + QString options = (Core()->cmdj("ecj").object().keys() << cutterSpecificOptions) + .join('|') + .replace(".", "\\."); + QRegExp regexp = QRegExp(QString("((ec\\s+(%1)\\s+(((rgb:|#)([0-9a-fA-F]{3}){1,2})|(%2))))\\s*") + .arg(options) + .arg(colors)); + + for (auto &line : QString(f.readAll()).split('\n', QString::SkipEmptyParts)) { + line.replace("#~", "ec "); + if (!line.isEmpty() && !regexp.exactMatch(line)) { + *ok = true; + return false; + } + } + + *ok = true; + return true; +} + +QStringList ColorThemeWorker::customThemes() const +{ + QStringList themes = Core()->getColorThemes(); + QStringList ret; + for (int i = 0; i < themes.size(); i++) { + if (isCustomTheme(themes[i])) { + ret.push_back(themes[i]); + } + } + return ret; +} diff --git a/src/common/ColorThemeWorker.h b/src/common/ColorThemeWorker.h new file mode 100644 index 00000000..e556ec1c --- /dev/null +++ b/src/common/ColorThemeWorker.h @@ -0,0 +1,130 @@ +#ifndef COLORTHEMEWORKER_H +#define COLORTHEMEWORKER_H + +#include +#include +#include +#include "Cutter.h" +#include + +#define ThemeWorker() (ColorThemeWorker::instance()) + +/** + * @brief The ColorThemeWorker class is a singletone that provides API for working with + * color themes. + */ +class ColorThemeWorker : public QObject +{ + Q_OBJECT +public: + /** + * @brief radare2SpecificOptions is list of all available radare2-only color options. + */ + const QStringList radare2SpecificOptions = Core()->cmdj("ecj").object().keys(); + + /** + * @brief cutterSpecificOptions is list of all available Cutter-only color options. + */ + static const QStringList cutterSpecificOptions; + + /** + * @brief radare2UnusedOptions is a list of all radare2 options that Cutter does not use. + */ + static const QStringList radare2UnusedOptions; + + static ColorThemeWorker &instance() + { + static ColorThemeWorker ex; + return ex; + } + + virtual ~ColorThemeWorker() {} + + + /** + * @brief Copies @a srcThemeName with name @a copyThemeName. + * @param srcThemeName + * Name of theme to be copied. + * @param copyThemeName + * Name of copy. + * @return "" on success or error message. + */ + QString copy(const QString &srcThemeName, const QString ©ThemeName) const; + + /** + * @brief Saves @a theme as @a themeName theme. + * @param theme + * Theme to be saved. + * @param themeName + * Name of theme to save. + * @return "" on success or error message. + */ + QString save(const QJsonDocument& theme, const QString &themeName) const; + + /** + * @brief Returns whether or not @a themeName theme is custom (created by user or imported) or not. + * @param themeName + * Name of theme to check. + */ + bool isCustomTheme(const QString &themeName) const; + + /** + * @brief Returns whether or not @a name theme already exists. + * @return true if theme exists, false - if not. + */ + bool isThemeExist(const QString &name) const; + + /** + * @brief Returns theme as Json where key is option name and value is array of 3 Ints (Red, Green, Blue). + * @param themeName + * Theme to get. + */ + QJsonDocument getTheme(const QString &themeName) const; + + /** + * @brief Deletes theme named @a themeName. + * @param themeName + * Name of theme to be removed. + * @return "" on success or error message. + */ + QString deleteTheme(const QString &themeName) const; + + /** + * @brief Imports theme from @a file. + * @return "" on success or error message. + */ + QString importTheme(const QString& file) const; + + /** + * @brief Renames theme from @a themeName to @a newName. + * @return "" on success or error message. + */ + QString renameTheme(const QString& themeName, const QString& newName) const; + + /** + * @brief Returns whether or not file at @a filePath is a color theme. + * @param filePath + * Path to file to check. + * @param ok + * Output parameter. Indicates wheter or not check was successfull. + * @return true if given file is color theme and ok == true, otherwise returns false. + */ + bool isFileTheme(const QString &filePath, bool *ok) const; + + /** + * @brief Returns list of all custom themes. + */ + QStringList customThemes() const; + +private: + QString standardR2ThemesLocationPath; + QString customR2ThemesLocationPath; + + ColorThemeWorker(QObject *parent = nullptr); + ColorThemeWorker(const ColorThemeWorker &root) = delete; + ColorThemeWorker &operator=(const ColorThemeWorker &) = delete; + + QColor mergeColors(const QColor &upper, const QColor &lower) const; +}; + +#endif // COLORTHEMEWORKER_H diff --git a/src/common/Configuration.cpp b/src/common/Configuration.cpp index 5c4af84a..e04e7093 100644 --- a/src/common/Configuration.cpp +++ b/src/common/Configuration.cpp @@ -7,9 +7,9 @@ #include #include -#include "common/ColorSchemeFileSaver.h" +#include "common/ColorThemeWorker.h" -const QList kCutterQtThemesList = { +const QList kCutterInterfaceThemesList = { { "Native", static_cast(LightFlag | DarkFlag) }, { "Dark", DarkFlag }, { "Light", LightFlag } @@ -54,7 +54,7 @@ static const QHash asmOptions = { }; -Configuration::Configuration() : QObject() +Configuration::Configuration() : QObject(), nativePalette(qApp->palette()) { mPtr = this; if (!s.isWritable()) { @@ -75,7 +75,7 @@ Configuration *Configuration::instance() void Configuration::loadInitial() { - setTheme(getTheme()); + setInterfaceTheme(getInterfaceTheme()); setColorTheme(getColorTheme()); applySavedAsmOptions(); } @@ -210,6 +210,15 @@ void Configuration::loadBaseThemeNative() qApp->setStyleSheet(stylesheet); } + qApp->setPalette(nativePalette); + /* Some widgets does not change its palette when QApplication changes one + * so this loop force all widgets do this, but all widgets take palette from + * QApplication::palette() when they are created so line above is necessary too. + */ + for (auto widget : qApp->allWidgets()) { + widget->setPalette(nativePalette); + } + /* Colors */ // GUI setColor("gui.cflow", QColor(0, 0, 0)); @@ -240,18 +249,18 @@ void Configuration::loadNativeTheme() setColor("gui.background", QColor(30, 30, 30)); setColor("gui.alt_background", QColor(42, 42, 42)); setColor("gui.disass_selected", QColor(35, 35, 35)); - setColor("highlight", QColor(255, 255, 255, 15)); - setColor("highlightWord", QColor(20, 20, 20, 255)); + setColor("linehl", QColor(255, 255, 255, 15)); + setColor("wordhl", QColor(20, 20, 20, 255)); setColor("highlightPC", QColor(87, 26, 7)); setColor("gui.tooltip.background", QColor(42, 44, 46)); setColor("gui.tooltip.foreground", QColor(250, 252, 254)); } else { - setColor("gui.border", QColor(0, 0, 0)); + setColor("gui.border", QColor(0, 0, 0)); setColor("gui.background", QColor(255, 255, 255)); setColor("gui.alt_background", QColor(245, 250, 255)); setColor("gui.disass_selected", QColor(255, 255, 255)); - setColor("highlight", QColor(210, 210, 255, 150)); - setColor("highlightWord", QColor(179, 119, 214, 60)); + setColor("linehl", QColor(210, 210, 255, 150)); + setColor("wordhl", QColor(179, 119, 214, 60)); setColor("highlightPC", QColor(214, 255, 210)); } } @@ -269,6 +278,11 @@ void Configuration::loadLightTheme() f.open(QFile::ReadOnly | QFile::Text); QTextStream ts(&f); QString stylesheet = ts.readAll(); + + QPalette p = qApp->palette(); + p.setColor(QPalette::Text, Qt::black); + qApp->setPalette(p); + qApp->setStyleSheet(stylesheet); } @@ -276,8 +290,8 @@ void Configuration::loadLightTheme() setColor("gui.background", QColor(255, 255, 255)); setColor("gui.alt_background", QColor(245, 250, 255)); setColor("gui.disass_selected", QColor(255, 255, 255)); - setColor("highlight", QColor(210, 210, 255, 150)); - setColor("highlightWord", QColor(179, 119, 214, 60)); + setColor("linehl", QColor(210, 210, 255, 150)); + setColor("wordhl", QColor(179, 119, 214, 60)); setColor("highlightPC", QColor(214, 255, 210)); setColor("gui.navbar.empty", QColor(220, 236, 245)); setColor("gui.navbar.err", QColor(3, 170, 245)); @@ -304,6 +318,9 @@ void Configuration::loadBaseThemeDark() " height: 12px;" "}"; #endif + QPalette p = qApp->palette(); + p.setColor(QPalette::Text, Qt::white); + qApp->setPalette(p); qApp->setStyleSheet(stylesheet); } @@ -343,10 +360,10 @@ void Configuration::loadDarkTheme() // Disassembly nodes background when selected setColor("gui.disass_selected", QColor(31, 34, 40)); // Disassembly line selected - setColor("highlight", QColor(21, 29, 29, 150)); - setColor("highlightWord", QColor(52, 58, 71, 255)); setColor("gui.tooltip.background", QColor(42, 44, 46)); setColor("gui.tooltip.foreground", QColor(250, 252, 254)); + setColor("linehl", QColor(21, 29, 29, 150)); + setColor("wordhl", QColor(52, 58, 71, 255)); } const QFont Configuration::getFont() const @@ -361,20 +378,20 @@ void Configuration::setFont(const QFont &font) emit fontsUpdated(); } -QString Configuration::getLastThemeOf(const CutterQtTheme &currQtTheme) const +QString Configuration::getLastThemeOf(const CutterInterfaceTheme &currInterfaceTheme) const { - return s.value("lastThemeOf." + currQtTheme.name, + return s.value("lastThemeOf." + currInterfaceTheme.name, Config()->getColorTheme()).toString(); } -void Configuration::setTheme(int theme) +void Configuration::setInterfaceTheme(int theme) { - if (theme >= kCutterQtThemesList.size() || + if (theme >= kCutterInterfaceThemesList.size() || theme < 0) { theme = 0; } s.setValue("ColorPalette", theme); - QString themeName = kCutterQtThemesList[theme].name; + QString themeName = kCutterInterfaceThemesList[theme].name; if (themeName == "Native") { loadNativeTheme(); @@ -386,18 +403,18 @@ void Configuration::setTheme(int theme) loadNativeTheme(); } - emit themeChanged(); + emit interfaceThemeChanged(); emit colorsUpdated(); } -const CutterQtTheme *Configuration::getCurrentTheme() +const CutterInterfaceTheme *Configuration::getCurrentTheme() { - int i = getTheme(); - if (i < 0 || i >= kCutterQtThemesList.size()) { + int i = getInterfaceTheme(); + if (i < 0 || i >= kCutterInterfaceThemesList.size()) { i = 0; - setTheme(i); + setInterfaceTheme(i); } - return &kCutterQtThemesList[i]; + return &kCutterInterfaceThemesList[i]; } QString Configuration::getLogoFile() @@ -417,9 +434,9 @@ void Configuration::setColor(const QString &name, const QColor &color) s.setValue("colors." + name, color); } -void Configuration::setLastThemeOf(const CutterQtTheme &currQtTheme, const QString &theme) +void Configuration::setLastThemeOf(const CutterInterfaceTheme &currInterfaceTheme, const QString &theme) { - s.setValue("lastThemeOf." + currQtTheme.name, theme); + s.setValue("lastThemeOf." + currInterfaceTheme.name, theme); } const QColor Configuration::getColor(const QString &name) const @@ -440,25 +457,19 @@ void Configuration::setColorTheme(const QString &theme) Core()->cmd(QStringLiteral("eco %1").arg(theme)); s.setValue("theme", theme); } - // Duplicate interesting colors into our Cutter Settings - // Dirty fix for arrow colors, TODO refactor getColor, setColor, etc. - QJsonDocument colors = Core()->cmdj("ecj"); - QJsonObject colorsObject = colors.object(); - for (auto it = colorsObject.constBegin(); it != colorsObject.constEnd(); it++) { + QJsonObject colorTheme = ThemeWorker().getTheme(theme).object(); + for (auto it = colorTheme.constBegin(); it != colorTheme.constEnd(); it++) { QJsonArray rgb = it.value().toArray(); if (rgb.size() != 3) { continue; } - s.setValue("colors." + it.key(), QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt())); + setColor(it.key(), QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt())); } - QMap cutterSpecific = ColorSchemeFileWorker().getCutterSpecific(); - for (auto &it : cutterSpecific.keys()) - setColor(it, cutterSpecific[it]); - - if (!ColorSchemeFileWorker().isCustomScheme(theme)) { - setTheme(getTheme()); + // Trick Cutter to load colors that are not specified in standard theme + if (!ThemeWorker().isCustomTheme(theme)) { + setInterfaceTheme(getInterfaceTheme()); } emit colorsUpdated(); diff --git a/src/common/Configuration.h b/src/common/Configuration.h index 07aaa8d2..8090b4d7 100644 --- a/src/common/Configuration.h +++ b/src/common/Configuration.h @@ -13,17 +13,18 @@ enum ColorFlags { DarkFlag = 2 }; -struct CutterQtTheme { +struct CutterInterfaceTheme { QString name; ColorFlags flag; }; -extern const QList kCutterQtThemesList; +extern const QList kCutterInterfaceThemesList; class Configuration : public QObject { Q_OBJECT private: + QPalette nativePalette; QSettings s; static Configuration *mPtr; @@ -33,7 +34,6 @@ private: void loadNativeTheme(); void loadLightTheme(); void loadDarkTheme(); - void setColor(const QString &name, const QColor &color); // Asm Options void applySavedAsmOptions(); @@ -63,16 +63,15 @@ public: // Colors bool windowColorIsDark(); - void setLastThemeOf(const CutterQtTheme &currQtTheme, const QString &theme); - QString getLastThemeOf(const CutterQtTheme &currQtTheme) const; - const QColor getColor(const QString &name) const; - void setTheme(int theme); - int getTheme() + void setLastThemeOf(const CutterInterfaceTheme &currInterfaceTheme, const QString &theme); + QString getLastThemeOf(const CutterInterfaceTheme &currInterfaceTheme) const; + void setInterfaceTheme(int theme); + int getInterfaceTheme() { return s.value("ColorPalette", 0).toInt(); } - const CutterQtTheme *getCurrentTheme(); + const CutterInterfaceTheme *getCurrentTheme(); QString getDirProjects(); void setDirProjects(const QString &dir); @@ -102,6 +101,9 @@ public: QString getColorTheme() const { return s.value("theme", "cutter").toString(); } void setColorTheme(const QString &theme); + void setColor(const QString &name, const QColor &color); + const QColor getColor(const QString &name) const; + /** * @brief Get the value of a config var either from r2 or settings, depending on the key. */ @@ -125,7 +127,7 @@ public: signals: void fontsUpdated(); void colorsUpdated(); - void themeChanged(); + void interfaceThemeChanged(); }; #endif // CONFIGURATION_H diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 74e4e06c..908ed196 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -240,7 +240,7 @@ void MainWindow::initToolBar() QObject::connect(configuration, &Configuration::colorsUpdated, [this]() { this->visualNavbar->updateGraphicsScene(); }); - QObject::connect(configuration, &Configuration::themeChanged, this, &MainWindow::chooseThemeIcons); + QObject::connect(configuration, &Configuration::interfaceThemeChanged, this, &MainWindow::chooseThemeIcons); } void MainWindow::initDocks() diff --git a/src/dialogs/WelcomeDialog.cpp b/src/dialogs/WelcomeDialog.cpp index 2b623dbb..ed00bdc7 100644 --- a/src/dialogs/WelcomeDialog.cpp +++ b/src/dialogs/WelcomeDialog.cpp @@ -19,7 +19,7 @@ WelcomeDialog::WelcomeDialog(QWidget *parent) : setWindowFlag(Qt::WindowContextHelpButtonHint, false); ui->logoSvgWidget->load(Config()->getLogoFile()); ui->versionLabel->setText("" + tr("Version ") + CUTTER_VERSION_FULL + ""); - ui->themeComboBox->setCurrentIndex(Config()->getTheme()); + ui->themeComboBox->setCurrentIndex(Config()->getInterfaceTheme()); QSignalBlocker s(ui->updatesCheckBox); ui->updatesCheckBox->setChecked(Config()->getAutoUpdateEnabled()); @@ -53,7 +53,7 @@ WelcomeDialog::~WelcomeDialog() */ void WelcomeDialog::on_themeComboBox_currentIndexChanged(int index) { - Config()->setTheme(index); + Config()->setInterfaceTheme(index); // use "ayu" as the default color theme for dark interface if (Config()->windowColorIsDark()) { diff --git a/src/dialogs/XrefsDialog.cpp b/src/dialogs/XrefsDialog.cpp index 53129a8b..61ab6063 100644 --- a/src/dialogs/XrefsDialog.cpp +++ b/src/dialogs/XrefsDialog.cpp @@ -119,7 +119,7 @@ void XrefsDialog::highlightCurrentLine() if (ui->previewTextEdit->isReadOnly()) { QTextEdit::ExtraSelection selection = QTextEdit::ExtraSelection(); - selection.format.setBackground(ConfigColor("highlight")); + selection.format.setBackground(ConfigColor("linehl")); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = ui->previewTextEdit->textCursor(); selection.cursor.clearSelection(); diff --git a/src/dialogs/preferences/AppearanceOptionsWidget.cpp b/src/dialogs/preferences/AppearanceOptionsWidget.cpp index 371c739f..1a0ebad5 100644 --- a/src/dialogs/preferences/AppearanceOptionsWidget.cpp +++ b/src/dialogs/preferences/AppearanceOptionsWidget.cpp @@ -1,9 +1,14 @@ #include +#include #include +#include #include +#include #include #include #include +#include +#include #include #include "PreferencesDialog.h" @@ -13,22 +18,9 @@ #include "common/Helpers.h" #include "common/Configuration.h" -#include "common/ColorSchemeFileSaver.h" -#include "widgets/ColorSchemePrefWidget.h" - -static const QHash kRelevantSchemes = { - { "ayu", DarkFlag }, - { "consonance", DarkFlag }, - { "darkda", DarkFlag }, - { "onedark", DarkFlag }, - { "solarized", DarkFlag }, - { "zenburn", DarkFlag }, - { "cutter", LightFlag }, - { "dark", LightFlag }, - { "matrix", LightFlag }, - { "tango", LightFlag }, - { "white", LightFlag } -}; +#include "common/ColorThemeWorker.h" +#include "dialogs/preferences/ColorThemeEditDialog.h" +#include "widgets/ColorPicker.h" AppearanceOptionsWidget::AppearanceOptionsWidget(PreferencesDialog *dialog) : QDialog(dialog), @@ -47,6 +39,19 @@ AppearanceOptionsWidget::AppearanceOptionsWidget(PreferencesDialog *dialog) curr = "English"; } ui->languageComboBox->setCurrentText(curr); + + auto setIcons = [this]() { + QColor textColor = palette().text().color(); + ui->editButton->setIcon(getIconFromSvg(":/img/icons/pencil_thin.svg", textColor)); + ui->deleteButton->setIcon(getIconFromSvg(":/img/icons/trash_bin.svg", textColor)); + ui->copyButton->setIcon(getIconFromSvg(":/img/icons/copy.svg", textColor)); + ui->importButton->setIcon(getIconFromSvg(":/img/icons/download_black.svg", textColor)); + ui->exportButton->setIcon(getIconFromSvg(":/img/icons/upload_black.svg", textColor)); + ui->renameButton->setIcon(getIconFromSvg(":/img/icons/rename.svg", textColor)); + }; + setIcons(); + connect(Config(), &Configuration::interfaceThemeChanged, this, setIcons); + connect(ui->languageComboBox, static_cast(&QComboBox::currentIndexChanged), this, @@ -54,6 +59,11 @@ AppearanceOptionsWidget::AppearanceOptionsWidget(PreferencesDialog *dialog) connect(Config(), &Configuration::fontsUpdated, this, &AppearanceOptionsWidget::updateFontFromConfig); + + connect(ui->colorComboBox, &QComboBox::currentTextChanged, + this, [this](const QString &str) { + ui->editButton->setEnabled(ThemeWorker().isCustomTheme(str)); + }); } AppearanceOptionsWidget::~AppearanceOptionsWidget() {} @@ -64,54 +74,23 @@ void AppearanceOptionsWidget::updateFontFromConfig() ui->fontSelectionLabel->setText(currentFont.toString()); } -void AppearanceOptionsWidget::updateThemeFromConfig(bool qtThemeChanged) +void AppearanceOptionsWidget::updateThemeFromConfig(bool interfaceThemeChanged) { // Disconnect currentIndexChanged because clearing the comboxBox and refiling it causes its index to change. - QSignalBlocker signalBlockerColorBox(ui->colorComboBox); QSignalBlocker signalBlockerThemeBox(ui->themeComboBox); - Q_UNUSED(signalBlockerColorBox); - Q_UNUSED(signalBlockerThemeBox); ui->themeComboBox->clear(); - for (auto &it : kCutterQtThemesList) { + for (auto &it : kCutterInterfaceThemesList) { ui->themeComboBox->addItem(it.name); } - int curQtThemeIndex = Config()->getTheme(); - if (curQtThemeIndex >= kCutterQtThemesList.size()) { - curQtThemeIndex = 0; - Config()->setTheme(curQtThemeIndex); + int currInterfaceThemeIndex = Config()->getInterfaceTheme(); + if (currInterfaceThemeIndex >= kCutterInterfaceThemesList.size()) { + currInterfaceThemeIndex = 0; + Config()->setInterfaceTheme(currInterfaceThemeIndex); } - ui->themeComboBox->setCurrentIndex(curQtThemeIndex); - - QList themes = Core()->getColorThemes(); - ui->colorComboBox->clear(); - for (const QString &theme : themes) { - if (ColorSchemeFileWorker().isCustomScheme(theme) || - (kCutterQtThemesList[curQtThemeIndex].flag & kRelevantSchemes[theme])) { - ui->colorComboBox->addItem(theme); - } - } - - QString curTheme = qtThemeChanged - ? Config()->getLastThemeOf(kCutterQtThemesList[curQtThemeIndex]) - : Config()->getColorTheme(); - const int index = ui->colorComboBox->findText(curTheme); - - ui->colorComboBox->setCurrentIndex(index == -1 ? 0 : index); - if (qtThemeChanged || index == -1) { - curTheme = ui->colorComboBox->currentText(); - Config()->setColorTheme(curTheme); - } - ui->colorSchemePrefWidget->updateSchemeFromConfig(); - int maxThemeLen = 0; - for (const QString &str : themes) { - int strLen = str.length(); - if (strLen > maxThemeLen) { - maxThemeLen = strLen; - } - } - ui->colorComboBox->setMinimumContentsLength(maxThemeLen); - ui->colorComboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + ui->themeComboBox->setCurrentIndex(currInterfaceThemeIndex); + ui->colorComboBox->updateFromConfig(interfaceThemeChanged); + ui->editButton->setEnabled(ThemeWorker().isCustomTheme(ui->colorComboBox->currentText())); } void AppearanceOptionsWidget::on_fontSelectionButton_clicked() @@ -127,68 +106,154 @@ void AppearanceOptionsWidget::on_fontSelectionButton_clicked() void AppearanceOptionsWidget::on_themeComboBox_currentIndexChanged(int index) { - Config()->setTheme(index); + Config()->setInterfaceTheme(index); updateThemeFromConfig(); } -void AppearanceOptionsWidget::on_colorComboBox_currentIndexChanged(int index) +void AppearanceOptionsWidget::on_editButton_clicked() { - QString theme = ui->colorComboBox->itemText(index); - - int curQtThemeIndex = Config()->getTheme(); - if (curQtThemeIndex >= kCutterQtThemesList.size()) { - curQtThemeIndex = 0; - Config()->setTheme(curQtThemeIndex); - } - - Config()->setLastThemeOf(kCutterQtThemesList[curQtThemeIndex], theme); - Config()->setColorTheme(theme); - ui->colorSchemePrefWidget->updateSchemeFromConfig(); + ColorThemeEditDialog dial; + dial.setWindowTitle(tr("Theme Editor - <%1>").arg(ui->colorComboBox->currentText())); + dial.exec(); + ui->colorComboBox->updateFromConfig(false); } void AppearanceOptionsWidget::on_copyButton_clicked() { - QString newSchemeName; - do { - newSchemeName = QInputDialog::getText(this, tr("Enter scheme name"), - tr("Name:"), QLineEdit::Normal, - QDir::home().dirName()); - } while ((!newSchemeName.isEmpty() && ColorSchemeFileWorker().isNameEngaged(newSchemeName)) - || newSchemeName.contains(QRegExp("[^\\w.()\\[\\]_-]")) - || newSchemeName.startsWith('.')); + QString currColorTheme = ui->colorComboBox->currentText(); - if (newSchemeName.isEmpty()) + QString newThemeName; + do { + newThemeName = QInputDialog::getText(this, tr("Enter theme name"), + tr("Name:"), QLineEdit::Normal, + currColorTheme + tr(" - copy")); + } while (!newThemeName.isEmpty() && ThemeWorker().isThemeExist(newThemeName)); + + if (newThemeName.isEmpty()) { return; - ColorSchemeFileWorker().copy(Config()->getColorTheme(), newSchemeName); - Config()->setColorTheme(newSchemeName); - ui->colorSchemePrefWidget->updateSchemeFromConfig(); + } + ThemeWorker().copy(currColorTheme, newThemeName); + Config()->setColorTheme(newThemeName); updateThemeFromConfig(false); } void AppearanceOptionsWidget::on_deleteButton_clicked() { - if (ColorSchemeFileWorker().isCustomScheme(Config()->getColorTheme())) { - QMessageBox mb; - mb.setWindowTitle(tr("Delete")); - mb.setText(tr("Are you sure you want to delete theme ") + Config()->getColorTheme()); - mb.setIcon(QMessageBox::Question); - mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - if (mb.exec() == QMessageBox::Yes) { - ColorSchemeFileWorker().deleteScheme(Config()->getColorTheme()); - updateThemeFromConfig(false); + QString currTheme = ui->colorComboBox->currentText(); + if (!ThemeWorker().isCustomTheme(currTheme)) { + QMessageBox::critical(nullptr, tr("Error"), ThemeWorker().deleteTheme(currTheme)); + return; + } + int ret = QMessageBox::question(nullptr, + tr("Delete"), + tr("Are you sure you want to delete %1?") + .arg(currTheme)); + if (ret == QMessageBox::Yes) { + QString err = ThemeWorker().deleteTheme(currTheme); + updateThemeFromConfig(false); + if (!err.isEmpty()) { + QMessageBox::critical(nullptr, tr("Error"), err); } } } +void AppearanceOptionsWidget::on_importButton_clicked() +{ + QString fileName = QFileDialog::getOpenFileName(this, + "", + QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); + if (fileName.isEmpty()) { + return; + } + + QString err = ThemeWorker().importTheme(fileName); + QString themeName = QFileInfo(fileName).fileName(); + if (err.isEmpty()) { + QMessageBox::information(this, + tr("Success"), + tr("Color theme %1 was successfully imported.").arg(themeName)); + Config()->setColorTheme(themeName); + updateThemeFromConfig(false); + } else { + QMessageBox::critical(this, tr("Error"), err); + } +} + +void AppearanceOptionsWidget::on_exportButton_clicked() +{ + QString theme = ui->colorComboBox->currentText(); + QString file = QFileDialog::getSaveFileName(this, + "", + QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + + QDir::separator() + theme); + if (file.isEmpty()) { + return; + } + + // User already gave his consent for this in QFileDialog::getSaveFileName() + if (QFileInfo(file).exists()) { + QFile(file).remove(); + } + QString err = ThemeWorker().save(ThemeWorker().getTheme(theme), file); + if (err.isEmpty()) { + QMessageBox::information(this, + tr("Success"), + tr("Color theme %1 was successfully exported.").arg(theme)); + } else { + QMessageBox::critical(this, tr("Error"), err); + } +} + +void AppearanceOptionsWidget::on_renameButton_clicked() +{ + QString currColorTheme = Config()->getColorTheme(); + QString newName = QInputDialog::getText(this, + tr("Enter new theme name"), + tr("Name:"), + QLineEdit::Normal, + currColorTheme); + if (newName.isEmpty() || newName == currColorTheme) { + return; + } + + QString err = ThemeWorker().renameTheme(currColorTheme, newName); + if (!err.isEmpty()) { + QMessageBox::critical(this, tr("Error"), err); + } else { + Config()->setColorTheme(newName); + updateThemeFromConfig(false); + } +} + void AppearanceOptionsWidget::onLanguageComboBoxCurrentIndexChanged(int index) { QString language = ui->languageComboBox->itemText(index).toLower(); if (Config()->setLocaleByName(language)) { QMessageBox::information(this, - tr("Language settings"), - tr("Language will be changed after next application start.")); + tr("Language settings"), + tr("Language will be changed after next application start.")); return; } qWarning() << tr("Cannot set language, not found in available ones"); -} \ No newline at end of file +} + +QIcon AppearanceOptionsWidget::getIconFromSvg(const QString& fileName, const QColor& after, const QColor& before) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + return QIcon(); + } + QString data = file.readAll(); + data.replace(QRegExp(QString("#%1").arg(before.isValid() ? before.name().remove(0, 1) : "[0-9a-fA-F]{6}")), + QString("%1").arg(after.name())); + + QSvgRenderer svgRenderer(data.toUtf8()); + QPixmap pix(svgRenderer.defaultSize()); + pix.fill(Qt::transparent); + + QPainter pixPainter(&pix); + svgRenderer.render(&pixPainter); + + return pix; +} diff --git a/src/dialogs/preferences/AppearanceOptionsWidget.h b/src/dialogs/preferences/AppearanceOptionsWidget.h index 1182944c..d3d84845 100644 --- a/src/dialogs/preferences/AppearanceOptionsWidget.h +++ b/src/dialogs/preferences/AppearanceOptionsWidget.h @@ -27,14 +27,45 @@ private: private slots: void updateFontFromConfig(); - void updateThemeFromConfig(bool qtThemeChanged = true); + void updateThemeFromConfig(bool interfaceThemeChanged = true); void on_fontSelectionButton_clicked(); void on_themeComboBox_currentIndexChanged(int index); - void on_colorComboBox_currentIndexChanged(int index); void on_copyButton_clicked(); void on_deleteButton_clicked(); + + /** + * @brief Imports theme file specified by user to custom themes + * directory. + */ + void on_importButton_clicked(); + + /** + * @brief Exports current color theme to file + * specified by user. + */ + void on_exportButton_clicked(); + + /** + * @brief Shows dialog to rename current color theme. + */ + void on_renameButton_clicked(); + void on_editButton_clicked(); void onLanguageComboBoxCurrentIndexChanged(int index); + + + /** + * @brief Changes all @a before colors in given @a fileName svg file to @a after + * and returns result icon. If @a before is not specified, changes all colors. + * @param fileName + * Path to svg file. + * @param after + * What color should be inserted instead of old one. + * @param before + * Color that should be repalced. + */ + QIcon getIconFromSvg(const QString &fileName, const QColor &after, const QColor &before = QColor()); + }; diff --git a/src/dialogs/preferences/AppearanceOptionsWidget.ui b/src/dialogs/preferences/AppearanceOptionsWidget.ui index 23dae6f5..060c20a9 100644 --- a/src/dialogs/preferences/AppearanceOptionsWidget.ui +++ b/src/dialogs/preferences/AppearanceOptionsWidget.ui @@ -6,19 +6,22 @@ 0 0 - 442 - 268 + 619 + 225 Appearance - - QLayout::SetMinAndMaxSize - + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + Qt::AlignCenter + @@ -56,10 +59,27 @@ + + + + Language: + + + + + + + + 0 + 0 + + + + - Qt Theme: + Interface Theme: @@ -86,14 +106,14 @@ - Color Theme + Color Theme: - + 0 @@ -102,6 +122,34 @@ + + + + Edit Theme + + + + + + + :/img/icons/pencil_thin.svg:/img/icons/pencil_thin.svg + + + + + + + Rename + + + + + + + :/img/icons/rename.svg:/img/icons/rename.svg + + + @@ -110,18 +158,63 @@ 0 - + Copy + + + + + + :/img/icons/copy.svg:/img/icons/copy.svg + - + Delete + + + + + + :/img/icons/trash_bin.svg:/img/icons/trash_bin.svg + + + + + Export + + + + + + + :/img/icons/upload_black.svg:/img/icons/upload_black.svg + + + + + + + Import + + + + + + + :/img/icons/download_black.svg:/img/icons/download_black.svg + + + + + + @@ -137,27 +230,20 @@ - - - - Language - - - - - - - - 0 - 0 - - - - - + + + Qt::Vertical + + + + 20 + 40 + + + @@ -168,12 +254,13 @@ - ColorSchemePrefWidget - QWidget -
widgets/ColorSchemePrefWidget.h
- 1 + ColorThemeComboBox + QComboBox +
widgets/ColorThemeComboBox.h
- + + + diff --git a/src/dialogs/preferences/ColorThemeEditDialog.cpp b/src/dialogs/preferences/ColorThemeEditDialog.cpp new file mode 100644 index 00000000..5e8db794 --- /dev/null +++ b/src/dialogs/preferences/ColorThemeEditDialog.cpp @@ -0,0 +1,146 @@ +#include "ColorThemeEditDialog.h" +#include "ui_ColorThemeEditDialog.h" + +#include "common/ColorThemeWorker.h" +#include "common/Configuration.h" + +#include "widgets/ColorThemeListView.h" +#include "widgets/DisassemblyWidget.h" + +#include +#include + +ColorThemeEditDialog::ColorThemeEditDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ColorThemeEditDialog), + configSignalBlocker(Config()), // Blocks signals from Config to avoid updating of widgets during editing + colorTheme(Config()->getColorTheme()) +{ + ui->setupUi(this); + ui->colorComboBox->setShowOnlyCustom(true); + + previewDisasmWidget = new DisassemblyWidget(nullptr); + previewDisasmWidget->setObjectName("Preview Disasm"); + previewDisasmWidget->setPreviewMode(true); + previewDisasmWidget->setMinimumSize(qApp->screenAt(previewDisasmWidget->pos())->size() * 0.5); + previewDisasmWidget->setWindowTitle(tr("Disassembly Preview")); + previewDisasmWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); + ui->colorPickerAndPreviewLayout->addWidget(previewDisasmWidget); + + + connect(ui->colorThemeListView, &ColorThemeListView::blink, + previewDisasmWidget, &DisassemblyWidget::colorsUpdatedSlot); + + connect(ui->colorThemeListView, &ColorThemeListView::itemChanged, + ui->colorPicker, &ColorPicker::updateColor); + ui->colorThemeListView->setCurrentIndex(ui->colorThemeListView->model()->index(0, 0)); + + connect(ui->colorPicker, &ColorPicker::colorChanged, this, &ColorThemeEditDialog::colorOptionChanged); + + connect(ui->colorComboBox, &ColorThemeComboBox::currentTextChanged, + this, &ColorThemeEditDialog::editThemeChanged); +} + +ColorThemeEditDialog::~ColorThemeEditDialog() +{ + delete ui; + previewDisasmWidget->deleteLater(); +} + +void ColorThemeEditDialog::accept() +{ + colorTheme = Config()->getColorTheme(); + QJsonDocument sch = qobject_cast(ui->colorThemeListView->model())->getTheme(); + if (ThemeWorker().isCustomTheme(colorTheme)) { + QString err = ThemeWorker().save(sch, colorTheme); + if (!err.isEmpty()) { + QMessageBox::critical(this, tr("Error"), err); + return; + } + } + + configSignalBlocker.unblock(); + Config()->setColorTheme(colorTheme); + + QDialog::accept(); +} + +void ColorThemeEditDialog::reject() +{ + if (themeWasEdited(ui->colorComboBox->currentText()) && + QMessageBox::question(this, + tr("Unsaved changes"), + tr("Are you sure you want to exit without saving? " + "All changes will be lost.")) == QMessageBox::No) { + return; + } + configSignalBlocker.unblock(); + Config()->setColorTheme(colorTheme); + + QDialog::reject(); +} + +void ColorThemeEditDialog::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Escape: + if (ui->colorPicker->isPickingFromScreen()) { + ui->colorPicker->stopPickingFromScreen(); + } + // fallthrough + case Qt::Key_Return: + event->accept(); + return; + default: + QDialog::keyPressEvent(event); + } +} + +void ColorThemeEditDialog::colorOptionChanged(const QColor& newColor) +{ + auto model = qobject_cast(ui->colorThemeListView->model()); + QModelIndex currIndex = ui->colorThemeListView->currentIndex(); + + if (!currIndex.isValid()) { + return; + } + + ColorOption currOption = currIndex.data(Qt::UserRole).value(); + currOption.color = newColor; + currOption.changed = true; + model->setData(currIndex, QVariant::fromValue(currOption)); + + Config()->setColor(currOption.optionName, currOption.color); + if (!ColorThemeWorker::cutterSpecificOptions.contains(currOption.optionName)) { + Core()->cmd(QString("ec %1 %2").arg(currOption.optionName).arg(currOption.color.name())); + } + previewDisasmWidget->colorsUpdatedSlot(); +} + +void ColorThemeEditDialog::editThemeChanged(const QString& newTheme) +{ + if (themeWasEdited(colorTheme)) { + int ret = QMessageBox::question(this, + tr("Unsaved changes"), + tr("Are you sure you want to exit without saving? " + "All changes will be lost.")); + if (ret == QMessageBox::No) { + QSignalBlocker s(ui->colorComboBox); // avoid second call of this func + int index = ui->colorComboBox->findText(colorTheme); + index = index == -1 ? 0 : index; + ui->colorComboBox->setCurrentIndex(index); + Config()->setColorTheme(colorTheme); + return; + } + } + colorTheme = newTheme; + qobject_cast(ui->colorThemeListView->model())->updateTheme(); + previewDisasmWidget->colorsUpdatedSlot(); + setWindowTitle(tr("Theme Editor - <%1>").arg(colorTheme)); +} + +bool ColorThemeEditDialog::themeWasEdited(const QString& theme) const +{ + auto model = qobject_cast(ui->colorThemeListView->model()); + return ThemeWorker().getTheme(theme) != model->getTheme(); +} diff --git a/src/dialogs/preferences/ColorThemeEditDialog.h b/src/dialogs/preferences/ColorThemeEditDialog.h new file mode 100644 index 00000000..417219d2 --- /dev/null +++ b/src/dialogs/preferences/ColorThemeEditDialog.h @@ -0,0 +1,52 @@ +#ifndef COLORTHEMEEDITDIALOG_H +#define COLORTHEMEEEDITDIALOG_H + +#include + +class DisassemblyWidget; + +namespace Ui { +class ColorThemeEditDialog; +} + +class ColorThemeEditDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ColorThemeEditDialog(QWidget *parent = nullptr); + ~ColorThemeEditDialog() override; + +public slots: + void accept() override; + void reject() override; + +protected slots: + void keyPressEvent(QKeyEvent *event) override; + +private slots: + /** + * @brief Sets @a newColor color for current option. + * @param newColor + * New color for current color option. + */ + void colorOptionChanged(const QColor &newColor); + + /** + * @brief Changes current theme to edit. + * @param newTheme + * Name of new theme. + */ + void editThemeChanged(const QString &newTheme); + +private: + bool themeWasEdited(const QString &theme) const; + +private: + Ui::ColorThemeEditDialog *ui; + QSignalBlocker configSignalBlocker; + DisassemblyWidget *previewDisasmWidget; + QString colorTheme; +}; + +#endif // COLORTHEMEEDITDIALOG_H diff --git a/src/dialogs/preferences/ColorThemeEditDialog.ui b/src/dialogs/preferences/ColorThemeEditDialog.ui new file mode 100644 index 00000000..daea4cc7 --- /dev/null +++ b/src/dialogs/preferences/ColorThemeEditDialog.ui @@ -0,0 +1,158 @@ + + + ColorThemeEditDialog + + + + 0 + 0 + 1437 + 617 + + + + Dialog + + + + + + + + + + + + Color Theme: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + + 400 + 400 + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + ColorPicker + QWidget +
widgets/ColorPicker.h
+ 1 +
+ + ColorThemeListView + QWidget +
widgets/ColorThemeListView.h
+ 1 +
+ + ColorThemeComboBox + QComboBox +
widgets/ColorThemeComboBox.h
+
+
+ + + + buttonBox + accepted() + ColorThemeEditDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ColorThemeEditDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/dialogs/preferences/PreferencesDialog.cpp b/src/dialogs/preferences/PreferencesDialog.cpp index c4657bc7..d12130ff 100644 --- a/src/dialogs/preferences/PreferencesDialog.cpp +++ b/src/dialogs/preferences/PreferencesDialog.cpp @@ -69,7 +69,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) QTreeWidgetItem *defitem = ui->configCategories->topLevelItem(0); ui->configCategories->setCurrentItem(defitem, 0); - connect(Config(), &Configuration::themeChanged, this, &PreferencesDialog::chooseThemeIcons); + connect(Config(), &Configuration::interfaceThemeChanged, this, &PreferencesDialog::chooseThemeIcons); chooseThemeIcons(); } diff --git a/src/img/icons/download_black.svg b/src/img/icons/download_black.svg new file mode 100644 index 00000000..48e01c50 --- /dev/null +++ b/src/img/icons/download_black.svg @@ -0,0 +1,101 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/img/icons/pencil_thin.svg b/src/img/icons/pencil_thin.svg new file mode 100644 index 00000000..50e09eb6 --- /dev/null +++ b/src/img/icons/pencil_thin.svg @@ -0,0 +1,99 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/img/icons/rename.svg b/src/img/icons/rename.svg new file mode 100644 index 00000000..36331c81 --- /dev/null +++ b/src/img/icons/rename.svg @@ -0,0 +1,126 @@ + + + + + + + + Part of the Flat Icon Collection (Wed Aug 25 23:29:46 2004) + + + + hash + + action + computer + icons + theme + + + + + Danny Allen + + + + + Danny Allen + + + + + Danny Allen + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + diff --git a/src/img/icons/reset.svg b/src/img/icons/reset.svg new file mode 100644 index 00000000..d1912f0d --- /dev/null +++ b/src/img/icons/reset.svg @@ -0,0 +1,53 @@ + + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon image/svg+xml + + \ No newline at end of file diff --git a/src/img/icons/save_black.svg b/src/img/icons/save_black.svg new file mode 100644 index 00000000..f03e7b46 --- /dev/null +++ b/src/img/icons/save_black.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/src/img/icons/trash_bin.svg b/src/img/icons/trash_bin.svg new file mode 100644 index 00000000..b96ca169 --- /dev/null +++ b/src/img/icons/trash_bin.svg @@ -0,0 +1,103 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/img/icons/upload_black.svg b/src/img/icons/upload_black.svg new file mode 100644 index 00000000..1c2e7c75 --- /dev/null +++ b/src/img/icons/upload_black.svg @@ -0,0 +1,101 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index c51225e2..87c71bb6 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -879,10 +879,7 @@ void DisassemblyContextMenu::initAction(QAction *action, QString name, return; } action->setShortcut(keySequence); - auto pWidget = parentWidget(); - auto shortcut = new QShortcut(keySequence, pWidget); - shortcut->setContext(Qt::WidgetWithChildrenShortcut); - connect(shortcut, SIGNAL(activated()), this, slot); + action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } void DisassemblyContextMenu::initAction(QAction *action, QString name, @@ -893,10 +890,5 @@ void DisassemblyContextMenu::initAction(QAction *action, QString name, return; } action->setShortcuts(keySequenceList); - auto pWidget = parentWidget(); - for (auto keySequence : keySequenceList) { - auto shortcut = new QShortcut(keySequence, pWidget); - shortcut->setContext(Qt::WidgetWithChildrenShortcut); - connect(shortcut, SIGNAL(activated()), this, slot); - } + action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } diff --git a/src/resources.qrc b/src/resources.qrc index dcf19e80..45b5a8c8 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -90,5 +90,12 @@ img/icons/light/plugins.svg python/cutter.py python/reg_qtres_importer.py + img/icons/download_black.svg + img/icons/pencil_thin.svg + img/icons/save_black.svg + img/icons/trash_bin.svg + img/icons/upload_black.svg + img/icons/rename.svg + img/icons/reset.svg diff --git a/src/widgets/ColorPicker.cpp b/src/widgets/ColorPicker.cpp new file mode 100644 index 00000000..fe60a8af --- /dev/null +++ b/src/widgets/ColorPicker.cpp @@ -0,0 +1,333 @@ +#include "ColorPicker.h" +#include "ui_ColorPicker.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace ColorPickerHelpers; + +ColorPickArea::ColorPickArea(QWidget *parent) : ColorPickerWidget(parent) +{ + setMouseTracking(false); +} + +void ColorPickArea::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + + for (int x = event->rect().x(); x <= event->rect().right(); x++) { + for (int y = event->rect().y(); y <= event->rect().bottom(); y++) { + qreal h, s, v; + QColor c = pointToColor(x, y); + c.getHsvF(&h, &s, &v); + c.setHsvF(h, s, 1); + p.setPen(c); + p.drawPoint(x, y); + } + } + + p.setPen(QPen(Qt::black, 3)); + QPoint curr = colorToPoint(currColor); + p.drawLine(curr - QPoint(0, 10), curr + QPoint(0, 10)); + p.drawLine(curr - QPoint(10, 0), curr + QPoint(10, 0)); + + p.end(); + QWidget::paintEvent(event); +} + +ColorPickerWidget::ColorPickerWidget(QWidget* parent) : ColorPickWidgetAbstract(parent) +{ + +} + +void ColorPickerWidget::mouseReleaseEvent(QMouseEvent* event) +{ + mouseEvent(event); +} + +void ColorPickerWidget::mousePressEvent(QMouseEvent* event) +{ + mouseEvent(event); +} + +void ColorPickerWidget::mouseMoveEvent(QMouseEvent* event) +{ + mouseEvent(event); +} + +QColor ColorPickArea::pointToColor(int x, int y) const +{ + QColor color; + qreal h, s, v; + currColor.getHsvF(&h, &s, &v); + color.setHsvF((qreal)x / width(), + 1.0 - (qreal)y / height(), + v); + return color; +} + +QPoint ColorPickArea::colorToPoint(const QColor& color) const +{ + qreal h, s, v; + color.getHsvF(&h, &s, &v); + return QPoint(h * width(), (1.0 - s) * height()); +} + +void ColorPickerWidget::mouseEvent(QMouseEvent* event) +{ + QPoint pos = event->pos(); + if (!rect().contains(pos.x(), rect().y())) { + pos.setX(rect().x() < pos.x() + ? rect().right() + 1 + : rect().x()); + } + if (!rect().contains(rect().x(), pos.y())) { + pos.setY(rect().y() < pos.y() + ? rect().bottom() + 1 + : rect().y()); + } + currColor = pointToColor(pos.x(), pos.y()); + emit colorChanged(currColor); + repaint(QRect(event->pos() - QPoint(10, 10), event->pos() + QPoint(10, 10))); +} + +void ColorPickWidgetAbstract::setColor(const QColor& color) +{ + currColor = color; + repaint(); +} + +void ColorValueBar::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + QColor color = currColor; + qreal h, s, v; + currColor.getHsvF(&h, &s, &v); + v = 1.0 - v; + + const int trianleSize = 10; + QRect barRect = rect(); + barRect.setWidth(barRect.width() - trianleSize); + + + for (int y = barRect.y(); y <= barRect.bottom(); y++) { + color.setHsvF(h, s, 1.0 - (qreal)y / height()); + p.setPen(color); + p.drawLine(barRect.x(), y, barRect.right(), y); + } + + p.setPen(palette().alternateBase().color()); + p.drawRect(rect()); + + QRectF triangleRect = QRectF(barRect.right(), v * height() - trianleSize / 2, + trianleSize, trianleSize); + + QPainterPath path; + path.moveTo(triangleRect.left(), triangleRect.top() + triangleRect.height() / 2); + path.lineTo(triangleRect.topRight()); + path.lineTo(triangleRect.bottomRight()); + path.lineTo(triangleRect.left(), triangleRect.top() + triangleRect.height() / 2); + + p.fillPath(path, palette().text().color()); + + p.end(); + QWidget::paintEvent(event); +} + +QColor ColorValueBar::pointToColor(int x, int y) const +{ + Q_UNUSED(x) + QColor color = currColor; + qreal h, s, v; + color.getHsvF(&h, &s, &v); + color.setHsvF(h, s, 1.0 - (qreal)y / height()); + return color; +} + +QPoint ColorValueBar::colorToPoint(const QColor& color) const +{ + qreal h, s, v; + color.getHsvF(&h, &s, &v); + return QPoint(rect().x(), (1.0 - v) * height()); +} + +ColorPicker::ColorPicker(QWidget* parent) : + ColorPickWidgetAbstract(parent), + ui(new Ui::ColorPicker), + pickingFromScreen(false) +{ + ui->setupUi(this); + connect(ui->colorPickArea, &ColorPickArea::colorChanged, + this, &ColorPicker::setColor); + connect(ui->valuePickBar, &ColorValueBar::colorChanged, + this, &ColorPicker::setColor); + connect(this, &ColorPicker::colorChanged, + ui->colorPickArea, &ColorPickArea::setColor); + connect(this, &ColorPicker::colorChanged, + ui->valuePickBar, &ColorValueBar::setColor); + connect(this, &ColorPicker::colorChanged, + ui->colorShow, &ColorShowWidget::setColor); + + connect(ui->hueSpinBox, static_cast(&QSpinBox::valueChanged), + this, &ColorPicker::colorChannelChanged); + connect(ui->satSpinBox, static_cast(&QSpinBox::valueChanged), + this, &ColorPicker::colorChannelChanged); + connect(ui->valSpinBox, static_cast(&QSpinBox::valueChanged), + this, &ColorPicker::colorChannelChanged); + connect(ui->redSpinBox, static_cast(&QSpinBox::valueChanged), + this, &ColorPicker::colorChannelChanged); + connect(ui->blueSpinBox, static_cast(&QSpinBox::valueChanged), + this, &ColorPicker::colorChannelChanged); + connect(ui->greenSpinBox, static_cast(&QSpinBox::valueChanged), + this, &ColorPicker::colorChannelChanged); + + connect(ui->hexLineEdit, &QLineEdit::textChanged, this, &ColorPicker::colorChannelChanged); + + connect(ui->pickColorFromScreenButton, &QPushButton::clicked, this, &ColorPicker::startPickingFromScreen); +} + +ColorPicker::~ColorPicker() +{ + if (pickingFromScreen) { + setColor(QApplication::screenAt(QCursor::pos())->grabWindow(QApplication::desktop()->winId()) + .toImage().pixelColor(QCursor::pos())); + stopPickingFromScreen(); + } +} + +void ColorPicker::setColor(const QColor& color) +{ + updateColor(color); + emit colorChanged(currColor); +} + +void ColorPicker::colorChannelChanged() +{ + QString txt = ui->hexLineEdit->text(); + if (!QRegExp("#[0-9a-fA-F]{6}").exactMatch(txt)) { + return; + } + QColor hexColor = txt; + + int h, s, v; + h = ui->hueSpinBox->value(); + s = ui->satSpinBox->value(); + v = ui->valSpinBox->value(); + QColor hsvColor; + hsvColor.setHsv(h, s, v); + + int r, g, b; + r = ui->redSpinBox->value(); + g = ui->greenSpinBox->value(); + b = ui->blueSpinBox->value(); + QColor rgbColor; + rgbColor.setRgb(r, g, b); + + if (hexColor.isValid() && hexColor != currColor) { + setColor(hexColor); + } else if (rgbColor.isValid() && rgbColor != currColor) { + setColor(rgbColor); + } else if (hsvColor.isValid() && hsvColor != currColor) { + setColor(hsvColor); + } +} + +void ColorPicker::updateColor(const QColor& color) +{ + QSignalBlocker s0(ui->redSpinBox); + QSignalBlocker s1(ui->blueSpinBox); + QSignalBlocker s2(ui->greenSpinBox); + + QSignalBlocker s3(ui->valSpinBox); + QSignalBlocker s4(ui->satSpinBox); + QSignalBlocker s5(ui->hueSpinBox); + + QSignalBlocker s6(ui->hexLineEdit); + currColor = color; + + ui->hexLineEdit->setText(currColor.name()); + + int h, s, v; + currColor.getHsv(&h, &s, &v); + ui->hueSpinBox->setValue(h); + ui->satSpinBox->setValue(s); + ui->valSpinBox->setValue(v); + + int r, g, b; + currColor.getRgb(&r, &g, &b); + ui->redSpinBox->setValue(r); + ui->greenSpinBox->setValue(g); + ui->blueSpinBox->setValue(b); + + ui->valuePickBar->setColor(color); + ui->colorPickArea->setColor(color); + ui->colorShow->setColor(color); +} + +void ColorPicker::startPickingFromScreen() +{ + if (!pickingFromScreen) { + setMouseTracking(true); + grabMouse(Qt::CursorShape::CrossCursor); + pickingFromScreen = true; + bufferColor = currColor; + } +} + +void ColorPicker::mouseReleaseEvent(QMouseEvent* event) +{ + if (pickingFromScreen) { + const QDesktopWidget *desktop = QApplication::desktop(); + const QPixmap pixmap = QGuiApplication::screens().at(desktop->screenNumber()) + ->grabWindow(desktop->winId(), + QCursor::pos().x(), QCursor::pos().y(), 1, 1); + setColor(pixmap.toImage().pixel(0, 0)); + pickingFromScreen = false; + setMouseTracking(false); + releaseMouse(); + } + QWidget::mouseReleaseEvent(event); +} + +void ColorPicker::mouseMoveEvent(QMouseEvent* event) +{ + if (pickingFromScreen) { + const QDesktopWidget *desktop = QApplication::desktop(); + const QPixmap pixmap = QGuiApplication::screens().at(desktop->screenNumber()) + ->grabWindow(desktop->winId(), + QCursor::pos().x(), QCursor::pos().y(), 1, 1); + updateColor(pixmap.toImage().pixel(0, 0)); + } + QWidget::mouseMoveEvent(event); +} + +bool ColorPicker::isPickingFromScreen() const +{ + return pickingFromScreen; +} + +void ColorPicker::stopPickingFromScreen() +{ + if (pickingFromScreen) { + pickingFromScreen = false; + updateColor(bufferColor); + releaseMouse(); + setMouseTracking(false); + } +} + +ColorShowWidget::ColorShowWidget(QWidget* parent) : ColorPickWidgetAbstract(parent) { } + +void ColorShowWidget::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + p.setPen(currColor); + p.setBrush(QBrush(currColor)); + p.drawRect(event->rect()); + p.end(); +} diff --git a/src/widgets/ColorPicker.h b/src/widgets/ColorPicker.h new file mode 100644 index 00000000..41b6987e --- /dev/null +++ b/src/widgets/ColorPicker.h @@ -0,0 +1,165 @@ +#ifndef COLORPICKER_H +#define COLORPICKER_H + +#include + +/** + * @namespace ColorPickerHelpers is a namespace that hides all classes needed for ColorPicker class, + * because classes inherite QObject can not be declared in *.cpp files or inside of another class. + */ +namespace ColorPickerHelpers { +class ColorPickWidgetAbstract : public QWidget +{ + Q_OBJECT +public: + ColorPickWidgetAbstract(QWidget *parent = nullptr): QWidget(parent) {} + virtual ~ColorPickWidgetAbstract() {} + +signals: + void colorChanged(const QColor& color); + +public slots: + virtual void setColor(const QColor& color); + +protected: + QColor currColor; +}; +} + +namespace Ui { +class ColorPicker; +} + +/** + * @brief The ColorPicker class provides widget that allows user to pick color + * from screen or from palette or type in HSV or RGB or HEX representation of color. + */ +class ColorPicker : public ColorPickerHelpers::ColorPickWidgetAbstract +{ + Q_OBJECT +public: + explicit ColorPicker(QWidget *parent = nullptr); + ~ColorPicker(); + + /** + * @brief isPickingFromScreen returns true if color picker is picking from screen. + */ + bool isPickingFromScreen() const; + + +public slots: + /** + * @brief setColor sets displayed color to @a color and emits colorChanged signal. + */ + virtual void setColor(const QColor &color) override; + + void colorChannelChanged(); + + /** + * @brief updateColor sets displayed color to @a color. + */ + void updateColor(const QColor& color); + + /** + * @brief startPickingFromScreen starts process of picking from screen. + * Function is called automatically when "Pick from screen" button is clicked. + */ + void startPickingFromScreen(); + + /** + * @brief stopPickingFromScreen terminates process of picking from screen. + */ + void stopPickingFromScreen(); + +protected: + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + +private: + Ui::ColorPicker *ui; + bool pickingFromScreen; + + /** + * @brief bufferColor is used to buffer current color while picking from screen. + */ + QColor bufferColor; +}; + +namespace ColorPickerHelpers { +/** + * @brief The ColorPickerWidget class is parent class for ColorPickArea and ColorValueBar classes. + */ +class ColorPickerWidget : public ColorPickWidgetAbstract { + Q_OBJECT +public: + ColorPickerWidget(QWidget *parent = nullptr); + +protected: + void mouseReleaseEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + +private: + void mouseEvent(QMouseEvent* event); + + /** + * @brief pointToColor converts coordinates on widget to color these coordinates represents. + */ + virtual QColor pointToColor(int x, int y) const = 0; + + /** + * @brief colorToPoint converts color to coordinates that represent this color. + */ + virtual QPoint colorToPoint(const QColor& color) const = 0; +}; + +class ColorShowWidget : public ColorPickWidgetAbstract +{ + Q_OBJECT +public: + explicit ColorShowWidget(QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +/** + * @brief The ColorPickArea class provides widget that helps to pick + * Saturation and Hue of color in HSV colorspace. + */ +class ColorPickArea : public ColorPickerWidget +{ + Q_OBJECT +public: + explicit ColorPickArea(QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QColor pointToColor(int x, int y) const override; + + QPoint colorToPoint(const QColor& color) const override; +}; + +/** + * @brief The ColorValueBar class provides widget that helps to set Valuse of color + * in HSV colorspace. + */ +class ColorValueBar : public ColorPickerWidget +{ + Q_OBJECT +public: + ColorValueBar(QWidget *parent = nullptr) : ColorPickerWidget(parent) {} + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QColor pointToColor(int x, int y) const override; + + QPoint colorToPoint(const QColor& color) const override; +}; +} + +#endif // COLORPICKER_H diff --git a/src/widgets/ColorPicker.ui b/src/widgets/ColorPicker.ui new file mode 100644 index 00000000..d6a4ea19 --- /dev/null +++ b/src/widgets/ColorPicker.ui @@ -0,0 +1,349 @@ + + + ColorPicker + + + + 0 + 0 + 1027 + 232 + + + + Form + + + + + + + + + + + 200 + 200 + + + + + + + + + 30 + 0 + + + + + + + + + 30 + 40 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Val: + + + + + + + 255 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Sat: + + + + + + + 255 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Hue: + + + + + + + 359 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Red: + + + + + + + 255 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Green: + + + + + + + 255 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Blue: + + + + + + + 255 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Hex: + + + + + + + \#HHHHHH + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Pick color from screen + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + ColorPickerHelpers::ColorPickArea + QWidget +
widgets/ColorPicker.h
+ 1 +
+ + ColorPickerHelpers::ColorValueBar + QWidget +
widgets/ColorPicker.h
+ 1 +
+ + ColorPickerHelpers::ColorShowWidget + QWidget +
widgets/ColorPicker.h
+ 1 +
+
+ + +
diff --git a/src/widgets/ColorSchemePrefWidget.cpp b/src/widgets/ColorSchemePrefWidget.cpp deleted file mode 100644 index 0f6ecfbf..00000000 --- a/src/widgets/ColorSchemePrefWidget.cpp +++ /dev/null @@ -1,562 +0,0 @@ -#include "common/Configuration.h" -#include "common/Configuration.h" -#include "ColorSchemePrefWidget.h" -#include "ui_ColorSchemePrefWidget.h" -#include "common/ColorSchemeFileSaver.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct OptionIfo { - QString info; - QString displayingtext; - bool isUsedStandardTextColor; -}; - -static const QMap optionInfoMap = { - { - "comment", { - QObject::tr("Color of comment generated by radare2"), - QObject::tr("Comment"), false - } - }, - { - "usrcmt", { - QObject::tr("Comment created by user"), - QObject::tr("Color of user Comment"), false - } - }, - { - "args", { - QObject::tr("Color of function arguments"), - QObject::tr("Arguments"), false - } - }, - { - "fname", { - QObject::tr("Color of names of functions"), - QObject::tr("Function name"), false - } - }, - { - "floc", { - QObject::tr("Color of function location"), - QObject::tr("Function location"), false - } - }, - { - "fline", { - "", - "fline", false - } - }, - { - "flag", { - QObject::tr("Color of flags (similar to bookmarks for offset)"), - QObject::tr("Flag"), false - } - }, - { "label", { "", QObject::tr("Label"), false } }, - { "help", { "", QObject::tr("Help"), false } }, - { "flow", { "", QObject::tr("flow"), false } }, - { "flow2", { "", QObject::tr("flow2"), false } }, - { "prompt", { QObject::tr("Info"), QObject::tr("prompt"), false } }, - { - "offset", { - QObject::tr("Color of offsets"), - QObject::tr("Offset"), false - } - }, - { "input", { QObject::tr("Info"), QObject::tr("input"), false } }, - { - "invalid", { - QObject::tr("Invalid opcode color"), - QObject::tr("invalid"), false - } - }, - { "other", { "", QObject::tr("other"), false } }, - { - "b0x00", { - QObject::tr("0x00 opcode color"), - "b0x00", false - } - }, - { - "b0x7f", { - QObject::tr("0x7f opcode color"), - "b0x7f", false - } - }, - { - "b0xff", { - QObject::tr("0xff opcode color"), - "b0xff", false - } - }, - { - "math", { - QObject::tr("arithmetic color (+, -, *, / etc.)"), - "math", false - } - }, - { "bin", { "", QObject::tr("bin"), false } }, - { "btext", { "", QObject::tr("btext"), false } }, - { "push", { QObject::tr("push opcode color"), "push", false } }, - { "pop", { QObject::tr("pop opcode color"), "pop", false } }, - { "crypto", { QObject::tr("Cryptographic color"), "crypto", false } }, - { - "jmp", { - QObject::tr("jmp instructions color"), - "jmp", false - } - }, - { - "cjmp", { - "", - "cjmp", false - } - }, - { - "call", { - QObject::tr("call instructions color (ccall, rcall, call etc)"), - "call", false - } - }, - { "nop", { QObject::tr("nop opcode color"), "nop", false } }, - { - "ret", { - QObject::tr("ret opcode color"), - "ret", false - } - }, - { - "trap", { - QObject::tr("Color of interrupts"), - QObject::tr("Interrupts"), false - } - }, - { "swi", { QObject::tr("swi opcode color"), "swi", false } }, - { "cmp", { QObject::tr("cmp opcode color"), "cmp", false } }, - { "reg", { QObject::tr("Registers color"), QObject::tr("Register"), false } }, - { "creg", { QObject::tr("Info"), "creg", false } }, - { "num", { QObject::tr("Numeric constants color"), QObject::tr("Numbers"), false } }, - { - "mov", { - QObject::tr("mov instructions color (mov, movd, movw etc"), - QObject::tr("mov"), false - } - }, - { - "func_var", { - QObject::tr("Function variable color"), - QObject::tr("Function variable"), false - } - }, - { - "func_var_type", { - QObject::tr("Function variable (local or argument) type color"), - QObject::tr("Variable type"), false - } - }, - { - "func_var_addr", { - QObject::tr("Function variable address color"), - QObject::tr("Variable address"), false - } - }, - { "widget_bg", { "", "widget_bg", false } }, - { "widget_sel", { "", "widget_sel", false } }, - { "ai.read", { "", "ai.read", false } }, - { "ai.write", { "", "ai.write", false } }, - { "ai.exec", { "", "ai.exec", false } }, - { "ai.seq", { "", "ai.seq", false } }, - { "ai.ascii", { "", "ai.ascii", false } }, - { "graph.box", { "", "graph.box", false } }, - { "graph.box2", { "", "graph.box2", false } }, - { "graph.box3", { "", "graph.box3", false } }, - { "graph.box4", { "", "graph.box4", false } }, - { - "graph.true", { - QObject::tr("In graph view jump arrow true"), - QObject::tr("Arrow true"), false - } - }, - { - "graph.false", { - QObject::tr("In graph view jump arrow false"), - QObject::tr("Arrow false"), false - } - }, - { - "graph.trufae", { - QObject::tr("In graph view jump arrow (no condition)"), - QObject::tr("Arrow"), false - } - }, - { "graph.current", { "", "graph.current", false } }, - { "graph.traced", { "", "graph.traced", false } }, - { - "gui.overview.node", { - QObject::tr("Background color of Graph Overview's node"), - QObject::tr("Graph Overview node"), true - } - }, - { "gui.cflow", { "", "gui.cflow", true } }, - { "gui.dataoffset", { "", "gui.dataoffset", true } }, - { - "gui.background", { - QObject::tr("General background color"), - QObject::tr("Background"), true - } - }, - { - "gui.alt_background", { - "", - QObject::tr("Alt. background"), true - } - }, - { - "gui.disass_selected", { - QObject::tr("Background of current graph node"), - QObject::tr("Current graph node"), true - } - }, - { "gui.border", { "", "gui.border", true } }, - - // TODO: find out different - { - "linehl", { - QObject::tr("Selected line background color"), - QObject::tr("Line highlight"), true - } - }, - { - "highlight", { - QObject::tr("Selected line background color"), - QObject::tr("Line highlight"), true - } - }, - - // TODO: find out different - { - "highlightWord", { - QObject::tr("Highlighted word text color"), - QObject::tr("Word higlight"), true - } - }, - { - "wordhl", { - QObject::tr("Highlighted word text color"), - QObject::tr("Word higlight"), true - } - }, - - { - "gui.main", { - QObject::tr("Main function color"), - QObject::tr("Main"), true - } - }, - { "gui.imports", { "", "gui.imports", true } }, - { "highlightPC", { "", "highlightPC", true } }, - { "gui.navbar.err", { "", "gui.navbar.err", true } }, - { "gui.navbar.seek", { "", "gui.navbar.seek", true } }, - { "gui.navbar.pc", { "", "gui.navbar.pc", true } }, - { "gui.navbar.sym", { "", "gui.navbar.sym", true } }, - { - "gui.navbar.code", { - QObject::tr("Code section color in navigation bar"), - QObject::tr("Navbar code"), true - } - }, - { - "gui.navbar.empty", { - QObject::tr("Empty section color in navigation bar"), - QObject::tr("Navbar empty"), true - } - }, - { "gui.breakpoint_background", { "", QObject::tr("Breakpoint background"), true } }, - { - "gui.tooltip.background", { - QObject::tr("Background color for tooltips"), - QObject::tr("Tooltip background"), true - } - }, - { - "gui.tooltip.foreground", { - QObject::tr("Foregorund color for tooltips"), - QObject::tr("Tooltip foreground"), true - } - } -}; - -void ColorOptionDelegate::paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - painter->setPen(QPen(Qt::OpaqueMode)); - painter->setBrush(QBrush(backgroundColor)); - painter->drawRect(option.rect); - - QPalette pal; - - QStyleOptionViewItem op = option; - - op.palette = pal; - op.displayAlignment = Qt::AlignCenter; - - if (option.state & QStyle::State_Selected) { - QLinearGradient lgrd = QLinearGradient(option.rect.topLeft(), option.rect.bottomRight()); - QColor highlighted = QApplication::palette().highlight().color(), - highlightedOpaque = highlighted; - highlightedOpaque.setAlpha(0); - - lgrd.setColorAt(0.00, highlighted); - lgrd.setColorAt(0.25, highlightedOpaque); - lgrd.setColorAt(0.75, highlightedOpaque); - lgrd.setColorAt(1.00, highlighted); - painter->setBrush(lgrd); - painter->drawRect(option.rect); - op.state &= ~ QStyle::State_Selected; - } - - op.state &= ~ QStyle::State_Editing; - pal = option.palette; - - QColor txtColor; - - ColorOption co = index.data(Qt::UserRole).value(); - txtColor = co.color; - - if (optionInfoMap[co.optionName].isUsedStandardTextColor) { - txtColor = textColor; - } - pal.setColor(QPalette::Text, txtColor); - - op.palette = pal; - QStyledItemDelegate::paint(painter, op, index); -} - -void ColorOptionDelegate::setBackgroundColor(const QColor &c) -{ - backgroundColor = c; -} - -void ColorOptionDelegate::setTextColor(const QColor &c) -{ - textColor = c; -} - -ColorViewButton::ColorViewButton(QWidget *parent) : QFrame (parent) -{ - setLineWidth(3); - setFrameShape(QFrame::Panel); - setFrameShadow(QFrame::Raised); - setColor(palette().background().color()); - setMaximumWidth(100); -} - -void ColorViewButton::setColor(const QColor &c) -{ - setStyleSheet(QString("background-color:%1;").arg(c.name().toLower())); - repaint(); -} - -void ColorViewButton::mouseReleaseEvent(QMouseEvent *event) -{ - if (event->button() != Qt::LeftButton) { - return; - } - - emit clicked(); -} - -PreferencesListView::PreferencesListView(QWidget *parent) : - QListView (parent) -{ - setModel(new ColorSettingsModel(static_cast(this))); - - static_cast(this->model())->updateScheme(); - setStandardColors(); -} - -void PreferencesListView::setStandardColors() -{ - delegate = new ColorOptionDelegate(this); - ColorSettingsModel *model = static_cast(this->model()); - - delegate->setBackgroundColor(model->getBackroundColor()); - delegate->setTextColor(model->getTextColor()); - // I can't free last delegate, but PreferencesListView will delete it, - // because every delegate is its child. - setItemDelegate(static_cast(delegate)); -} - -void PreferencesListView::currentChanged(const QModelIndex ¤t, - const QModelIndex &previous) -{ - emit indexChanged(current); - QListView::currentChanged(current, previous); -} - -ColorSchemePrefWidget::ColorSchemePrefWidget(QWidget *parent) : QWidget (parent), - ui (new Ui::ColorSchemePrefWidget), isEditable (false) -{ - ui->setupUi(this); - connect(ui->colorViewFore, &ColorViewButton::clicked, this, &ColorSchemePrefWidget::newColor); - connect(ui->preferencesListView, &PreferencesListView::indexChanged, this, - &ColorSchemePrefWidget::indexChanged); - connect(ui->preferencesListView, &PreferencesListView::indexChanged, [this](const QModelIndex & i) { - ui->infoBoard->setText(optionInfoMap[i.data(Qt::UserRole).value().optionName].info); - }); -} - -ColorSchemePrefWidget::~ColorSchemePrefWidget() -{ - apply(); - delete ui; -} - -void ColorSchemePrefWidget::apply() -{ - if (!isEditable) { - return; - } - ColorSettingsModel *model = static_cast(ui->preferencesListView->model()); - - QString scheme = ""; - ColorOption curr; - QMap cutterSpecific = ColorSchemeFileWorker().getCutterSpecific(); - - for (int i = 0; i < model->rowCount(); i++) { - curr = model->data(model->index(i), Qt::UserRole).value(); - if (cutterSpecific.contains(curr.optionName)) { - scheme += "#~"; - } else { - scheme += "ec "; - } - scheme += curr.optionName + " rgb:" + curr.color.name().remove(QLatin1Char('#')).toLower() + "\n"; - } - ColorSchemeFileWorker().save(scheme, Config()->getColorTheme()); - Config()->setColorTheme(Config()->getColorTheme()); -} - -void ColorSchemePrefWidget::newColor() -{ - if (ui->preferencesListView->currentIndex().row() == -1 || !isEditable) { - return; - } - - ColorOption currCO = ui->preferencesListView->model()->data(ui->preferencesListView->currentIndex(), - Qt::UserRole).value(); - QColorDialog d(currCO.color, this); - if (QDialog::Accepted != d.exec()) - return; - - static_cast(ui->preferencesListView->model())->setColor(currCO.optionName, - d.selectedColor()); - ui->preferencesListView->setStandardColors(); - if (ui->preferencesListView->model()->rowCount()) - indexChanged(ui->preferencesListView->selectionModel()->selectedIndexes().first()); -} - -void ColorSchemePrefWidget::indexChanged(const QModelIndex &ni) -{ - ui->colorViewFore->setColor(ni.data(Qt::UserRole).value().color); -} - -void ColorSchemePrefWidget::updateSchemeFromConfig() -{ - isEditable = ColorSchemeFileWorker().isCustomScheme(Config()->getColorTheme()); - static_cast(ui->preferencesListView->model())->updateScheme(); -} - -ColorSettingsModel::ColorSettingsModel(QObject *parent) : QAbstractListModel (parent) { } - -QVariant ColorSettingsModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - return QVariant(); - } - - if (index.row() < 0 || index.row() >= m_data.size()) { - return QVariant(); - } - - if (role == Qt::DisplayRole) { - return QVariant::fromValue(m_data.at(index.row()).displayingText); - } - - if (role == Qt::UserRole) { - return QVariant::fromValue(m_data.at(index.row())); - } - - return QVariant(); -} - -void ColorSettingsModel::setColor(const QString &option, const QColor &color) -{ - int row = 0; - for (auto &it : m_data) { - if (it.optionName == option) { - it.color = color; - emit dataChanged(index(row), index(row)); - return; - } - row++; - } -} - -QColor ColorSettingsModel::getBackroundColor() const -{ - if (!ColorSchemeFileWorker().isCustomScheme(Config()->getColorTheme())) { - return Config()->getColor(standardBackgroundOptionName); - } - - for (auto &it : m_data) { - if (it.optionName == standardBackgroundOptionName) { - return it.color; - } - } - return QColor(); -} - -QColor ColorSettingsModel::getTextColor() const -{ - if (!ColorSchemeFileWorker().isCustomScheme(Config()->getColorTheme())) { - return Config()->getColor(standardTextOptionName); - } - - for (auto &it : m_data) { - if (it.optionName == standardTextOptionName) { - return it.color; - } - } - return QColor(); -} - -void ColorSettingsModel::updateScheme() -{ - m_data.clear(); - QJsonObject obj = Core()->cmdj("ecj").object(); - - m_data.reserve(obj.size()); - for (auto &it : obj.keys()) { - QJsonArray rgb = obj[it].toArray(); - m_data.push_back({it, optionInfoMap[it].displayingtext, QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt())}); - } - - QMap cutterSpecific = ColorSchemeFileWorker().getCutterSpecific(); - for (auto &it : cutterSpecific.keys()) { - m_data.push_back({it, optionInfoMap[it].displayingtext, cutterSpecific[it]}); - } - - qobject_cast(parent())->setStandardColors(); -} diff --git a/src/widgets/ColorSchemePrefWidget.h b/src/widgets/ColorSchemePrefWidget.h deleted file mode 100644 index 6bc06a76..00000000 --- a/src/widgets/ColorSchemePrefWidget.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef COLORSCHEMEPREFWIDGET_H -#define COLORSCHEMEPREFWIDGET_H - -#include -#include -#include -#include -#include - -namespace Ui { -class ColorSchemePrefWidget; -} - -class ColorSchemePrefWidget : public QWidget -{ - Q_OBJECT -public: - ColorSchemePrefWidget(QWidget *parent = nullptr); - virtual ~ColorSchemePrefWidget(); - -public slots: - void apply(); - - void updateSchemeFromConfig(); - -private slots: - void newColor(); - - void indexChanged(const QModelIndex &ni); - -private: - Ui::ColorSchemePrefWidget *ui; - bool isEditable; -}; - -//===============SERVICE STUFF BELOW=============== - -class ColorViewButton : public QFrame -{ - Q_OBJECT -public: - ColorViewButton(QWidget *parent = nullptr); - virtual ~ColorViewButton() override {} - -public slots: - void setColor(const QColor &c); - -protected slots: - void mouseReleaseEvent(QMouseEvent *event) override; - -signals: - void clicked(); -}; - -struct ColorOption { - QString optionName; - QString displayingText; - QColor color; -}; -Q_DECLARE_METATYPE(ColorOption); - - -class ColorSettingsModel : public QAbstractListModel -{ - Q_OBJECT -public: - ColorSettingsModel(QObject *parent = nullptr); - virtual ~ColorSettingsModel() override {} - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - void setColor(const QString &option, const QColor &color); - - QColor getBackroundColor() const; - - QColor getTextColor() const; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - Q_UNUSED(parent) - return m_data.size(); - } - - void updateScheme(); - -private: - QList m_data; -}; - -class ColorOptionDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - ColorOptionDelegate(QObject *parent = nullptr) : QStyledItemDelegate (parent) {} - ~ColorOptionDelegate() override {} - - void paint(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - - void setBackgroundColor(const QColor &c); - - void setTextColor(const QColor &c); - -private: - QColor backgroundColor; - QColor textColor; -}; - -class PreferencesListView : public QListView -{ - Q_OBJECT -public: - PreferencesListView(QWidget *parent = nullptr); - virtual ~PreferencesListView() override {} - - void setStandardColors(); - -protected slots: - void currentChanged(const QModelIndex ¤t, - const QModelIndex &previous) override; - -signals: - void indexChanged(const QModelIndex &ni); - -private: - ColorOptionDelegate *delegate; -}; - -#endif // COLORSCHEMEPREFWIDGET_H diff --git a/src/widgets/ColorSchemePrefWidget.ui b/src/widgets/ColorSchemePrefWidget.ui deleted file mode 100644 index fb3787b4..00000000 --- a/src/widgets/ColorSchemePrefWidget.ui +++ /dev/null @@ -1,101 +0,0 @@ - - - ColorSchemePrefWidget - - - - 0 - 0 - 670 - 306 - - - - Form - - - - - - - - - - - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - 0 - 0 - - - - Set Default - - - - - - - - - - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - PreferencesListView - QListView -
widgets/ColorSchemePrefWidget.h
-
- - ColorViewButton - QFrame -
widgets/ColorSchemePrefWidget.h
- 1 -
-
- - -
diff --git a/src/widgets/ColorThemeComboBox.cpp b/src/widgets/ColorThemeComboBox.cpp new file mode 100644 index 00000000..1fecbdc0 --- /dev/null +++ b/src/widgets/ColorThemeComboBox.cpp @@ -0,0 +1,89 @@ +#include "ColorThemeComboBox.h" + +#include "core/Cutter.h" + +#include "common/ColorThemeWorker.h" +#include "common/Configuration.h" + +/* Map with names of themes associated with its color palette + * (Dark or Light), so for dark interface themes will be shown only Dark color themes + * and for light - only light ones. + */ +static const QHash kRelevantThemes = { + { "ayu", DarkFlag }, + { "consonance", DarkFlag }, + { "darkda", DarkFlag }, + { "onedark", DarkFlag }, + { "solarized", DarkFlag }, + { "zenburn", DarkFlag }, + { "cutter", LightFlag }, + { "dark", LightFlag }, + { "matrix", LightFlag }, + { "tango", LightFlag }, + { "white", LightFlag } +}; + +ColorThemeComboBox::ColorThemeComboBox(QWidget *parent) : QComboBox(parent), showOnlyCustom(false) +{ + connect(this, static_cast(&QComboBox::currentIndexChanged), + this, &ColorThemeComboBox::onCurrentIndexChanged); + updateFromConfig(); +} + +void ColorThemeComboBox::updateFromConfig(bool interfaceThemeChanged) +{ + QSignalBlocker signalBlockerColorBox(this); + + const int curInterfaceThemeIndex = Config()->getInterfaceTheme(); + const QList themes(showOnlyCustom + ? ThemeWorker().customThemes() + : Core()->getColorThemes()); + + clear(); + for (const QString &theme : themes) { + if (ThemeWorker().isCustomTheme(theme) || + (kCutterInterfaceThemesList[curInterfaceThemeIndex].flag & kRelevantThemes[theme])) { + addItem(theme); + } + } + + QString curTheme = interfaceThemeChanged + ? Config()->getLastThemeOf(kCutterInterfaceThemesList[curInterfaceThemeIndex]) + : Config()->getColorTheme(); + const int index = findText(curTheme); + + setCurrentIndex(index == -1 ? 0 : index); + if (interfaceThemeChanged || index == -1) { + curTheme = currentText(); + Config()->setColorTheme(curTheme); + } + int maxThemeLen = 0; + for (const QString &str : themes) { + int strLen = str.length(); + if (strLen > maxThemeLen) { + maxThemeLen = strLen; + } + } + setMinimumContentsLength(maxThemeLen); + setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); +} + +void ColorThemeComboBox::onCurrentIndexChanged(int index) +{ + QString theme = itemText(index); + + int curQtThemeIndex = Config()->getInterfaceTheme(); + if (curQtThemeIndex >= kCutterInterfaceThemesList.size()) { + curQtThemeIndex = 0; + Config()->setInterfaceTheme(curQtThemeIndex); + } + + Config()->setLastThemeOf(kCutterInterfaceThemesList[curQtThemeIndex], theme); + Config()->setColorTheme(theme); +} + +void ColorThemeComboBox::setShowOnlyCustom(bool value) +{ + showOnlyCustom = value; + updateFromConfig(); +} diff --git a/src/widgets/ColorThemeComboBox.h b/src/widgets/ColorThemeComboBox.h new file mode 100644 index 00000000..01edd818 --- /dev/null +++ b/src/widgets/ColorThemeComboBox.h @@ -0,0 +1,37 @@ +#ifndef COLORTHEMECOMBOBOX_H +#define COLORTHEMECOMBOBOX_H + +#include + +/** + * @brief The ColorThemeComboBox class provides combobox with Cutter color themes. + */ +class ColorThemeComboBox : public QComboBox +{ + Q_OBJECT +public: + explicit ColorThemeComboBox(QWidget *parent = nullptr); + + /** + * @brief setShowOnlyCustom sets whether or not combobox should contain only + * custom themes (created by user or imported) or custom and srandard radare2 themes. + */ + void setShowOnlyCustom(bool value); + +public slots: + /** + * @brief updateFromConfig updates list of themes to be shown. + * @param interfaceThemeChanged should be set to true if the interface theme of Cutter was changed + * since the last call to the function. This will preserve the selected item in the combo box. + * + */ + void updateFromConfig(bool interfaceThemeChanged = false); + +private slots: + void onCurrentIndexChanged(int index); + +private: + bool showOnlyCustom; +}; + +#endif // COLORTHEMECOMBOBOX_H diff --git a/src/widgets/ColorThemeListView.cpp b/src/widgets/ColorThemeListView.cpp new file mode 100644 index 00000000..0948b760 --- /dev/null +++ b/src/widgets/ColorThemeListView.cpp @@ -0,0 +1,823 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/Configuration.h" +#include "common/ColorThemeWorker.h" + +#include "widgets/ColorThemeListView.h" + + +struct OptionInfo { + QString info; + QString displayingtext; +}; + +extern const QMap optionInfoMap__; + +ColorOptionDelegate::ColorOptionDelegate(QObject* parent) : QStyledItemDelegate (parent) +{ + resetButtonPixmap = getPixmapFromSvg(":/img/icons/reset.svg", qApp->palette().text().color()); + connect(qApp, &QGuiApplication::paletteChanged, this, + [this](const QPalette &p){ + resetButtonPixmap = getPixmapFromSvg(":/img/icons/reset.svg", qApp->palette().text().color()); + }); +} + +void ColorOptionDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + int margin = this->margin * qApp->screenAt(option.rect.topLeft())->devicePixelRatio(); + painter->save(); + painter->setFont(option.font); + painter->setRenderHint(QPainter::Antialiasing); + + ColorOption currCO = index.data(Qt::UserRole).value(); + + int penWidth = painter->pen().width(); + int fontHeight = painter->fontMetrics().height(); + QPoint tl = option.rect.topLeft(); + + QRect optionNameRect; + optionNameRect.setTopLeft(tl + QPoint(margin, penWidth)); + optionNameRect.setWidth(option.rect.width() - margin * 2); + optionNameRect.setHeight(fontHeight); + + QRect optionRect; + optionRect.setTopLeft(optionNameRect.bottomLeft() + QPoint(margin / 2, margin / 2)); + optionRect.setWidth(option.rect.width() - (optionRect.topLeft() - tl).x() * 2); + optionRect.setHeight(option.rect.height() - (optionRect.topLeft() - tl).y() - margin); + + QRect colorRect; + colorRect.setTopLeft(optionRect.topLeft() + QPoint(margin / 4, margin / 4)); + colorRect.setBottom(optionRect.bottom() - margin / 4); + colorRect.setWidth(colorRect.height()); + + QRect descTextRect; + descTextRect.setTopLeft(colorRect.topRight() + QPoint(margin, colorRect.height() / 2 - fontHeight / 2)); + descTextRect.setWidth(optionRect.width() - (descTextRect.left() - optionRect.left()) - margin); + descTextRect.setHeight(fontHeight); + + bool paintResetButton = false; + QRect resetButtonRect; + + if (option.state & (QStyle::State_Selected | QStyle::State_MouseOver)) { + QBrush br; + QPen pen; + if (option.state.testFlag(QStyle::State_Selected)) { + QColor c = qApp->palette().highlight().color(); + c.setAlphaF(0.4); + br = c; + pen = QPen(qApp->palette().highlight().color(), margin / 2); + if (currCO.changed) { + paintResetButton = true; + descTextRect.setWidth(descTextRect.width() - descTextRect.height() - margin / 2); + resetButtonRect.setTopLeft(descTextRect.topRight() + QPoint(margin, 0)); + resetButtonRect.setWidth(descTextRect.height()); + resetButtonRect.setHeight(descTextRect.height()); + resetButtonRect.setSize(resetButtonRect.size() * 1.0); + } + } else { + QColor c = qApp->palette().placeholderText().color(); + c.setAlphaF(0.2); + br = c; + pen = QPen(qApp->palette().placeholderText().color().darker(), margin / 2); + } + + painter->fillRect(option.rect, br); + + painter->setPen(pen); + int pw = painter->pen().width() / 2; + QPoint top = option.rect.topLeft() + QPoint(pw, pw); + QPoint bottom = option.rect.bottomLeft() - QPoint(-pw, pw - 1); + painter->drawLine(top, bottom); + } + + if (paintResetButton) { + painter->drawPixmap(resetButtonRect, resetButtonPixmap); + auto self = const_cast(this); + self->resetButtonRect = resetButtonRect; + } + if (option.rect.contains(this->resetButtonRect) && this->resetButtonRect != resetButtonRect) { + auto self = const_cast(this); + self->resetButtonRect = QRect(0,0,0,0); + } + + painter->setPen(qApp->palette().text().color()); + + QString name = painter->fontMetrics().elidedText( + optionInfoMap__[currCO.optionName].displayingtext, + Qt::ElideRight, optionNameRect.width()); + painter->drawText(optionNameRect, name); + + QPainterPath roundedOptionRect; + roundedOptionRect.addRoundedRect(optionRect, fontHeight / 4, fontHeight / 4); + painter->setPen(qApp->palette().text().color()); + painter->drawPath(roundedOptionRect); + + QPainterPath roundedColorRect; + roundedColorRect.addRoundedRect(colorRect, fontHeight / 4, fontHeight / 4); + painter->setPen(Qt::NoPen); + painter->fillPath(roundedColorRect, currCO.color); + + QString desc = painter->fontMetrics().elidedText( + currCO.optionName + ": " + + optionInfoMap__[currCO.optionName].info, Qt::ElideRight, + descTextRect.width()); + painter->setPen(qApp->palette().text().color()); + painter->setBrush(qApp->palette().text()); + painter->drawText(descTextRect, desc); + + painter->restore(); +} + +QSize ColorOptionDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + int margin = this->margin * qApp->screenAt(option.rect.topLeft())->devicePixelRatio(); + int fontHeight = option.fontMetrics.height(); + int h = QPen().width(); + h += fontHeight; // option name + h += margin / 2; // margin between option rect and option name + h += margin / 4; // margin betveen option rect and color rect + h += fontHeight; // color rect + h += margin / 4; // margin betveen option rect and color rect + h += margin; // last margin + + Q_UNUSED(index) + return QSize(-1, h); +} + +QRect ColorOptionDelegate::getResetButtonRect() const +{ + return resetButtonRect; +} + +QPixmap ColorOptionDelegate::getPixmapFromSvg(const QString& fileName, const QColor& after) const +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + return QPixmap(); + } + QString data = file.readAll(); + data.replace(QRegExp("#[0-9a-fA-F]{6}"), QString("%1").arg(after.name())); + + QSvgRenderer svgRenderer(data.toUtf8()); + QPixmap pix(QSize(qApp->fontMetrics().height(), qApp->fontMetrics().height())); + pix.fill(Qt::transparent); + + QPainter pixPainter(&pix); + svgRenderer.render(&pixPainter); + + return pix; +} + +ColorThemeListView::ColorThemeListView(QWidget *parent) : + QListView (parent) +{ + setModel(new ColorSettingsModel(static_cast(this))); + static_cast(this->model())->updateTheme(); + setItemDelegate(new ColorOptionDelegate(this)); + + QJsonArray rgb = qobject_cast(model())->getTheme() + .object().find("gui.background").value().toArray(); + if (rgb.size() == 3) { + backgroundColor = QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt()); + } else { + backgroundColor = palette().base().color(); + } + + connect(&blinkTimer, &QTimer::timeout, this, &ColorThemeListView::blinkTimeout); + + blinkTimer.setInterval(400); + blinkTimer.start(); + + setMouseTracking(true); +} + +void ColorThemeListView::currentChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + ColorOption prev = previous.data(Qt::UserRole).value(); + Config()->setColor(prev.optionName, prev.color); + if (ThemeWorker().radare2SpecificOptions.contains(prev.optionName)) { + Core()->cmd(QString("ec %1 %2").arg(prev.optionName).arg(prev.color.name())); + } + + QListView::currentChanged(current, previous); + emit itemChanged(current.data(Qt::UserRole).value().color); +} + +void ColorThemeListView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, + const QVector& roles) +{ + ColorOption curr = topLeft.data(Qt::UserRole).value(); + if (curr.optionName == "gui.background") { + backgroundColor = curr.color; + } + QListView::dataChanged(topLeft, bottomRight, roles); + emit itemChanged(curr.color); + emit dataChanged(curr); +} + +void ColorThemeListView::mouseReleaseEvent(QMouseEvent* e) +{ + if (qobject_cast(itemDelegate())->getResetButtonRect().contains(e->pos())) { + auto model = qobject_cast(this->model()); + ColorOption co = currentIndex().data(Qt::UserRole).value(); + co.changed = false; + QJsonArray rgb = ThemeWorker().getTheme(Config()->getColorTheme())[co.optionName].toArray(); + co.color = QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt()); + model->setData(currentIndex(), QVariant::fromValue(co)); + QCursor c; + c.setShape(Qt::CursorShape::ArrowCursor); + setCursor(c); + } +} + +void ColorThemeListView::mouseMoveEvent(QMouseEvent* e) +{ + if (qobject_cast(itemDelegate())->getResetButtonRect().contains(e->pos())) { + QCursor c; + c.setShape(Qt::CursorShape::PointingHandCursor); + setCursor(c); + } else if (cursor().shape() == Qt::CursorShape::PointingHandCursor) { + QCursor c; + c.setShape(Qt::CursorShape::ArrowCursor); + setCursor(c); + } +} + +void ColorThemeListView::blinkTimeout() +{ + static enum { Normal, Invisible } state = Normal; + state = state == Normal ? Invisible : Normal; + backgroundColor.setAlphaF(1); + + auto updateColor = [](const QString &name, const QColor &color) { + Config()->setColor(name, color); + if (ThemeWorker().radare2SpecificOptions.contains(name)) { + Core()->cmd(QString("ec %1 %2").arg(name).arg(color.name())); + } + }; + + ColorOption curr = currentIndex().data(Qt::UserRole).value(); + switch (state) { + case Normal: + updateColor(curr.optionName, curr.color); + break; + case Invisible: + updateColor(curr.optionName, backgroundColor); + break; + } + emit blink(); +} + +ColorSettingsModel::ColorSettingsModel(QObject *parent) : QAbstractListModel (parent) { } + +QVariant ColorSettingsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (index.row() < 0 || index.row() >= theme.size()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return QVariant::fromValue(optionInfoMap__[theme.at(index.row()).optionName].displayingtext); + } + + if (role == Qt::UserRole) { + return QVariant::fromValue(theme.at(index.row())); + } + + if (role == Qt::ToolTipRole) { + return QVariant::fromValue(optionInfoMap__[theme.at(index.row()).optionName].info); + } + + return QVariant(); +} + +bool ColorSettingsModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || role != Qt::EditRole) { + return false; + } + + ColorOption currOpt = value.value(); + theme[index.row()] = currOpt; + emit dataChanged(index, index); + return true; +} + +void ColorSettingsModel::updateTheme() +{ + theme.clear(); + QJsonObject obj = ThemeWorker().getTheme(Config()->getColorTheme()).object(); + + for (auto it = obj.constBegin(); it != obj.constEnd(); it++) { + QJsonArray rgb = it.value().toArray(); + if (rgb.size() != 3) { + continue; + } + theme.push_back({it.key(), QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt()), false}); + } + + if (!theme.isEmpty()) { + dataChanged(index(0), index(theme.size() - 1)); + } +} + +QJsonDocument ColorSettingsModel::getTheme() const +{ + QJsonObject obj; + int r, g, b; + for (auto &it : theme) { + it.color.getRgb(&r, &g, &b); + obj.insert(it.optionName, QJsonArray({r, g, b})); + } + return QJsonDocument(obj); +} + +const QMap optionInfoMap__ = { +{ + "comment", { + QObject::tr("Color of comment generated by radare2"), + QObject::tr("Comment") + } +}, +{ + "usrcmt", { + QObject::tr("Comment created by user"), + QObject::tr("Color of user Comment") + } +}, +{ + "args", { + "", + "args" + } +}, +{ + "fname", { + QObject::tr("Color of names of functions"), + QObject::tr("Function name") + } +}, +{ + "floc", { + QObject::tr("Color of function location"), + QObject::tr("Function location") + } +}, +{ + "fline", { + QObject::tr("Color of ascii line in left side that shows what opcodes are belong to function"), + QObject::tr("Function line") + } +}, +{ + "flag", { + QObject::tr("Color of flags (similar to bookmarks for offset)"), + QObject::tr("Flag") + } +}, +{ + "label", { + "", + QObject::tr("Label") + } +}, +{ + "help", { + "", + QObject::tr("Help") + } +}, +{ + "flow", { + QObject::tr("Color of lines showing jump destination"), + QObject::tr("Flow") + } +}, +{ + "flow2", { + "", + QObject::tr("flow2") + } +}, +{ + "prompt", { + QObject::tr("Info"), + QObject::tr("prompt") + } +}, +{ + "offset", { + QObject::tr("Color of offsets"), + QObject::tr("Offset") + } +}, +{ + "input", { + QObject::tr("Info"), + QObject::tr("input") + } +}, +{ + "invalid", { + QObject::tr("Invalid opcode color"), + QObject::tr("invalid") + } +}, +{ + "other", { + "", + QObject::tr("other") + } +}, +{ + "b0x00", { + QObject::tr("0x00 opcode color"), + "b0x00" + } +}, +{ + "b0x7f", { + QObject::tr("0x7f opcode color"), + "b0x7f" + } +}, +{ + "b0xff", { + QObject::tr("0xff opcode color"), + "b0xff" + } +}, +{ + "math", { + QObject::tr("Color of arithmetic opcodes (add, div, mul etc)"), + QObject::tr("Arithmetic") + } +}, +{ + "bin", { + QObject::tr("Color of binary operations (and, or, xor etc)."), + QObject::tr("Binary") + } +}, +{ + "btext", { + QObject::tr("Color of object names, commas between operators, squared brackets and operators " + "inside them."), + QObject::tr("Text") + } +}, +{ + "push", { + QObject::tr("push opcode color"), + "push" + } +}, +{ + "pop", { + QObject::tr("pop opcode color"), + "pop" + } +}, +{ + "crypto", { + QObject::tr("Cryptographic color"), + "crypto" + } +}, +{ + "jmp", { + QObject::tr("jmp instructions color"), + "jmp" + } +}, +{ + "cjmp", { + QObject::tr("Color of conditional jump opcodes such as je, jg, jne etc"), + QObject::tr("Conditional jump") + } +}, +{ + "call", { + QObject::tr("call instructions color (ccall, rcall, call etc)"), + "call" + } +}, +{ + "nop", { + QObject::tr("nop opcode color"), + "nop" + } +}, +{ + "ret", { + QObject::tr("ret opcode color"), + "ret" + } +}, +{ + "trap", { + QObject::tr("Color of interrupts"), + QObject::tr("Interrupts") + } +}, +{ + "swi", { + QObject::tr("swi opcode color"), + "swi" + } +}, +{ + "cmp", { + QObject::tr("Color of compare instructions such as test and cmp"), + QObject::tr("Compare instructions") + } +}, +{ + "reg", { + QObject::tr("Registers color"), + QObject::tr("Register") + } +}, +{ + "creg", { + "", + "creg" + } +}, +{ + "num", { + QObject::tr("Color of numeric constants and object pointers"), + QObject::tr("Constants") + } +}, +{ + "mov", { + QObject::tr("Color of move instructions such as mov, movd, lea etc"), + QObject::tr("Move instructions") + } +}, +{ + "func_var", { + QObject::tr("Function variable color"), + QObject::tr("Function variable") + } +}, +{ + "func_var_type", { + QObject::tr("Function variable (local or argument) type color"), + QObject::tr("Variable type") + } +}, +{ + "func_var_addr", { + QObject::tr("Function variable address color"), + QObject::tr("Variable address") + } +}, +{ + "widget_bg", { + "", + "widget_bg" + } +}, +{ + "widget_sel", { + "", + "widget_sel" + } +}, +{ + "ai.read", { + "", + "ai.read" + } +}, +{ + "ai.write", { + "", + "ai.write" + } +}, +{ + "ai.exec", { + "", + "ai.exec" + } +}, +{ + "ai.seq", { + "", + "ai.seq" + } +}, +{ + "ai.ascii", { + "", + "ai.ascii" + } +}, +{ + "graph.box", { + "", + "graph.box" + } +}, +{ + "graph.box2", { + "", + "graph.box2" + } +}, +{ + "graph.box3", { + "", + "graph.box3" + } +}, +{ + "graph.box4", { + "", + "graph.box4" + } +}, +{ + "graph.true", { + QObject::tr("In graph view jump arrow true"), + QObject::tr("Arrow true") + } +}, +{ + "graph.false", { + QObject::tr("In graph view jump arrow false"), + QObject::tr("Arrow false") + } +}, +{ + "graph.trufae", { + QObject::tr("In graph view jump arrow (no condition)"), + QObject::tr("Arrow") + } +}, +{ + "graph.current", { + "", + "graph.current" + } +}, +{ + "graph.traced", { + "", + "graph.traced" + } +}, +{ + "gui.overview.node", { + QObject::tr("Background color of Graph Overview's node"), + QObject::tr("Graph Overview node") + } +}, +{ + "gui.cflow", { + "", + "gui.cflow" + } +}, +{ + "gui.dataoffset", { + "", + "gui.dataoffset" + } +}, +{ + "gui.background", { + QObject::tr("General background color"), + QObject::tr("Background") + } +}, +{ + "gui.alt_background", { + QObject::tr("Background color of non-focused graph node"), + QObject::tr("Node background") + } +}, +{ + "gui.disass_selected", { + QObject::tr("Background of current graph node"), + QObject::tr("Current graph node") + } +}, +{ + "gui.border", { + QObject::tr("Color of node border in graph view"), + QObject::tr("Node border") + } +}, +{ + "linehl", { + QObject::tr("Selected line background color"), + QObject::tr("Line highlight") + } +}, +{ + "wordhl", { + QObject::tr("Background color of selected word"), + QObject::tr("Word higlight") + } +}, +{ + "gui.main", { + QObject::tr("Main function color"), + QObject::tr("Main") + } +}, +{ + "gui.imports", { + "", + "gui.imports" + } +}, +{ + "highlightPC", { + "", + "highlightPC" + } +}, +{ + "gui.navbar.err", { + "", + "gui.navbar.err" + } +}, +{ + "gui.navbar.seek", { + "", + "gui.navbar.seek" + } +}, +{ + "angui.navbar.str", { + "", + "angui.navbar.str" + } +}, +{ + "gui.navbar.pc", { + "", + "gui.navbar.pc" + } +}, +{ + "gui.navbar.sym", { + "", + "gui.navbar.sym" + } +}, +{ + "gui.navbar.code", { + QObject::tr("Code section color in navigation bar"), + QObject::tr("Navbar code") + } +}, +{ + "gui.navbar.empty", { + QObject::tr("Empty section color in navigation bar"), + QObject::tr("Navbar empty") + } +}, +{ + "ucall", { + "", + QObject::tr("ucall") + } +}, +{ + "ujmp", { + "", + QObject::tr("ujmp") + } +}, +{ + "gui.breakpoint_background", { + "", + QObject::tr("Breakpoint background") + } +} +}; diff --git a/src/widgets/ColorThemeListView.h b/src/widgets/ColorThemeListView.h new file mode 100644 index 00000000..b259ee7c --- /dev/null +++ b/src/widgets/ColorThemeListView.h @@ -0,0 +1,101 @@ +#ifndef COLORTHEMELISTVIEW_H +#define COLORTHEMELISTVIEW_H + +#include +#include +#include +#include +#include + +struct ColorOption { + QString optionName; + QColor color; + bool changed; +}; +Q_DECLARE_METATYPE(ColorOption); + + +class ColorThemeListView : public QListView +{ + Q_OBJECT +public: + ColorThemeListView(QWidget *parent = nullptr); + virtual ~ColorThemeListView() override {} + +protected slots: + void currentChanged(const QModelIndex ¤t, + const QModelIndex &previous) override; + + void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, + const QVector &roles = QVector()) override; + + void mouseReleaseEvent(QMouseEvent *e) override; + + void mouseMoveEvent(QMouseEvent *e) override; + +private slots: + void blinkTimeout(); + +signals: + void itemChanged(const QColor &option); + + void dataChanged(const ColorOption &data); + + void blink(); + +private: + QTimer blinkTimer; + QColor backgroundColor; +}; + +//============================================== + +class ColorSettingsModel : public QAbstractListModel +{ + Q_OBJECT +public: + ColorSettingsModel(QObject *parent = nullptr); + virtual ~ColorSettingsModel() override {} + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + Q_UNUSED(parent) + return theme.size(); + } + + void updateTheme(); + + QJsonDocument getTheme() const; + +private: + QList theme; +}; + +class ColorOptionDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + ColorOptionDelegate(QObject *parent = nullptr); + ~ColorOptionDelegate() override {} + + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + QRect getResetButtonRect() const; + +private: + const int margin = 12; + QPixmap resetButtonPixmap; + QRect resetButtonRect; + + QPixmap getPixmapFromSvg(const QString& fileName, const QColor& after) const; +}; + +#endif // COLORTHEMELISTVIEW_H diff --git a/src/widgets/CutterDockWidget.cpp b/src/widgets/CutterDockWidget.cpp index 8bf37e06..8f4d5822 100644 --- a/src/widgets/CutterDockWidget.cpp +++ b/src/widgets/CutterDockWidget.cpp @@ -8,10 +8,12 @@ CutterDockWidget::CutterDockWidget(MainWindow *parent, QAction *action) : QDockWidget(parent), action(action) { - parent->addToDockWidgetList(this); - if (action) { - parent->addDockWidgetAction(this, action); - connect(action, &QAction::triggered, this, &CutterDockWidget::toggleDockWidget); + if (parent) { + parent->addToDockWidgetList(this); + if (action) { + parent->addDockWidgetAction(this, action); + connect(action, &QAction::triggered, this, &CutterDockWidget::toggleDockWidget); + } } // Install event filter to catch redraw widgets when needed diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 2eca5174..e1f5eac8 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -487,7 +487,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block) highlightWidth = static_cast(block.width - widthBefore - (10 + 4 * charWidth)); } - QColor selectionColor = ConfigColor("highlightWord"); + QColor selectionColor = ConfigColor("wordhl"); p.fillRect(QRect(static_cast(blockX + charWidth * 3 + widthBefore), y, highlightWidth, charHeight), selectionColor); @@ -653,7 +653,7 @@ void DisassemblerGraphView::colorsUpdatedSlot() mDisabledBreakpointColor = disassemblyBackgroundColor; graphNodeColor = ConfigColor("gui.border"); backgroundColor = ConfigColor("gui.background"); - disassemblySelectionColor = ConfigColor("highlight"); + disassemblySelectionColor = ConfigColor("linehl"); PCSelectionColor = ConfigColor("highlightPC"); jmpColor = ConfigColor("graph.trufae"); diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 9b23af4f..3c219a5c 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -99,15 +99,6 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) this, SLOT(showDisasContextMenu(const QPoint &))); - // Space to switch to graph - QShortcut *graphShortcut = new QShortcut(QKeySequence(Qt::Key_Space), this); - graphShortcut->setContext(Qt::WidgetWithChildrenShortcut); - connect(graphShortcut, &QShortcut::activated, this, [] { - Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Graph); - Core()->triggerRaisePrioritizedMemoryWidget(); - }); - - connect(mDisasScrollArea, SIGNAL(scrollLines(int)), this, SLOT(scrollInstructions(int))); connect(mDisasScrollArea, SIGNAL(disassemblyResized()), this, SLOT(updateMaxLines())); @@ -146,46 +137,54 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) connect(Core(), &CutterCore::refreshAll, this, [this]() { refreshDisasm(seekable->getOffset()); }); + refreshDisasm(seekable->getOffset()); connect(mCtxMenu, SIGNAL(copy()), mDisasTextEdit, SLOT(copy())); - // Dirty - QShortcut *shortcut_escape = new QShortcut(QKeySequence(Qt::Key_Escape), this); - shortcut_escape->setContext(Qt::WidgetShortcut); - connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev())); - mCtxMenu->addSeparator(); syncIt.setText(tr("Sync/unsync offset")); mCtxMenu->addAction(&syncIt); connect(&syncIt, SIGNAL(triggered(bool)), this, SLOT(toggleSync())); connect(seekable, &CutterSeekable::seekableSeekChanged, this, &DisassemblyWidget::on_seekChanged); -#define ADD_SHORTCUT(ksq, slot) { \ - QShortcut *s = new QShortcut((ksq), this); \ - s->setContext(Qt::WidgetShortcut); \ - connect(s, &QShortcut::activated, this, (slot)); \ -} - ADD_SHORTCUT(QKeySequence(Qt::Key_J), [this]() { + addActions(mCtxMenu->actions()); + +#define ADD_ACTION(ksq, ctx, slot) {\ + QAction *a = new QAction(this); \ + a->setShortcut(ksq); \ + a->setShortcutContext(ctx); \ + addAction(a); \ + connect(a, &QAction::triggered, this, (slot)); } + + // Space to switch to graph + ADD_ACTION(Qt::Key_Space, Qt::WidgetWithChildrenShortcut, [] { + Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Graph); + Core()->triggerRaisePrioritizedMemoryWidget(); + }) + + ADD_ACTION(Qt::Key_Escape, Qt::WidgetWithChildrenShortcut, &DisassemblyWidget::seekPrev) + + ADD_ACTION(Qt::Key_J, Qt::WidgetWithChildrenShortcut, [this]() { moveCursorRelative(false, false); }) - ADD_SHORTCUT(QKeySequence::MoveToNextLine, [this]() { + ADD_ACTION(QKeySequence::MoveToNextLine, Qt::WidgetWithChildrenShortcut, [this]() { moveCursorRelative(false, false); }) - ADD_SHORTCUT(QKeySequence(Qt::Key_K), [this]() { + ADD_ACTION(Qt::Key_K, Qt::WidgetWithChildrenShortcut, [this]() { moveCursorRelative(true, false); }) - ADD_SHORTCUT(QKeySequence::MoveToPreviousLine, [this]() { + ADD_ACTION(QKeySequence::MoveToPreviousLine, Qt::WidgetWithChildrenShortcut, [this]() { moveCursorRelative(true, false); }) - ADD_SHORTCUT(QKeySequence::MoveToNextPage, [this]() { + ADD_ACTION(QKeySequence::MoveToNextPage, Qt::WidgetWithChildrenShortcut, [this]() { moveCursorRelative(false, true); }) - ADD_SHORTCUT(QKeySequence::MoveToPreviousPage, [this]() { + ADD_ACTION(QKeySequence::MoveToPreviousPage, Qt::WidgetWithChildrenShortcut, [this]() { moveCursorRelative(true, true); }) - ADD_SHORTCUT(QKeySequence(Qt::CTRL + Qt::Key_Plus), &DisassemblyWidget::zoomIn) - ADD_SHORTCUT(QKeySequence(Qt::CTRL + Qt::Key_Minus), &DisassemblyWidget::zoomOut) -#undef ADD_SHORTCUT + ADD_ACTION(QKeySequence(Qt::CTRL + Qt::Key_Equal), Qt::WidgetWithChildrenShortcut, &DisassemblyWidget::zoomIn) + ADD_ACTION(QKeySequence(Qt::CTRL + Qt::Key_Minus), Qt::WidgetWithChildrenShortcut, &DisassemblyWidget::zoomOut) +#undef ADD_ACTION } void DisassemblyWidget::toggleSync() @@ -199,6 +198,26 @@ void DisassemblyWidget::toggleSync() } } +void DisassemblyWidget::setPreviewMode(bool previewMode) +{ + mDisasTextEdit->setContextMenuPolicy(previewMode + ? Qt::NoContextMenu + : Qt::CustomContextMenu); + mCtxMenu->setEnabled(!previewMode); + for (auto action : mCtxMenu->actions()) { + action->setEnabled(!previewMode); + } + for (auto action : actions()) { + if (action->shortcut() == Qt::Key_Space || + action->shortcut() == Qt::Key_Escape) { + action->setEnabled(!previewMode); + } + } + if (seekable->isSynchronized() && previewMode) { + toggleSync(); + } +} + QWidget *DisassemblyWidget::getTextWidget() { return mDisasTextEdit; @@ -335,7 +354,7 @@ void DisassemblyWidget::highlightCurrentLine() { QList extraSelections; - QColor highlightColor = ConfigColor("highlight"); + QColor highlightColor = ConfigColor("linehl"); QColor highlightPCColor = ConfigColor("highlightPC"); // Highlight the current word @@ -581,13 +600,13 @@ QList DisassemblyWidget::getSameWordsSelections() QList selections; QTextEdit::ExtraSelection highlightSelection; QTextDocument *document = mDisasTextEdit->document(); - QColor highlightWordColor = ConfigColor("highlightWord"); + QColor highlightWordColor = ConfigColor("wordhl"); if (curHighlightedWord.isNull()) { return QList(); } - highlightSelection.cursor = mDisasTextEdit->textCursor();; + highlightSelection.cursor = mDisasTextEdit->textCursor(); highlightSelection.cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); while (!highlightSelection.cursor.isNull() && !highlightSelection.cursor.atEnd()) { diff --git a/src/widgets/DisassemblyWidget.h b/src/widgets/DisassemblyWidget.h index 2c2fc35c..1ab5787d 100644 --- a/src/widgets/DisassemblyWidget.h +++ b/src/widgets/DisassemblyWidget.h @@ -30,8 +30,9 @@ public slots: void colorsUpdatedSlot(); void seekPrev(); void toggleSync(); + void setPreviewMode(bool previewMode); -private slots: +protected slots: void on_seekChanged(RVA offset); void refreshDisasm(RVA offset = RVA_INVALID); @@ -43,11 +44,12 @@ private slots: void zoomIn(); void zoomOut(); -private: +protected: DisassemblyContextMenu *mCtxMenu; DisassemblyScrollArea *mDisasScrollArea; DisassemblyTextEdit *mDisasTextEdit; +private: RVA topOffset; RVA bottomOffset; int maxLines;