| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- #include "theme.h"
- #include <QDir>
- #include <QRegularExpression>
- #include <QSettings>
- #include <QFileInfo>
- #include <QJsonDocument>
- #include "exception.h"
- #include <utils/fileutils.h>
- #include <utils/pathutils.h>
- #include <utils/utils.h>
- #include <utils/widgetutils.h>
- using namespace vnotex;
- Theme::Theme(const QString &p_themeFolderPath,
- const Metadata &p_metadata,
- const Palette &p_palette)
- : m_themeFolderPath(p_themeFolderPath),
- m_metadata(p_metadata),
- m_palette(p_palette)
- {
- }
- bool Theme::isValidThemeFolder(const QString &p_folder)
- {
- QDir dir(p_folder);
- if (!dir.exists()) {
- qWarning() << "theme folder does not exist" << p_folder;
- return false;
- }
- // The Palette file must exist.
- auto file = getFileName(File::Palette);
- if (!dir.exists(file)) {
- qWarning() << "Not a valid theme folder" << p_folder;
- return false;
- }
- return true;
- }
- QString Theme::getDisplayName(const QString &p_folder, const QString &p_locale)
- {
- auto obj = readPaletteFile(p_folder);
- const auto metaObj = obj[QStringLiteral("metadata")].toObject();
- QString prefix("display_name");
- if (!p_locale.isEmpty()) {
- // Check full locale.
- auto fullLocale = QString("%1_%2").arg(prefix, p_locale);
- if (metaObj.contains(fullLocale)) {
- return metaObj.value(fullLocale).toString();
- }
- auto shortLocale = QString("%1_%2").arg(prefix, p_locale.split('_')[0]);
- if (metaObj.contains(shortLocale)) {
- return metaObj.value(shortLocale).toString();
- }
- }
- if (metaObj.contains(prefix)) {
- return metaObj.value(prefix).toString();
- }
- return PathUtils::dirName(p_folder);
- }
- Theme *Theme::fromFolder(const QString &p_folder)
- {
- Q_ASSERT(!p_folder.isEmpty());
- auto obj = readPaletteFile(p_folder);
- auto metadata = readMetadata(obj);
- auto paletteObj = translatePalette(obj);
- return new Theme(p_folder,
- metadata,
- paletteObj);
- }
- Theme::Metadata Theme::readMetadata(const Palette &p_obj)
- {
- Metadata data;
- const auto metaObj = p_obj[QStringLiteral("metadata")].toObject();
- data.m_revision = metaObj[QStringLiteral("revision")].toInt();
- data.m_editorHighlightTheme = metaObj[QStringLiteral("editor-highlight-theme")].toString();
- data.m_markdownEditorHighlightTheme = metaObj[QStringLiteral("markdown-editor-highlight-theme")].toString();
- return data;
- }
- Theme::Palette Theme::translatePalette(const QJsonObject &p_obj)
- {
- const QString paletteSection("palette");
- const QString baseSection("base");
- const QString widgetsSection("widgets");
- // @p_palette may contain referenced definitons: derived=@base#sub#sub2.
- Palette palette;
- palette[paletteSection] = p_obj[paletteSection];
- palette[baseSection] = p_obj[baseSection];
- palette[widgetsSection] = p_obj[widgetsSection];
- // Skip paletteSection since it will not contain any reference.
- translatePaletteObject(palette, palette, baseSection);
- translatePaletteObject(palette, palette, widgetsSection);
- return palette;
- }
- void Theme::translatePaletteObject(const Palette &p_palette,
- QJsonObject &p_obj,
- const QString &p_key)
- {
- int lastUnresolvedRefs = 0;
- while (true)
- {
- auto ret = translatePaletteObjectOnce(p_palette, p_obj, p_key);
- if (!ret.first) {
- break;
- }
- if (ret.second > 0 && ret.second == lastUnresolvedRefs) {
- qWarning() << "found cyclic references in palette definitions" << p_obj[p_key];
- break;
- }
- lastUnresolvedRefs = ret.second;
- }
- }
- QPair<bool, int> Theme::translatePaletteObjectOnce(const Palette &p_palette,
- QJsonObject &p_obj,
- const QString &p_key)
- {
- bool changed = false;
- int unresolvedRefs = 0;
- // May contain referenced definitions: derived=@base#sub#sub2.
- QRegularExpression refRe("\\A@(\\w+(?:#\\w+)*)\\z");
- const int baseCapturedIdx = 1;
- auto obj = p_obj[p_key].toObject();
- for (auto it = obj.begin(); it != obj.end(); ++it) {
- auto val = it.value();
- if (val.isString()) {
- // Check if it references to another key.
- auto match = refRe.match(val.toString());
- if (match.hasMatch()) {
- auto refVal = findValueByKeyPath(p_palette, match.captured(baseCapturedIdx));
- if (refVal.isUndefined()) {
- ++unresolvedRefs;
- qWarning() << "failed to find palette key" << match.captured(baseCapturedIdx);
- break;
- } else if (val.toString() == refVal.toString()) {
- ++unresolvedRefs;
- qWarning() << "found cyclic references in palette definitions" << it.key() << val.toString();
- break;
- }
- Q_ASSERT_X(refVal.isString(), "translatePaletteObjectOnce", val.toString().toStdString().c_str());
- it.value() = refVal.toString();
- if (isRef(refVal.toString())) {
- // It is another ref again.
- ++unresolvedRefs;
- }
- changed = true;
- }
- } else if (val.isObject()) {
- auto ret = translatePaletteObjectOnce(p_palette, obj, it.key());
- changed = changed || ret.first;
- unresolvedRefs += ret.second;
- } else {
- Q_ASSERT(false);
- }
- }
- if (changed) {
- p_obj[p_key] = obj;
- }
- return qMakePair(changed, unresolvedRefs);
- }
- QString Theme::fetchQtStyleSheet() const
- {
- const auto qtStyleFile = getFile(File::QtStyleSheet);
- if (qtStyleFile.isEmpty()) {
- return "";
- }
- auto style = FileUtils::readTextFile(qtStyleFile);
- translateStyleByPalette(m_palette, style);
- translateUrlToAbsolute(m_themeFolderPath, style);
- translateFontFamilyList(style);
- translateScaledSize(WidgetUtils::calculateScaleFactor(), style);
- return style;
- }
- void Theme::translateStyleByPalette(const Palette &p_palette, QString &p_style)
- {
- QRegularExpression refRe("(\\s|:)@(\\w+(?:#\\w+)*)");
- const int prefixCapturedIdx = 1;
- const int refCapturedIdx = 2;
- int pos = 0;
- QRegularExpressionMatch match;
- while (pos < p_style.size()) {
- int idx = p_style.indexOf(refRe, pos, &match);
- if (idx == -1) {
- break;
- }
- auto name = match.captured(refCapturedIdx);
- auto val = findValueByKeyPath(p_palette, name).toString();
- if (val.isEmpty() || isRef(val)) {
- qWarning() << "failed to translate style" << name << val;
- pos = idx + match.capturedLength();
- } else {
- pos = idx + match.capturedLength() + val.size() - (name.size() + 1);
- p_style.replace(idx + match.captured(prefixCapturedIdx).size(),
- name.size() + 1,
- val);
- }
- }
- }
- void Theme::translateUrlToAbsolute(const QString &p_basePath, QString &p_style)
- {
- QRegularExpression urlRe("(\\s|:)url\\(([^\\(\\)]+)\\)");
- const int prefixCapturedIdx = 1;
- const int urlCapturedIdx = 2;
- QDir dir(p_basePath);
- const int literalSize = QString("url(").size();
- int pos = 0;
- QRegularExpressionMatch match;
- while (pos < p_style.size()) {
- int idx = p_style.indexOf(urlRe, pos, &match);
- if (idx == -1) {
- break;
- }
- auto url = match.captured(urlCapturedIdx);
- if (QFileInfo(url).isRelative()) {
- auto absoluteUrl = dir.filePath(url);
- pos = idx + match.capturedLength() + absoluteUrl.size() - url.size();
- p_style.replace(idx + match.captured(prefixCapturedIdx).size() + literalSize,
- url.size(),
- absoluteUrl);
- } else {
- pos = idx + match.capturedLength();
- }
- }
- }
- void Theme::translateFontFamilyList(QString &p_style)
- {
- QRegularExpression fontRe("(\\s|^)font-family:([^;]+);");
- const int prefixCapturedIdx = 1;
- const int fontCapturedIdx = 2;
- int pos = 0;
- QRegularExpressionMatch match;
- while (pos < p_style.size()) {
- int idx = p_style.indexOf(fontRe, pos, &match);
- if (idx == -1) {
- break;
- }
- auto familyList = match.captured(fontCapturedIdx).trimmed();
- familyList.remove('"');
- auto family = Utils::pickAvailableFontFamily(familyList.split(','));
- if (family.isEmpty()) {
- // Could not find available font. Remove it.
- auto newStr = match.captured(prefixCapturedIdx);
- p_style.replace(idx, match.capturedLength(), newStr);
- pos = idx + newStr.size();
- } else if (family != familyList) {
- if (family.contains(' ')) {
- family = "\"" + family + "\"";
- }
- auto newStr = QString("%1font-family: %2;").arg(match.captured(prefixCapturedIdx), family);
- p_style.replace(idx, match.capturedLength(), newStr);
- pos = idx + newStr.size();
- } else {
- pos = idx + match.capturedLength();
- }
- }
- }
- void Theme::translateScaledSize(qreal p_factor, QString &p_style)
- {
- QRegularExpression scaleRe("(\\s|:)\\$([+-]?)(\\d+)(?=\\D)");
- const int prefixCapturedIdx = 1;
- const int signCapturedIdx = 2;
- const int numCapturedIdx = 3;
- int pos = 0;
- QRegularExpressionMatch match;
- while (pos < p_style.size()) {
- int idx = p_style.indexOf(scaleRe, pos, &match);
- if (idx == -1) {
- break;
- }
- auto numStr = match.captured(numCapturedIdx);
- bool ok = false;
- int val = numStr.toInt(&ok);
- if (!ok) {
- pos = idx + match.capturedLength();
- continue;
- }
- val = val * p_factor + 0.5;
- auto newStr = QString("%1%2%3").arg(match.captured(prefixCapturedIdx),
- match.captured(signCapturedIdx),
- QString::number(val));
- p_style.replace(idx, match.capturedLength(), newStr);
- pos = idx + newStr.size();
- }
- }
- QString Theme::paletteColor(const QString &p_name) const
- {
- auto val = findValueByKeyPath(m_palette, p_name).toString();
- if (!val.isEmpty() && !isRef(val)) {
- return val;
- }
- qWarning() << "undefined or invalid palette color" << p_name;
- return QString("#ff0000");
- }
- QJsonObject Theme::readJsonFile(const QString &p_filePath)
- {
- auto bytes = FileUtils::readFile(p_filePath);
- return QJsonDocument::fromJson(bytes).object();
- }
- QJsonObject Theme::readPaletteFile(const QString &p_folder)
- {
- auto obj = readJsonFile(QDir(p_folder).filePath(getFileName(File::Palette)));
- return obj;
- }
- QJsonValue Theme::findValueByKeyPath(const Palette &p_palette, const QString &p_keyPath)
- {
- auto keys = p_keyPath.split('#');
- Q_ASSERT(!keys.isEmpty());
- if (keys.size() == 1) {
- return p_palette[keys.first()];
- }
- auto obj = p_palette;
- for (int i = 0; i < keys.size() - 1; ++i) {
- obj = obj[keys[i]].toObject();
- }
- return obj[keys.last()];
- }
- bool Theme::isRef(const QString &p_str)
- {
- return p_str.startsWith('@');
- }
- QString Theme::getFile(File p_fileType) const
- {
- QDir dir(m_themeFolderPath);
- if (dir.exists(getFileName(p_fileType))) {
- return dir.filePath(getFileName(p_fileType));
- } else if (p_fileType == File::MarkdownEditorStyle) {
- // Fallback to text editor style.
- if (dir.exists(getFileName(File::TextEditorStyle))) {
- return dir.filePath(getFileName(File::TextEditorStyle));
- }
- }
- return "";
- }
- QString Theme::getFileName(File p_fileType)
- {
- switch (p_fileType) {
- case File::Palette:
- return QStringLiteral("palette.json");
- case File::QtStyleSheet:
- return QStringLiteral("interface.qss");
- case File::WebStyleSheet:
- return QStringLiteral("web.css");
- case File::HighlightStyleSheet:
- return QStringLiteral("highlight.css");
- case File::TextEditorStyle:
- return QStringLiteral("text-editor.theme");
- case File::MarkdownEditorStyle:
- return QStringLiteral("markdown-text-editor.theme");
- case File::EditorHighlightStyle:
- return QStringLiteral("editor-highlight.theme");
- case File::MarkdownEditorHighlightStyle:
- return QStringLiteral("markdown-editor-highlight.theme");
- case File::Cover:
- return QStringLiteral("cover.png");
- default:
- Q_ASSERT(false);
- return "";
- }
- }
- QString Theme::getEditorHighlightTheme() const
- {
- auto file = getFile(File::EditorHighlightStyle);
- if (file.isEmpty()) {
- return m_metadata.m_editorHighlightTheme;
- } else {
- return file;
- }
- }
- QString Theme::getMarkdownEditorHighlightTheme() const
- {
- auto file = getFile(File::MarkdownEditorHighlightStyle);
- if (!file.isEmpty()) {
- return file;
- }
- if (!m_metadata.m_markdownEditorHighlightTheme.isEmpty()) {
- return m_metadata.m_markdownEditorHighlightTheme;
- }
- return getEditorHighlightTheme();
- }
- QString Theme::name() const
- {
- return PathUtils::dirName(m_themeFolderPath);
- }
- QPixmap Theme::getCover(const QString &p_folder)
- {
- QDir dir(p_folder);
- if (dir.exists(getFileName(File::Cover))) {
- const auto coverFile = dir.filePath(getFileName(File::Cover));
- return QPixmap(coverFile);
- }
- return QPixmap();
- }
|