Browse Source

support themes

Le Tan 8 years ago
parent
commit
262c6acfd9

+ 2 - 2
src/dialog/veditsnippetdialog.cpp

@@ -39,7 +39,7 @@ void VEditSnippetDialog::setupUI(const QString &p_title, const QString &p_info)
     m_nameEdit->setValidator(validator);
 
     // Type.
-    m_typeCB = new QComboBox();
+    m_typeCB = VUtils::getComboBox();
     for (int i = 0; i < VSnippet::Type::Invalid; ++i) {
         m_typeCB->addItem(VSnippet::typeStr(static_cast<VSnippet::Type>(i)), i);
     }
@@ -49,7 +49,7 @@ void VEditSnippetDialog::setupUI(const QString &p_title, const QString &p_info)
     m_typeCB->setCurrentIndex(typeIdx);
 
     // Shortcut.
-    m_shortcutCB = new QComboBox();
+    m_shortcutCB = VUtils::getComboBox();
     m_shortcutCB->addItem(tr("None"), QChar());
     auto shortcuts = getAvailableShortcuts();
     for (auto it : shortcuts) {

+ 1 - 1
src/dialog/vnewfiledialog.cpp

@@ -52,7 +52,7 @@ void VNewFileDialog::setupUI(const QString &p_title,
     m_nameEdit->setSelection(0, (dotIndex == -1) ? p_defaultName.size() : dotIndex);
 
     // Template.
-    m_templateCB = new QComboBox();
+    m_templateCB = VUtils::getComboBox();
     m_templateCB->setToolTip(tr("Choose a template (magic word supported)"));
     m_templateCB->setSizeAdjustPolicy(QComboBox::AdjustToContents);
 

+ 5 - 5
src/dialog/vsettingsdialog.cpp

@@ -161,7 +161,7 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
     : QWidget(p_parent)
 {
     // Language combo.
-    m_langCombo = new QComboBox(this);
+    m_langCombo = VUtils::getComboBox(this);
     m_langCombo->setToolTip(tr("Choose the language of VNote interface"));
     m_langCombo->addItem(tr("System"), "System");
     auto langs = VUtils::getAvailableLanguages();
@@ -194,7 +194,7 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
 
 QLayout *VGeneralTab::setupStartupPagesLayout()
 {
-    m_startupPageTypeCombo = new QComboBox(this);
+    m_startupPageTypeCombo = VUtils::getComboBox(this);
     m_startupPageTypeCombo->setToolTip(tr("Restore tabs or open specific notes on startup"));
     m_startupPageTypeCombo->addItem(tr("None"), (int)StartupPageType::None);
     m_startupPageTypeCombo->addItem(tr("Continue where you left off"), (int)StartupPageType::ContinueLeftOff);
@@ -598,19 +598,19 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
     : QWidget(p_parent)
 {
     // Default note open mode.
-    m_openModeCombo = new QComboBox();
+    m_openModeCombo = VUtils::getComboBox();
     m_openModeCombo->setToolTip(tr("Default mode to open a note"));
     m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read);
     m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
 
     // Heading sequence.
-    m_headingSequenceTypeCombo = new QComboBox();
+    m_headingSequenceTypeCombo = VUtils::getComboBox();
     m_headingSequenceTypeCombo->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)"));
     m_headingSequenceTypeCombo->addItem(tr("Disabled"), (int)HeadingSequenceType::Disabled);
     m_headingSequenceTypeCombo->addItem(tr("Enabled"), (int)HeadingSequenceType::Enabled);
     m_headingSequenceTypeCombo->addItem(tr("Enabled for notes only"), (int)HeadingSequenceType::EnabledNoteOnly);
 
-    m_headingSequenceLevelCombo = new QComboBox();
+    m_headingSequenceLevelCombo = VUtils::getComboBox();
     m_headingSequenceLevelCombo->setToolTip(tr("Base level to start heading sequence"));
     m_headingSequenceLevelCombo->addItem(tr("1"), 1);
     m_headingSequenceLevelCombo->addItem(tr("2"), 2);

+ 7 - 2
src/main.cpp

@@ -12,9 +12,12 @@
 #include "utils/vutils.h"
 #include "vsingleinstanceguard.h"
 #include "vconfigmanager.h"
+#include "vpalette.h"
 
 VConfigManager *g_config;
 
+VPalette *g_palette;
+
 #if defined(QT_NO_DEBUG)
 // 5MB log size.
 #define MAX_LOG_SIZE 5 * 1024 * 1024
@@ -164,10 +167,12 @@ int main(int argc, char *argv[])
         app.installTranslator(&translator);
     }
 
+    VPalette palette(g_config->getThemeFile());
+    g_palette = &palette;
+
     VMainWindow w(&guard);
-    QString style = VUtils::readFileFromDisk(":/resources/vnote.qss");
+    QString style = palette.fetchQtStyleSheet();
     if (!style.isEmpty()) {
-        VUtils::processStyle(style, w.getPalette());
         app.setStyleSheet(style);
     }
 

+ 0 - 9
src/resources/icons/arrow_dropup.svg

@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
-<g>
-	<polygon points="128,320 256,192 384,320 	"/>
-</g>
-</svg>

+ 9 - 9
src/resources/icons/arrow_dropdown.svg → src/resources/themes/v_white/arrow_dropdown.svg

@@ -1,9 +1,9 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
-<g>
-	<polygon points="128,192 256,320 384,192 	"/>
-</g>
-</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g>
+	<polygon points="128,192 256,320 384,192 	"/>
+</g>
+</svg>

+ 10 - 0
src/resources/themes/v_white/close.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4
+	L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1
+	c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1
+	c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
+</svg>

+ 10 - 10
src/resources/icons/close_grey.svg → src/resources/themes/v_white/close_grey.svg

@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
-<path style="fill:#BDBDBD" d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4
-	L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1
-	c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1
-	c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
-</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<path style="fill:#9E9E9E" d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4
+	L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1
+	c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1
+	c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
+</svg>

+ 12 - 12
src/resources/icons/float.svg → src/resources/themes/v_white/float.svg

@@ -1,12 +1,12 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
-<g>
-	<path d="M64,144v304h303.9V144H64z M351.9,432H80V160h271.9V432z"/>
-	<g>
-		<polygon points="448,64 144,64 144,128 160,128 160,80 432,80 432,352 384,352 384,368 448,368 		"/>
-	</g>
-</g>
-</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<path d="M64,144v304h303.9V144H64z M351.9,432H80V160h271.9V432z"/>
+	<g>
+		<polygon points="448,64 144,64 144,128 160,128 160,80 432,80 432,352 384,352 384,368 448,368 		"/>
+	</g>
+</g>
+</svg>

+ 171 - 0
src/resources/themes/v_white/v_white.palette

@@ -0,0 +1,171 @@
+; File path could be absolute path or relative path (related to this file).
+; Use @color_tag to reference a style.
+
+[metadata]
+qss_file=v_white.qss
+
+[phony]
+; Abstract color attributes.
+base_fg=#000000
+base_color_1=#EEEEEE
+hover_color_1=#C4C4C4
+selected_color_1=#BEBEBE
+base_color_2=#E0E0E0
+separator_color=#E0E0E0
+border_color=#9E9E9E
+focus_color=#BDBDBD
+content_color=#FAFAFA
+selection_color=#64B5F6
+
+[widgets]
+; Widget color attributes.
+
+; QWidget.
+widget_fg=@base_fg
+
+; Separator of dock widgets.
+dock_separator_bg=@base_color_2
+dock_separator_hover_bg=@base_color_2
+
+; Menubar.
+menubar_bg=@base_color_1
+menubar_fg=@base_fg
+menubar_item_selected_bg=@selected_color_1
+
+; Menu.
+menu_bg=@base_color_1
+menu_fg=@base_fg
+menu_item_selected_bg=@selected_color_1
+menu_separator_bg=@separator_color
+
+; Tooltip.
+tooltip_border=@base_color_2
+tooltip_bg=@base_color_1
+tooltip_fg=@base_fg
+
+; Toolbar.
+toolbar_bg=@base_color_1
+toolbutton_hover_bg=@hover_color_1
+toolbutton_pressed_bg=@selected_color_1
+
+; Dockwidget.
+dockwidget_title_bg=@base_color_2
+dockwidget_button_hover_bg=@hover_color_1
+
+; PushButton.
+pushbutton_fg=@base_fg
+pushbutton_bg=transparent
+pushbutton_border=@border_color
+pushbutton_pressed_bg=@selected_color_1
+pushbutton_hover_bg=@hover_color_1
+pushbutton_default_border=#424242
+
+pushbutton_cornerbtn_hover_bg=@hover_color_1
+pushbutton_cornerbtn_focus_bg=@focus_color
+
+pushbutton_statusbtn_hover_bg=@hover_color_1
+pushbutton_statusbtn_focus_bg=@focus_color
+
+pushbutton_flatbtn_hover_bg=@hover_color_1
+pushbutton_flatbtn_focus_bg=@focus_color
+
+pushbutton_selectionbtn_hover_bg=@hover_color_1
+pushbutton_selectionbtn_focus_bg=@focus_color
+
+pushbutton_titlebtn_bg=@base_color_2
+pushbutton_titlebtn_hover_bg=@hover_color_1
+pushbutton_titlebtn_focus_bg=@focus_color
+
+pushbutton_dangerbtn_fg=#FFF
+pushbutton_dangerbtn_border=#D43F3A
+pushbutton_dangerbtn_bg=#D9534F
+pushbutton_dangerbtn_hover_fg=#FFF
+pushbutton_dangerbtn_hover_bg=#C9302C
+pushbutton_dangerbtn_hover_border=#AC2925
+
+; ComboBox.
+combobox_border=@border_color
+combobox_fg=@base_fg
+combobox_bg=@content_color
+combobox_view_border=@border_color
+combobox_view_selected_bg=@selected_color_1
+combobox_view_selected_fg=@base_fg
+combobox_view_item_hover_fg=@base_fg
+combobox_view_item_hover_bg=@hover_color_1
+combobox_focus_bg=@focus_color
+
+; Label.
+label_fg=@base_fg
+label_titlelabel_fg=@base_fg
+label_titlelabel_bg=@base_color_2
+
+; LineEdit.
+lineedit_border=@border_color
+lineedit_fg=@base_fg
+lineedit_bg=@content_color
+lineedit_selection_fg=@base_fg
+lineedit_selection_bg=@selection_color
+
+; TabWidget.
+tabwidget_pane_border=@selected_color_1
+
+; TabBar.
+tabbar_fg=@base_fg
+tabbar_bg=@base_color_1
+tabbar_border=@border_color
+
+tabbar_selected_fg=@base_fg
+tabbar_selected_bg=@selected_color_1
+tabbar_selected_border=@border_color
+
+tabbar_hover_fg=@base_fg
+tabbar_hover_bg=@hover_color_1
+tabbar_hover_border=@border_color
+
+tabbar_closebutton_hover_bg=@hover_color_1
+tabbar_clostbutton_focus_bg=@focus_color
+
+; SelectorItem.
+selectoritem_border=@base_fg
+selectoritem_fg=@base_fg
+selectoritem_bg=@base_color_1
+
+; InsertSelector.
+insertselector_bg=@base_color_1
+
+; TreeView.
+treeview_fg=@base_fg
+treeview_bg=@content_color
+treeview_item_hover_fg=@base_fg
+treeview_item_hover_bg=@hover_color_1
+treeview_item_selected_fg=@base_fg
+treeview_item_selected_bg=@selected_color_1
+treeview_item_selected_avtive_fg=@base_fg
+treeview_item_selected_avtive_bg=@selected_color_1
+treeview_item_selected_inactive_fg=@base_fg
+treeview_item_selected_inactive_bg=#D0D0D0
+
+; ListView.
+listview_fg=@base_fg
+listview_bg=@content_color
+listview_item_hover_fg=@base_fg
+listview_item_hover_bg=@hover_color_1
+listview_item_selected_fg=@base_fg
+listview_item_selected_bg=@selected_color_1
+listview_item_selected_avtive_fg=@base_fg
+listview_item_selected_avtive_bg=@selected_color_1
+listview_item_selected_inactive_fg=@base_fg
+listview_item_selected_inactive_bg=#D0D0D0
+
+; Splitter.
+splitter_handle_bg=@base_color_2
+splitter_mainsplitter_border=@base_color_2
+
+; StatusBar.
+statusbar_fg=@base_fg
+statusbar_bg=@base_color_1
+
+; ScrollBar.
+scrollbar_bg=#EEEEEE
+scrollbar_handle_bg=#D0D0D0
+scrollbar_addline_bg=#E0E0E0

+ 561 - 0
src/resources/themes/v_white/v_white.qss

@@ -0,0 +1,561 @@
+QToolTip
+{
+    border: 1px solid @tooltip_border;
+    background: @tooltip_bg;
+    color: @tooltip_fg;
+    padding: 5px;
+}
+
+/* QWidget */
+QWidget
+{
+    color: @widget_fg;
+}
+
+QWidget[NotebookPanel="true"] {
+    padding-left: 3px;
+}
+/* End QWidget */
+
+QMainWindow::separator {
+    background: @dock_separator_bg;
+    width: 2px;
+    height: 2px;
+}
+
+QMainWindow::separator:hover {
+    background: @dock_separator_hover_bg;
+}
+
+QMenuBar {
+    border: none;
+    background: @menubar_bg;
+    color: @menubar_fg;
+}
+
+QMenuBar::item:selected {
+    background: @menubar_item_selected_bg;
+}
+
+QMenu {
+    background: @menu_bg;
+    color: @menu_fg;
+    margin: 2px;
+}
+
+QMenu::icon {
+    margin: 5px;
+}
+
+QMenu::item {
+    padding: 5px 30px 5px 30px;
+    background: 1px solid transparent;
+}
+
+QMenu::item:selected {
+    background: @menu_item_selected_bg;
+}
+
+QMenu::icon:checked { /* appearance of a 'checked' icon */
+    border: 2px solid @menu_item_selected_bg;
+}
+
+QMenu::separator {
+    height: 2px;
+    background: @menu_separator_bg;
+    margin-left: 10px;
+    margin-right: 5px;
+}
+
+QMenu::indicator {
+    width: 20px;
+    height: 20px;
+}
+
+QToolBar {
+    border: none;
+    background: @toolbar_bg;
+}
+
+QToolButton {
+    border: none;
+    background: transparent;
+    margin: 1px 3px 1px 3px;
+    padding: 0px;
+}
+
+QToolButton[popupMode="1"] { /* only for MenuButtonPopup */
+    padding-right: 16px; /* make way for the popup button */
+}
+
+QToolButton[popupMode="2"] { /* only for InstantPopup */
+    padding-right: 10px; /* make way for the popup button */
+}
+
+QToolButton:hover {
+    border:none;
+    background: @toolbutton_hover_bg;
+}
+
+QToolButton::pressed {
+    background: @toolbutton_pressed_bg;
+}
+
+/* the subcontrols below are used only in the MenuButtonPopup mode */
+QToolButton::menu-button {
+    border: none;
+    width: 16px;
+}
+
+/* DockWidget */
+QDockWidget {
+    titlebar-close-icon: url(close.svg);
+    titlebar-normal-icon: url(float.svg);
+}
+
+QDockWidget::Title {
+    background: @dockwidget_title_bg;
+    text-align: center left;
+}
+
+QDockWidget::close-button, QDockWidget::float-button {
+    border: none;
+}
+
+QDockWidget::close-button:hover, QDockWidget::float-button:hover {
+    background: @dockwidget_button_hover_bg;
+}
+/* End DockWidget */
+
+/* QPushButton */
+QPushButton {
+    color: @pushbutton_fg;
+    background: @pushbutton_bg;
+    border: 1px solid @pushbutton_border;
+    padding: 3px;
+    min-width: 80px;
+}
+
+QPushButton:pressed {
+    background-color: @pushbutton_pressed_bg;
+}
+
+QPushButton:hover {
+    background-color: @pushbutton_hover_bg;
+}
+
+QPushButton:flat {
+    border: none;
+}
+
+QPushButton::default {
+    border-color: @pushbutton_default_border;
+}
+
+QPushButton[CornerBtn="true"] {
+    padding: 4px -2px 4px -2px;
+    margin: 0px;
+    border: none;
+    background-color: transparent;
+    min-width: -1;
+}
+
+QPushButton[CornerBtn="true"]::menu-indicator {
+    image: none;
+}
+
+QPushButton[CornerBtn="true"]:hover {
+    background-color: @pushbutton_cornerbtn_hover_bg;
+}
+
+QPushButton[CornerBtn="true"]:focus {
+    background-color: @pushbutton_cornerbtn_focus_bg;
+}
+
+QPushButton[StatusBtn="true"] {
+    font: bold;
+    padding: 0px 2px 0px 2px;
+    margin: 0px;
+    border: none;
+    background-color: transparent;
+    min-width: -1;
+}
+
+QPushButton[StatusBtn="true"]:hover {
+    background-color: @pushbutton_statusbtn_hover_bg;
+}
+
+QPushButton[StatusBtn="true"]:focus {
+    background-color: @pushbutton_statusbtn_focus_bg;;
+}
+
+QPushButton[FlatBtn="true"] {
+    padding: 4px;
+    margin: 0px;
+    border: none;
+    background-color: transparent;
+    min-width: -1;
+}
+
+QPushButton[FlatBtn="true"]:hover {
+    background-color: @pushbutton_flatbtn_hover_bg;
+}
+
+QPushButton[FlatBtn="true"]:focus {
+    background-color: @pushbutton_flatbtn_focus_bg;
+}
+
+QPushButton[SelectionBtn="true"] {
+    padding: 4px 10px 4px 10px;
+    border: none;
+    background-color: transparent;
+    font-size: 15pt;
+    text-align: left;
+    min-width: -1;
+}
+
+QPushButton[SelectionBtn="true"]:hover {
+    background-color: @pushbutton_selectionbtn_hover_bg;
+}
+
+QPushButton[SelectionBtn="true"]:focus {
+    background-color: @pushbutton_selectionbtn_focus_bg;
+}
+
+QPushButton[TitleBtn="true"] {
+    padding: 4px;
+    margin: 0px;
+    border: none;
+    background-color: @pushbutton_titlebtn_bg;
+    min-width: -1;
+}
+
+QPushButton[TitleBtn="true"]:hover {
+    background-color: @pushbutton_titlebtn_hover_bg;
+}
+
+QPushButton[TitleBtn="true"]:focus {
+    background-color: @pushbutton_titlebtn_focus_bg;
+}
+
+QPushButton[DangerBtn="true"] {
+    color: @pushbutton_dangerbtn_fg;
+    border-color: @pushbutton_dangerbtn_border;
+    background-color: @pushbutton_dangerbtn_bg;
+    min-width: -1;
+}
+
+QPushButton[DangerBtn="true"]:hover {
+    color: @pushbutton_dangerbtn_hover_fg;
+    border-color: @pushbutton_dangerbtn_hover_border;
+    background-color: @pushbutton_dangerbtn_hover_bg;
+}
+/* End QPushButton*/
+
+/* QComboBox */
+QComboBox {
+    padding: 3px;
+    color: @combobox_fg;
+    background: @combobox_bg;
+    border: 1px solid @combobox_border;
+}
+
+QComboBox:focus {
+    background-color: @combobox_focus_bg;
+}
+
+QComboBox::drop-down {
+    subcontrol-origin: padding;
+    subcontrol-position: top right;
+    width: 20px;
+    border: none;
+    background: transparent;
+}
+
+QComboBox::down-arrow {
+    image: url(arrow_dropdown.svg);
+    width: 20px;
+    height: 20px;
+}
+
+QComboBox QAbstractItemView {
+    padding: 2px;
+    border: 1px solid @combobox_view_border;
+    background: @combobox_bg;
+    selection-color: @combobox_view_selected_fg;
+    selection-background-color: @combobox_view_selected_bg;
+}
+
+QComboBox QAbstractItemView::item {
+    background: transparent;
+    padding: 3px;
+}
+
+QComboBox QAbstractItemView::item:hover {
+    color: @combobox_view_item_hover_fg;
+    background: @combobox_view_item_hover_bg;
+}
+
+QComboBox#NotebookSelector {
+    border: none;
+    font-size: 13pt;
+    padding-top: 3px;
+    padding-bottom: 3px;
+    icon-size: 30px;
+}
+
+QComboBox#NotebookSelector QListWidget {
+    border: 1px solid @combobox_view_border;
+    background-color: @combobox_bg;
+    font-size: 13pt;
+    icon-size: 30px;
+}
+
+QComboBox#NotebookSelector QListWidget::item {
+    padding-top: 10px;
+    padding-bottom: 10px;
+}
+
+QComboBox#NotebookSelector QListWidget::item:hover {
+    color: @combobox_view_item_hover_fg;
+    background-color: @combobox_view_item_hover_bg;
+}
+/* End QComboBox */
+
+/* QLabel */
+QLabel {
+    border: none;
+    color: @label_fg;
+    background: transparent;
+}
+
+QLabel[TitleLabel="true"] {
+    padding-top: 5px;
+    padding-bottom: 5px;
+    color: @label_titlelabel_fg;
+    background-color: @label_titlelabel_bg;
+}
+
+QLabel[ColorRedLabel="true"] {
+    padding-left: 5px;
+    padding-right: 5px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: #D32F2F;
+}
+
+QLabel[ColorGreenLabel="true"] {
+    padding-left: 5px;
+    padding-right: 5px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: #388E3C;
+}
+
+QLabel[ColorGreyLabel="true"] {
+    padding-left: 5px;
+    padding-right: 5px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: #616161;
+}
+
+QLabel[ColorTealLabel="true"] {
+    padding-left: 5px;
+    padding-right: 5px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: #00796B;
+}
+/* End QLabel */
+
+/* QLineEdit */
+QLineEdit {
+    border: 1px solid @lineedit_border;
+    padding: 3px;
+    color: @lineedit_fg;
+    background: @lineedit_bg;
+    selection-color: @lineedit_selection_fg;
+    selection-background-color: @lineedit_selection_bg;
+}
+
+QLineEdit[VimCommandLine="true"] {
+    padding: 0px;
+    margin: 0px;
+    border: none;
+}
+/* End QLineEdit */
+
+/* QTabWidget */
+QTabWidget {
+    border: none;
+}
+
+QTabWidget::pane {
+    border: none;
+    border-top: 3px solid @tabwidget_pane_border;
+}
+/* End QTabWidget */
+
+/* QTabBar */
+QTabBar::tab {
+    color: @tabbar_fg;
+    background: @tabbar_bg;
+    border: 1px solid @tabbar_border;
+    border-bottom: none;
+    padding: 2px;
+}
+
+QTabBar::tab:selected {
+    color: @tabbar_selected_fg;
+    background: @tabbar_selected_bg;
+    border: 1px solid @tabbar_selected_border;
+    border-bottom: none;
+}
+
+QTabBar::tab:hover {
+    color: @tabbar_hover_fg;
+    background: @tabbar_hover_bg;
+    border: 1px solid @tabbar_hover_border;
+    border-bottom: none;
+}
+
+QTabBar::tab:!selected {
+    margin-top: 2px; /* make non-selected tabs look smaller */
+}
+
+QTabBar::close-button {
+    image: url(close_grey.svg);
+}
+
+QTabBar::close-button:hover {
+    image: url(close.svg);
+    background-color: @tabbar_closebutton_hover_bg;
+}
+
+QTabBar::close-button:focus {
+    image: url(close.svg);
+    background-color: @tabbar_clostbutton_focus_bg;
+}
+/* End QTabBar */
+
+VSelectorItemWidget QLabel[SelectorItemShortcutLabel="true"] {
+    font: bold;
+    border: 2px solid @selectoritem_border;
+    padding: 3px;
+    border-radius: 5px;
+    background-color: @selectoritem_bg;
+    color: @selectoritem_fg;
+}
+
+VInsertSelector {
+    border: none;
+    background: @insertselector_bg;
+}
+
+/* QTreeView */
+QTreeView {
+    color: @treeview_fg;
+    background: @treeview_bg;
+    show-decoration-selected: 1;
+    padding-top: 3px;
+    border: none;
+}
+
+QTreeView::item {
+    padding-top: 5px;
+    padding-bottom: 5px;
+}
+
+QTreeView::item:hover {
+    color: @treeview_item_hover_fg;
+    background: @treeview_item_hover_bg;
+}
+
+QTreeView::item:selected {
+    color: @treeview_item_selected_fg;
+    background: @treeview_item_selected_bg;
+}
+
+QTreeView::item:selected:active {
+    color: @treeview_item_selected_active_fg;
+    background: @treeview_item_selected_active_bg;
+}
+
+QTreeView::item:selected:!active {
+    color: @treeview_item_selected_inactive_fg;
+    background: @treeview_item_selected_inactive_bg;
+}
+/* End QTreeView */
+
+/* QListView */
+QListView {
+    color: @listview_fg;
+    background: @listview_bg;
+    show-decoration-selected: 1;
+    padding-top: 3px;
+    border: none;
+}
+
+QListView::item {
+    padding-top: 5px;
+    padding-bottom: 5px;
+}
+
+QListView::item:hover {
+    color: @listview_item_hover_fg;
+    background: @listview_item_hover_bg;
+}
+
+QListView::item:selected {
+    color: @listview_item_selected_fg;
+    background: @listview_item_selected_bg;
+}
+
+QListView::item:selected:active {
+    color: @listview_item_selected_active_fg;
+    background: @listview_item_selected_active_bg;
+}
+
+QListView::item:selected:!active {
+    color: @listview_item_selected_inactive_fg;
+    background: @listview_item_selected_inactive_bg;
+}
+/* End QListView */
+
+/* QSplitter */
+QSplitter {
+    border: none;
+}
+
+QSplitter::handle {
+    background-color: @splitter_handle_bg;
+}
+
+QSplitter::handle:vertical {
+    height: 2px;
+}
+
+QSplitter::handle:horizontal {
+    width: 2px;
+}
+
+QSplitter#MainSplitter {
+    border-top: 2px solid @splitter_mainsplitter_border;
+}
+/* End QSplitter */
+
+/* QStatusBar */
+QStatusBar {
+    color: @statusbar_fg;
+    background: @statusbar_bg;
+}
+/* End QStatusBar */
+
+QWidget[MainEditor="true"] {
+    border: none;
+}

+ 3 - 0
src/resources/vnote.ini

@@ -1,4 +1,7 @@
 [global]
+; Theme name
+theme=v_white
+
 welcome_page_path=:/resources/welcome.html
 
 ; CSS style for Markdown template

+ 4 - 2
src/src.pro

@@ -98,7 +98,8 @@ SOURCES += main.cpp\
     utils/vimnavigationforwidget.cpp \
     vtoolbox.cpp \
     vinsertselector.cpp \
-    utils/vclipboardutils.cpp
+    utils/vclipboardutils.cpp \
+    vpalette.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -183,7 +184,8 @@ HEADERS  += vmainwindow.h \
     utils/vimnavigationforwidget.h \
     vtoolbox.h \
     vinsertselector.h \
-    utils/vclipboardutils.h
+    utils/vclipboardutils.h \
+    vpalette.h
 
 RESOURCES += \
     vnote.qrc \

+ 11 - 0
src/utils/vutils.cpp

@@ -21,6 +21,8 @@
 #include <QRegExpValidator>
 #include <QRegExp>
 #include <QKeySequence>
+#include <QComboBox>
+#include <QStyledItemDelegate>
 
 #include "vorphanfile.h"
 #include "vnote.h"
@@ -1074,3 +1076,12 @@ bool VUtils::isMetaKey(int p_key)
            || p_key == Qt::Key_Meta
            || p_key == Qt::Key_Alt;
 }
+
+QComboBox *VUtils::getComboBox(QWidget *p_parent)
+{
+    QComboBox *box = new QComboBox(p_parent);
+    QStyledItemDelegate *itemDelegate = new QStyledItemDelegate(box);
+    box->setItemDelegate(itemDelegate);
+
+    return box;
+}

+ 5 - 0
src/utils/vutils.h

@@ -15,6 +15,8 @@ class QKeyEvent;
 class VFile;
 class VOrphanFile;
 class VNotebook;
+class QWidget;
+class QComboBox;
 
 #if !defined(V_ASSERT)
     #define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
@@ -261,6 +263,9 @@ public:
     // Ctrl, Meta, Shift, Alt.
     static bool isMetaKey(int p_key);
 
+    // Create and return a QComboBox.
+    static QComboBox *getComboBox(QWidget *p_parent = nullptr);
+
     // Regular expression for image link.
     // ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" )
     // Captured texts (need to be trimmed):

+ 57 - 5
src/vconfigmanager.cpp

@@ -32,6 +32,8 @@ const QString VConfigManager::c_snippetConfigFile = QString("snippet.json");
 
 const QString VConfigManager::c_styleConfigFolder = QString("styles");
 
+const QString VConfigManager::c_themeConfigFolder = QString("themes");
+
 const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles");
 
 const QString VConfigManager::c_templateConfigFolder = QString("templates");
@@ -74,6 +76,8 @@ void VConfigManager::initialize()
     outputDefaultCodeBlockCssStyle();
     outputDefaultEditorStyle();
 
+    initThemes();
+
     m_defaultEditPalette = QTextEdit().palette();
 
     m_editorStyle = getConfigFromSettings("global", "editor_style").toString();
@@ -283,6 +287,9 @@ void VConfigManager::initialize()
 
     m_vimExemptionKeys = getConfigFromSettings("global",
                                                "vim_exemption_keys").toString();
+
+    m_theme = getConfigFromSettings("global",
+                                    "theme").toString();
 }
 
 void VConfigManager::initSettings()
@@ -783,36 +790,53 @@ QString VConfigManager::getConfigFilePath() const
     return userSettings->fileName();
 }
 
-QString VConfigManager::getStyleConfigFolder() const
+const QString &VConfigManager::getStyleConfigFolder() const
 {
     static QString path = QDir(getConfigFolder()).filePath(c_styleConfigFolder);
     return path;
 }
 
-QString VConfigManager::getCodeBlockStyleConfigFolder() const
+const QString &VConfigManager::getThemeConfigFolder() const
+{
+    static QString path = QDir(getConfigFolder()).filePath(c_themeConfigFolder);
+    return path;
+}
+
+const QString &VConfigManager::getCodeBlockStyleConfigFolder() const
 {
     static QString path = QDir(getStyleConfigFolder()).filePath(c_codeBlockStyleConfigFolder);
     return path;
 }
 
-QString VConfigManager::getTemplateConfigFolder() const
+const QString &VConfigManager::getTemplateConfigFolder() const
 {
     static QString path = QDir(getConfigFolder()).filePath(c_templateConfigFolder);
     return path;
 }
 
-QString VConfigManager::getSnippetConfigFolder() const
+const QString &VConfigManager::getSnippetConfigFolder() const
 {
     static QString path = QDir(getConfigFolder()).filePath(c_snippetConfigFolder);
     return path;
 }
 
-QString VConfigManager::getSnippetConfigFilePath() const
+const QString &VConfigManager::getSnippetConfigFilePath() const
 {
     static QString path = QDir(getSnippetConfigFolder()).filePath(c_snippetConfigFile);
     return path;
 }
 
+QString VConfigManager::getThemeFile() const
+{
+    QString file;
+    auto it = m_themes.find(m_theme);
+    if (it != m_themes.end()) {
+        file = QDir(getThemeConfigFolder()).filePath(it.value());
+    }
+
+    return file;
+}
+
 QVector<QString> VConfigManager::getCssStyles() const
 {
     QVector<QString> res;
@@ -1368,3 +1392,31 @@ const QString &VConfigManager::getFlashPage() const
 
     return m_flashPage;
 }
+
+void VConfigManager::initThemes()
+{
+    m_themes.clear();
+
+    // Built-in.
+    m_themes.insert(tr("v_white"), ":/resources/themes/v_white/v_white.palette");
+
+    // User theme folder.
+    QDir dir(getThemeConfigFolder());
+    if (!dir.exists()) {
+        dir.mkpath(getThemeConfigFolder());
+        return;
+    }
+
+    dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
+    QStringList dirs = dir.entryList();
+    for (auto const &item : dirs) {
+        QDir themeDir(dir.filePath(item));
+        QStringList files = themeDir.entryList(QStringList() << "*.palette");
+        if (files.size() != 1) {
+            continue;
+        }
+
+        QFileInfo fi(files[0]);
+        m_themes.insert(fi.completeBaseName(), themeDir.filePath(files[0]));
+    }
+}

+ 51 - 5
src/vconfigmanager.h

@@ -353,15 +353,20 @@ public:
     QString getConfigFilePath() const;
 
     // Get the folder c_styleConfigFolder in the config folder.
-    QString getStyleConfigFolder() const;
+    const QString &getStyleConfigFolder() const;
 
     // Get the folder c_templateConfigFolder in the config folder.
-    QString getTemplateConfigFolder() const;
+    const QString &getTemplateConfigFolder() const;
+
+    // Get the folder c_themeConfigFolder in the config folder.
+    const QString &getThemeConfigFolder() const;
 
     // Get the folder c_snippetConfigFolder in the config folder.
-    QString getSnippetConfigFolder() const;
+    const QString &getSnippetConfigFolder() const;
+
+    const QString &getSnippetConfigFilePath() const;
 
-    QString getSnippetConfigFilePath() const;
+    QString getThemeFile() const;
 
     // Read all available css files in c_styleConfigFolder.
     QVector<QString> getCssStyles() const;
@@ -370,7 +375,7 @@ public:
     QVector<QString> getNoteTemplates(DocType p_type = DocType::Unknown) const;
 
     // Get the folder c_codeBlockStyleConfigFolder in the config folder.
-    QString getCodeBlockStyleConfigFolder() const;
+    const QString &getCodeBlockStyleConfigFolder() const;
 
     // Read all available css files in c_codeBlockStyleConfigFolder.
     QVector<QString> getCodeBlockCssStyles() const;
@@ -397,6 +402,14 @@ public:
 
     const QString &getFlashPage() const;
 
+    // All the themes.
+    QList<QString> getThemes() const;
+
+    // Return current theme name.
+    const QString &getTheme() const;
+
+    void setTheme(const QString &p_theme);
+
 private:
     // Look up a config from user and default settings.
     QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@@ -474,6 +487,9 @@ private:
     // Whether @p_seq is a valid key sequence for shortcuts.
     bool isValidKeySequence(const QString &p_seq);
 
+    // Init the themes name-file mappings.
+    void initThemes();
+
     // Default font and palette.
     QFont m_defaultEditFont;
     QPalette m_defaultEditPalette;
@@ -760,6 +776,13 @@ private:
     // Absolute path of flash page.
     QString m_flashPage;
 
+    // The theme name.
+    QString m_theme;
+
+    // All the themes.
+    // [name] -> [file path].
+    QMap<QString, QString> m_themes;
+
     // The name of the config file in each directory, obsolete.
     // Use c_dirConfigFile instead.
     static const QString c_obsoleteDirConfigFile;
@@ -792,6 +815,9 @@ private:
     // The folder name of style files.
     static const QString c_styleConfigFolder;
 
+    // The folder name of theme files.
+    static const QString c_themeConfigFolder;
+
     // The folder name of code block style files.
     static const QString c_codeBlockStyleConfigFolder;
 
@@ -1881,4 +1907,24 @@ inline const QString &VConfigManager::getVimExemptionKeys() const
     return m_vimExemptionKeys;
 }
 
+inline QList<QString> VConfigManager::getThemes() const
+{
+    return m_themes.keys();
+}
+
+inline const QString &VConfigManager::getTheme() const
+{
+    return m_theme;
+}
+
+inline void VConfigManager::setTheme(const QString &p_theme)
+{
+    if (p_theme == m_theme) {
+        return;
+    }
+
+    m_theme = p_theme;
+    setConfigToSettings("global", "theme", m_theme);
+}
+
 #endif // VCONFIGMANAGER_H

+ 10 - 0
src/vinsertselector.cpp

@@ -111,3 +111,13 @@ void VInsertSelector::showEvent(QShowEvent *p_event)
         setFocus();
     }
 }
+
+void VInsertSelector::paintEvent(QPaintEvent *p_event)
+{
+    QStyleOption opt;
+    opt.init(this);
+    QPainter p(this);
+    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+
+    QWidget::paintEvent(p_event);
+}

+ 3 - 0
src/vinsertselector.h

@@ -7,6 +7,7 @@
 class QPushButton;
 class QKeyEvent;
 class QShowEvent;
+class QPaintEvent;
 
 struct VInsertSelectorItem
 {
@@ -64,6 +65,8 @@ protected:
 
     void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
 
+    void paintEvent(QPaintEvent *p_event) Q_DECL_OVERRIDE;
+
 private slots:
     void itemClicked(const QString &p_name);
 

+ 36 - 0
src/vmainwindow.cpp

@@ -925,6 +925,9 @@ void VMainWindow::initFileMenu()
             this, &VMainWindow::printNote);
     m_printAct->setEnabled(false);
 
+    // Themes.
+    initThemeMenu(fileMenu);
+
     // Settings.
     QAction *settingsAct = newAction(QIcon(":/resources/icons/settings.svg"),
                                      tr("&Settings"), this);
@@ -2775,3 +2778,36 @@ void VMainWindow::initHeadingButton(QToolBar *p_tb)
     m_headingBtn->setMenu(menu);
     p_tb->addWidget(m_headingBtn);
 }
+
+void VMainWindow::initThemeMenu(QMenu *p_menu)
+{
+    QMenu *themeMenu = p_menu->addMenu(tr("Theme"));
+    themeMenu->setToolTipsVisible(true);
+
+    QActionGroup *ag = new QActionGroup(this);
+    connect(ag, &QActionGroup::triggered,
+            this, [this](QAction *p_action) {
+                if (!p_action) {
+                    return;
+                }
+
+                QString data = p_action->data().toString();
+                g_config->setTheme(data);
+            });
+
+    QList<QString> themes = g_config->getThemes();
+    QString theme = g_config->getTheme();
+    for (auto const &item : themes) {
+        QAction *act = new QAction(item, ag);
+        act->setToolTip(tr("Set as the theme of VNote (restart VNote to make it work)"));
+        act->setCheckable(true);
+        act->setData(item);
+
+        // Add it to the menu.
+        themeMenu->addAction(act);
+
+        if (theme == item) {
+            act->setChecked(true);
+        }
+    }
+}

+ 2 - 0
src/vmainwindow.h

@@ -263,6 +263,8 @@ private:
 
     void initHeadingButton(QToolBar *p_tb);
 
+    void initThemeMenu(QMenu *p_emnu);
+
     // Captain mode functions.
 
     // Popup the attachment list if it is enabled.

+ 1 - 0
src/vmdtab.cpp

@@ -413,6 +413,7 @@ void VMdTab::setupMarkdownEditor()
     Q_ASSERT(!m_editor);
 
     m_editor = new VMdEditor(m_file, m_document, m_mdConType, this);
+    m_editor->setProperty("MainEditor", true);
     connect(m_editor, &VMdEditor::headersChanged,
             this, &VMdTab::updateOutlineFromHeaders);
     connect(m_editor, SIGNAL(currentHeaderChanged(int)),

+ 6 - 4
src/vnote.qrc

@@ -65,7 +65,6 @@
         <file>resources/icons/paste.svg</file>
         <file>resources/icons/dir_item.svg</file>
         <file>resources/icons/notebook_item.svg</file>
-        <file>resources/icons/arrow_dropdown.svg</file>
         <file>resources/icons/vnote.png</file>
         <file>resources/icons/insert_image.svg</file>
         <file>resources/icons/import_note.svg</file>
@@ -93,8 +92,6 @@
         <file>utils/mermaid/mermaid.forest.css</file>
         <file>utils/mermaid/mermaidAPI.min.js</file>
         <file>resources/icons/close_red.svg</file>
-        <file>resources/icons/close_grey.svg</file>
-        <file>resources/icons/float.svg</file>
         <file>resources/docs/shortcuts_en.md</file>
         <file>resources/docs/shortcuts_zh.md</file>
         <file>resources/styles/default.css</file>
@@ -107,7 +104,6 @@
         <file>utils/markdown-it/markdown-it-sub.min.js</file>
         <file>utils/markdown-it/markdown-it-sup.min.js</file>
         <file>utils/markdown-it/markdown-it-footnote.min.js</file>
-        <file>resources/icons/arrow_dropup.svg</file>
         <file>resources/icons/vnote_update.svg</file>
         <file>utils/flowchart.js/flowchart.min.js</file>
         <file>utils/flowchart.js/raphael.min.js</file>
@@ -144,5 +140,11 @@
         <file>resources/icons/reading_modified.svg</file>
         <file>resources/icons/flash_page.svg</file>
         <file>resources/icons/heading.svg</file>
+        <file>resources/themes/v_white/arrow_dropdown.svg</file>
+        <file>resources/themes/v_white/close.svg</file>
+        <file>resources/themes/v_white/close_grey.svg</file>
+        <file>resources/themes/v_white/float.svg</file>
+        <file>resources/themes/v_white/v_white.palette</file>
+        <file>resources/themes/v_white/v_white.qss</file>
     </qresource>
 </RCC>

+ 129 - 0
src/vpalette.cpp

@@ -0,0 +1,129 @@
+#include "vpalette.h"
+
+#include <QSettings>
+#include <QRegExp>
+#include <QFileInfo>
+#include <QDir>
+#include <QDebug>
+
+#include "utils/vutils.h"
+
+VPalette::VPalette(const QString &p_file)
+{
+    init(p_file);
+}
+
+void VPalette::init(const QString &p_file)
+{
+    m_file = QFileInfo(p_file).absoluteFilePath();
+
+    QSettings settings(p_file, QSettings::IniFormat);
+    initMetaData(&settings, "metadata");
+    initPaleteFromSettings(&settings, "phony");
+    initPaleteFromSettings(&settings, "widgets");
+}
+
+void VPalette::initMetaData(QSettings *p_settings, const QString &p_group)
+{
+    p_settings->beginGroup(p_group);
+    // Qss file.
+    QString val = p_settings->value("qss_file").toString();
+    if (!val.isEmpty()) {
+        m_qssFile = QDir(VUtils::basePathFromPath(m_file)).filePath(val);
+        qDebug() << "theme file" << m_file << "qss file" << m_qssFile;
+    }
+
+    p_settings->endGroup();
+}
+
+void VPalette::initPaleteFromSettings(QSettings *p_settings, const QString &p_group)
+{
+    QRegExp reg("@(\\w+)");
+
+    p_settings->beginGroup(p_group);
+    QStringList keys = p_settings->childKeys();
+    for (auto const & key : keys) {
+        if (key.isEmpty()) {
+            continue;
+        }
+
+        QString val = p_settings->value(key).toString();
+        if (reg.exactMatch(val)) {
+            auto it = m_palette.find(reg.cap(1));
+            if (it != m_palette.end()) {
+                val = it.value();
+            } else {
+                qWarning() << "non-defined reference attribute" << key << "in palette" << p_settings->fileName();
+                val.clear();
+            }
+        }
+
+        m_palette.insert(key, val);
+    }
+
+    p_settings->endGroup();
+}
+
+QString VPalette::color(const QString &p_name) const
+{
+    auto it = m_palette.find(p_name);
+    if (it != m_palette.end()) {
+        return it.value();
+    }
+
+    return QString();
+}
+
+void VPalette::fillStyle(QString &p_style) const
+{
+    // Cap(2) is the string to be replaced.
+    QRegExp reg("(\\s|:)@(\\w+)(?=\\W)");
+
+    int pos = 0;
+    while (pos < p_style.size()) {
+        int idx = p_style.indexOf(reg, pos);
+        if (idx == -1) {
+            break;
+        }
+
+        QString name = reg.cap(2);
+        QString val = color(name);
+
+        if (val.isEmpty()) {
+            pos = idx + reg.matchedLength();
+        } else {
+            pos = idx + reg.matchedLength() + val.size() - name.size() - 1;
+            p_style.replace(idx + reg.cap(1).size(), name.size() + 1, val);
+        }
+    }
+}
+
+QString VPalette::fetchQtStyleSheet() const
+{
+    QString style = VUtils::readFileFromDisk(m_qssFile);
+    fillStyle(style);
+    fillAbsoluteUrl(style);
+
+    return style;
+}
+
+void VPalette::fillAbsoluteUrl(QString &p_style) const
+{
+    // Cap(2) is the string to be replaced.
+    QRegExp reg("(\\s|:)url\\(([^\\(\\)]+)\\)(?=\\W)");
+    int literalSize = QString("url(").size();
+    QDir dir(VUtils::basePathFromPath(m_file));
+
+    int pos = 0;
+    while (pos < p_style.size()) {
+        int idx = p_style.indexOf(reg, pos);
+        if (idx == -1) {
+            break;
+        }
+
+        QString url = reg.cap(2);
+        QString abUrl = dir.filePath(url);
+        pos = idx + reg.matchedLength() + abUrl.size() - url.size();
+        p_style.replace(idx + reg.cap(1).size() + literalSize, url.size(), abUrl);
+    }
+}

+ 39 - 0
src/vpalette.h

@@ -0,0 +1,39 @@
+#ifndef VPALETTE_H
+#define VPALETTE_H
+
+#include <QString>
+#include <QHash>
+
+class QSettings;
+
+
+class VPalette
+{
+public:
+    explicit VPalette(const QString &p_file);
+
+    QString color(const QString &p_name) const;
+
+    // Read QSS file.
+    QString fetchQtStyleSheet() const;
+
+private:
+    void init(const QString &p_file);
+
+    void initPaleteFromSettings(QSettings *p_settings, const QString &p_group);
+
+    void initMetaData(QSettings *p_settings, const QString &p_group);
+
+    void fillStyle(QString &p_style) const;
+
+    void fillAbsoluteUrl(QString &p_style) const;
+
+    // File path of the palette file.
+    QString m_file;
+
+    QHash<QString, QString> m_palette;
+
+    QString m_qssFile;
+};
+
+#endif // VPALETTE_H