2019-05-01 16:15:33 +00:00
|
|
|
#include "ColorThemeWorker.h"
|
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QColor>
|
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QStandardPaths>
|
2019-10-13 14:59:12 +00:00
|
|
|
#include <QRegularExpression>
|
2019-06-12 17:46:07 +00:00
|
|
|
|
2019-05-01 16:15:33 +00:00
|
|
|
#include "common/Configuration.h"
|
|
|
|
|
|
|
|
const QStringList ColorThemeWorker::cutterSpecificOptions = {
|
2019-05-23 16:22:31 +00:00
|
|
|
"wordHighlight",
|
|
|
|
"lineHighlight",
|
2019-05-01 16:15:33 +00:00
|
|
|
"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",
|
2019-06-05 11:28:05 +00:00
|
|
|
"gui.overview.fill",
|
|
|
|
"gui.overview.border",
|
2019-05-01 16:15:33 +00:00
|
|
|
"gui.border",
|
|
|
|
"gui.background",
|
|
|
|
"gui.alt_background",
|
|
|
|
"gui.disass_selected"
|
|
|
|
};
|
|
|
|
|
|
|
|
const QStringList ColorThemeWorker::radare2UnusedOptions = {
|
2019-05-23 16:22:31 +00:00
|
|
|
"linehl",
|
|
|
|
"wordhl",
|
2019-05-01 16:15:33 +00:00
|
|
|
"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"),
|
2020-03-15 13:43:42 +00:00
|
|
|
tr("The radare2 standard themes could not be found in '%1'. "
|
2019-05-01 16:15:33 +00:00
|
|
|
"Most likely, radare2 is not properly installed.")
|
2020-03-15 13:43:42 +00:00
|
|
|
.arg(currDir.path())
|
2019-05-01 16:15:33 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 <b>%1</b> 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 <b>%1</b> cannot be opened.")
|
|
|
|
.arg(QFileInfo(fOut).filePath());
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject obj = theme.object();
|
|
|
|
for (auto it = obj.constBegin(); it != obj.constEnd(); it++) {
|
2020-08-07 14:01:50 +00:00
|
|
|
|
2019-05-01 16:15:33 +00:00
|
|
|
QJsonArray arr = it.value().toArray();
|
2020-08-07 14:01:50 +00:00
|
|
|
QColor color;
|
2019-05-01 16:15:33 +00:00
|
|
|
if (arr.isEmpty()) {
|
2020-08-07 14:01:50 +00:00
|
|
|
color = it.value().toVariant().value<QColor>();
|
2019-05-23 16:22:31 +00:00
|
|
|
} else if (arr.size() == 4) {
|
2020-08-07 14:01:50 +00:00
|
|
|
color = QColor(arr[0].toInt(), arr[1].toInt(), arr[2].toInt(), arr[3].toInt());
|
2019-05-01 16:15:33 +00:00
|
|
|
} else if (arr.size() == 3) {
|
2020-08-07 14:01:50 +00:00
|
|
|
color = QColor(arr[0].toInt(), arr[1].toInt(), arr[2].toInt());
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (cutterSpecificOptions.contains(it.key())) {
|
|
|
|
fOut.write(QString("#~%1 rgb:%2\n")
|
|
|
|
.arg(it.key(), color.name(QColor::HexArgb).remove('#'))
|
|
|
|
.toUtf8());
|
|
|
|
} else {
|
|
|
|
fOut.write(QString("ec %1 rgb:%2\n")
|
|
|
|
.arg(it.key(), color.name(QColor::HexRgb).remove('#'))
|
|
|
|
.toUtf8());
|
2019-05-01 16:15:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2019-05-23 16:22:31 +00:00
|
|
|
int r, g, b, a;
|
2019-06-12 17:46:07 +00:00
|
|
|
QVariantMap theme;
|
2019-05-01 16:15:33 +00:00
|
|
|
QString curr = Config()->getColorTheme();
|
|
|
|
|
|
|
|
if (themeName != curr) {
|
2020-03-20 18:11:38 +00:00
|
|
|
Core()->cmdRaw(QString("eco %1").arg(themeName));
|
2019-06-12 17:46:07 +00:00
|
|
|
theme = Core()->cmdj("ecj").object().toVariantMap();
|
2020-03-20 18:11:38 +00:00
|
|
|
Core()->cmdRaw(QString("eco %1").arg(curr));
|
2019-05-01 16:15:33 +00:00
|
|
|
} else {
|
2019-06-12 17:46:07 +00:00
|
|
|
theme = Core()->cmdj("ecj").object().toVariantMap();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto it = theme.begin(); it != theme.end(); it++) {
|
|
|
|
auto arr = it.value().toList();
|
|
|
|
QColor(arr[0].toInt(), arr[1].toInt(), arr[2].toInt()).getRgb(&r, &g, &b, &a);
|
|
|
|
theme[it.key()] = QJsonArray({r, g, b, a});
|
|
|
|
}
|
|
|
|
|
2019-06-17 17:42:17 +00:00
|
|
|
ColorFlags colorFlags = ColorFlags::DarkFlag;
|
|
|
|
if (Configuration::relevantThemes.contains(themeName)) {
|
|
|
|
colorFlags = Configuration::relevantThemes[themeName];
|
|
|
|
}
|
|
|
|
|
2019-06-12 17:46:07 +00:00
|
|
|
for (auto& it : cutterSpecificOptions) {
|
2019-06-17 17:42:17 +00:00
|
|
|
Configuration::cutterOptionColors[it][colorFlags].getRgb(&r, &g, &b, &a);
|
2019-06-12 17:46:07 +00:00
|
|
|
theme.insert(it, QJsonArray{r, g, b, a});
|
2019-05-01 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isCustomTheme(themeName)) {
|
|
|
|
QFile src(QDir(customR2ThemesLocationPath).filePath(themeName));
|
|
|
|
if (!src.open(QFile::ReadOnly)) {
|
|
|
|
return QJsonDocument();
|
|
|
|
}
|
|
|
|
QStringList sl;
|
2020-06-06 16:02:24 +00:00
|
|
|
for (auto &line : QString(src.readAll()).split('\n', CUTTER_QT_SKIP_EMPTY_PARTS)) {
|
|
|
|
sl = line.replace("#~", "ec ").replace("rgb:", "#").split(' ', CUTTER_QT_SKIP_EMPTY_PARTS);
|
2019-05-01 16:15:33 +00:00
|
|
|
if (sl.size() != 3 || sl[0][0] == '#') {
|
|
|
|
continue;
|
|
|
|
}
|
2019-05-23 16:22:31 +00:00
|
|
|
QColor(sl[2]).getRgb(&r, &g, &b, &a);
|
|
|
|
theme.insert(sl[1], QJsonArray({r, g, b, a}));
|
|
|
|
}
|
2019-05-01 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (auto &key : radare2UnusedOptions) {
|
|
|
|
theme.remove(key);
|
|
|
|
}
|
2019-06-12 17:46:07 +00:00
|
|
|
|
2019-10-06 17:35:44 +00:00
|
|
|
// manualy converting instead of using QJsonObject::fromVariantMap because
|
|
|
|
// Qt < 5.6 QJsonValue.fromVariant doesn't expect QVariant to already contain
|
|
|
|
// QJson values like QJsonArray. https://github.com/qt/qtbase/commit/26237f0a2d8db80024b601f676bbce54d483e672
|
|
|
|
QJsonObject obj;
|
|
|
|
for (auto it = theme.begin(); it != theme.end(); it++) {
|
|
|
|
auto &value = it.value();
|
|
|
|
if (value.canConvert<QJsonArray>()) {
|
|
|
|
obj[it.key()] = it.value().value<QJsonArray>();
|
|
|
|
} else {
|
|
|
|
obj[it.key()] = QJsonValue::fromVariant(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return QJsonDocument(obj);
|
2019-05-01 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 <b>%1</b> does not exist.").arg(themeName);
|
|
|
|
}
|
|
|
|
|
|
|
|
QFile file(QDir(customR2ThemesLocationPath).filePath(themeName));
|
|
|
|
if (file.isWritable()) {
|
|
|
|
return tr("You have no permission to write to <b>%1</b>")
|
|
|
|
.arg(QFileInfo(file).filePath());
|
|
|
|
}
|
|
|
|
if (!file.open(QFile::ReadOnly)) {
|
|
|
|
return tr("File <b>%1</b> can not be opened.")
|
|
|
|
.arg(QFileInfo(file).filePath());
|
|
|
|
}
|
|
|
|
if (!file.remove()) {
|
|
|
|
return tr("File <b>%1</b> 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 <b>%1</b> does not exist.").arg(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ok;
|
|
|
|
bool isTheme = isFileTheme(file, &ok);
|
|
|
|
if (!ok) {
|
|
|
|
return tr("File <b>%1</b> could not be opened. "
|
|
|
|
"Please make sure you have access to it and try again.")
|
|
|
|
.arg(file);
|
|
|
|
} else if (!isTheme) {
|
|
|
|
return tr("File <b>%1</b> is not a Cutter color theme").arg(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString name = src.fileName();
|
|
|
|
if (isThemeExist(name)) {
|
|
|
|
return tr("A color theme named <b>%1</b> 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 <b>%1</b> 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 <b>\"%1\"</b> 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 <b>\"%1\"</b>.").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(".", "\\.");
|
2019-10-13 14:59:12 +00:00
|
|
|
|
|
|
|
QString pattern = QString("((ec\\s+(%1)\\s+(((rgb:|#)[0-9a-fA-F]{3,8})|(%2))))\\s*").arg(options).arg(colors);
|
|
|
|
// The below construct mimics the behaviour of QRegexP::exactMatch(), which was here before
|
|
|
|
QRegularExpression regexp("\\A(?:" + pattern + ")\\z");
|
2019-05-01 16:15:33 +00:00
|
|
|
|
2020-06-06 16:02:24 +00:00
|
|
|
for (auto &line : QString(f.readAll()).split('\n', CUTTER_QT_SKIP_EMPTY_PARTS)) {
|
2019-05-01 16:15:33 +00:00
|
|
|
line.replace("#~", "ec ");
|
2019-10-13 14:59:12 +00:00
|
|
|
if (!line.isEmpty() && !regexp.match(line).hasMatch()) {
|
2019-05-01 16:15:33 +00:00
|
|
|
*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;
|
|
|
|
}
|