add theme page

This commit is contained in:
Le Tan 2020-12-26 15:05:30 +08:00
parent 4636771081
commit 40a3df305b
12 changed files with 338 additions and 38 deletions

View File

@ -315,7 +315,9 @@ QString ConfigMgr::getAppThemeFolder() const
QString ConfigMgr::getUserThemeFolder() const
{
return PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("themes"));
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("themes"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getAppDocsFolder() const
@ -325,7 +327,9 @@ QString ConfigMgr::getAppDocsFolder() const
QString ConfigMgr::getUserDocsFolder() const
{
return PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("docs"));
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("docs"));
QDir().mkpath(folderPath);
return folderPath;
}
QString ConfigMgr::getAppSyntaxHighlightingFolder() const

View File

@ -41,12 +41,35 @@ bool Theme::isValidThemeFolder(const QString &p_folder)
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());
QDir dir(p_folder);
auto obj = readJsonFile(QDir(p_folder).filePath(getFileName(File::Palette)));
auto obj = readPaletteFile(p_folder);
auto metadata = readMetadata(obj);
auto paletteObj = translatePalette(obj);
return new Theme(p_folder,
@ -312,6 +335,12 @@ QJsonObject Theme::readJsonFile(const QString &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('#');
@ -366,6 +395,8 @@ QString Theme::getFileName(File p_fileType)
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 "";
@ -395,3 +426,18 @@ QString Theme::getMarkdownEditorHighlightTheme() const
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();
}

View File

@ -5,6 +5,7 @@
#include <QHash>
#include <QJsonObject>
#include <QPair>
#include <QPixmap>
namespace tests
{
@ -26,6 +27,7 @@ namespace vnotex
MarkdownEditorStyle,
EditorHighlightStyle,
MarkdownEditorHighlightStyle,
Cover,
Max
};
@ -42,10 +44,16 @@ namespace vnotex
// Return the file path of the theme or just the theme name.
QString getMarkdownEditorHighlightTheme() const;
QString name() const;
static bool isValidThemeFolder(const QString &p_folder);
static Theme *fromFolder(const QString &p_folder);
static QString getDisplayName(const QString &p_folder, const QString &p_locale);
static QPixmap getCover(const QString &p_folder);
private:
struct Metadata
{
@ -100,6 +108,8 @@ namespace vnotex
static QJsonObject readJsonFile(const QString &p_filePath);
static QJsonObject readPaletteFile(const QString &p_folder);
// Whether @p_str is a reference definition like "@xxxx".
static bool isRef(const QString &p_str);

View File

@ -8,6 +8,8 @@
#include "exception.h"
#include <utils/iconutils.h>
#include <vtextedit/vtexteditor.h>
#include "configmgr.h"
#include "coreconfig.h"
using namespace vnotex;
@ -37,11 +39,13 @@ QString ThemeMgr::getIconFile(const QString &p_icon) const
void ThemeMgr::loadAvailableThemes()
{
m_themes.clear();
for (const auto &pa : s_searchPaths) {
loadThemes(pa);
}
if (m_availableThemes.isEmpty()) {
if (m_themes.isEmpty()) {
Exception::throwOne(Exception::Type::EssentialFileMissing,
QString("no available themes found in paths: %1").arg(s_searchPaths.join(QLatin1Char(';'))));
}
@ -53,17 +57,21 @@ void ThemeMgr::loadThemes(const QString &p_path)
QDir dir(p_path);
dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
auto themeFolders = dir.entryList();
const auto localeStr = ConfigMgr::getInst().getCoreConfig().getLocaleToUse();
for (auto &folder : themeFolders) {
checkAndAddThemeFolder(PathUtils::concatenateFilePath(p_path, folder));
checkAndAddThemeFolder(PathUtils::concatenateFilePath(p_path, folder), localeStr);
}
}
void ThemeMgr::checkAndAddThemeFolder(const QString &p_folder)
void ThemeMgr::checkAndAddThemeFolder(const QString &p_folder, const QString &p_locale)
{
if (Theme::isValidThemeFolder(p_folder)) {
QString themeName = PathUtils::dirName(p_folder);
m_availableThemes.insert(themeName, p_folder);
qDebug() << "add theme" << themeName << p_folder;
ThemeInfo info;
info.m_name = PathUtils::dirName(p_folder);
info.m_displayName = Theme::getDisplayName(p_folder, p_locale);
info.m_folderPath = p_folder;
m_themes.push_back(info);
qDebug() << "add theme" << info.m_name << info.m_displayName << info.m_folderPath;
}
}
@ -76,14 +84,14 @@ void ThemeMgr::loadCurrentTheme(const QString &p_themeName)
{
auto themeFolder = findThemeFolder(p_themeName);
if (themeFolder.isNull()) {
qCritical() << "fail to locate theme" << p_themeName;
qWarning() << "failed to locate theme" << p_themeName;
} else {
m_currentTheme.reset(loadTheme(themeFolder));
}
if (!m_currentTheme) {
const QString defaultTheme("native");
qInfo() << "fall back to default theme" << defaultTheme;
qWarning() << "fall back to default theme" << defaultTheme;
m_currentTheme.reset(loadTheme(findThemeFolder(defaultTheme)));
}
}
@ -91,28 +99,38 @@ void ThemeMgr::loadCurrentTheme(const QString &p_themeName)
Theme *ThemeMgr::loadTheme(const QString &p_themeFolder)
{
if (p_themeFolder.isEmpty()) {
qCritical("fail to load theme from empty folder");
qWarning("failed to load theme from empty folder");
return nullptr;
}
try {
return Theme::fromFolder(p_themeFolder);
} catch (Exception &p_e) {
qCritical("fail to load theme from folder %s (%s)",
p_themeFolder.toStdString().c_str(),
p_e.what());
qWarning("failed to load theme from folder %s (%s)",
p_themeFolder.toStdString().c_str(),
p_e.what());
return nullptr;
}
}
QString ThemeMgr::findThemeFolder(const QString &p_name) const
{
auto it = m_availableThemes.find(p_name);
if (it != m_availableThemes.end()) {
return it.value();
auto theme = findTheme(p_name);
if (theme) {
return theme->m_folderPath;
}
return QString();
}
const ThemeMgr::ThemeInfo *ThemeMgr::findTheme(const QString &p_name) const
{
for (const auto &info : m_themes) {
if (info.m_name == p_name) {
return &info;
}
}
return QString();
return nullptr;
}
QString ThemeMgr::fetchQtStyleSheet() const
@ -165,3 +183,22 @@ void ThemeMgr::setBaseBackground(const QColor &p_bg)
{
m_baseBackground = p_bg;
}
const QVector<ThemeMgr::ThemeInfo> &ThemeMgr::getAllThemes() const
{
return m_themes;
}
QPixmap ThemeMgr::getThemePreview(const QString &p_name) const
{
auto theme = findTheme(p_name);
if (theme) {
return Theme::getCover(theme->m_folderPath);
}
return QPixmap();
}
void ThemeMgr::refresh()
{
loadAvailableThemes();
}

View File

@ -4,10 +4,11 @@
#include <QObject>
#include <QString>
#include <QHash>
#include <QScopedPointer>
#include <QStringList>
#include <QVector>
#include <QColor>
#include <QPixmap>
#include "theme.h"
@ -17,6 +18,17 @@ namespace vnotex
{
Q_OBJECT
public:
struct ThemeInfo
{
// Id.
QString m_name;
// Locale supported.
QString m_displayName;
QString m_folderPath;
};
ThemeMgr(const QString &p_currentThemeName, QObject *p_parent = nullptr);
// @p_icon: file path or file name of the icon.
@ -40,6 +52,18 @@ namespace vnotex
const QColor &getBaseBackground() const;
void setBaseBackground(const QColor &p_bg);
const QVector<ThemeInfo> &getAllThemes() const;
const Theme &getCurrentTheme() const;
QPixmap getThemePreview(const QString &p_name) const;
const ThemeInfo *findTheme(const QString &p_name) const;
// Refresh the themes list.
// Won't affect current theme since we do not support changing theme real time for now.
void refresh();
static void addSearchPath(const QString &p_path);
static void addSyntaxHighlightingSearchPaths(const QStringList &p_paths);
@ -49,9 +73,7 @@ namespace vnotex
void loadThemes(const QString &p_path);
void checkAndAddThemeFolder(const QString &p_folder);
const Theme &getCurrentTheme() const;
void checkAndAddThemeFolder(const QString &p_folder, const QString &p_locale);
void loadCurrentTheme(const QString &p_themeName);
@ -59,8 +81,7 @@ namespace vnotex
QString findThemeFolder(const QString &p_name) const;
// Theme name to folder path mapping.
QHash<QString, QString> m_availableThemes;
QVector<ThemeInfo> m_themes;
QScopedPointer<Theme> m_currentTheme;

View File

@ -5,6 +5,7 @@
<file>themes/native/interface.qss</file>
<file>themes/native/web.css</file>
<file>themes/native/palette.json</file>
<file>themes/native/cover.png</file>
<file>docs/en/get_started.txt</file>
<file>docs/en/about_vnotex.txt</file>
<file>docs/en/shortcuts.md</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

View File

@ -7,7 +7,10 @@
"//comment" : "If there is a file named 'markdown-editor-highlight.theme' under theme folder, this value will be ignored.",
"//comment" : "Otherwise, this value specify the theme name to use.",
"//comment" : "If empty, editor-highlight-theme will be used.",
"markdown-editor-highlight-theme" : "Markdown Default"
"markdown-editor-highlight-theme" : "Markdown Default",
"display_name" : "Native",
"//comment" : "Display name for different locales",
"display_name_zh_CN" : "原素"
},
"base" : {
"fg1" : "#31373c",

View File

@ -20,6 +20,7 @@
#include <QModelIndex>
#include <QFontDatabase>
#include <QMenu>
#include <QDebug>
using namespace vnotex;
@ -188,29 +189,62 @@ void WidgetUtils::resizeToHideScrollBarLater(QScrollArea *p_scroll, bool p_verti
void WidgetUtils::resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal)
{
p_scroll->adjustSize();
bool changed = false;
auto parentWidget = p_scroll->parentWidget();
if (p_horizontal && WidgetUtils::isScrollBarVisible(p_scroll, true)) {
auto scrollBar = p_scroll->horizontalScrollBar();
auto delta = scrollBar->maximum() - scrollBar->minimum();
int newWidth = p_scroll->width() + delta;
auto availableSize = WidgetUtils::availableScreenSize(p_scroll);
if (newWidth <= availableSize.width()) {
p_scroll->resize(newWidth, p_scroll->height());
if (parentWidget) {
int newWidth = parentWidget->width() + delta;
if (newWidth <= availableSize.width()) {
changed = true;
p_scroll->resize(p_scroll->width() + delta, p_scroll->height());
auto geo = parentWidget->geometry();
parentWidget->setGeometry(geo.x() - delta / 2,
geo.y(),
newWidth,
geo.height());
}
} else {
int newWidth = p_scroll->width() + delta;
if (newWidth <= availableSize.width()) {
changed = true;
p_scroll->resize(newWidth, p_scroll->height());
}
}
}
if (p_vertical && WidgetUtils::isScrollBarVisible(p_scroll, false)) {
auto scrollBar = p_scroll->verticalScrollBar();
auto delta = scrollBar->maximum() - scrollBar->minimum();
int newHeight = p_scroll->height() + delta;
auto availableSize = WidgetUtils::availableScreenSize(p_scroll);
if (newHeight <= availableSize.height()) {
p_scroll->resize(p_scroll->width(), newHeight);
if (parentWidget) {
int newHeight = parentWidget->height() + delta;
if (newHeight <= availableSize.height()) {
changed = true;
p_scroll->resize(p_scroll->width(), p_scroll->height() + delta);
auto geo = parentWidget->geometry();
parentWidget->setGeometry(geo.x(),
geo.y() - delta / 2,
geo.width(),
newHeight);
}
} else {
int newHeight = p_scroll->height() + delta;
if (newHeight <= availableSize.height()) {
changed = true;
p_scroll->resize(p_scroll->width(), newHeight);
}
}
}
p_scroll->updateGeometry();
if (changed) {
p_scroll->updateGeometry();
}
}
QShortcut *WidgetUtils::createShortcut(const QString &p_shortcut,

View File

@ -35,7 +35,7 @@ void SettingsDialog::setupUI()
setupPageExplorer(mainLayout, widget);
m_pageLayout = new QStackedLayout();
mainLayout->addLayout(m_pageLayout, 3);
mainLayout->addLayout(m_pageLayout, 5);
setDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Apply
@ -56,6 +56,7 @@ void SettingsDialog::setupPageExplorer(QBoxLayout *p_layout, QWidget *p_parent)
m_pageExplorer = new TreeWidget(TreeWidget::None, p_parent);
TreeWidget::setupSingleColumnHeaderlessTree(m_pageExplorer, false, false);
TreeWidget::showHorizontalScrollbar(m_pageExplorer);
m_pageExplorer->setMinimumWidth(128);
layout->addWidget(m_pageExplorer);
connect(m_pageExplorer, &QTreeWidget::currentItemChanged,
@ -65,7 +66,7 @@ void SettingsDialog::setupPageExplorer(QBoxLayout *p_layout, QWidget *p_parent)
m_pageLayout->setCurrentWidget(page);
});
p_layout->addLayout(layout, 1);
p_layout->addLayout(layout, 2);
}
void SettingsDialog::setupPages()

View File

@ -2,8 +2,22 @@
#include <QComboBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QLabel>
#include <QGroupBox>
#include <QDebug>
#include <QUrl>
#include <QScrollArea>
#include <widgets/listwidget.h>
#include <QPushButton>
#include <QListWidgetItem>
#include <widgets/widgetsfactory.h>
#include <core/thememgr.h>
#include <core/vnotex.h>
#include <core/configmgr.h>
#include <utils/widgetutils.h>
using namespace vnotex;
@ -16,10 +30,71 @@ ThemePage::ThemePage(QWidget *p_parent)
void ThemePage::setupUI()
{
auto mainLayout = new QVBoxLayout(this);
// Theme.
{
auto layout = new QGridLayout();
mainLayout->addLayout(layout);
m_themeListWidget = new ListWidget(this);
layout->addWidget(m_themeListWidget, 0, 0, 3, 2);
connect(m_themeListWidget, &QListWidget::currentItemChanged,
this, [this](QListWidgetItem *p_current, QListWidgetItem *p_previous) {
Q_UNUSED(p_previous);
loadThemePreview(p_current ? p_current->data(Qt::UserRole).toString() : QString());
pageIsChanged();
});
auto refreshBtn = new QPushButton(tr("Refresh"), this);
layout->addWidget(refreshBtn, 3, 0, 1, 1);
connect(refreshBtn, &QPushButton::clicked,
this, [this]() {
VNoteX::getInst().getThemeMgr().refresh();
loadThemes();
});
auto addBtn = new QPushButton(tr("Add/Delete"), this);
layout->addWidget(addBtn, 3, 1, 1, 1);
// TODO: open an editor to edit the theme list.
connect(addBtn, &QPushButton::clicked,
this, []() {
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(ConfigMgr::getInst().getUserThemeFolder()));
});
auto updateBtn = new QPushButton(tr("Update"), this);
layout->addWidget(updateBtn, 4, 0, 1, 1);
auto openLocationBtn = new QPushButton(tr("Open Location"), this);
layout->addWidget(openLocationBtn, 4, 1, 1, 1);
connect(openLocationBtn, &QPushButton::clicked,
this, [this]() {
auto theme = VNoteX::getInst().getThemeMgr().findTheme(currentTheme());
if (theme) {
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(theme->m_folderPath));
}
});
m_noPreviewText = tr("No Preview Available");
m_previewLabel = new QLabel(m_noPreviewText, this);
m_previewLabel->setScaledContents(true);
m_previewLabel->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
auto scrollArea = new QScrollArea(this);
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setWidget(m_previewLabel);
scrollArea->setMinimumSize(256, 256);
layout->addWidget(scrollArea, 0, 2, 5, 1);
}
// Override.
{
auto box = new QGroupBox(tr("Style Override"), this);
mainLayout->addWidget(box);
}
}
void ThemePage::loadInternal()
{
loadThemes();
}
void ThemePage::saveInternal()
@ -30,3 +105,56 @@ QString ThemePage::title() const
{
return tr("Theme");
}
void ThemePage::loadThemes()
{
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
const auto &themes = themeMgr.getAllThemes();
m_themeListWidget->clear();
for (const auto &info : themes) {
auto item = new QListWidgetItem(info.m_displayName, m_themeListWidget);
item->setData(Qt::UserRole, info.m_name);
item->setToolTip(info.m_folderPath);
}
// Set current theme.
bool found = false;
const auto curThemeName = themeMgr.getCurrentTheme().name();
for (int i = 0; i < m_themeListWidget->count(); ++i) {
if (m_themeListWidget->item(i)->data(Qt::UserRole).toString() == curThemeName) {
m_themeListWidget->setCurrentRow(i);
found = true;
break;
}
}
if (!found && m_themeListWidget->count() > 0) {
m_themeListWidget->setCurrentRow(0);
}
}
void ThemePage::loadThemePreview(const QString &p_name)
{
if (p_name.isEmpty()) {
m_previewLabel->setText(m_noPreviewText);
}
auto pixmap = VNoteX::getInst().getThemeMgr().getThemePreview(p_name);
if (pixmap.isNull()) {
m_previewLabel->setText(m_noPreviewText);
} else {
const int pwidth = 512;
m_previewLabel->setPixmap(pixmap.scaledToWidth(pwidth, Qt::SmoothTransformation));
}
m_previewLabel->adjustSize();
}
QString ThemePage::currentTheme() const
{
auto item = m_themeListWidget->currentItem();
if (item) {
return item->data(Qt::UserRole).toString();
}
return QString();
}

View File

@ -3,6 +3,9 @@
#include "settingspage.h"
class QListWidget;
class QLabel;
namespace vnotex
{
class ThemePage : public SettingsPage
@ -20,6 +23,18 @@ namespace vnotex
private:
void setupUI();
void loadThemes();
void loadThemePreview(const QString &p_name);
QString currentTheme() const;
QListWidget *m_themeListWidget = nullptr;
QLabel *m_previewLabel = nullptr;
QString m_noPreviewText;
};
}