Browse Source

add VTipsDialog to display hints about some actions

Le Tan 8 years ago
parent
commit
c0c4b8602a

+ 74 - 0
src/dialog/vtipsdialog.cpp

@@ -0,0 +1,74 @@
+#include "vtipsdialog.h"
+
+#include <QtWidgets>
+#include <QWebEngineView>
+
+#include "vconfigmanager.h"
+#include "vmarkdownconverter.h"
+#include "utils/vutils.h"
+#include "vnote.h"
+
+extern VConfigManager *g_config;
+
+VTipsDialog::VTipsDialog(const QString &p_tipFile,
+                         const QString &p_actionText,
+                         TipsDialogFunc p_action,
+                         QWidget *p_parent)
+    : QDialog(p_parent), m_actionBtn(NULL), m_action(p_action)
+{
+    setupUI(p_actionText);
+
+    readFile(p_tipFile);
+}
+
+void VTipsDialog::setupUI(const QString &p_actionText)
+{
+    m_viewer = VUtils::getWebEngineView();
+
+    m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok);
+    connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+
+    m_okBtn = m_btnBox->button(QDialogButtonBox::Ok);
+
+    if (!p_actionText.isEmpty()) {
+        Q_ASSERT(m_action != nullptr);
+        m_actionBtn = m_btnBox->addButton(p_actionText, QDialogButtonBox::ActionRole);
+        m_actionBtn->setProperty("SpecialBtn", true);
+        m_actionBtn->setDefault(true);
+
+        connect(m_actionBtn, &QPushButton::clicked,
+                this, [this]() {
+                    m_action();
+                });
+    }
+
+    QVBoxLayout *mainLayout = new QVBoxLayout();
+    mainLayout->addWidget(m_viewer);
+    mainLayout->addWidget(m_btnBox);
+
+    setLayout(mainLayout);
+    setWindowTitle(tr("VNote Tips"));
+}
+
+void VTipsDialog::readFile(const QString &p_tipFile)
+{
+    QString content = VUtils::readFileFromDisk(p_tipFile);
+    VMarkdownConverter mdConverter;
+    QString toc;
+    QString html = mdConverter.generateHtml(content,
+                                            g_config->getMarkdownExtensions(),
+                                            toc);
+    html = VUtils::generateSimpleHtmlTemplate(html);
+    m_viewer->setHtml(html);
+}
+
+void VTipsDialog::showEvent(QShowEvent *p_event)
+{
+    QDialog::showEvent(p_event);
+
+    if (m_actionBtn) {
+        m_actionBtn->setFocus();
+    } else {
+        m_okBtn->setFocus();
+    }
+}

+ 43 - 0
src/dialog/vtipsdialog.h

@@ -0,0 +1,43 @@
+#ifndef VTIPSDIALOG_H
+#define VTIPSDIALOG_H
+
+#include <functional>
+
+#include <QDialog>
+
+class QDialogButtonBox;
+class QWebEngineView;
+class QPushButton;
+class QShowEvent;
+
+typedef std::function<void()> TipsDialogFunc;
+
+class VTipsDialog : public QDialog
+{
+    Q_OBJECT
+public:
+    VTipsDialog(const QString &p_tipFile,
+                const QString &p_actionText = QString(),
+                TipsDialogFunc p_action = nullptr,
+                QWidget *p_parent = nullptr);
+
+protected:
+    void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
+
+private:
+    void setupUI(const QString &p_actionText);
+
+    void readFile(const QString &p_tipFile);
+
+    QWebEngineView *m_viewer;
+
+    QDialogButtonBox *m_btnBox;
+
+    QPushButton *m_actionBtn;
+
+    QPushButton *m_okBtn;
+
+    TipsDialogFunc m_action;
+};
+
+#endif // VTIPSDIALOG_H

+ 19 - 7
src/dialog/vupdater.cpp

@@ -4,9 +4,13 @@
 #include <QJsonDocument>
 #include <QJsonObject>
 #include <QTimer>
+#include <QWebEngineView>
 
 #include "vconfigmanager.h"
 #include "vdownloader.h"
+#include "vmarkdownconverter.h"
+#include "utils/vutils.h"
+#include "vnote.h"
 
 extern VConfigManager *g_config;
 
@@ -31,7 +35,8 @@ void VUpdater::setupUI()
     m_proBar = new QProgressBar();
     m_proBar->setTextVisible(false);
 
-    m_descriptionTb = new QTextBrowser();
+    m_descriptionWV = VUtils::getWebEngineView();
+    m_descriptionWV->setHtml(VUtils::generateSimpleHtmlTemplate(VNote::s_sloganTemplate));
 
     m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok);
     m_btnBox->button(QDialogButtonBox::Ok)->setProperty("SpecialBtn", true);
@@ -47,15 +52,17 @@ void VUpdater::setupUI()
     QHBoxLayout *topLayout = new QHBoxLayout();
     topLayout->addWidget(imgLabel);
     topLayout->addLayout(verLayout);
+    topLayout->addStretch();
 
-    QVBoxLayout *mainLayout = new QVBoxLayout(this);
+    QVBoxLayout *mainLayout = new QVBoxLayout();
     mainLayout->addLayout(topLayout);
-    mainLayout->addWidget(m_descriptionTb, 1);
+    mainLayout->addWidget(m_descriptionWV, 1);
     mainLayout->addWidget(m_btnBox);
 
     m_proLabel->hide();
     m_proBar->hide();
-    m_descriptionTb->hide();
+
+    m_descriptionWV->setMaximumSize(600, 400);
 
     setLayout(mainLayout);
     mainLayout->setSizeConstraint(QLayout::SetFixedSize);
@@ -64,7 +71,6 @@ void VUpdater::setupUI()
 
 void VUpdater::showEvent(QShowEvent *p_event)
 {
-    Q_UNUSED(p_event);
     QDialog::showEvent(p_event);
 
     QTimer *timer = new QTimer(this);
@@ -166,7 +172,13 @@ void VUpdater::parseResult(const QByteArray &p_data)
         m_proLabel->setText(tr("VNote is already the latest version."));
     }
 
-    m_descriptionTb->setText(releaseName + "\n==========\n" + body);
-    m_descriptionTb->show();
+    QString mdText = "# " + releaseName + "\n" + body;
+    VMarkdownConverter mdConverter;
+    QString toc;
+    QString html = mdConverter.generateHtml(mdText,
+                                            g_config->getMarkdownExtensions(),
+                                            toc);
+    html = VUtils::generateSimpleHtmlTemplate(html);
+    m_descriptionWV->setHtml(html);
     m_proBar->hide();
 }

+ 2 - 2
src/dialog/vupdater.h

@@ -6,7 +6,7 @@
 
 class QLabel;
 class QDialogButtonBox;
-class QTextBrowser;
+class QWebEngineView;
 class QProgressBar;
 class QShowEvent;
 
@@ -30,7 +30,7 @@ private:
     void checkUpdates();
 
     QLabel *m_versionLabel;
-    QTextBrowser *m_descriptionTb;
+    QWebEngineView *m_descriptionWV;
     QDialogButtonBox *m_btnBox;
 
     // Progress label and bar.

+ 19 - 0
src/resources/docs/tips_add_style_en.md

@@ -0,0 +1,19 @@
+# VNote Styles
+VNote supports custom styles for both edit and read modes. The best way to make a custom style is to download the default styles [here](https://github.com/tamlok/vnote/tree/master/src/resources/themes), rename them, put them in the style folder, and tune it to your taste.
+
+A restart is needed to detect a new style. A re-open of current tabs is needed to apply a new style.
+
+For detailed information about a style, please visit [VNote](https://github.com/tamlok/vnote).
+
+## Editor Style
+An editor style is a file with suffix `.mdhl`. By default, it locates in subfolder `styles` in VNote's configuration folder.
+
+You could visit [Stylesheets from PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/docs/stylesheet_syntax.html) for detailed syntax.
+
+## Read Mode Style
+A read mode style is a file with suffix `.css`. By default, it locates in subfolder `styles` in VNote's configuration folder.
+
+### Code Block Style
+A code block style is a file with suffix `.css`. By default, it locates in subfolder `styles/codeblock_styles` in VNote's configuration folder.
+
+You could visit [Highlight.js](https://github.com/isagalaev/highlight.js) for more styles.

+ 19 - 0
src/resources/docs/tips_add_style_zh.md

@@ -0,0 +1,19 @@
+# VNote样式
+VNote支持自定义编辑和阅读模式的样式。自定义样式最好的方法是下载默认的[样式](https://github.com/tamlok/vnote/tree/master/src/resources/themes),重命名,将这些文件放到样式文件夹下,并逐步修改调试。
+
+VNote需要重新启动以检测新的样式。需要重新打开当前标签页以应用新的样式。
+
+关于样式的更多信息,请访问 [VNote](https://github.com/tamlok/vnote) 。
+
+## 编辑器样式
+一个编辑器样式是一个后缀名为`.mhdl`的文件。编辑器样式默认位于VNote的配置文件夹的子文件夹`styles`中。
+
+关于样式的详细语法,请访问 [Stylesheets from PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/docs/stylesheet_syntax.html) 。
+
+## 阅读模式样式
+一个阅读模式样式是一个后缀名为`.css`的文件。阅读模式样式默认位于VNote的配置文件夹的子文件夹`styles`中。
+
+### 代码块样式
+一个代码块样式是一个后缀名为`.css`的文件。代码块样式默认位于VNote的配置文件夹的子文件夹`styles/codeblock_styles`中。
+
+请访问 [Highlight.js](https://github.com/isagalaev/highlight.js) 来获取更多的样式。

+ 6 - 0
src/resources/docs/tips_add_theme_en.md

@@ -0,0 +1,6 @@
+# VNote Themes
+VNote supports themes. A theme is a folder in the subfolder `themes` in VNote's configuration folder. The best way to make a custom theme is to download the default themes [here](https://github.com/tamlok/vnote/tree/master/src/resources/themes), rename related files, put them in the theme folder, and tune it to your taste.
+
+A restart is needed to detect or apply a new theme.
+
+For detailed information about a theme, please visit [VNote](https://github.com/tamlok/vnote).

+ 6 - 0
src/resources/docs/tips_add_theme_zh.md

@@ -0,0 +1,6 @@
+# VNote主题
+VNote支持主题。一个主题对应于VNote的配置文件夹的子文件夹`themes`下的一个文件夹。自定义主题最好的方法是下载默认的[主题](https://github.com/tamlok/vnote/tree/master/src/resources/themes),重命名相关文件,将该文件夹放到主题文件夹下,并逐步修改调试。
+
+VNote需要重新启动以检测新的主题或应用一个主题。
+
+关于主题的更多信息,请访问 [VNote](https://github.com/tamlok/vnote) 。

+ 91 - 0
src/resources/docs/tips_custom_shortcut_en.md

@@ -0,0 +1,91 @@
+# Custom Shortcuts
+VNote supports customing some standard shortcuts, though it is not recommended. VNote stores shortcuts' configuration information in the `[shortcuts]` and `[captain_mode_shortcuts]` sections of user configuration file `vnote.ini`.
+
+For example, the default configruation may look like this:
+
+```ini
+[shortcuts]
+; Define shortcuts here, with each item in the form "operation=keysequence".
+; Leave keysequence empty to disable the shortcut of an operation.
+; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
+; Ctrl+Q is reserved for quitting VNote.
+
+; Leader key of Captain mode
+CaptainMode=Ctrl+E
+; Create a note in current folder
+NewNote=Ctrl+Alt+N
+; Save current note
+SaveNote=Ctrl+S
+; Save changes and enter read mode
+SaveAndRead=Ctrl+T
+; Edit current note
+EditNote=Ctrl+W
+; Close current note
+CloseNote=
+; Open file/replace dialog
+Find=Ctrl+F
+; Find next occurence
+FindNext=F3
+; Find previous occurence
+FindPrevious=Shift+F3
+
+[captain_mode_shortcuts]
+; Define shortcuts in Captain mode here.
+; There shortcuts are the sub-sequence after the CaptainMode key sequence
+; in [shortcuts].
+
+; Enter Navigation mode
+NavigationMode=W
+; Show attachment list of current note
+AttachmentList=A
+; Locate to the folder of current note
+LocateCurrentFile=D
+; Toggle Expand mode
+ExpandMode=E
+; Alternate one/two panels view
+OnePanelView=P
+; Discard changes and enter read mode
+DiscardAndRead=Q
+; Toggle Tools dock widget
+ToolsDock=T
+; Close current note
+CloseNote=X
+; Show shortcuts help document
+ShortcutsHelp=?
+; Flush the log file
+FlushLogFile=";"
+; Show opened files list
+OpenedFileList=F
+; Activate the ith tab
+ActivateTab1=1
+ActivateTab2=2
+ActivateTab3=3
+ActivateTab4=4
+ActivateTab5=5
+ActivateTab6=6
+ActivateTab7=7
+ActivateTab8=8
+ActivateTab9=9
+; Alternate between current and last tab
+AlternateTab=0
+; Activate next tab
+ActivateNextTab=J
+; Activate previous tab
+ActivatePreviousTab=K
+; Activate the window split on the left
+ActivateSplitLeft=H
+; Activate the window split on the right
+ActivateSplitRight=L
+; Move current tab one split left
+MoveTabSplitLeft=Shift+H
+; Move current tab one split right
+MoveTabSplitRight=Shift+L
+; Create a vertical split
+VerticalSplit=V
+; Remove current split
+RemoveSplit=R
+```
+
+Each item is in the form `operation=keysequence`, with `keysequence` empty to disable shortcuts for that operation.
+
+Pay attention that `Ctrl+Q` is reserved for quitting VNote.

+ 93 - 0
src/resources/docs/tips_custom_shortcut_zh.md

@@ -0,0 +1,93 @@
+# 自定义快捷键
+VNote支持自定义部分标准快捷键(但并不建议这么做)。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]`和`[captain_mode_shortcuts]`两个小节。
+
+例如,默认的配置可能是这样子的:
+
+
+```ini
+[shortcuts]
+; Define shortcuts here, with each item in the form "operation=keysequence".
+; Leave keysequence empty to disable the shortcut of an operation.
+; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
+; Ctrl+Q is reserved for quitting VNote.
+
+; Leader key of Captain mode
+CaptainMode=Ctrl+E
+; Create a note in current folder
+NewNote=Ctrl+Alt+N
+; Save current note
+SaveNote=Ctrl+S
+; Save changes and enter read mode
+SaveAndRead=Ctrl+T
+; Edit current note
+EditNote=Ctrl+W
+; Close current note
+CloseNote=
+; Open file/replace dialog
+Find=Ctrl+F
+; Find next occurence
+FindNext=F3
+; Find previous occurence
+FindPrevious=Shift+F3
+
+[captain_mode_shortcuts]
+; Define shortcuts in Captain mode here.
+; There shortcuts are the sub-sequence after the CaptainMode key sequence
+; in [shortcuts].
+
+; Enter Navigation mode
+NavigationMode=W
+; Show attachment list of current note
+AttachmentList=A
+; Locate to the folder of current note
+LocateCurrentFile=D
+; Toggle Expand mode
+ExpandMode=E
+; Alternate one/two panels view
+OnePanelView=P
+; Discard changes and enter read mode
+DiscardAndRead=Q
+; Toggle Tools dock widget
+ToolsDock=T
+; Close current note
+CloseNote=X
+; Show shortcuts help document
+ShortcutsHelp=?
+; Flush the log file
+FlushLogFile=";"
+; Show opened files list
+OpenedFileList=F
+; Activate the ith tab
+ActivateTab1=1
+ActivateTab2=2
+ActivateTab3=3
+ActivateTab4=4
+ActivateTab5=5
+ActivateTab6=6
+ActivateTab7=7
+ActivateTab8=8
+ActivateTab9=9
+; Alternate between current and last tab
+AlternateTab=0
+; Activate next tab
+ActivateNextTab=J
+; Activate previous tab
+ActivatePreviousTab=K
+; Activate the window split on the left
+ActivateSplitLeft=H
+; Activate the window split on the right
+ActivateSplitRight=L
+; Move current tab one split left
+MoveTabSplitLeft=Shift+H
+; Move current tab one split right
+MoveTabSplitRight=Shift+L
+; Create a vertical split
+VerticalSplit=V
+; Remove current split
+RemoveSplit=R
+```
+
+每一项配置的形式为`操作=按键序列`。如果`按键序列`为空,则表示禁用该操作的快捷键。
+
+注意,`Ctrl+Q`保留为退出VNote。
+

+ 18 - 0
src/resources/docs/tips_external_program_en.md

@@ -0,0 +1,18 @@
+# External Programs
+VNote supports opening notes with external programs. VNote stores external programs' configuration information in the `[external_editors]` section of user configuration file `vnote.ini`.
+
+A sample configuration may look like this:
+
+```ini
+[external_editors]
+; Define external editors which could be called to edit notes
+; One program per line with the format name="program %0 arg1 arg2"
+; in which %0 will be replaced with the note file path
+; Need to escape \ and ", use double quotes to quote paths/arguments with spaces
+
+GVim=C:\\\"Program Files (x86)\"\\Vim\\vim80\\gvim.exe %0
+Notepad=notepad %0
+Notepad%2B%2B=C:\\\"Program Files (x86)\"\\notepad++\\notepad++.exe %0
+```
+
+A restart is needed to detect new external programs.

+ 18 - 0
src/resources/docs/tips_external_program_zh.md

@@ -0,0 +1,18 @@
+# 外部程序
+VNote支持使用外部程序来打开笔记。VNote将外部程序的配置信息保存在用户配置文件`vnote.ini`的`[external_editors]`小节中。
+
+一个配置可能如下:
+
+```ini
+[external_editors]
+; Define external editors which could be called to edit notes
+; One program per line with the format name="program %0 arg1 arg2"
+; in which %0 will be replaced with the note file path
+; Need to escape \ and ", use double quotes to quote paths/arguments with spaces
+
+GVim=C:\\\"Program Files (x86)\"\\Vim\\vim80\\gvim.exe %0
+Notepad=notepad %0
+Notepad%2B%2B=C:\\\"Program Files (x86)\"\\notepad++\\notepad++.exe %0
+```
+
+VNote需要重启以检测新的外部程序。

+ 7 - 0
src/resources/icons/add_program.svg

@@ -0,0 +1,7 @@
+<?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">
+<polygon style="fill:#000000" points="448,224 288,224 288,64 224,64 224,224 64,224 64,288 224,288 224,448 288,448 288,288 448,288 "/>
+</svg>

+ 7 - 0
src/resources/icons/add_style.svg

@@ -0,0 +1,7 @@
+<?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">
+<polygon style="fill:#000000" points="448,224 288,224 288,64 224,64 224,224 64,224 64,288 224,288 224,448 288,448 288,288 448,288 "/>
+</svg>

+ 11 - 0
src/resources/simple_template.html

@@ -0,0 +1,11 @@
+<!doctype html>
+<html lang="en">
+<meta charset="utf-8">
+<head>
+    <link rel="stylesheet" type="text/css" href="CSS_PLACE_HOLDER">
+    <link rel="stylesheet" type="text/css" href="qrc:/resources/typewriter.css">
+</head>
+<body>
+    <!-- BODY_PLACE_HOLDER -->
+</body>
+</html>

+ 4 - 3
src/resources/themes/v_moonlight/v_moonlight.css

@@ -90,9 +90,10 @@ code {
 }
 
 pre code {
-    margin: 0;
-    padding: 0;
-    border: none;
+    display: block;
+    overflow-x: auto;
+    padding: 0.5em;
+    border: 1px solid #373E47;
     color: #98C379;
 }
 

+ 4 - 3
src/resources/themes/v_pure/v_pure.css

@@ -90,9 +90,10 @@ code {
 }
 
 pre code {
-    margin: 0;
-    padding: 0;
-    border: none;
+    display: block;
+    overflow-x: auto;
+    padding: 0.5em;
+    border: 1px solid #D3D3D3;
     color: #363636;
 }
 

+ 4 - 3
src/resources/themes/v_white/v_white.css

@@ -89,9 +89,10 @@ code {
 }
 
 pre code {
-    margin: 0;
-    padding: 0;
-    border: none;
+    display: block;
+    overflow-x: auto;
+    padding: 0.5em;
+    border: 1px solid #C0C0C0;
     color: #363636;
 }
 

+ 16 - 0
src/resources/typewriter.css

@@ -0,0 +1,16 @@
+.typewriter h3 {
+    font-family: monospace;
+    padding: 4px 0px 4px 4px;
+    display: inline-block;
+    overflow: hidden;
+    border-right: 0.5em solid #00897B;
+    white-space: nowrap;
+    margin: 0 auto;
+    animation:
+        blink-caret 1s step-end infinite;
+}
+
+@keyframes blink-caret {
+    from, to { border-color: transparent }
+    50% { border-color: #00897B }
+}

+ 4 - 2
src/src.pro

@@ -102,7 +102,8 @@ SOURCES += main.cpp\
     vpalette.cpp \
     vbuttonmenuitem.cpp \
     utils/viconutils.cpp \
-    lineeditdelegate.cpp
+    lineeditdelegate.cpp \
+    dialog/vtipsdialog.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -191,7 +192,8 @@ HEADERS  += vmainwindow.h \
     vpalette.h \
     vbuttonmenuitem.h \
     utils/viconutils.h \
-    lineeditdelegate.h
+    lineeditdelegate.h \
+    dialog/vtipsdialog.h
 
 RESOURCES += \
     vnote.qrc \

+ 44 - 2
src/utils/vutils.cpp

@@ -23,11 +23,13 @@
 #include <QKeySequence>
 #include <QComboBox>
 #include <QStyledItemDelegate>
+#include <QWebEngineView>
 
 #include "vorphanfile.h"
 #include "vnote.h"
 #include "vnotebook.h"
 #include "hgmarkdownhighlighter.h"
+#include "vpreviewpage.h"
 
 extern VConfigManager *g_config;
 
@@ -564,6 +566,7 @@ QString VUtils::getLocale()
     if (locale == "System" || !isValidLanguage(locale)) {
         locale = QLocale::system().name();
     }
+
     return locale;
 }
 
@@ -598,6 +601,12 @@ DocType VUtils::docTypeFromName(const QString &p_name)
     return DocType::Unknown;
 }
 
+QString VUtils::generateSimpleHtmlTemplate(const QString &p_body)
+{
+    QString html = VNote::s_simpleHtmlTemplate;
+    return html.replace(HtmlHolder::c_bodyHolder, p_body);
+}
+
 QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exportPdf)
 {
     QString jsFile, extraFile;
@@ -684,9 +693,9 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exp
         htmlTemplate = VNote::s_markdownTemplate;
     }
 
-    htmlTemplate.replace(c_htmlJSHolder, jsFile);
+    htmlTemplate.replace(HtmlHolder::c_JSHolder, jsFile);
     if (!extraFile.isEmpty()) {
-        htmlTemplate.replace(c_htmlExtraHolder, extraFile);
+        htmlTemplate.replace(HtmlHolder::c_extraHolder, extraFile);
     }
 
     return htmlTemplate;
@@ -1128,3 +1137,36 @@ void VUtils::setDynamicProperty(QWidget *p_widget, const char *p_prop, bool p_va
     p_widget->style()->unpolish(p_widget);
     p_widget->style()->polish(p_widget);
 }
+
+QWebEngineView *VUtils::getWebEngineView(QWidget *p_parent)
+{
+    QWebEngineView *viewer = new QWebEngineView(p_parent);
+    VPreviewPage *page = new VPreviewPage(viewer);
+    page->setBackgroundColor(Qt::transparent);
+    viewer->setPage(page);
+    viewer->setZoomFactor(g_config->getWebZoomFactor());
+
+    return viewer;
+}
+
+QString VUtils::getFileNameWithLocale(const QString &p_name)
+{
+    QString locale = getLocale();
+    locale = locale.split('_')[0];
+
+    QFileInfo fi(p_name);
+    QString baseName = fi.completeBaseName();
+    QString suffix = fi.suffix();
+
+    if (suffix.isEmpty()) {
+        return QString("%1_%2").arg(baseName).arg(locale);
+    } else {
+        return QString("%1_%2.%3").arg(baseName).arg(locale).arg(suffix);
+    }
+}
+
+QString VUtils::getDocFile(const QString &p_name)
+{
+    QDir dir(VNote::c_docFileFolder);
+    return dir.filePath(getFileNameWithLocale(p_name));
+}

+ 11 - 0
src/utils/vutils.h

@@ -17,6 +17,7 @@ class VOrphanFile;
 class VNotebook;
 class QWidget;
 class QComboBox;
+class QWebEngineView;
 
 #if !defined(V_ASSERT)
     #define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
@@ -171,6 +172,8 @@ public:
     // Generate HTML template.
     static QString generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exportPdf);
 
+    static QString generateSimpleHtmlTemplate(const QString &p_body);
+
     // Get an available file name in @p_directory with base @p_baseFileName.
     // If there already exists a file named @p_baseFileName, try to add sequence
     // suffix to the name, such as _001.
@@ -273,8 +276,16 @@ public:
     // Create and return a QComboBox.
     static QComboBox *getComboBox(QWidget *p_parent = nullptr);
 
+    static QWebEngineView *getWebEngineView(QWidget *p_parent = nullptr);
+
     static void setDynamicProperty(QWidget *p_widget, const char *p_prop, bool p_val = true);
 
+    // Return a file name with locale.
+    static QString getFileNameWithLocale(const QString &p_name);
+
+    // Return a doc file path.
+    static QString getDocFile(const QString &p_name);
+
     // Regular expression for image link.
     // ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" )
     // Captured texts (need to be trimmed):

+ 6 - 2
src/vconstants.h

@@ -32,8 +32,12 @@ static const qreal c_webZoomFactorMin = 0.25;
 static const int c_tabSequenceBase = 1;
 
 // HTML and JS.
-static const QString c_htmlJSHolder = "JS_PLACE_HOLDER";
-static const QString c_htmlExtraHolder = "<!-- EXTRA_PLACE_HOLDER -->";
+namespace HtmlHolder
+{
+    static const QString c_JSHolder = "JS_PLACE_HOLDER";
+    static const QString c_extraHolder = "<!-- EXTRA_PLACE_HOLDER -->";
+    static const QString c_bodyHolder = "<!-- BODY_PLACE_HOLDER -->";
+}
 
 // Directory Config file items.
 namespace DirConfig

+ 24 - 0
src/vfilelist.cpp

@@ -17,6 +17,7 @@
 #include "vmainwindow.h"
 #include "utils/vimnavigationforwidget.h"
 #include "utils/viconutils.h"
+#include "dialog/vtipsdialog.h"
 
 extern VConfigManager *g_config;
 extern VNote *g_vnote;
@@ -1000,6 +1001,29 @@ void VFileList::initOpenWithMenu()
             });
 
     m_openWithMenu->addAction(defaultAct);
+
+    QAction *addAct = new QAction(VIconUtils::menuIcon(":/resources/icons/add_program.svg"),
+                                  tr("Add External Program"),
+                                  this);
+    addAct->setToolTip(tr("Add external program"));
+    connect(addAct, &QAction::triggered,
+            this, [this]() {
+                VTipsDialog dialog(VUtils::getDocFile("tips_external_program.md"),
+                                   tr("Add External Program"),
+                                   []() {
+#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
+                                       // On macOS, it seems that we could not open that ini file directly.
+                                       QUrl url = QUrl::fromLocalFile(g_config->getConfigFolder());
+#else
+                                       QUrl url = QUrl::fromLocalFile(g_config->getConfigFilePath());
+#endif
+                                       QDesktopServices::openUrl(url);
+                                   },
+                                   this);
+                dialog.exec();
+            });
+
+    m_openWithMenu->addAction(addAct);
 }
 
 void VFileList::handleOpenWithActionTriggered()

+ 95 - 36
src/vmainwindow.cpp

@@ -35,6 +35,7 @@
 #include "vbuttonmenuitem.h"
 #include "vpalette.h"
 #include "utils/viconutils.h"
+#include "dialog/vtipsdialog.h"
 
 VMainWindow *g_mainWin;
 
@@ -706,13 +707,8 @@ void VMainWindow::initHelpMenu()
     mdGuideAct->setToolTip(tr("A quick guide of Markdown syntax"));
     connect(mdGuideAct, &QAction::triggered,
             this, [this](){
-                QString locale = VUtils::getLocale();
-                QString docName = VNote::c_markdownGuideDocFile_en;
-                if (locale == "zh_CN") {
-                    docName = VNote::c_markdownGuideDocFile_zh;
-                }
-
-                VFile *file = vnote->getOrphanFile(docName, false, true);
+                QString docFile = VUtils::getDocFile(VNote::c_markdownGuideDocFile);
+                VFile *file = vnote->getOrphanFile(docFile, false, true);
                 editArea->openFile(file, OpenFileMode::Read);
             });
 
@@ -965,28 +961,7 @@ void VMainWindow::initFileMenu()
     QAction *customShortcutAct = new QAction(tr("Custom Shortcuts"), this);
     customShortcutAct->setToolTip(tr("Custom some standard shortcuts"));
     connect(customShortcutAct, &QAction::triggered,
-            this, [this](){
-                int ret = VUtils::showMessage(QMessageBox::Information,
-                                              tr("Custom Shortcuts"),
-                                              tr("VNote supports customing some standard shorcuts by "
-                                                 "editing user's configuration file (vnote.ini). Please "
-                                                 "reference the shortcuts help documentation for more "
-                                                 "information."),
-                                              tr("Click \"OK\" to custom shortcuts."),
-                                              QMessageBox::Ok | QMessageBox::Cancel,
-                                              QMessageBox::Ok,
-                                              this);
-
-                if (ret == QMessageBox::Ok) {
-#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
-                    // On macOS, it seems that we could not open that ini file directly.
-                    QUrl url = QUrl::fromLocalFile(g_config->getConfigFolder());
-#else
-                    QUrl url = QUrl::fromLocalFile(g_config->getConfigFilePath());
-#endif
-                    QDesktopServices::openUrl(url);
-                }
-            });
+            this, &VMainWindow::customShortcut);
 
     fileMenu->addAction(customShortcutAct);
 
@@ -1523,6 +1498,24 @@ void VMainWindow::initRenderStyleMenu(QMenu *p_menu)
     QMenu *styleMenu = p_menu->addMenu(tr("Rendering &Style"));
     styleMenu->setToolTipsVisible(true);
 
+    QAction *addAct = newAction(VIconUtils::menuIcon(":/resources/icons/add_style.svg"),
+                                tr("Add Style"),
+                                styleMenu);
+    addAct->setToolTip(tr("Add custom style of read mode"));
+    connect(addAct, &QAction::triggered,
+            this, [this]() {
+                VTipsDialog dialog(VUtils::getDocFile("tips_add_style.md"),
+                                   tr("Add Style"),
+                                   []() {
+                                       QUrl url = QUrl::fromLocalFile(g_config->getStyleConfigFolder());
+                                       QDesktopServices::openUrl(url);
+                                   },
+                                   this);
+                dialog.exec();
+            });
+
+    styleMenu->addAction(addAct);
+
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
             this, [this](QAction *p_action) {
@@ -1558,6 +1551,24 @@ void VMainWindow::initCodeBlockStyleMenu(QMenu *p_menu)
     QMenu *styleMenu = p_menu->addMenu(tr("Code Block Style"));
     styleMenu->setToolTipsVisible(true);
 
+    QAction *addAct = newAction(VIconUtils::menuIcon(":/resources/icons/add_style.svg"),
+                                tr("Add Style"),
+                                styleMenu);
+    addAct->setToolTip(tr("Add custom style of code block in read mode"));
+    connect(addAct, &QAction::triggered,
+            this, [this]() {
+                VTipsDialog dialog(VUtils::getDocFile("tips_add_style.md"),
+                                   tr("Add Style"),
+                                   []() {
+                                       QUrl url = QUrl::fromLocalFile(g_config->getCodeBlockStyleConfigFolder());
+                                       QDesktopServices::openUrl(url);
+                                   },
+                                   this);
+                dialog.exec();
+            });
+
+    styleMenu->addAction(addAct);
+
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
             this, [this](QAction *p_action) {
@@ -1684,6 +1695,24 @@ void VMainWindow::initEditorStyleMenu(QMenu *p_menu)
     QMenu *styleMenu = p_menu->addMenu(tr("Editor &Style"));
     styleMenu->setToolTipsVisible(true);
 
+    QAction *addAct = newAction(VIconUtils::menuIcon(":/resources/icons/add_style.svg"),
+                                tr("Add Style"),
+                                styleMenu);
+    addAct->setToolTip(tr("Add custom style of editor"));
+    connect(addAct, &QAction::triggered,
+            this, [this]() {
+                VTipsDialog dialog(VUtils::getDocFile("tips_add_style.md"),
+                                   tr("Add Style"),
+                                   []() {
+                                       QUrl url = QUrl::fromLocalFile(g_config->getStyleConfigFolder());
+                                       QDesktopServices::openUrl(url);
+                                   },
+                                   this);
+                dialog.exec();
+            });
+
+    styleMenu->addAction(addAct);
+
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
             this, [this](QAction *p_action) {
@@ -2243,13 +2272,8 @@ void VMainWindow::enableImageCaption(bool p_checked)
 
 void VMainWindow::shortcutsHelp()
 {
-    QString locale = VUtils::getLocale();
-    QString docName = VNote::c_shortcutsDocFile_en;
-    if (locale == "zh_CN") {
-        docName = VNote::c_shortcutsDocFile_zh;
-    }
-
-    VFile *file = vnote->getOrphanFile(docName, false, true);
+    QString docFile = VUtils::getDocFile(VNote::c_shortcutsDocFile);
+    VFile *file = vnote->getOrphanFile(docFile, false, true);
     editArea->openFile(file, OpenFileMode::Read);
 }
 
@@ -2717,6 +2741,24 @@ void VMainWindow::initThemeMenu(QMenu *p_menu)
     QMenu *themeMenu = p_menu->addMenu(tr("Theme"));
     themeMenu->setToolTipsVisible(true);
 
+    QAction *addAct = newAction(VIconUtils::menuIcon(":/resources/icons/add_style.svg"),
+                                tr("Add Theme"),
+                                themeMenu);
+    addAct->setToolTip(tr("Add custom theme"));
+    connect(addAct, &QAction::triggered,
+            this, [this]() {
+                VTipsDialog dialog(VUtils::getDocFile("tips_add_theme.md"),
+                                   tr("Add Theme"),
+                                   []() {
+                                       QUrl url = QUrl::fromLocalFile(g_config->getThemeConfigFolder());
+                                       QDesktopServices::openUrl(url);
+                                   },
+                                   this);
+                dialog.exec();
+            });
+
+    themeMenu->addAction(addAct);
+
     QActionGroup *ag = new QActionGroup(this);
     connect(ag, &QActionGroup::triggered,
             this, [this](QAction *p_action) {
@@ -2744,3 +2786,20 @@ void VMainWindow::initThemeMenu(QMenu *p_menu)
         }
     }
 }
+
+void VMainWindow::customShortcut()
+{
+    VTipsDialog dialog(VUtils::getDocFile("tips_custom_shortcut.md"),
+                       tr("Custom Shortcuts"),
+                       []() {
+#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
+                           // On macOS, it seems that we could not open that ini file directly.
+                           QUrl url = QUrl::fromLocalFile(g_config->getConfigFolder());
+#else
+                           QUrl url = QUrl::fromLocalFile(g_config->getConfigFilePath());
+#endif
+                           QDesktopServices::openUrl(url);
+                       },
+                       this);
+    dialog.exec();
+}

+ 2 - 0
src/vmainwindow.h

@@ -161,6 +161,8 @@ private slots:
     // Open flash page in edit mode.
     void openFlashPage();
 
+    void customShortcut();
+
 protected:
     void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
     void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;

+ 23 - 4
src/vnote.cpp

@@ -22,9 +22,14 @@ extern VPalette *g_palette;
 // Meta word manager.
 VMetaWordManager *g_mwMgr;
 
+QString VNote::s_simpleHtmlTemplate;
+
 QString VNote::s_markdownTemplate;
+
 QString VNote::s_markdownTemplatePDF;
 
+QString VNote::s_sloganTemplate = "<div class=\"typewriter\"><h3>Hi Markdown, I'm VNote</h3></div>";
+
 const QString VNote::c_hoedownJsFile = ":/resources/hoedown.js";
 
 const QString VNote::c_markedJsFile = ":/resources/marked.js";
@@ -52,11 +57,11 @@ const QString VNote::c_raphaelJsFile = ":/utils/flowchart.js/raphael.min.js";
 
 const QString VNote::c_highlightjsLineNumberExtraFile = ":/utils/highlightjs/highlightjs-line-numbers.min.js";
 
-const QString VNote::c_shortcutsDocFile_en = ":/resources/docs/shortcuts_en.md";
-const QString VNote::c_shortcutsDocFile_zh = ":/resources/docs/shortcuts_zh.md";
+const QString VNote::c_docFileFolder = ":/resources/docs";
 
-const QString VNote::c_markdownGuideDocFile_en = ":/resources/docs/markdown_guide_en.md";
-const QString VNote::c_markdownGuideDocFile_zh = ":/resources/docs/markdown_guide_zh.md";
+const QString VNote::c_shortcutsDocFile = "shortcuts.md";
+
+const QString VNote::c_markdownGuideDocFile = "markdown_guide.md";
 
 VNote::VNote(QObject *parent)
     : QObject(parent)
@@ -73,10 +78,24 @@ VNote::VNote(QObject *parent)
 void VNote::initTemplate()
 {
     if (s_markdownTemplate.isEmpty()) {
+        updateSimpletHtmlTemplate();
+
         updateTemplate();
     }
 }
 
+void VNote::updateSimpletHtmlTemplate()
+{
+    const QString c_simpleHtmlTemplatePath(":/resources/simple_template.html");
+
+    const QString cssHolder("CSS_PLACE_HOLDER");
+
+    s_simpleHtmlTemplate = VUtils::readFileFromDisk(c_simpleHtmlTemplatePath);
+    g_palette->fillStyle(s_simpleHtmlTemplate);
+
+    s_simpleHtmlTemplate.replace(cssHolder, g_config->getCssStyleUrl());
+}
+
 void VNote::updateTemplate()
 {
     const QString c_markdownTemplatePath(":/resources/markdown_template.html");

+ 11 - 4
src/vnote.h

@@ -29,7 +29,12 @@ public:
 
     void initTemplate();
 
+    static QString s_sloganTemplate;
+
+    static QString s_simpleHtmlTemplate;
+
     static QString s_markdownTemplate;
+
     static QString s_markdownTemplatePDF;
 
     // Hoedown
@@ -66,11 +71,11 @@ public:
     // Highlight.js line number plugin
     static const QString c_highlightjsLineNumberExtraFile;
 
-    static const QString c_shortcutsDocFile_en;
-    static const QString c_shortcutsDocFile_zh;
+    static const QString c_docFileFolder;
 
-    static const QString c_markdownGuideDocFile_en;
-    static const QString c_markdownGuideDocFile_zh;
+    static const QString c_shortcutsDocFile;
+
+    static const QString c_markdownGuideDocFile;
 
     // Get the label style in Navigation mode.
     QString getNavigationLabelStyle(const QString &p_str) const;
@@ -97,6 +102,8 @@ public:
 public slots:
     void updateTemplate();
 
+    void updateSimpletHtmlTemplate();
+
 private:
     const QString &getMonospacedFont() const;
 

+ 12 - 0
src/vnote.qrc

@@ -218,5 +218,17 @@
         <file>resources/themes/v_moonlight/v_moonlight.palette</file>
         <file>resources/themes/v_moonlight/v_moonlight.qss</file>
         <file>resources/themes/v_moonlight/v_moonlight_codeblock.css</file>
+        <file>resources/simple_template.html</file>
+        <file>resources/typewriter.css</file>
+        <file>resources/docs/tips_custom_shortcut_en.md</file>
+        <file>resources/docs/tips_custom_shortcut_zh.md</file>
+        <file>resources/icons/add_style.svg</file>
+        <file>resources/docs/tips_add_theme_en.md</file>
+        <file>resources/docs/tips_add_theme_zh.md</file>
+        <file>resources/docs/tips_add_style_en.md</file>
+        <file>resources/docs/tips_add_style_zh.md</file>
+        <file>resources/icons/add_program.svg</file>
+        <file>resources/docs/tips_external_program_en.md</file>
+        <file>resources/docs/tips_external_program_zh.md</file>
     </qresource>
 </RCC>