diff --git a/.travis_linux.sh b/.travis_linux.sh
index c793b23c..f1acda01 100644
--- a/.travis_linux.sh
+++ b/.travis_linux.sh
@@ -1,11 +1,11 @@
#!/bin/bash
project_dir=$(pwd)
-# Install qt5.7
+# Install qt5.9
sudo add-apt-repository ppa:george-edison55/cmake-3.x -y
-sudo add-apt-repository ppa:beineri/opt-qt571-trusty -y
+sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y
sudo apt-get update -qq
-sudo apt-get -y install qt57base qt57webengine qt57webchannel qt57svg qt57location qt57tools qt57translations
+sudo apt-get -y install qt59base qt59webengine qt59webchannel qt59svg qt59location qt59tools qt59translations
source /opt/qt*/bin/qt*-env.sh
# Compile newer version fcitx-qt5
@@ -34,7 +34,7 @@ make -j$(nproc) && sudo make install
sudo cp /usr/local/lib/libFcitxQt5DBusAddons.so* /opt/qt*/lib/
sudo cp /usr/local/lib/libFcitxQt5WidgetsAddons.so* /opt/qt*/lib/
-tree /opt/qt57/lib/
+tree /opt/qt59/lib/
cd ${project_dir}
mkdir build
@@ -53,9 +53,9 @@ INSTALL_ROOT=${project_dir}/build/dist make install ; tree dist/
mkdir -p ./dist/usr/plugins/iconengines
mkdir -p ./dist/usr/plugins/imageformats
mkdir -p ./dist/usr/plugins/platforminputcontexts
-cp /opt/qt57/plugins/iconengines/* ./dist/usr/plugins/iconengines/
-cp /opt/qt57/plugins/imageformats/* ./dist/usr/plugins/imageformats/
-cp /opt/qt57/plugins/platforminputcontexts/* ./dist/usr/plugins/platforminputcontexts/
+cp /opt/qt59/plugins/iconengines/* ./dist/usr/plugins/iconengines/
+cp /opt/qt59/plugins/imageformats/* ./dist/usr/plugins/imageformats/
+cp /opt/qt59/plugins/platforminputcontexts/* ./dist/usr/plugins/platforminputcontexts/
# Copy other project files
cp "${project_dir}/README.md" "dist/README.md"
@@ -70,7 +70,7 @@ unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
./linuxdeployqt*.AppImage ./dist/usr/share/applications/*.desktop -bundle-non-qt-libs
# Copy translations
-cp /opt/qt57/translations/*_zh_CN.qm ./dist/usr/translations/
+cp /opt/qt59/translations/*_zh_CN.qm ./dist/usr/translations/
# Package it for the second time.
./linuxdeployqt*.AppImage ./dist/usr/share/applications/*.desktop -appimage
diff --git a/.travis_macos.sh b/.travis_macos.sh
index 4e76c9a4..39215947 100644
--- a/.travis_macos.sh
+++ b/.travis_macos.sh
@@ -2,8 +2,8 @@
project_dir=$(pwd)
brew update > /dev/null
-brew install qt@5.7
-QTDIR="/usr/local/opt/qt@5.7"
+brew install qt@5.9
+QTDIR="/usr/local/opt/qt@5.9"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include
diff --git a/README.md b/README.md
index 84de95a3..5242f992 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,8 @@ Utilizing Qt, VNote could run on **Linux**, **Windows**, and **macOS** (due to t
- Supports infinite levels of folders;
- Supports multiple tabs and splitting windows;
- Supports [Mermaid](http://knsv.github.io/mermaid/), [Flowchart.js](http://flowchart.js.org/), and [MathJax](https://www.mathjax.org/);
-- Supports HiDPI.
+- Supports HiDPI;
+- Supports attachments of notes.

@@ -136,7 +137,7 @@ VNote also supports many other features, like:
- Auto indent and auto list;
# Build & Development
-VNote needs Qt 5.7 or above to build.
+VNote needs Qt 5.9.1 or above to build.
1. Clone & Init
```
@@ -145,15 +146,15 @@ VNote needs Qt 5.7 or above to build.
git submodule update --init
```
2. Download Qt & Have Fun
-Download [Qt 5.7.0](http://info.qt.io/download-qt-for-application-development) and open `VNote.pro` as a project.
+Download [Qt 5.9.1](http://info.qt.io/download-qt-for-application-development) and open `VNote.pro` as a project.
## Linux
-If your distribution does not have Qt 5.7 or above, you need to add it from other sources. In Ubuntu, you could do this:
+If your distribution does not have Qt 5.9.1 or above, you need to add it from other sources. In Ubuntu, you could do this:
```
-sudo add-apt-repository ppa:beineri/opt-qt571-trusty -y
+sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y
sudo apt-get update -qq
-sudo apt-get -y install qt57base qt57webengine qt57webchannel qt57svg qt57location qt57tools qt57translations
+sudo apt-get -y install qt59base qt59webengine qt59webchannel qt59svg qt59location qt59tools qt59translations
source /opt/qt*/bin/qt*-env.sh
```
@@ -174,13 +175,13 @@ For details, you could reference [.travis_linux.sh](.travis_linux.sh) in the sou
If you prefer command line on macOS, you could follow these steps.
1. Install Xcode and Homebrew;
-2. Install Qt5.7 via Homebrew:
+2. Install Qt 5.9.1 via Homebrew:
```
- brew install qt@5.7
+ brew install qt@5.9.1
```
3. In the project directory, create `build_macos.sh` like this:
```sh
- QTDIR="/usr/local/opt/qt@5.7"
+ QTDIR="/usr/local/opt/qt@5.9.1"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include
@@ -199,7 +200,7 @@ If you prefer command line on macOS, you could follow these steps.
5. Now you got the bundle `path/to/project/build/src/VNote.app`. Enjoy yourself!
# Dependencies
-- [Qt 5.7](http://qt-project.org) (L-GPL v3)
+- [Qt 5.9](http://qt-project.org) (L-GPL v3)
- [PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/) (MIT License)
- [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License)
- [Marked](https://github.com/chjj/marked) (MIT License)
@@ -212,6 +213,7 @@ If you prefer command line on macOS, you could follow these steps.
- [MathJax](https://www.mathjax.org/) (Apache-2.0)
- [showdown](https://github.com/showdownjs/showdown) (Unknown)
- [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License)
+- Icons made by a326703305@qq.com
# License
VNote is licensed under the [MIT license](http://opensource.org/licenses/MIT).
diff --git a/README_zh.md b/README_zh.md
index e7a149ef..33e46e56 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -54,8 +54,9 @@ VNote不是一个简单的Markdown编辑器。通过提供笔记管理功能,V
- 支持Vim模式以及一系列强大的快捷键;
- 支持无限层级的文件夹;
- 支持多个标签页和窗口分割;
-- 支持[Mermaid](http://knsv.github.io/mermaid/), [Flowchart.js](http://flowchart.js.org/) 和 [MathJax](https://www.mathjax.org/);
+- 支持[Mermaid](http://knsv.github.io/mermaid/), [Flowchart.js](http://flowchart.js.org/) 和 [MathJax](https://www.mathjax.org/);
- 支持高分辨率;
+- 支持笔记附件。

@@ -137,7 +138,7 @@ VNote还支持其他很多的功能,比如:
- 自动缩进和自动列表;
# 构建与开发
-VNote需要5.7版本以上的Qt进行构建。
+VNote需要5.9.1版本以上的Qt进行构建。
1. 克隆代码仓库
```
@@ -146,15 +147,15 @@ VNote需要5.7版本以上的Qt进行构建。
git submodule update --init
```
2. 下载Qt
-下载[Qt 5.7.0](http://info.qt.io/download-qt-for-application-development),导入`VNote.pro`创建一个工程。
+下载[Qt 5.9.1](http://info.qt.io/download-qt-for-application-development),导入`VNote.pro`创建一个工程。
## Linux
-如果您的Linux发行版不提供5.7以上版本的Qt,那么您需要从其他来源获取Qt。在Ubuntu中,您可以执行以下步骤:
+如果您的Linux发行版不提供5.9.1以上版本的Qt,那么您需要从其他来源获取Qt。在Ubuntu中,您可以执行以下步骤:
```
-sudo add-apt-repository ppa:beineri/opt-qt571-trusty -y
+sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y
sudo apt-get update -qq
-sudo apt-get -y install qt57base qt57webengine qt57webchannel qt57svg qt57location qt57tools qt57translations
+sudo apt-get -y install qt59base qt59webengine qt59webchannel qt59svg qt59location qt59tools qt59translations
source /opt/qt*/bin/qt*-env.sh
```
@@ -175,15 +176,15 @@ sudo make install
在macOS下,您可以执行以下步骤来编译:
1. 安装Xcode和Homebrew:
-2. 通过Homebrew安装Qt5.7:
+2. 通过Homebrew安装Qt5.9.1:
```
- brew install qt@5.7
+ brew install qt@5.9.1
```
3. 在VNote源码根目录下,新建一个文件`build_macos.sh`:
```sh
- QTDIR="/usr/local/opt/qt@5.7"
+ QTDIR="/usr/local/opt/qt@5.9.1"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include
@@ -203,7 +204,7 @@ sudo make install
5. 此时得到VNote的Bundle `path/to/project/build/src/VNote.app`,打开即可。
# 依赖
-- [Qt 5.7](http://qt-project.org) (L-GPL v3)
+- [Qt 5.9](http://qt-project.org) (L-GPL v3)
- [PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/) (MIT License)
- [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License)
- [Marked](https://github.com/chjj/marked) (MIT License)
@@ -216,6 +217,7 @@ sudo make install
- [MathJax](https://www.mathjax.org/) (Apache-2.0)
- [showdown](https://github.com/showdownjs/showdown) (Unknown)
- [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License)
+- 图标由九梦岛主(a326703305@qq.com)制作
# 代码许可
VNote使用[MIT许可](http://opensource.org/licenses/MIT)。
diff --git a/appveyor.yml b/appveyor.yml
index 714a91e9..4c32fe15 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -11,9 +11,9 @@ environment:
VSVER: 14
matrix:
- - QT: C:\Qt\5.7\msvc2015_64
+ - QT: C:\Qt\5.9.1\msvc2015_64
PLATFORM: amd64
- - QT: C:\Qt\5.7\msvc2015
+ - QT: C:\Qt\5.9.1\msvc2015
PLATFORM: x86
clone_depth: 1
@@ -29,8 +29,8 @@ before_build:
# After calling vcvarsall.bat, %PLATFORM% will be X64 or x86
- mkdir build
- cd build
- - if "%PLATFORM%" EQU "X64" (qmake -r -spec win32-msvc2015 CONFIG+=x86_64 CONFIG-=debug CONFIG+=release ../VNote.pro)
- - if "%PLATFORM%" EQU "x86" (qmake -r -spec win32-msvc2015 CONFIG+=Win32 CONFIG-=debug CONFIG+=release ../VNote.pro)
+ - if "%PLATFORM%" EQU "X64" (qmake -r -spec win32-msvc CONFIG+=x86_64 CONFIG-=debug CONFIG+=release ../VNote.pro)
+ - if "%PLATFORM%" EQU "x86" (qmake -r -spec win32-msvc CONFIG+=Win32 CONFIG-=debug CONFIG+=release ../VNote.pro)
# custom build scripts
build_script:
diff --git a/src/dialog/vdirinfodialog.cpp b/src/dialog/vdirinfodialog.cpp
index 63a9d477..e67c98a3 100644
--- a/src/dialog/vdirinfodialog.cpp
+++ b/src/dialog/vdirinfodialog.cpp
@@ -2,6 +2,7 @@
#include "vdirinfodialog.h"
#include "vdirectory.h"
#include "vconfigmanager.h"
+#include "vlineedit.h"
#include "utils/vutils.h"
extern VConfigManager *g_config;
@@ -16,7 +17,7 @@ VDirInfoDialog::VDirInfoDialog(const QString &title,
{
setupUI();
- connect(nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
handleInputChanged();
}
@@ -28,15 +29,18 @@ void VDirInfoDialog::setupUI()
infoLabel = new QLabel(info);
}
- nameEdit = new QLineEdit(m_directory->getName());
- nameEdit->selectAll();
+ m_nameEdit = new VLineEdit(m_directory->getName());
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
+ m_nameEdit->selectAll();
// Created time.
QString createdTimeStr = VUtils::displayDateTime(m_directory->getCreatedTimeUtc().toLocalTime());
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
QFormLayout *topLayout = new QFormLayout();
- topLayout->addRow(tr("Folder &name:"), nameEdit);
+ topLayout->addRow(tr("Folder &name:"), m_nameEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel);
m_warnLabel = new QLabel();
@@ -49,7 +53,7 @@ void VDirInfoDialog::setupUI()
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
+ m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) {
@@ -67,19 +71,35 @@ void VDirInfoDialog::setupUI()
void VDirInfoDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk && name != m_directory->getName()) {
// Check if the name conflicts with existing directory name.
// Case-insensitive when creating note.
const VDirectory *directory = m_parentDirectory->findSubDirectory(name, false);
+ QString warnText;
if (directory && directory != m_directory) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -91,5 +111,5 @@ void VDirInfoDialog::handleInputChanged()
QString VDirInfoDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
diff --git a/src/dialog/vdirinfodialog.h b/src/dialog/vdirinfodialog.h
index 558334df..d23a0211 100644
--- a/src/dialog/vdirinfodialog.h
+++ b/src/dialog/vdirinfodialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VDirectory;
@@ -27,7 +27,7 @@ private slots:
private:
void setupUI();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QLabel *m_warnLabel;
QDialogButtonBox *m_btnBox;
diff --git a/src/dialog/vfileinfodialog.cpp b/src/dialog/vfileinfodialog.cpp
index 40373a87..4fd4a8a8 100644
--- a/src/dialog/vfileinfodialog.cpp
+++ b/src/dialog/vfileinfodialog.cpp
@@ -4,6 +4,7 @@
#include "vnotefile.h"
#include "vconfigmanager.h"
#include "utils/vutils.h"
+#include "vlineedit.h"
extern VConfigManager *g_config;
@@ -16,7 +17,7 @@ VFileInfoDialog::VFileInfoDialog(const QString &title,
{
setupUI(title, info);
- connect(nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
handleInputChanged();
}
@@ -30,7 +31,10 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
// File name.
QString name = m_file->getName();
- nameEdit = new QLineEdit(name);
+ m_nameEdit = new VLineEdit(name);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
int baseStart = 0, baseLength = name.size();
int dotIdx = name.lastIndexOf('.');
if (dotIdx != -1) {
@@ -38,7 +42,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
}
// Select without suffix.
- nameEdit->setSelection(baseStart, baseLength);
+ m_nameEdit->setSelection(baseStart, baseLength);
// Attachment folder.
QLineEdit *attachmentFolderEdit = new QLineEdit(m_file->getAttachmentFolder());
@@ -56,7 +60,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
modifiedTimeLabel->setToolTip(tr("Last modified time within VNote"));
QFormLayout *topLayout = new QFormLayout();
- topLayout->addRow(tr("Note &name:"), nameEdit);
+ topLayout->addRow(tr("Note &name:"), m_nameEdit);
topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel);
topLayout->addRow(tr("Modified time:"), modifiedTimeLabel);
@@ -71,7 +75,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
+ m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) {
@@ -90,28 +94,43 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
void VFileInfoDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk && name != m_file->getName()) {
// Check if the name conflicts with existing note name.
// Case-insensitive when creating note.
const VNoteFile *file = m_directory->findFile(name, false);
+ QString warnText;
if (file && file != m_file) {
nameOk = false;
- showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
} else if (m_file->getDocType() != DocType::Unknown
&& VUtils::docTypeFromName(name) != m_file->getDocType()) {
// Check if the name change the doc type.
nameOk = false;
+ warnText = tr("WARNING: "
+ "Changing type of the note is not supported. "
+ "Please use the same suffix as the old one.")
+ .arg(g_config->c_warningTextStyle);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Changing type of the note is not supported. "
- "Please use the same suffix as the old one.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -123,5 +142,5 @@ void VFileInfoDialog::handleInputChanged()
QString VFileInfoDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
diff --git a/src/dialog/vfileinfodialog.h b/src/dialog/vfileinfodialog.h
index 939cfcdf..015705f0 100644
--- a/src/dialog/vfileinfodialog.h
+++ b/src/dialog/vfileinfodialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VDirectory;
@@ -29,7 +29,7 @@ private slots:
private:
void setupUI(const QString &p_title, const QString &p_info);
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QLabel *m_warnLabel;
QDialogButtonBox *m_btnBox;
diff --git a/src/dialog/vinsertimagedialog.cpp b/src/dialog/vinsertimagedialog.cpp
index 9b0b090e..214d8258 100644
--- a/src/dialog/vinsertimagedialog.cpp
+++ b/src/dialog/vinsertimagedialog.cpp
@@ -3,6 +3,7 @@
#include
#include "vinsertimagedialog.h"
#include "utils/vutils.h"
+#include "vlineedit.h"
VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
const QString &defaultPath, QWidget *parent)
@@ -11,11 +12,14 @@ VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defa
{
setupUI();
- connect(imageTitleEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
- connect(pathEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
- connect(browseBtn, &QPushButton::clicked, this, &VInsertImageDialog::handleBrowseBtnClicked);
+ connect(m_imageTitleEdit, &QLineEdit::textChanged,
+ this, &VInsertImageDialog::handleInputChanged);
+ connect(pathEdit, &QLineEdit::textChanged,
+ this, &VInsertImageDialog::handleInputChanged);
+ connect(browseBtn, &QPushButton::clicked,
+ this, &VInsertImageDialog::handleBrowseBtnClicked);
- enableOkButton();
+ handleInputChanged();
}
VInsertImageDialog::~VInsertImageDialog()
@@ -34,19 +38,19 @@ void VInsertImageDialog::setupUI()
browseBtn = new QPushButton(tr("&Browse"));
imageTitleLabel = new QLabel(tr("&Image title:"));
- imageTitleEdit = new QLineEdit(defaultImageTitle);
- imageTitleEdit->selectAll();
- imageTitleLabel->setBuddy(imageTitleEdit);
- QRegExp regExp("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]+");
- QValidator *validator = new QRegExpValidator(regExp, this);
- imageTitleEdit->setValidator(validator);
+ m_imageTitleEdit = new VLineEdit(defaultImageTitle);
+ m_imageTitleEdit->selectAll();
+ imageTitleLabel->setBuddy(m_imageTitleEdit);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp),
+ m_imageTitleEdit);
+ m_imageTitleEdit->setValidator(validator);
QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(pathLabel, 0, 0);
topLayout->addWidget(pathEdit, 0, 1);
topLayout->addWidget(browseBtn, 0, 2);
topLayout->addWidget(imageTitleLabel, 1, 0);
- topLayout->addWidget(imageTitleEdit, 1, 1, 1, 2);
+ topLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 2);
topLayout->setColumnStretch(0, 0);
topLayout->setColumnStretch(1, 1);
topLayout->setColumnStretch(2, 0);
@@ -67,22 +71,31 @@ void VInsertImageDialog::setupUI()
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(title);
- imageTitleEdit->setFocus();
+ m_imageTitleEdit->setFocus();
}
-void VInsertImageDialog::enableOkButton()
+void VInsertImageDialog::handleInputChanged()
{
- bool enabled = true;
- if (imageTitleEdit->text().isEmpty() || !image) {
- enabled = false;
+ bool pathOk = true;
+ if (pathEdit->isVisible() && !pathEdit->isReadOnly()) {
+ QString path = pathEdit->text();
+ if (path.isEmpty()
+ || !VUtils::checkPathLegal(path)) {
+ pathOk = false;
+ }
}
+
+ QString title = m_imageTitleEdit->getEvaluatedText();
+ QRegExp reg(VUtils::c_imageTitleRegExp);
+ bool titleOk = reg.exactMatch(title);
+
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- okBtn->setEnabled(enabled);
+ okBtn->setEnabled(pathOk && titleOk);
}
QString VInsertImageDialog::getImageTitleInput() const
{
- return imageTitleEdit->text();
+ return m_imageTitleEdit->getEvaluatedText();
}
QString VInsertImageDialog::getPathInput() const
@@ -130,7 +143,8 @@ void VInsertImageDialog::setImage(const QImage &image)
imagePreviewLabel->setPixmap(pixmap);
imagePreviewLabel->setVisible(true);
- enableOkButton();
+
+ handleInputChanged();
}
void VInsertImageDialog::setBrowseable(bool browseable, bool visible)
@@ -141,6 +155,8 @@ void VInsertImageDialog::setBrowseable(bool browseable, bool visible)
pathLabel->setVisible(visible);
pathEdit->setVisible(visible);
browseBtn->setVisible(visible);
+
+ handleInputChanged();
}
void VInsertImageDialog::imageDownloaded(const QByteArray &data)
diff --git a/src/dialog/vinsertimagedialog.h b/src/dialog/vinsertimagedialog.h
index b16b7cb8..65855af0 100644
--- a/src/dialog/vinsertimagedialog.h
+++ b/src/dialog/vinsertimagedialog.h
@@ -8,6 +8,7 @@
class QLabel;
class QLineEdit;
+class VLineEdit;
class QPushButton;
class QDialogButtonBox;
@@ -30,14 +31,14 @@ public slots:
void imageDownloaded(const QByteArray &data);
private slots:
- void enableOkButton();
+ void handleInputChanged();
void handleBrowseBtnClicked();
private:
void setupUI();
QLabel *imageTitleLabel;
- QLineEdit *imageTitleEdit;
+ VLineEdit *m_imageTitleEdit;
QLabel *pathLabel;
QLineEdit *pathEdit;
QPushButton *browseBtn;
diff --git a/src/dialog/vinsertlinkdialog.cpp b/src/dialog/vinsertlinkdialog.cpp
new file mode 100644
index 00000000..866300d5
--- /dev/null
+++ b/src/dialog/vinsertlinkdialog.cpp
@@ -0,0 +1,132 @@
+#include "vinsertlinkdialog.h"
+
+#include
+
+#include "vlineedit.h"
+
+VInsertLinkDialog::VInsertLinkDialog(const QString &p_title,
+ const QString &p_text,
+ const QString &p_info,
+ const QString &p_linkText,
+ const QString &p_linkUrl,
+ QWidget *p_parent)
+ : QDialog(p_parent)
+{
+ setupUI(p_title, p_text, p_info, p_linkText, p_linkUrl);
+
+ fetchLinkFromClipboard();
+
+ handleInputChanged();
+}
+
+void VInsertLinkDialog::setupUI(const QString &p_title,
+ const QString &p_text,
+ const QString &p_info,
+ const QString &p_linkText,
+ const QString &p_linkUrl)
+{
+ QLabel *textLabel = NULL;
+ if (!p_text.isEmpty()) {
+ textLabel = new QLabel(p_text);
+ textLabel->setWordWrap(true);
+ }
+
+ QLabel *infoLabel = NULL;
+ if (!p_info.isEmpty()) {
+ infoLabel = new QLabel(p_info);
+ infoLabel->setWordWrap(true);
+ }
+
+ m_linkTextEdit = new VLineEdit(p_linkText);
+ m_linkTextEdit->selectAll();
+
+ m_linkUrlEdit = new QLineEdit(p_linkUrl);
+ m_linkUrlEdit->setToolTip(tr("Absolute or relative path of the link"));
+
+ QFormLayout *inputLayout = new QFormLayout();
+ inputLayout->addRow(tr("&Text:"), m_linkTextEdit);
+ inputLayout->addRow(tr("&URL:"), m_linkUrlEdit);
+
+ // Ok is the default button.
+ m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
+ m_linkTextEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout;
+ if (textLabel) {
+ mainLayout->addWidget(textLabel);
+ }
+
+ if (infoLabel) {
+ mainLayout->addWidget(infoLabel);
+ }
+
+ mainLayout->addLayout(inputLayout);
+ mainLayout->addWidget(m_btnBox);
+
+ setLayout(mainLayout);
+ setWindowTitle(p_title);
+
+ connect(m_linkTextEdit, &QLineEdit::textChanged,
+ this, &VInsertLinkDialog::handleInputChanged);
+ connect(m_linkUrlEdit, &QLineEdit::textChanged,
+ this, &VInsertLinkDialog::handleInputChanged);
+}
+
+void VInsertLinkDialog::handleInputChanged()
+{
+ bool textOk = true;
+ if (m_linkTextEdit->getEvaluatedText().isEmpty()) {
+ textOk = false;
+ }
+
+ bool urlOk = true;
+ if (m_linkUrlEdit->text().isEmpty()) {
+ urlOk = false;
+ }
+
+ QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
+ okBtn->setEnabled(textOk && urlOk);
+}
+
+void VInsertLinkDialog::fetchLinkFromClipboard()
+{
+ if (!m_linkUrlEdit->text().isEmpty()
+ || !m_linkTextEdit->text().isEmpty()) {
+ return;
+ }
+
+ QClipboard *clipboard = QApplication::clipboard();
+ const QMimeData *mimeData = clipboard->mimeData();
+
+ if (!mimeData->hasText()) {
+ return;
+ }
+
+ QString text = mimeData->text();
+ if (text.isEmpty()) {
+ return;
+ }
+
+ QUrl url = QUrl::fromUserInput(text);
+ if (url.isValid()) {
+ if (m_linkUrlEdit->text().isEmpty()) {
+ m_linkUrlEdit->setText(text);
+ }
+ } else if (m_linkTextEdit->text().isEmpty()) {
+ m_linkTextEdit->setText(text);
+ }
+}
+
+QString VInsertLinkDialog::getLinkText() const
+{
+ return m_linkTextEdit->getEvaluatedText();
+}
+
+QString VInsertLinkDialog::getLinkUrl() const
+{
+ return m_linkUrlEdit->text();
+}
diff --git a/src/dialog/vinsertlinkdialog.h b/src/dialog/vinsertlinkdialog.h
new file mode 100644
index 00000000..d31d8982
--- /dev/null
+++ b/src/dialog/vinsertlinkdialog.h
@@ -0,0 +1,46 @@
+#ifndef VINSERTLINKDIALOG_H
+#define VINSERTLINKDIALOG_H
+
+#include
+#include
+
+class VLineEdit;
+class QLineEdit;
+class QDialogButtonBox;
+
+class VInsertLinkDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ VInsertLinkDialog(const QString &p_title,
+ const QString &p_text,
+ const QString &p_info,
+ const QString &p_linkText,
+ const QString &p_linkUrl,
+ QWidget *p_parent = nullptr);
+
+ QString getLinkText() const;
+
+ QString getLinkUrl() const;
+
+private slots:
+ void handleInputChanged();
+
+private:
+ void setupUI(const QString &p_title,
+ const QString &p_text,
+ const QString &p_info,
+ const QString &p_linkText,
+ const QString &p_linkUrl);
+
+ // Infer link text/url from clipboard only when text and url are both empty.
+ void fetchLinkFromClipboard();
+
+ VLineEdit *m_linkTextEdit;
+
+ QLineEdit *m_linkUrlEdit;
+
+ QDialogButtonBox *m_btnBox;
+};
+
+#endif // VINSERTLINKDIALOG_H
diff --git a/src/dialog/vnewdirdialog.cpp b/src/dialog/vnewdirdialog.cpp
index 7a8bff59..24ad8ccc 100644
--- a/src/dialog/vnewdirdialog.cpp
+++ b/src/dialog/vnewdirdialog.cpp
@@ -2,6 +2,8 @@
#include "vnewdirdialog.h"
#include "vdirectory.h"
#include "vconfigmanager.h"
+#include "vlineedit.h"
+#include "utils/vutils.h"
extern VConfigManager *g_config;
@@ -15,7 +17,7 @@ VNewDirDialog::VNewDirDialog(const QString &title,
{
setupUI();
- connect(nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
handleInputChanged();
}
@@ -29,9 +31,12 @@ void VNewDirDialog::setupUI()
}
QLabel *nameLabel = new QLabel(tr("Folder &name:"));
- nameEdit = new QLineEdit(defaultName);
- nameEdit->selectAll();
- nameLabel->setBuddy(nameEdit);
+ m_nameEdit = new VLineEdit(defaultName);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
+ m_nameEdit->selectAll();
+ nameLabel->setBuddy(m_nameEdit);
m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true);
@@ -44,10 +49,10 @@ void VNewDirDialog::setupUI()
QHBoxLayout *topLayout = new QHBoxLayout();
topLayout->addWidget(nameLabel);
- topLayout->addWidget(nameEdit);
+ topLayout->addWidget(m_nameEdit);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
+ m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) {
@@ -64,18 +69,34 @@ void VNewDirDialog::setupUI()
void VNewDirDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk) {
// Check if the name conflicts with existing directory name.
// Case-insensitive when creating folder.
+ QString warnText;
if (m_directory->findSubDirectory(name, false)) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -87,5 +108,5 @@ void VNewDirDialog::handleInputChanged()
QString VNewDirDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
diff --git a/src/dialog/vnewdirdialog.h b/src/dialog/vnewdirdialog.h
index 30ca9727..8594c594 100644
--- a/src/dialog/vnewdirdialog.h
+++ b/src/dialog/vnewdirdialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VDirectory;
@@ -27,7 +27,7 @@ private slots:
private:
void setupUI();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QDialogButtonBox *m_btnBox;
QLabel *m_warnLabel;
diff --git a/src/dialog/vnewfiledialog.cpp b/src/dialog/vnewfiledialog.cpp
index 6ef321ff..730728cc 100644
--- a/src/dialog/vnewfiledialog.cpp
+++ b/src/dialog/vnewfiledialog.cpp
@@ -2,6 +2,8 @@
#include "vnewfiledialog.h"
#include "vconfigmanager.h"
#include "vdirectory.h"
+#include "vlineedit.h"
+#include "utils/vutils.h"
extern VConfigManager *g_config;
@@ -13,7 +15,7 @@ VNewFileDialog::VNewFileDialog(const QString &title, const QString &info,
{
setupUI();
- connect(nameEdit, &QLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
+ connect(m_nameEdit, &VLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
handleInputChanged();
}
@@ -27,10 +29,13 @@ void VNewFileDialog::setupUI()
// Name.
QLabel *nameLabel = new QLabel(tr("Note &name:"));
- nameEdit = new QLineEdit(defaultName);
+ m_nameEdit = new VLineEdit(defaultName);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
int dotIndex = defaultName.lastIndexOf('.');
- nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex);
- nameLabel->setBuddy(nameEdit);
+ m_nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex);
+ nameLabel->setBuddy(m_nameEdit);
// InsertTitle.
m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)"));
@@ -42,10 +47,10 @@ void VNewFileDialog::setupUI()
});
QFormLayout *topLayout = new QFormLayout();
- topLayout->addRow(nameLabel, nameEdit);
+ topLayout->addRow(nameLabel, m_nameEdit);
topLayout->addWidget(m_insertTitleCB);
- nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
+ m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true);
@@ -73,18 +78,34 @@ void VNewFileDialog::setupUI()
void VNewFileDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk) {
// Check if the name conflicts with existing note name.
// Case-insensitive when creating note.
+ QString warnText;
if (m_directory->findFile(name, false)) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -96,7 +117,7 @@ void VNewFileDialog::handleInputChanged()
QString VNewFileDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
bool VNewFileDialog::getInsertTitleInput() const
diff --git a/src/dialog/vnewfiledialog.h b/src/dialog/vnewfiledialog.h
index 2eeb8370..496e90df 100644
--- a/src/dialog/vnewfiledialog.h
+++ b/src/dialog/vnewfiledialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QCheckBox;
class VDirectory;
@@ -27,7 +27,7 @@ private slots:
private:
void setupUI();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QCheckBox *m_insertTitleCB;
QPushButton *okBtn;
diff --git a/src/dialog/vnewnotebookdialog.cpp b/src/dialog/vnewnotebookdialog.cpp
index f19d8ca8..f211bdc0 100644
--- a/src/dialog/vnewnotebookdialog.cpp
+++ b/src/dialog/vnewnotebookdialog.cpp
@@ -4,6 +4,7 @@
#include "vconfigmanager.h"
#include "utils/vutils.h"
#include "vnotebook.h"
+#include "vlineedit.h"
extern VConfigManager *g_config;
@@ -18,7 +19,7 @@ VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info
{
setupUI(title, info);
- connect(nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
connect(pathEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
connect(browseBtn, &QPushButton::clicked, this, &VNewNotebookDialog::handleBrowseBtnClicked);
@@ -34,8 +35,11 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
}
QLabel *nameLabel = new QLabel(tr("Notebook &name:"));
- nameEdit = new QLineEdit(defaultName);
- nameLabel->setBuddy(nameEdit);
+ m_nameEdit = new VLineEdit(defaultName);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
+ nameLabel->setBuddy(m_nameEdit);
QLabel *pathLabel = new QLabel(tr("Notebook &root folder:"));
pathEdit = new QLineEdit(defaultPath);
@@ -50,7 +54,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
"(empty to use global configuration)"));
imageFolderLabel->setToolTip(m_imageFolderEdit->toolTip());
- QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
+ validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
m_imageFolderEdit->setValidator(validator);
QLabel *attachmentFolderLabel = new QLabel(tr("&Attachment folder:"));
@@ -66,7 +70,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(nameLabel, 0, 0);
- topLayout->addWidget(nameEdit, 0, 1, 1, 2);
+ topLayout->addWidget(m_nameEdit, 0, 1, 1, 2);
topLayout->addWidget(pathLabel, 1, 0);
topLayout->addWidget(pathEdit, 1, 1);
topLayout->addWidget(browseBtn, 1, 2);
@@ -104,7 +108,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
QString VNewNotebookDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
QString VNewNotebookDialog::getPathInput() const
@@ -142,7 +146,8 @@ void VNewNotebookDialog::handleBrowseBtnClicked()
}
}
- QString dirPath = QFileDialog::getExistingDirectory(this, tr("Select Root Folder Of The Notebook"),
+ QString dirPath = QFileDialog::getExistingDirectory(this,
+ tr("Select Root Folder Of The Notebook"),
defaultPath,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
@@ -160,7 +165,7 @@ bool VNewNotebookDialog::isImportExistingNotebook() const
void VNewNotebookDialog::showEvent(QShowEvent *event)
{
- nameEdit->setFocus();
+ m_nameEdit->setFocus();
QDialog::showEvent(event);
}
@@ -182,7 +187,7 @@ void VNewNotebookDialog::handleInputChanged()
m_manualPath = true;
}
- if (nameEdit->isModified()) {
+ if (m_nameEdit->isModified()) {
m_manualName = true;
}
@@ -247,7 +252,7 @@ void VNewNotebookDialog::handleInputChanged()
}
}
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (pathOk && nameOk) {
// Check if the name conflicts with existing notebook name.
@@ -259,13 +264,29 @@ void VNewNotebookDialog::handleInputChanged()
}
}
+ QString warnText;
if (idx < m_notebooks.size()) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -280,6 +301,8 @@ void VNewNotebookDialog::handleInputChanged()
bool VNewNotebookDialog::autoComplete()
{
+ QString nameText = m_nameEdit->getEvaluatedText();
+
if (m_manualPath) {
if (m_manualName) {
return false;
@@ -289,8 +312,8 @@ bool VNewNotebookDialog::autoComplete()
QString pathText = pathEdit->text();
if (!pathText.isEmpty()) {
QString autoName = VUtils::directoryNameFromPath(pathText);
- if (autoName != nameEdit->text()) {
- nameEdit->setText(autoName);
+ if (autoName != nameText) {
+ m_nameEdit->setText(autoName);
return true;
}
}
@@ -306,7 +329,6 @@ bool VNewNotebookDialog::autoComplete()
}
bool ret = false;
- QString nameText = nameEdit->text();
if (nameText.isEmpty()) {
if (m_manualName) {
return false;
@@ -314,8 +336,8 @@ bool VNewNotebookDialog::autoComplete()
// Get a folder name under vnoteFolder and set it as the name of the notebook.
QString name = "vnotebook";
- name = VUtils::getFileNameWithSequence(vnoteFolder, name);
- nameEdit->setText(name);
+ name = VUtils::getDirNameWithSequence(vnoteFolder, name);
+ m_nameEdit->setText(name);
ret = true;
} else {
// Use the name as the folder name under vnoteFolder.
diff --git a/src/dialog/vnewnotebookdialog.h b/src/dialog/vnewnotebookdialog.h
index d1f38105..87d0d9b8 100644
--- a/src/dialog/vnewnotebookdialog.h
+++ b/src/dialog/vnewnotebookdialog.h
@@ -6,6 +6,7 @@
class QLabel;
class QLineEdit;
+class VLineEdit;
class QPushButton;
class QDialogButtonBox;
class VNotebook;
@@ -52,7 +53,7 @@ private:
// Returns true if name or path is modified.
bool autoComplete();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QLineEdit *pathEdit;
QPushButton *browseBtn;
QLabel *m_warnLabel;
diff --git a/src/dialog/vnotebookinfodialog.cpp b/src/dialog/vnotebookinfodialog.cpp
index d52eec1d..f7e41eb2 100644
--- a/src/dialog/vnotebookinfodialog.cpp
+++ b/src/dialog/vnotebookinfodialog.cpp
@@ -3,6 +3,7 @@
#include "vnotebook.h"
#include "utils/vutils.h"
#include "vconfigmanager.h"
+#include "vlineedit.h"
extern VConfigManager *g_config;
@@ -29,7 +30,10 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
infoLabel = new QLabel(p_info);
}
- m_nameEdit = new QLineEdit(m_notebook->getName());
+ m_nameEdit = new VLineEdit(m_notebook->getName());
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
m_nameEdit->selectAll();
m_pathEdit = new QLineEdit(m_notebook->getPath());
@@ -41,7 +45,7 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
.arg(g_config->getImageFolder()));
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
"(empty to use global configuration)"));
- QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
+ validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
m_imageFolderEdit->setValidator(validator);
// Attachment folder.
@@ -98,7 +102,7 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
void VNotebookInfoDialog::handleInputChanged()
{
- QString name = m_nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
bool showWarnLabel = false;
@@ -112,13 +116,29 @@ void VNotebookInfoDialog::handleInputChanged()
}
}
+ QString warnText;
if (idx < m_notebooks.size() && m_notebooks[idx] != m_notebook) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -130,7 +150,7 @@ void VNotebookInfoDialog::handleInputChanged()
QString VNotebookInfoDialog::getName() const
{
- return m_nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
QString VNotebookInfoDialog::getImageFolder() const
diff --git a/src/dialog/vnotebookinfodialog.h b/src/dialog/vnotebookinfodialog.h
index 68d02fbd..d1b4c234 100644
--- a/src/dialog/vnotebookinfodialog.h
+++ b/src/dialog/vnotebookinfodialog.h
@@ -6,6 +6,7 @@
class QLabel;
class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VNotebook;
@@ -38,7 +39,7 @@ private:
const VNotebook *m_notebook;
- QLineEdit *m_nameEdit;
+ VLineEdit *m_nameEdit;
QLineEdit *m_pathEdit;
QLineEdit *m_imageFolderEdit;
// Read-only.
diff --git a/src/dialog/vorphanfileinfodialog.cpp b/src/dialog/vorphanfileinfodialog.cpp
index 579865d6..aa9a48af 100644
--- a/src/dialog/vorphanfileinfodialog.cpp
+++ b/src/dialog/vorphanfileinfodialog.cpp
@@ -25,7 +25,6 @@ void VOrphanFileInfoDialog::setupUI()
QLabel *fileLabel = new QLabel(m_file->fetchPath());
topLayout->addRow(tr("File:"), fileLabel);
- QLabel *imageFolderLabel = new QLabel(tr("Image folder:"));
m_imageFolderEdit = new QLineEdit(m_file->getImageFolder());
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
.arg(g_config->getImageFolderExt()));
@@ -33,9 +32,8 @@ void VOrphanFileInfoDialog::setupUI()
"of this file.\nIf absolute path is used, "
"VNote will not manage those images."
"(empty to use global configuration)");
- imageFolderLabel->setToolTip(imgFolderTip);
m_imageFolderEdit->setToolTip(imgFolderTip);
- topLayout->addRow(imageFolderLabel, m_imageFolderEdit);
+ topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
// Ok is the default button.
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
diff --git a/src/dialog/vsettingsdialog.cpp b/src/dialog/vsettingsdialog.cpp
index e52f1ea5..5191f219 100644
--- a/src/dialog/vsettingsdialog.cpp
+++ b/src/dialog/vsettingsdialog.cpp
@@ -169,9 +169,6 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
m_langCombo->addItem(lang.second, lang.first);
}
- QLabel *langLabel = new QLabel(tr("Language:"), this);
- langLabel->setToolTip(m_langCombo->toolTip());
-
// System tray checkbox.
m_systemTray = new QCheckBox(tr("System tray"), this);
m_systemTray->setToolTip(tr("Minimized to the system tray after closing VNote"
@@ -181,9 +178,13 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
m_systemTray->setEnabled(false);
#endif
+ // Startup pages.
+ QLayout *startupLayout = setupStartupPagesLayout();
+
QFormLayout *optionLayout = new QFormLayout();
- optionLayout->addRow(langLabel, m_langCombo);
+ optionLayout->addRow(tr("Language:"), m_langCombo);
optionLayout->addRow(m_systemTray);
+ optionLayout->addRow(tr("Startup pages:"), startupLayout);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addLayout(optionLayout);
@@ -191,6 +192,60 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
setLayout(mainLayout);
}
+QLayout *VGeneralTab::setupStartupPagesLayout()
+{
+ m_startupPageTypeCombo = new QComboBox(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);
+ m_startupPageTypeCombo->addItem(tr("Open specific pages"), (int)StartupPageType::SpecificPages);
+ connect(m_startupPageTypeCombo, static_cast(&QComboBox::activated),
+ this, [this](int p_index) {
+ int type = m_startupPageTypeCombo->itemData(p_index).toInt();
+ bool pagesEditVisible = type == (int)StartupPageType::SpecificPages;
+ m_startupPagesEdit->setVisible(pagesEditVisible);
+ m_startupPagesAddBtn->setVisible(pagesEditVisible);
+ });
+
+ m_startupPagesEdit = new QPlainTextEdit(this);
+ m_startupPagesEdit->setToolTip(tr("Absolute path of the notes to open on startup (one note per line)"));
+
+ m_startupPagesAddBtn = new QPushButton(tr("Browse"), this);
+ m_startupPagesAddBtn->setToolTip(tr("Select files to add as startup pages"));
+ connect(m_startupPagesAddBtn, &QPushButton::clicked,
+ this, [this]() {
+ static QString lastPath = QDir::homePath();
+ QStringList files = QFileDialog::getOpenFileNames(this,
+ tr("Select Files As Startup Pages"),
+ lastPath);
+ if (files.isEmpty()) {
+ return;
+ }
+
+ // Update lastPath
+ lastPath = QFileInfo(files[0]).path();
+
+ m_startupPagesEdit->appendPlainText(files.join("\n"));
+ });
+
+ QHBoxLayout *startupPagesBtnLayout = new QHBoxLayout();
+ startupPagesBtnLayout->addStretch();
+ startupPagesBtnLayout->addWidget(m_startupPagesAddBtn);
+
+ QVBoxLayout *startupPagesLayout = new QVBoxLayout();
+ startupPagesLayout->addWidget(m_startupPagesEdit);
+ startupPagesLayout->addLayout(startupPagesBtnLayout);
+
+ QVBoxLayout *startupLayout = new QVBoxLayout();
+ startupLayout->addWidget(m_startupPageTypeCombo);
+ startupLayout->addLayout(startupPagesLayout);
+
+ m_startupPagesEdit->hide();
+ m_startupPagesAddBtn->hide();
+
+ return startupLayout;
+}
+
bool VGeneralTab::loadConfiguration()
{
if (!loadLanguage()) {
@@ -201,6 +256,10 @@ bool VGeneralTab::loadConfiguration()
return false;
}
+ if (!loadStartupPageType()) {
+ return false;
+ }
+
return true;
}
@@ -214,6 +273,10 @@ bool VGeneralTab::saveConfiguration()
return false;
}
+ if (!saveStartupPageType()) {
+ return false;
+ }
+
return true;
}
@@ -264,6 +327,42 @@ bool VGeneralTab::saveSystemTray()
return true;
}
+bool VGeneralTab::loadStartupPageType()
+{
+ StartupPageType type = g_config->getStartupPageType();
+ bool found = false;
+ for (int i = 0; i < m_startupPageTypeCombo->count(); ++i) {
+ if (m_startupPageTypeCombo->itemData(i).toInt() == (int)type) {
+ found = true;
+ m_startupPageTypeCombo->setCurrentIndex(i);
+ }
+ }
+
+ Q_ASSERT(found);
+
+ const QStringList &pages = g_config->getStartupPages();
+ m_startupPagesEdit->setPlainText(pages.join("\n"));
+
+ bool pagesEditVisible = type == StartupPageType::SpecificPages;
+ m_startupPagesEdit->setVisible(pagesEditVisible);
+ m_startupPagesAddBtn->setVisible(pagesEditVisible);
+
+ return true;
+}
+
+bool VGeneralTab::saveStartupPageType()
+{
+ StartupPageType type = (StartupPageType)m_startupPageTypeCombo->currentData().toInt();
+ g_config->setStartupPageType(type);
+
+ if (type == StartupPageType::SpecificPages) {
+ QStringList pages = m_startupPagesEdit->toPlainText().split("\n");
+ g_config->setStartupPages(pages);
+ }
+
+ return true;
+}
+
VReadEditTab::VReadEditTab(QWidget *p_parent)
: QWidget(p_parent)
{
@@ -504,28 +603,34 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read);
m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
- QLabel *openModeLabel = new QLabel(tr("Note open mode:"));
- openModeLabel->setToolTip(m_openModeCombo->toolTip());
-
// Heading sequence.
- m_headingSequence = new QCheckBox(tr("Heading sequence"));
- m_headingSequence->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)"));
- m_headingSequenceCombo = new QComboBox();
- m_headingSequenceCombo->setToolTip(tr("Base level to start heading sequence"));
- m_headingSequenceCombo->addItem(tr("1"), 1);
- m_headingSequenceCombo->addItem(tr("2"), 2);
- m_headingSequenceCombo->addItem(tr("3"), 3);
- m_headingSequenceCombo->addItem(tr("4"), 4);
- m_headingSequenceCombo->addItem(tr("5"), 5);
- m_headingSequenceCombo->addItem(tr("6"), 6);
- m_headingSequenceCombo->setEnabled(false);
- connect(m_headingSequence, &QCheckBox::stateChanged,
- this, [this](int p_state){
- this->m_headingSequenceCombo->setEnabled(p_state == Qt::Checked);
+ m_headingSequenceTypeCombo = new QComboBox();
+ 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->setToolTip(tr("Base level to start heading sequence"));
+ m_headingSequenceLevelCombo->addItem(tr("1"), 1);
+ m_headingSequenceLevelCombo->addItem(tr("2"), 2);
+ m_headingSequenceLevelCombo->addItem(tr("3"), 3);
+ m_headingSequenceLevelCombo->addItem(tr("4"), 4);
+ m_headingSequenceLevelCombo->addItem(tr("5"), 5);
+ m_headingSequenceLevelCombo->addItem(tr("6"), 6);
+ m_headingSequenceLevelCombo->setEnabled(false);
+
+ connect(m_headingSequenceTypeCombo, static_cast(&QComboBox::activated),
+ this, [this](int p_index){
+ if (p_index > -1) {
+ HeadingSequenceType type = (HeadingSequenceType)m_headingSequenceTypeCombo->itemData(p_index).toInt();
+ m_headingSequenceLevelCombo->setEnabled(type != HeadingSequenceType::Disabled);
+ }
});
+
QHBoxLayout *headingSequenceLayout = new QHBoxLayout();
- headingSequenceLayout->addWidget(m_headingSequence);
- headingSequenceLayout->addWidget(m_headingSequenceCombo);
+ headingSequenceLayout->addWidget(m_headingSequenceTypeCombo);
+ headingSequenceLayout->addWidget(m_headingSequenceLevelCombo);
// Web Zoom Factor.
m_customWebZoom = new QCheckBox(tr("Custom Web zoom factor"), this);
@@ -553,8 +658,8 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
colorColumnLabel->setToolTip(m_colorColumnEdit->toolTip());
QFormLayout *mainLayout = new QFormLayout();
- mainLayout->addRow(openModeLabel, m_openModeCombo);
- mainLayout->addRow(headingSequenceLayout);
+ mainLayout->addRow(tr("Note open mode:"), m_openModeCombo);
+ mainLayout->addRow(tr("Heading sequence:"), headingSequenceLayout);
mainLayout->addRow(zoomFactorLayout);
mainLayout->addRow(colorColumnLabel, m_colorColumnEdit);
@@ -628,21 +733,28 @@ bool VMarkdownTab::saveOpenMode()
bool VMarkdownTab::loadHeadingSequence()
{
- bool enabled = g_config->getEnableHeadingSequence();
+ HeadingSequenceType type = g_config->getHeadingSequenceType();
int level = g_config->getHeadingSequenceBaseLevel();
if (level < 1 || level > 6) {
level = 1;
}
- m_headingSequence->setChecked(enabled);
- m_headingSequenceCombo->setCurrentIndex(level - 1);
+ int idx = m_headingSequenceTypeCombo->findData((int)type);
+ Q_ASSERT(idx > -1);
+ m_headingSequenceTypeCombo->setCurrentIndex(idx);
+ m_headingSequenceLevelCombo->setCurrentIndex(level - 1);
+ m_headingSequenceLevelCombo->setEnabled(type != HeadingSequenceType::Disabled);
+
return true;
}
bool VMarkdownTab::saveHeadingSequence()
{
- g_config->setEnableHeadingSequence(m_headingSequence->isChecked());
- g_config->setHeadingSequenceBaseLevel(m_headingSequenceCombo->currentData().toInt());
+ QVariant typeData = m_headingSequenceTypeCombo->currentData();
+ Q_ASSERT(typeData.isValid());
+ g_config->setHeadingSequenceType((HeadingSequenceType)typeData.toInt());
+ g_config->setHeadingSequenceBaseLevel(m_headingSequenceLevelCombo->currentData().toInt());
+
return true;
}
diff --git a/src/dialog/vsettingsdialog.h b/src/dialog/vsettingsdialog.h
index d9b1eeeb..2406134f 100644
--- a/src/dialog/vsettingsdialog.h
+++ b/src/dialog/vsettingsdialog.h
@@ -13,6 +13,8 @@ class QCheckBox;
class QLineEdit;
class QStackedLayout;
class QListWidget;
+class QPlainTextEdit;
+class QVBoxLayout;
class VGeneralTab : public QWidget
{
@@ -23,18 +25,32 @@ public:
bool saveConfiguration();
private:
+ QLayout *setupStartupPagesLayout();
+
bool loadLanguage();
bool saveLanguage();
bool loadSystemTray();
bool saveSystemTray();
+ bool loadStartupPageType();
+ bool saveStartupPageType();
+
// Language
QComboBox *m_langCombo;
// System tray
QCheckBox *m_systemTray;
+ // Startup page type.
+ QComboBox *m_startupPageTypeCombo;
+
+ // Startup pages.
+ QPlainTextEdit *m_startupPagesEdit;
+
+ // Startup pages add files button.
+ QPushButton *m_startupPagesAddBtn;
+
static const QVector c_availableLangs;
};
@@ -101,8 +117,8 @@ public:
QComboBox *m_openModeCombo;
// Whether enable heading sequence.
- QCheckBox *m_headingSequence;
- QComboBox *m_headingSequenceCombo;
+ QComboBox *m_headingSequenceTypeCombo;
+ QComboBox *m_headingSequenceLevelCombo;
// Web zoom factor.
QCheckBox *m_customWebZoom;
diff --git a/src/main.cpp b/src/main.cpp
index 98db9020..5bae6076 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -13,7 +13,6 @@
#include "vconfigmanager.h"
VConfigManager *g_config;
-VMainWindow *g_mainWin;
#if defined(QT_NO_DEBUG)
QFile g_logFile;
@@ -116,22 +115,10 @@ int main(int argc, char *argv[])
QApplication app(argc, argv);
// The file path passed via command line arguments.
- QStringList filePaths;
- QStringList args = app.arguments();
- for (int i = 1; i < args.size(); ++i) {
- if (QFileInfo::exists(args[i])) {
- QString filePath = args[i];
- QFileInfo fi(filePath);
- if (fi.isFile()) {
- // Need to use absolute path here since VNote may be launched
- // in different working directory.
- filePath = QDir::cleanPath(fi.absoluteFilePath());
- filePaths.append(filePath);
- }
- }
- }
+ QStringList filePaths = VUtils::filterFilePathsToOpen(app.arguments().mid(1));
- qDebug() << "command line arguments" << args;
+ qDebug() << "command line arguments" << app.arguments();
+ qDebug() << "files to open from arguments" << filePaths;
if (!canRun) {
// Ask another instance to open files passed in.
@@ -169,7 +156,6 @@ int main(int argc, char *argv[])
}
VMainWindow w(&guard);
- g_mainWin = &w;
QString style = VUtils::readFileFromDisk(":/resources/vnote.qss");
if (!style.isEmpty()) {
VUtils::processStyle(style, w.getPalette());
@@ -178,7 +164,11 @@ int main(int argc, char *argv[])
w.show();
- w.openExternalFiles(filePaths);
+ w.openStartupPages();
+
+ w.openFiles(filePaths);
+
+ w.promptNewNotebookIfEmpty();
return app.exec();
}
diff --git a/src/resources/docs/shortcuts_en.md b/src/resources/docs/shortcuts_en.md
index 0942ac2f..5a0c2948 100644
--- a/src/resources/docs/shortcuts_en.md
+++ b/src/resources/docs/shortcuts_en.md
@@ -49,13 +49,17 @@ Save current changes and exit edit mode.
#### Text Editing
- `Ctrl+B`
-Insert bold. Press `Ctrl+B` again to exit. Current selected text will be changed to bold if exist.
+Insert bold. Press `Ctrl+B` again to exit. Current selected text will be changed to bold if exists.
- `Ctrl+I`
-Insert italic. Press `Ctrl+I` again to exit. Current selected text will be changed to italic if exist.
+Insert italic. Press `Ctrl+I` again to exit. Current selected text will be changed to italic if exists.
- `Ctrl+D`
-Insert strikethrought. Press `Ctrl+D` again to exit. Current selected text will be changed to strikethrough if exist.
+Insert strikethrought. Press `Ctrl+D` again to exit. Current selected text will be changed to strikethrough if exists.
- `Ctrl+O`
-Insert inline code. Press `Ctrl+O` again to exit. Current selected text will be changed to inline code if exist.
+Insert inline code. Press `Ctrl+O` again to exit. Current selected text will be changed to inline code if exists.
+- `Ctrl+M`
+Insert fenced code block. Press `Ctrl+M` again to exit. Current selected text will be wrapped into a code block if exists.
+- `Ctrl+L`
+Insert link.
- `Ctrl+H`
Backspace. Delete a character backward.
- `Ctrl+W`
@@ -63,7 +67,7 @@ Delete all the characters from current cursor to the first space backward.
- `Ctrl+U`
Delete all the characters from current cursor to the beginning of current line.
- `Ctrl+`
-Insert title at level ``. `` should be 1 to 6. Current selected text will be changed to title if exist.
+Insert title at level ``. `` should be 1 to 6. Current selected text will be changed to title if exists.
- `Tab`/`Shift+Tab`
Increase or decrease the indentation. If any text is selected, the indentation will operate on all these selected lines.
- `Shift+Enter`
@@ -80,42 +84,102 @@ Expand the selection to the beginning or end of current line.
Expand the selection to the beginning or end of current note.
## Custom Shortcuts
-VNote supports customing some standard shortcuts, though it is not recommended. VNote stores shortcuts' configuration information in the `[shortcuts]` section of user configuration file `vnote.ini`.
+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]
-1\operation=NewNote
-1\keysequence=Ctrl+N
-2\operation=SaveNote
-2\keysequence=Ctrl+S
-3\operation=SaveAndRead
-3\keysequence=Ctrl+T
-4\operation=EditNote
-4\keysequence=Ctrl+W
-5\operation=CloseNote
-5\keysequence=
-6\operation=Find
-6\keysequence=Ctrl+F
-7\operation=FindNext
-7\keysequence=F3
-8\operation=FindPrevious
-8\keysequence=Shift+F3
-size=8
+; 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
```
-`size=8` tells VNote that there are 8 shotcuts defined here, with each beginning with the number sequence. You could change the `keysequence` value to change the default key sequence of a specified operation. Leave the `keysequence` empty (`keysequence=`) to disable shortcut for that operation.
+Each item is in the form `operation=keysequence`, with `keysequence` empty to disable shortcuts for that operation.
-Pay attention that `Ctrl+E` is reserved for *Captain Mode* and `Ctrl+Q` is reserved for quitting VNote.
+Pay attention that `Ctrl+Q` is reserved for quitting VNote.
# Captain Mode
To efficiently utilize the shortcuts, VNote supports the **Captain Mode**.
Press the leader key `Ctrl+E`, then VNote will enter the Captain Mode, within which VNote supports more efficient shortcuts.
-By the way, in this mode, `Ctrl+W` and `W` is equivalent, thus pressing `Ctrl+E+W` equals to `Ctrl+E W`.
-
- `E`
Toggle expanding the edit area.
- `P`
diff --git a/src/resources/docs/shortcuts_zh.md b/src/resources/docs/shortcuts_zh.md
index 926c0aba..adb72f1f 100644
--- a/src/resources/docs/shortcuts_zh.md
+++ b/src/resources/docs/shortcuts_zh.md
@@ -56,6 +56,10 @@
插入删除线;再次按`Ctrl+D`退出。如果已经选择文本,则将当前选择文本改为删除线。
- `Ctrl+O`
插入行内代码;再次按`Ctrl+O`退出。如果已经选择文本,则将当前选择文本改为行内代码。
+- `Ctrl+M`
+插入代码块;再次按`Ctrl+M`退出。如果已经选择文本,则将当前选择文本嵌入到代码块中。
+- `Ctrl+L`
+插入链接。
- `Ctrl+H`
退格键,向前删除一个字符。
- `Ctrl+W`
@@ -80,43 +84,103 @@
扩展选定到笔记开始或结尾处。
## 自定义快捷键
-VNote支持自定义部分标准快捷键(但并不建议这么做)。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]`小节。
+VNote支持自定义部分标准快捷键(但并不建议这么做)。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]`和`[captain_mode_shortcuts]`两个小节。
例如,默认的配置可能是这样子的:
```ini
[shortcuts]
-1\operation=NewNote
-1\keysequence=Ctrl+N
-2\operation=SaveNote
-2\keysequence=Ctrl+S
-3\operation=SaveAndRead
-3\keysequence=Ctrl+T
-4\operation=EditNote
-4\keysequence=Ctrl+W
-5\operation=CloseNote
-5\keysequence=
-6\operation=Find
-6\keysequence=Ctrl+F
-7\operation=FindNext
-7\keysequence=F3
-8\operation=FindPrevious
-8\keysequence=Shift+F3
-size=8
+; 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
```
-`size=8` 告诉VNote这里定义了8组快捷键,每组快捷键都以一个数字序号开始。通过改变每组快捷键中`keysequence`的值来改变某个操作的默认快捷键。将`keysequence`设置为空(`keysequence=`)则会禁用该操作的任何快捷键。
+每一项配置的形式为`操作=按键序列`。如果`按键序列`为空,则表示禁用该操作的快捷键。
-注意,`Ctrl+E`保留作为*舰长模式*的前导键,`Ctrl+Q`保留为退出VNote。
+注意,`Ctrl+Q`保留为退出VNote。
# 舰长模式
为了更有效地利用快捷键,VNote支持 **舰长模式**。
按前导键`Ctrl+E`后,VNote会进入舰长模式。在舰长模式中,VNote会支持更多高效的快捷操作。
-另外,在该模式中,`Ctrl+W`和`W`是等效的,因此,可以`Ctrl+E+W`来实现`Ctrl+E W`的操作。
-
- `E`
是否扩展编辑区域。
- `P`
diff --git a/src/resources/icons/128x128/vnote.png b/src/resources/icons/128x128/vnote.png
index 97d2d795..d325b223 100644
Binary files a/src/resources/icons/128x128/vnote.png and b/src/resources/icons/128x128/vnote.png differ
diff --git a/src/resources/icons/16x16/vnote.png b/src/resources/icons/16x16/vnote.png
index e3346ddb..184b5bf7 100644
Binary files a/src/resources/icons/16x16/vnote.png and b/src/resources/icons/16x16/vnote.png differ
diff --git a/src/resources/icons/256x256/vnote.png b/src/resources/icons/256x256/vnote.png
index a7bff7b1..69fee17d 100644
Binary files a/src/resources/icons/256x256/vnote.png and b/src/resources/icons/256x256/vnote.png differ
diff --git a/src/resources/icons/32x32/vnote.png b/src/resources/icons/32x32/vnote.png
index ed794c1e..155456b5 100644
Binary files a/src/resources/icons/32x32/vnote.png and b/src/resources/icons/32x32/vnote.png differ
diff --git a/src/resources/icons/48x48/vnote.png b/src/resources/icons/48x48/vnote.png
index 3716cb6c..d6d9a156 100644
Binary files a/src/resources/icons/48x48/vnote.png and b/src/resources/icons/48x48/vnote.png differ
diff --git a/src/resources/icons/64x64/vnote.png b/src/resources/icons/64x64/vnote.png
index a0c3eb01..e889ca0e 100644
Binary files a/src/resources/icons/64x64/vnote.png and b/src/resources/icons/64x64/vnote.png differ
diff --git a/src/resources/icons/code_block.svg b/src/resources/icons/code_block.svg
new file mode 100644
index 00000000..82e44a14
--- /dev/null
+++ b/src/resources/icons/code_block.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/resources/icons/compact_mode.svg b/src/resources/icons/compact_mode.svg
new file mode 100644
index 00000000..6cc49aa2
--- /dev/null
+++ b/src/resources/icons/compact_mode.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/resources/icons/heading_sequence.svg b/src/resources/icons/heading_sequence.svg
new file mode 100644
index 00000000..b8014ac6
--- /dev/null
+++ b/src/resources/icons/heading_sequence.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/resources/icons/link.svg b/src/resources/icons/link.svg
new file mode 100644
index 00000000..97465186
--- /dev/null
+++ b/src/resources/icons/link.svg
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/resources/icons/vnote.icns b/src/resources/icons/vnote.icns
index 7411d59f..5635a216 100644
Binary files a/src/resources/icons/vnote.icns and b/src/resources/icons/vnote.icns differ
diff --git a/src/resources/icons/vnote.ico b/src/resources/icons/vnote.ico
index f1b8f90b..a196a1a6 100644
Binary files a/src/resources/icons/vnote.ico and b/src/resources/icons/vnote.ico differ
diff --git a/src/resources/icons/vnote.png b/src/resources/icons/vnote.png
index 573d58fe..2c1ecb69 100644
Binary files a/src/resources/icons/vnote.png and b/src/resources/icons/vnote.png differ
diff --git a/src/resources/icons/vnote.svg b/src/resources/icons/vnote.svg
index 38ce5a55..8cd2fb03 100644
--- a/src/resources/icons/vnote.svg
+++ b/src/resources/icons/vnote.svg
@@ -1,17 +1 @@
-
-
\ No newline at end of file
+
diff --git a/src/resources/icons/vnote_update.png b/src/resources/icons/vnote_update.png
new file mode 100644
index 00000000..8e7b3f9c
Binary files /dev/null and b/src/resources/icons/vnote_update.png differ
diff --git a/src/resources/icons/vnote_update.svg b/src/resources/icons/vnote_update.svg
index 34d5b17c..54bc77cf 100644
--- a/src/resources/icons/vnote_update.svg
+++ b/src/resources/icons/vnote_update.svg
@@ -1,17 +1 @@
-
-
\ No newline at end of file
+
diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini
index 564adf03..d6e42680 100644
--- a/src/resources/vnote.ini
+++ b/src/resources/vnote.ini
@@ -97,7 +97,8 @@ insert_title_from_note_name=true
note_open_mode=0
; Whether auto generate heading sequence
-enable_heading_sequence=false
+; 0 - Disabled, 1 - Enabled, 2 - Enabled only for notes
+heading_sequence_type=0
; Heading sequence base level
heading_sequence_base_level=1
@@ -135,13 +136,26 @@ confirm_reload_folder=true
; Whether double click on a tab to close it
double_click_close_tab=true
+; Whether put folder and note panel in one vertical column
+enable_compact_mode=false
+
+; Whether enable tools dock widget
+tools_dock_checked=true
+
+; Pages to open on startup
+; 0 - none; 1 - Continue where you left off; 2 - specific pages
+startup_page_type=0
+
+; Specific pages to open on startup when startup_page_type is 2
+; A list of file path separated by ,
+; Notice: should escape \ by \\
+; C:\users\vnote\vnote.md -> C:\\users\\vnote\\vnote.md
+startup_pages=
+
[web]
; Location and configuration for Mathjax
mathjax_javascript=https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML
-[session]
-tools_dock_checked=true
-
[predefined_colors]
1\name=White
1\rgb=EEEEEE
@@ -154,25 +168,84 @@ tools_dock_checked=true
size=4
[shortcuts]
-; Define shortcuts here, with each item in the form "operation->keysequence".
+; 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+E is reserved for Captain Mode.
; Ctrl+Q is reserved for quitting VNote.
-1\operation=NewNote
-1\keysequence=Ctrl+Alt+N
-2\operation=SaveNote
-2\keysequence=Ctrl+S
-3\operation=SaveAndRead
-3\keysequence=Ctrl+T
-4\operation=EditNote
-4\keysequence=Ctrl+W
-5\operation=CloseNote
-5\keysequence=
-6\operation=Find
-6\keysequence=Ctrl+F
-7\operation=FindNext
-7\keysequence=F3
-8\operation=FindPrevious
-8\keysequence=Shift+F3
-size=8
+
+; 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
+; Evaluate selected text or cursor word as magic words
+MagicWord=M
diff --git a/src/resources/vnote.qss b/src/resources/vnote.qss
index 059d3125..45ad190e 100644
--- a/src/resources/vnote.qss
+++ b/src/resources/vnote.qss
@@ -9,11 +9,11 @@ QPushButton[CornerBtn="true"]::menu-indicator {
image: none;
}
-QPushButton[CornerBtn="true"]::hover {
+QPushButton[CornerBtn="true"]:hover {
background-color: @hover-color;
}
-QPushButton[CornerBtn="true"]::focus {
+QPushButton[CornerBtn="true"]:focus {
background-color: @focus-color;
}
@@ -25,11 +25,11 @@ QPushButton[StatusBtn="true"] {
background-color: transparent;
}
-QPushButton[StatusBtn="true"]::hover {
+QPushButton[StatusBtn="true"]:hover {
background-color: @hover-color;
}
-QPushButton[StatusBtn="true"]::focus {
+QPushButton[StatusBtn="true"]:focus {
background-color: @focus-color;
}
@@ -40,11 +40,11 @@ QPushButton[FlatBtn="true"] {
background-color: transparent;
}
-QPushButton[FlatBtn="true"]::hover {
+QPushButton[FlatBtn="true"]:hover {
background-color: @hover-color;
}
-QPushButton[FlatBtn="true"]::focus {
+QPushButton[FlatBtn="true"]:focus {
background-color: @focus-color;
}
@@ -56,11 +56,11 @@ QPushButton[SelectionBtn="true"] {
text-align: left;
}
-QPushButton[SelectionBtn="true"]::hover {
+QPushButton[SelectionBtn="true"]:hover {
background-color: @hover-color;
}
-QPushButton[SelectionBtn="true"]::focus {
+QPushButton[SelectionBtn="true"]:focus {
background-color: @focus-color;
}
@@ -71,11 +71,11 @@ QPushButton[TitleBtn="true"] {
background-color: @base-color;
}
-QPushButton[TitleBtn="true"]::hover {
+QPushButton[TitleBtn="true"]:hover {
background-color: @hover-color;
}
-QPushButton[TitleBtn="true"]::focus {
+QPushButton[TitleBtn="true"]:focus {
background-color: @focus-color;
}
@@ -85,7 +85,7 @@ QPushButton[DangerBtn="true"] {
background-color: #d9534f;
}
-QPushButton[DangerBtn="true"]::hover {
+QPushButton[DangerBtn="true"]:hover {
color: #fff;
border-color: #ac2925;
background-color: #c9302c;
@@ -95,10 +95,14 @@ QToolBar {
border: none;
}
-QToolButton::hover {
+QToolButton:hover {
background-color: @hover-color;
}
+/* Override default shift behavior */
+QToolButton::menu-arrow:open {
+}
+
QMenuBar {
border: none;
}
@@ -141,7 +145,7 @@ QComboBox#NotebookSelector {
icon-size: 30px;
}
-QComboBox#NotebookSelector::focus {
+QComboBox#NotebookSelector:focus {
background-color: @focus-color;
}
diff --git a/src/src.pro b/src/src.pro
index 39730061..4dcc8d70 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -44,7 +44,6 @@ SOURCES += main.cpp\
veditwindow.cpp \
vedittab.cpp \
voutline.cpp \
- vtoc.cpp \
vsingleinstanceguard.cpp \
vdirectory.cpp \
vfile.cpp \
@@ -77,7 +76,12 @@ SOURCES += main.cpp\
dialog/vconfirmdeletiondialog.cpp \
vnotefile.cpp \
vattachmentlist.cpp \
- dialog/vsortdialog.cpp
+ dialog/vsortdialog.cpp \
+ vfilesessioninfo.cpp \
+ vtableofcontent.cpp \
+ utils/vmetawordmanager.cpp \
+ vlineedit.cpp \
+ dialog/vinsertlinkdialog.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@@ -107,7 +111,6 @@ HEADERS += vmainwindow.h \
veditwindow.h \
vedittab.h \
voutline.h \
- vtoc.h \
vsingleinstanceguard.h \
vdirectory.h \
vfile.h \
@@ -142,7 +145,12 @@ HEADERS += vmainwindow.h \
dialog/vconfirmdeletiondialog.h \
vnotefile.h \
vattachmentlist.h \
- dialog/vsortdialog.h
+ dialog/vsortdialog.h \
+ vfilesessioninfo.h \
+ vtableofcontent.h \
+ utils/vmetawordmanager.h \
+ vlineedit.h \
+ dialog/vinsertlinkdialog.h
RESOURCES += \
vnote.qrc \
diff --git a/src/utils/veditutils.cpp b/src/utils/veditutils.cpp
index 5152e855..36d08495 100644
--- a/src/utils/veditutils.cpp
+++ b/src/utils/veditutils.cpp
@@ -48,7 +48,7 @@ bool VEditUtils::insertBlockWithIndent(QTextCursor &p_cursor)
{
V_ASSERT(!p_cursor.hasSelection());
p_cursor.insertBlock();
- return indentBlockAsPreviousBlock(p_cursor);
+ return indentBlockAsBlock(p_cursor, false);
}
bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor)
@@ -85,21 +85,16 @@ bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor)
}
-bool VEditUtils::indentBlockAsPreviousBlock(QTextCursor &p_cursor)
+bool VEditUtils::indentBlockAsBlock(QTextCursor &p_cursor, bool p_next)
{
bool changed = false;
QTextBlock block = p_cursor.block();
- if (block.blockNumber() == 0) {
- // The first block.
+ QTextBlock refBlock = p_next ? block.next() : block.previous();
+ if (!refBlock.isValid()) {
return false;
}
- QTextBlock preBlock = block.previous();
- QString text = preBlock.text();
- QRegExp regExp("(^\\s*)");
- regExp.indexIn(text);
- V_ASSERT(regExp.captureCount() == 1);
- QString leadingSpaces = regExp.capturedTexts()[1];
+ QString leadingSpaces = fetchIndentSpaces(refBlock);
moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::MoveAnchor);
if (!p_cursor.atBlockStart()) {
@@ -689,3 +684,130 @@ bool VEditUtils::needToCancelAutoIndent(int p_autoIndentPos, const QTextCursor &
return false;
}
+
+void VEditUtils::insertTitleMark(QTextCursor &p_cursor,
+ const QTextBlock &p_block,
+ int p_level)
+{
+ if (!p_block.isValid()) {
+ return;
+ }
+
+ Q_ASSERT(p_level >= 1 && p_level <= 6);
+
+ bool needInsert = true;
+
+ p_cursor.setPosition(p_block.position());
+
+ // Test if this block contains title marks.
+ QRegExp headerReg(VUtils::c_headerRegExp);
+ QString text = p_block.text();
+ bool matched = headerReg.exactMatch(text);
+ if (matched) {
+ int level = headerReg.cap(1).length();
+ if (level == p_level) {
+ needInsert = false;
+ } else {
+ // Remove the title mark.
+ p_cursor.movePosition(QTextCursor::NextCharacter,
+ QTextCursor::KeepAnchor,
+ level);
+ p_cursor.removeSelectedText();
+ }
+ }
+
+ // Insert titleMark + " " at the front of the block.
+ if (needInsert) {
+ // Remove the spaces at front.
+ // insertText() will remove the selection.
+ moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::KeepAnchor);
+
+ // Insert.
+ const QString titleMark(p_level, '#');
+ p_cursor.insertText(titleMark + " ");
+ }
+
+ // Go to the end of this block.
+ p_cursor.movePosition(QTextCursor::EndOfBlock);
+}
+
+void VEditUtils::findCurrentWord(QTextCursor p_cursor,
+ int &p_start,
+ int &p_end)
+{
+ QString text = p_cursor.block().text();
+ int pib = p_cursor.positionInBlock();
+
+ if (pib < text.size() && text[pib].isSpace()) {
+ p_start = p_end = p_cursor.position();
+ return;
+ }
+
+ p_cursor.movePosition(QTextCursor::StartOfWord);
+ p_start = p_cursor.position();
+ p_cursor.movePosition(QTextCursor::EndOfWord);
+ p_end = p_cursor.position();
+}
+
+void VEditUtils::findCurrentWORD(const QTextCursor &p_cursor,
+ int &p_start,
+ int &p_end)
+{
+ QTextBlock block = p_cursor.block();
+ QString text = block.text();
+ int pib = p_cursor.positionInBlock();
+
+ if (pib < text.size() && text[pib].isSpace()) {
+ p_start = p_end = p_cursor.position();
+ return;
+ }
+
+ // Find the start.
+ p_start = 0;
+ for (int i = pib - 1; i >= 0; --i) {
+ if (text[i].isSpace()) {
+ p_start = i + 1;
+ break;
+ }
+ }
+
+ // Find the end.
+ p_end = block.length() - 1;
+ for (int i = pib; i < text.size(); ++i) {
+ if (text[i].isSpace()) {
+ p_end = i;
+ break;
+ }
+ }
+
+ p_start += block.position();
+ p_end += block.position();
+}
+
+QString VEditUtils::fetchIndentSpaces(const QTextBlock &p_block)
+{
+ QString text = p_block.text();
+ QRegExp regExp("(^\\s*)");
+ regExp.indexIn(text);
+ Q_ASSERT(regExp.captureCount() == 1);
+ return regExp.capturedTexts()[1];
+}
+
+void VEditUtils::insertBlock(QTextCursor &p_cursor,
+ bool p_above)
+{
+ p_cursor.movePosition(p_above ? QTextCursor::StartOfBlock
+ : QTextCursor::EndOfBlock,
+ QTextCursor::MoveAnchor,
+ 1);
+
+ p_cursor.insertBlock();
+
+ if (p_above) {
+ p_cursor.movePosition(QTextCursor::PreviousBlock,
+ QTextCursor::MoveAnchor,
+ 1);
+ }
+
+ p_cursor.movePosition(QTextCursor::EndOfBlock);
+}
diff --git a/src/utils/veditutils.h b/src/utils/veditutils.h
index bae56cca..b630180d 100644
--- a/src/utils/veditutils.h
+++ b/src/utils/veditutils.h
@@ -24,10 +24,11 @@ public:
// Need to call setTextCursor() to make it take effect.
static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode);
- // Indent current block as previous block.
+ // Indent current block as next/previous block.
// Return true if some changes have been made.
// @p_cursor will be placed at the position after inserting leading spaces.
- static bool indentBlockAsPreviousBlock(QTextCursor &p_cursor);
+ // @p_next: indent as next block or previous block.
+ static bool indentBlockAsBlock(QTextCursor &p_cursor, bool p_next);
// Returns true if two blocks has the same indent.
static bool hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb);
@@ -132,7 +133,38 @@ public:
// Check if we need to cancel auto indent.
// @p_autoIndentPos: the position of the cursor after auto indent.
- static bool needToCancelAutoIndent(int p_autoIndentPos, const QTextCursor &p_cursor);
+ static bool needToCancelAutoIndent(int p_autoIndentPos,
+ const QTextCursor &p_cursor);
+
+ // Insert title Mark at level @p_level in front of block @p_block
+ // If there already exists title marks, remove it first.
+ // Move cursor at the end of the block after insertion.
+ static void insertTitleMark(QTextCursor &p_cursor,
+ const QTextBlock &p_block,
+ int p_level);
+
+ // Find the start and end of the word @p_cursor locates in (within a single block).
+ // @p_start and @p_end will be the global position of the start and end of the word.
+ // @p_start will equals to @p_end if @p_cursor is a space.
+ static void findCurrentWord(QTextCursor p_cursor,
+ int &p_start,
+ int &p_end);
+
+ // Find the start and end of the WORD @p_cursor locates in (within a single block).
+ // @p_start and @p_end will be the global position of the start and end of the WORD.
+ // @p_start will equals to @p_end if @p_cursor is a space.
+ // Attention: www|sss will select www, which is different from findCurrentWord().
+ static void findCurrentWORD(const QTextCursor &p_cursor,
+ int &p_start,
+ int &p_end);
+
+ // Return the leading spaces of @p_block.
+ static QString fetchIndentSpaces(const QTextBlock &p_block);
+
+ // Insert a block above/below current block. Move the cursor to the start of
+ // the new block after insertion.
+ static void insertBlock(QTextCursor &p_cursor,
+ bool p_above);
private:
VEditUtils() {}
diff --git a/src/utils/vmetawordmanager.cpp b/src/utils/vmetawordmanager.cpp
new file mode 100644
index 00000000..66cd4a67
--- /dev/null
+++ b/src/utils/vmetawordmanager.cpp
@@ -0,0 +1,596 @@
+#include "vmetawordmanager.h"
+
+#include
+#include
+#include
+#include
+
+#include "vconfigmanager.h"
+
+extern VConfigManager *g_config;
+
+
+// Used as the function template for some date/time related meta words.
+static QString formattedDateTime(const VMetaWord *p_metaWord,
+ const QString &p_format)
+{
+ return p_metaWord->getManager()->getDateTime().toString(p_format);
+}
+
+static QString allMetaWordsInfo(const VMetaWord *p_metaWord)
+{
+ QString msg = QObject::tr("All magic words:");
+
+ const VMetaWordManager *mgr = p_metaWord->getManager();
+ QList keys = mgr->getAllMetaWords().keys();
+ keys.sort(Qt::CaseInsensitive);
+
+ for (auto const & key : keys) {
+ const VMetaWord *word = mgr->findMetaWord(key);
+ Q_ASSERT(word);
+ msg += QString("\n%1:\t%2").arg(word->getWord()).arg(word->getDefinition());
+ }
+
+ QWidget *focusWid = QApplication::focusWidget();
+ if (focusWid) {
+ QPoint pos = focusWid->mapToGlobal(QPoint(0, focusWid->height()));
+ QToolTip::showText(pos, msg, focusWid);
+ }
+
+ // Just return the same word.
+ return QString("%1help%1").arg(VMetaWordManager::c_delimiter);
+}
+
+const QChar VMetaWordManager::c_delimiter = '%';
+
+VMetaWordManager::VMetaWordManager(QObject *p_parent)
+ : QObject(p_parent)
+{
+}
+
+void VMetaWordManager::init()
+{
+ using namespace std::placeholders;
+
+ // %d%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "d",
+ tr("the day as number without a leading zero (`1` to `31`)"),
+ std::bind(formattedDateTime, _1, "d"));
+
+ // %dd%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "dd",
+ tr("the day as number with a leading zero (`01` to `31`)"),
+ std::bind(formattedDateTime, _1, "dd"));
+
+ // %ddd%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "ddd",
+ tr("the abbreviated localized day name (e.g. `Mon` to `Sun`)"),
+ std::bind(formattedDateTime, _1, "ddd"));
+
+ // %dddd%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "dddd",
+ tr("the long localized day name (e.g. `Monday` to `Sunday`)"),
+ std::bind(formattedDateTime, _1, "dddd"));
+
+ // %M%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "M",
+ tr("the month as number without a leading zero (`1` to `12`)"),
+ std::bind(formattedDateTime, _1, "M"));
+
+ // %MM%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "MM",
+ tr("the month as number with a leading zero (`01` to `12`)"),
+ std::bind(formattedDateTime, _1, "MM"));
+
+ // %MMM%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "MMM",
+ tr("the abbreviated localized month name (e.g. `Jan` to `Dec`)"),
+ std::bind(formattedDateTime, _1, "MMM"));
+
+ // %MMMM%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "MMMM",
+ tr("the long localized month name (e.g. `January` to `December`"),
+ std::bind(formattedDateTime, _1, "MMMM"));
+
+ // %yy%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "yy",
+ tr("the year as two digit number (`00` to `99`)"),
+ std::bind(formattedDateTime, _1, "yy"));
+
+ // %yyyy%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "yyyy",
+ tr("the year as four digit number"),
+ std::bind(formattedDateTime, _1, "yyyy"));
+
+ // %h%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "h",
+ tr("the hour without a leading zero (`0` to `23` or `1` to `12` if AM/PM display"),
+ std::bind(formattedDateTime, _1, "h"));
+
+ // %hh%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "hh",
+ tr("the hour with a leading zero (`00` to `23` or `01` to `12` if AM/PM display"),
+ std::bind(formattedDateTime, _1, "hh"));
+
+ // %H%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "H",
+ tr("the hour without a leading zero (`0` to `23` even with AM/PM display"),
+ std::bind(formattedDateTime, _1, "H"));
+
+ // %HH%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "HH",
+ tr("the hour with a leading zero (`00` to `23` even with AM/PM display"),
+ std::bind(formattedDateTime, _1, "HH"));
+
+ // %m%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "m",
+ tr("the minute without a leading zero (`0` to `59`)"),
+ std::bind(formattedDateTime, _1, "m"));
+
+ // %mm%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "mm",
+ tr("the minute with a leading zero (`00` to `59`)"),
+ std::bind(formattedDateTime, _1, "mm"));
+
+ // %s%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "s",
+ tr("the second without a leading zero (`0` to `59`)"),
+ std::bind(formattedDateTime, _1, "s"));
+
+ // %ss%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "ss",
+ tr("the second with a leading zero (`00` to `59`)"),
+ std::bind(formattedDateTime, _1, "ss"));
+
+ // %z%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "z",
+ tr("the milliseconds without leading zeroes (`0` to `999`)"),
+ std::bind(formattedDateTime, _1, "z"));
+
+ // %zzz%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "zzz",
+ tr("the milliseconds with leading zeroes (`000` to `999`)"),
+ std::bind(formattedDateTime, _1, "zzz"));
+
+ // %AP%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "AP",
+ tr("use AM/PM display (`AM` or `PM`)"),
+ std::bind(formattedDateTime, _1, "AP"));
+
+ // %A%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "A",
+ tr("use AM/PM display (`AM` or `PM`)"),
+ std::bind(formattedDateTime, _1, "A"));
+
+ // %ap%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "ap",
+ tr("use am/pm display (`am` or `pm`)"),
+ std::bind(formattedDateTime, _1, "ap"));
+
+ // %a%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "a",
+ tr("use am/pm display (`am` or `pm`)"),
+ std::bind(formattedDateTime, _1, "a"));
+
+ // %t%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "t",
+ tr("the timezone (e.g. `CEST`)"),
+ std::bind(formattedDateTime, _1, "t"));
+
+ // %random%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "random",
+ tr("a random number"),
+ [](const VMetaWord *) {
+ return QString::number(qrand());
+ });
+
+ // %random_d%.
+ addMetaWord(MetaWordType::Dynamic,
+ "random_d",
+ tr("dynamic version of `random`"),
+ [](const VMetaWord *) {
+ return QString::number(qrand());
+ });
+
+ // %date%.
+ addMetaWord(MetaWordType::Compound,
+ "date",
+ QString("%1yyyy%1-%1MM%1-%1dd%1").arg(c_delimiter));
+
+ // %da%.
+ addMetaWord(MetaWordType::Compound,
+ "da",
+ QString("%1yyyy%1%1MM%1%1dd%1").arg(c_delimiter));
+
+ // %time%.
+ addMetaWord(MetaWordType::Compound,
+ "time",
+ QString("%1hh%1:%1mm%1:%1ss%1").arg(c_delimiter));
+
+ // %datetime%.
+ addMetaWord(MetaWordType::Compound,
+ "datetime",
+ QString("%1date%1 %1time%1").arg(c_delimiter));
+
+ // Custom meta words.
+ initCustomMetaWords();
+
+ // %help% to print all metaword info.
+ addMetaWord(MetaWordType::FunctionBased,
+ "help",
+ tr("information about all defined magic words"),
+ allMetaWordsInfo);
+}
+
+void VMetaWordManager::initCustomMetaWords()
+{
+ QVector words = g_config->getCustomMagicWords();
+ for (auto const & item : words) {
+ addMetaWord(MetaWordType::Compound,
+ item.m_name,
+ item.m_definition);
+ }
+}
+
+QString VMetaWordManager::evaluate(const QString &p_text) const
+{
+ if (p_text.isEmpty()) {
+ return p_text;
+ }
+
+ // Update datetime for later parse.
+ const_cast(this)->m_dateTime = QDateTime::currentDateTime();
+
+ // Treat the text as a Compound meta word.
+ const QString tmpWord("vnote_tmp_metaword");
+ Q_ASSERT(!contains(tmpWord));
+ VMetaWord metaWord(this,
+ MetaWordType::Compound,
+ tmpWord,
+ p_text,
+ nullptr,
+ true);
+ if (metaWord.isValid()) {
+ return metaWord.evaluate();
+ } else {
+ return p_text;
+ }
+}
+
+bool VMetaWordManager::contains(const QString &p_word) const
+{
+ return m_metaWords.contains(p_word);
+}
+
+const VMetaWord *VMetaWordManager::findMetaWord(const QString &p_word) const
+{
+ auto it = m_metaWords.find(p_word);
+ if (it != m_metaWords.end()) {
+ return &it.value();
+ }
+
+ return NULL;
+}
+
+void VMetaWordManager::addMetaWord(MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function)
+{
+ if (p_word.isEmpty() || contains(p_word)) {
+ return;
+ }
+
+ VMetaWord metaWord(this,
+ p_type,
+ p_word,
+ p_definition,
+ p_function);
+
+ if (metaWord.isValid()) {
+ m_metaWords.insert(p_word, metaWord);
+ qDebug() << QString("MetaWord %1%2%1[%3] added")
+ .arg(c_delimiter).arg(p_word).arg(p_definition);
+ }
+}
+
+VMetaWord::VMetaWord(const VMetaWordManager *p_manager,
+ MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function,
+ bool p_allowAllSpaces)
+ : m_manager(p_manager),
+ m_type(p_type),
+ m_word(p_word),
+ m_definition(p_definition),
+ m_valid(false)
+{
+ m_function = p_function;
+
+ if (checkType(MetaWordType::Simple)
+ || checkType(MetaWordType::Compound)) {
+ Q_ASSERT(!m_function);
+ } else {
+ Q_ASSERT(m_function);
+ }
+
+ checkAndParseDefinition(p_allowAllSpaces);
+}
+
+bool VMetaWord::checkType(MetaWordType p_type)
+{
+ return m_type == p_type;
+}
+
+void VMetaWord::checkAndParseDefinition(bool p_allowAllSpaces)
+{
+ if (m_word.contains(VMetaWordManager::c_delimiter)) {
+ m_valid = false;
+ return;
+ }
+
+ m_valid = true;
+ m_tokens.clear();
+
+ // We do not accept \n and \t in the definition.
+ QRegExp defReg("[\\S ]*");
+ if (!defReg.exactMatch(m_definition) && !p_allowAllSpaces) {
+ m_valid = false;
+ return;
+ }
+
+ if (checkType(MetaWordType::FunctionBased)
+ || checkType(MetaWordType::Dynamic)) {
+ if (!m_function) {
+ m_valid = false;
+ }
+ } else if (checkType(MetaWordType::Compound)) {
+ m_tokens = parseToTokens(m_definition);
+ if (m_tokens.isEmpty()) {
+ m_valid = false;
+ return;
+ }
+
+ for (auto const & to : m_tokens) {
+ if (to.isMetaWord()) {
+ if (!m_manager->contains(to.m_value)) {
+ // Dependency not defined.
+ m_valid = false;
+ break;
+ }
+ }
+ }
+ }
+}
+
+bool VMetaWord::isValid() const
+{
+ return m_valid;
+}
+
+QString VMetaWord::evaluate() const
+{
+ Q_ASSERT(m_valid);
+ qDebug() << "evaluate meta word" << m_word;
+ switch (m_type) {
+ case MetaWordType::Simple:
+ return m_definition;
+
+ case MetaWordType::FunctionBased:
+ case MetaWordType::Dynamic:
+ return m_function(this);
+
+ case MetaWordType::Compound:
+ {
+ QHash cache;
+ return evaluateTokens(m_tokens, cache);
+ }
+
+ default:
+ return "";
+ }
+}
+
+MetaWordType VMetaWord::getType() const
+{
+ return m_type;
+}
+
+const QString &VMetaWord::getWord() const
+{
+ return m_word;
+}
+
+const QString &VMetaWord::getDefinition() const
+{
+ return m_definition;
+}
+
+const VMetaWordManager *VMetaWord::getManager() const
+{
+ return m_manager;
+}
+
+
+QString VMetaWord::toString() const
+{
+ QChar typeChar('U');
+ switch (m_type) {
+ case MetaWordType::Simple:
+ typeChar = 'S';
+ break;
+
+ case MetaWordType::FunctionBased:
+ typeChar = 'F';
+ break;
+
+ case MetaWordType::Dynamic:
+ typeChar = 'D';
+ break;
+
+ case MetaWordType::Compound:
+ typeChar = 'C';
+ break;
+
+ default:
+ break;
+ }
+
+ return QString("%1%2%1[%3]: %4").arg(VMetaWordManager::c_delimiter)
+ .arg(m_word)
+ .arg(typeChar)
+ .arg(m_definition);
+}
+
+QVector VMetaWord::parseToTokens(const QString &p_text)
+{
+ QVector tokens;
+
+ TokenType type = TokenType::Raw;
+ QString value;
+ value.reserve(p_text.size());
+ for (int idx = 0; idx < p_text.size(); ++idx) {
+ const QChar &ch = p_text[idx];
+ if (ch == VMetaWordManager::c_delimiter) {
+ // Check if it is single or double.
+ int next = idx + 1;
+ if (next == p_text.size()
+ || p_text[next] != VMetaWordManager::c_delimiter) {
+ // Single delimiter.
+ if (type == TokenType::Raw) {
+ // End of a raw token, begin of a MetaWord token.
+ if (!value.isEmpty()) {
+ tokens.push_back(Token(type, value));
+ }
+
+ type = TokenType::MetaWord;
+ } else {
+ // End of a MetaWord token, begin of a Raw token.
+ Q_ASSERT(!value.isEmpty());
+
+ tokens.push_back(Token(type, value));
+ type = TokenType::Raw;
+ }
+
+ value.clear();
+ } else {
+ // Double delimiter.
+ // If now is parsing a MetaWord token, treat the first delimiter
+ // as the end of a token.
+ // Otherwise, store one single delimiter in value and skip next char.
+ if (type == TokenType::MetaWord) {
+ Q_ASSERT(!value.isEmpty());
+ tokens.push_back(Token(type, value));
+ type = TokenType::Raw;
+ value.clear();
+ } else {
+ value.push_back(ch);
+ ++idx;
+ }
+ }
+ } else {
+ // Push ch in value.
+ value.push_back(ch);
+ }
+ }
+
+ if (!value.isEmpty()) {
+ if (type == TokenType::Raw) {
+ tokens.push_back(Token(type, value));
+ } else {
+ // An imcomplete metaword token.
+ // Treat it as raw.
+ tokens.push_back(Token(TokenType::Raw, "%" + value));
+ }
+
+ value.clear();
+ }
+
+ return tokens;
+}
+
+QString VMetaWord::evaluateTokens(const QVector &p_tokens,
+ QHash &p_cache) const
+{
+ QString val;
+
+ for (auto const & to : p_tokens) {
+ switch (to.m_type) {
+ case TokenType::Raw:
+ val += to.m_value;
+ break;
+
+ case TokenType::MetaWord:
+ {
+ const VMetaWord *metaWord = m_manager->findMetaWord(to.m_value);
+ if (!metaWord) {
+ // Invalid meta word. Treat it as literal value.
+ val += VMetaWordManager::c_delimiter + to.m_value + VMetaWordManager::c_delimiter;
+ break;
+ }
+
+ QString wordVal;
+ switch (metaWord->getType()) {
+ case MetaWordType::FunctionBased:
+ {
+ auto it = p_cache.find(metaWord->getWord());
+ if (it != p_cache.end()) {
+ // Find it in the cache.
+ wordVal = it.value();
+ } else {
+ // First evaluate this meta word.
+ wordVal = metaWord->evaluate();
+ p_cache.insert(metaWord->getWord(), wordVal);
+ }
+
+ break;
+ }
+
+ case MetaWordType::Compound:
+ wordVal = evaluateTokens(metaWord->m_tokens, p_cache);
+ break;
+
+ default:
+ wordVal = metaWord->evaluate();
+ break;
+ }
+
+ val += wordVal;
+ break;
+ }
+
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+ }
+
+ return val;
+}
diff --git a/src/utils/vmetawordmanager.h b/src/utils/vmetawordmanager.h
new file mode 100644
index 00000000..cfba310b
--- /dev/null
+++ b/src/utils/vmetawordmanager.h
@@ -0,0 +1,205 @@
+#ifndef VMETAWORDMANAGER_H
+#define VMETAWORDMANAGER_H
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+
+enum class MetaWordType
+{
+ // Definition is plain text.
+ Simple = 0,
+
+ // Definition is a function call to get the value.
+ FunctionBased,
+
+ // Like FunctionBased, but should re-evaluate the value for each occurence.
+ Dynamic,
+
+ // Consists of other meta words.
+ Compound,
+
+ Invalid
+};
+
+// We call meta word "magic word" in user interaction.
+struct VMagicWord
+{
+ QString m_name;
+ QString m_definition;
+};
+
+class VMetaWordManager;
+class VMetaWord;
+
+typedef std::function MetaWordFunc;
+
+// A Meta Word is surrounded by %.
+// - Use %% for an escaped %;
+// - Built-in or user-defined;
+// - A meta word could contain other meta words as definition.
+class VMetaWord
+{
+public:
+ VMetaWord(const VMetaWordManager *p_manager,
+ MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function = nullptr,
+ bool p_allowAllSpaces = false);
+
+ bool isValid() const;
+
+ QString evaluate() const;
+
+ MetaWordType getType() const;
+
+ const QString &getWord() const;
+
+ const QString &getDefinition() const;
+
+ const VMetaWordManager *getManager() const;
+
+ QString toString() const;
+
+ enum class TokenType
+ {
+ Raw = 0,
+ MetaWord
+ };
+
+ struct Token
+ {
+ Token()
+ : m_type(TokenType::Raw)
+ {
+ }
+
+ Token(VMetaWord::TokenType p_type, const QString &p_value)
+ : m_type(p_type),
+ m_value(p_value)
+ {
+ }
+
+ QString toString() const
+ {
+ return QString("token %1[%2]").arg(m_type == TokenType::Raw
+ ? "Raw" : "MetaWord")
+ .arg(m_value);
+ }
+
+ bool isRaw() const
+ {
+ return m_type == TokenType::Raw;
+ }
+
+ bool isMetaWord() const
+ {
+ return m_type == TokenType::MetaWord;
+ }
+
+ TokenType m_type;
+
+ // For Raw type, m_value is the raw string of this token;
+ // For MetaWord type, m_value is the word of the meta word pointed to by
+ // this token.
+ QString m_value;
+ };
+
+private:
+ // Check if m_type is @p_type.
+ bool checkType(MetaWordType p_type);
+
+ // Parse children word from definition.
+ // Children word MUST be defined in m_manager already.
+ // @p_allowAllSpaces: if true then we allow all spaces including \n and \t
+ // to appear in the definition.
+ void checkAndParseDefinition(bool p_allowAllSpaces);
+
+ // Parse @p_text to a list of tokens.
+ static QVector parseToTokens(const QString &p_text);
+
+ // Used for Compound meta word with cache @p_cache.
+ // @p_cache: value cache for FunctionBased Token.
+ // For a meta word occuring more than once, we should evaluate it once for
+ // FunctionBased meta word.
+ QString evaluateTokens(const QVector &p_tokens,
+ QHash &p_cache) const;
+
+ const VMetaWordManager *m_manager;
+
+ MetaWordType m_type;
+
+ // Word could contains spaces but no %.
+ QString m_word;
+
+ // For Simple/Compound meta word, this contains the definition;
+ // For FunctionBased/Dynamic meta word, this makes no sense and is used
+ // for description.
+ QString m_definition;
+
+ // For FunctionBased and Dynamic meta word.
+ MetaWordFunc m_function;
+
+ bool m_valid;
+
+ // Tokens used for Compound meta word.
+ QVector m_tokens;
+};
+
+
+// Manager of meta word.
+class VMetaWordManager : public QObject
+{
+ Q_OBJECT
+public:
+ explicit VMetaWordManager(QObject *p_parent = nullptr);
+
+ void init();
+
+ // Expand meta words in @p_text and return the expanded text.
+ QString evaluate(const QString &p_text) const;
+
+ const VMetaWord *findMetaWord(const QString &p_word) const;
+
+ bool contains(const QString &p_word) const;
+
+ const QDateTime &getDateTime() const;
+
+ const QHash &getAllMetaWords() const;
+
+ // % by default.
+ static const QChar c_delimiter;
+
+private:
+ void addMetaWord(MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function = nullptr);
+
+ void initCustomMetaWords();
+
+ // Map using word as key.
+ QHash m_metaWords;
+
+ // Used for data/time related evaluate.
+ // Will be updated before each evaluation.
+ QDateTime m_dateTime;
+};
+
+inline const QDateTime &VMetaWordManager::getDateTime() const
+{
+ return m_dateTime;
+}
+
+inline const QHash &VMetaWordManager::getAllMetaWords() const
+{
+ return m_metaWords;
+}
+
+#endif // VMETAWORDMANAGER_H
diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp
index 4a22144a..92dab9b6 100644
--- a/src/utils/vutils.cpp
+++ b/src/utils/vutils.cpp
@@ -33,6 +33,8 @@ QVector> VUtils::s_availableLanguages;
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)");
+const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]*");
+
const QString VUtils::c_fileNameRegExp = QString("[^\\\\/:\\*\\?\"<>\\|]*");
const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
@@ -43,7 +45,7 @@ const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfff
const QString VUtils::c_headerRegExp = QString("^(#{1,6})\\s+(((\\d+\\.)+(?=\\s))?\\s?\\S.*)\\s*$");
-const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s?)\\S.*\\s*$");
+const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s?)($|\\S.*\\s*$)");
void VUtils::initAvailableLanguage()
{
@@ -104,10 +106,10 @@ QRgb VUtils::QRgbFromString(const QString &str)
return QRgb();
}
-QString VUtils::generateImageFileName(const QString &path, const QString &title,
+QString VUtils::generateImageFileName(const QString &path,
+ const QString &title,
const QString &format)
{
- Q_ASSERT(!title.isEmpty());
QRegExp regExp("\\W");
QString baseName(title.toLower());
@@ -117,7 +119,9 @@ QString VUtils::generateImageFileName(const QString &path, const QString &title,
// Constrain the length of the name.
baseName.truncate(10);
- baseName.prepend('_');
+ if (!baseName.isEmpty()) {
+ baseName.prepend('_');
+ }
// Add current time and random number to make the name be most likely unique
baseName = baseName + '_' + QString::number(QDateTime::currentDateTime().toTime_t());
@@ -377,31 +381,35 @@ int VUtils::showMessage(QMessageBox::Icon p_icon, const QString &p_title, const
return msgBox.exec();
}
-QString VUtils::generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName)
+QString VUtils::generateCopiedFileName(const QString &p_dirPath,
+ const QString &p_fileName,
+ bool p_completeBaseName)
{
- QString suffix;
- QString base = p_fileName;
- int dotIdx = p_fileName.lastIndexOf('.');
- if (dotIdx != -1) {
- // .md
- suffix = p_fileName.right(p_fileName.size() - dotIdx);
- base = p_fileName.left(dotIdx);
+ QDir dir(p_dirPath);
+ if (!dir.exists() || !dir.exists(p_fileName)) {
+ return p_fileName;
}
- QDir dir(p_dirPath);
- QString name = p_fileName;
+ QFileInfo fi(p_fileName);
+ QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
+ QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
+
int index = 0;
- while (dir.exists(name)) {
+ QString fileName;
+ do {
QString seq;
if (index > 0) {
- seq = QString::number(index);
+ seq = QString("%1").arg(QString::number(index), 3, '0');
}
index++;
- name = QString("%1_copy%2%3").arg(base).arg(seq).arg(suffix);
- }
+ fileName = QString("%1_copy%2").arg(baseName).arg(seq);
+ if (!suffix.isEmpty()) {
+ fileName = fileName + "." + suffix;
+ }
+ } while (fileExists(dir, fileName, true));
- return name;
+ return fileName;
}
QString VUtils::generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName)
@@ -614,7 +622,8 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exp
}
QString VUtils::getFileNameWithSequence(const QString &p_directory,
- const QString &p_baseFileName)
+ const QString &p_baseFileName,
+ bool p_completeBaseName)
{
QDir dir(p_directory);
if (!dir.exists() || !dir.exists(p_baseFileName)) {
@@ -623,8 +632,8 @@ QString VUtils::getFileNameWithSequence(const QString &p_directory,
// Append a sequence.
QFileInfo fi(p_baseFileName);
- QString baseName = fi.baseName();
- QString suffix = fi.completeSuffix();
+ QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
+ QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
int seq = 1;
QString fileName;
do {
@@ -637,6 +646,24 @@ QString VUtils::getFileNameWithSequence(const QString &p_directory,
return fileName;
}
+QString VUtils::getDirNameWithSequence(const QString &p_directory,
+ const QString &p_baseDirName)
+{
+ QDir dir(p_directory);
+ if (!dir.exists() || !dir.exists(p_baseDirName)) {
+ return p_baseDirName;
+ }
+
+ // Append a sequence.
+ int seq = 1;
+ QString fileName;
+ do {
+ fileName = QString("%1_%2").arg(p_baseDirName).arg(QString::number(seq++), 3, '0');
+ } while (fileExists(dir, fileName, true));
+
+ return fileName;
+}
+
QString VUtils::getRandomFileName(const QString &p_directory)
{
Q_ASSERT(!p_directory.isEmpty());
@@ -702,6 +729,16 @@ bool VUtils::checkPathLegal(const QString &p_path)
return ret;
}
+bool VUtils::checkFileNameLegal(const QString &p_name)
+{
+ if (p_name.isEmpty()) {
+ return false;
+ }
+
+ QRegExp exp(c_fileNameRegExp);
+ return exp.exactMatch(p_name);
+}
+
bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
{
QString a = QDir::cleanPath(p_patha);
@@ -850,7 +887,8 @@ bool VUtils::deleteFile(const QString &p_recycleBinFolderPath,
}
QString destName = getFileNameWithSequence(binPath,
- fileNameFromPath(p_path));
+ fileNameFromPath(p_path),
+ true);
qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
if (!binDir.rename(p_path, binDir.filePath(destName))) {
@@ -935,3 +973,30 @@ void VUtils::addErrMsg(QString *p_msg, const QString &p_str)
*p_msg = *p_msg + '\n' + p_str;
}
}
+
+QStringList VUtils::filterFilePathsToOpen(const QStringList &p_files)
+{
+ QStringList paths;
+ for (int i = 0; i < p_files.size(); ++i) {
+ QString path = validFilePathToOpen(p_files[i]);
+ if (!path.isEmpty()) {
+ paths.append(path);
+ }
+ }
+
+ return paths;
+}
+
+QString VUtils::validFilePathToOpen(const QString &p_file)
+{
+ if (QFileInfo::exists(p_file)) {
+ QFileInfo fi(p_file);
+ if (fi.isFile()) {
+ // Need to use absolute path here since VNote may be launched
+ // in different working directory.
+ return QDir::cleanPath(fi.absoluteFilePath());
+ }
+ }
+
+ return QString();
+}
diff --git a/src/utils/vutils.h b/src/utils/vutils.h
index d0886dde..e21a580f 100644
--- a/src/utils/vutils.h
+++ b/src/utils/vutils.h
@@ -20,6 +20,32 @@ class VNotebook;
#define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
#endif
+// Thanks to CGAL/cgal.
+#ifndef __has_attribute
+ #define __has_attribute(x) 0 // Compatibility with non-clang compilers.
+#endif
+
+#ifndef __has_cpp_attribute
+ #define __has_cpp_attribute(x) 0 // Compatibility with non-supporting compilers.
+#endif
+
+// The fallthrough attribute.
+// See for clang:
+// http://clang.llvm.org/docs/AttributeReference.html#statement-attributes
+// See for gcc:
+// https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
+#if __has_cpp_attribute(fallthrough)
+# define V_FALLTHROUGH [[fallthrough]]
+#elif __has_cpp_attribute(gnu::fallthrough)
+# define V_FALLTHROUGH [[gnu::fallthrough]]
+#elif __has_cpp_attribute(clang::fallthrough)
+# define V_FALLTHROUGH [[clang::fallthrough]]
+#elif __has_attribute(fallthrough) && ! __clang__
+# define V_FALLTHROUGH __attribute__ ((fallthrough))
+#else
+# define V_FALLTHROUGH while(false){}
+#endif
+
enum class MessageBoxType
{
Normal = 0,
@@ -54,9 +80,19 @@ public:
// Given the file name @p_fileName and directory path @p_dirPath, generate
// a file name based on @p_fileName which does not exist in @p_dirPath.
- static QString generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName);
+ // @p_completeBaseName: use complete base name or complete suffix. For example,
+ // "abc.tar.gz", if @p_completeBaseName is true, the base name is "abc.tar",
+ // otherwise, it is "abc".
+ static QString generateCopiedFileName(const QString &p_dirPath,
+ const QString &p_fileName,
+ bool p_completeBaseName = true);
+
+ // Given the directory name @p_dirName and directory path @p_parentDirPath,
+ // generate a directory name based on @p_dirName which does not exist in
+ // @p_parentDirPath.
+ static QString generateCopiedDirName(const QString &p_parentDirPath,
+ const QString &p_dirName);
- static QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
static void processStyle(QString &style, const QVector > &varMap);
// Return the last directory name of @p_path.
@@ -122,8 +158,18 @@ public:
// 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.
+ // @p_completeBaseName: use complete base name or complete suffix. For example,
+ // "abc.tar.gz", if @p_completeBaseName is true, the base name is "abc.tar",
+ // otherwise, it is "abc".
static QString getFileNameWithSequence(const QString &p_directory,
- const QString &p_baseFileName);
+ const QString &p_baseFileName,
+ bool p_completeBaseName = true);
+
+ // Get an available directory name in @p_directory with base @p_baseDirName.
+ // If there already exists a file named @p_baseFileName, try to add sequence
+ // suffix to the name, such as _001.
+ static QString getDirNameWithSequence(const QString &p_directory,
+ const QString &p_baseDirName);
// Get an available random file name in @p_directory.
static QString getRandomFileName(const QString &p_directory);
@@ -131,6 +177,9 @@ public:
// Try to check if @p_path is legal.
static bool checkPathLegal(const QString &p_path);
+ // Check if file/folder name is legal.
+ static bool checkFileNameLegal(const QString &p_name);
+
// Returns true if @p_patha and @p_pathb points to the same file/directory.
static bool equalPath(const QString &p_patha, const QString &p_pathb);
@@ -186,6 +235,13 @@ public:
// Assign @p_str to @p_msg if it is not NULL.
static void addErrMsg(QString *p_msg, const QString &p_str);
+ // Check each file of @p_files and return valid ones for VNote to open.
+ static QStringList filterFilePathsToOpen(const QStringList &p_files);
+
+ // Return the normalized file path of @p_file if it is valid to open.
+ // Return empty if it is not valid.
+ static QString validFilePathToOpen(const QString &p_file);
+
// Regular expression for image link.
// 
// Captured texts (need to be trimmed):
@@ -195,6 +251,9 @@ public:
// 4. Unused;
static const QString c_imageLinkRegExp;
+ // Regular expression for image title.
+ static const QString c_imageTitleRegExp;
+
// Regular expression for file/directory name.
// Forbidden char: \/:*?"<>|
static const QString c_fileNameRegExp;
diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp
index 74bb2c1c..5d9e1c6a 100644
--- a/src/utils/vvim.cpp
+++ b/src/utils/vvim.cpp
@@ -18,6 +18,7 @@ extern VConfigManager *g_config;
const QChar VVim::c_unnamedRegister = QChar('"');
const QChar VVim::c_blackHoleRegister = QChar('_');
const QChar VVim::c_selectionRegister = QChar('+');
+QMap VVim::s_registers;
const int VVim::SearchHistory::c_capacity = 50;
@@ -170,62 +171,6 @@ static void findCurrentSpace(const QTextCursor &p_cursor, int &p_start, int &p_e
p_end += block.position();
}
-// Find the start and end of the word @p_cursor locates in (within a single block).
-// @p_start and @p_end will be the global position of the start and end of the word.
-// @p_start will equals to @p_end if @p_cursor is a space.
-static void findCurrentWord(QTextCursor p_cursor, int &p_start, int &p_end)
-{
- QString text = p_cursor.block().text();
- int pib = p_cursor.positionInBlock();
-
- if (pib < text.size() && text[pib].isSpace()) {
- p_start = p_end = p_cursor.position();
- return;
- }
-
- p_cursor.movePosition(QTextCursor::StartOfWord);
- p_start = p_cursor.position();
- p_cursor.movePosition(QTextCursor::EndOfWord);
- p_end = p_cursor.position();
-}
-
-// Find the start and end of the WORD @p_cursor locates in (within a single block).
-// @p_start and @p_end will be the global position of the start and end of the WORD.
-// @p_start will equals to @p_end if @p_cursor is a space.
-// Attention: www|sss will select www, which is different from findCurrentWord().
-static void findCurrentWORD(const QTextCursor &p_cursor, int &p_start, int &p_end)
-{
- QTextBlock block = p_cursor.block();
- QString text = block.text();
- int pib = p_cursor.positionInBlock();
-
- if (pib < text.size() && text[pib].isSpace()) {
- p_start = p_end = p_cursor.position();
- return;
- }
-
- // Find the start.
- p_start = 0;
- for (int i = pib - 1; i >= 0; --i) {
- if (text[i].isSpace()) {
- p_start = i + 1;
- break;
- }
- }
-
- // Find the end.
- p_end = block.length() - 1;
- for (int i = pib; i < text.size(); ++i) {
- if (text[i].isSpace()) {
- p_end = i;
- break;
- }
- }
-
- p_start += block.position();
- p_end += block.position();
-}
-
// Move @p_cursor to skip spaces if current cursor is placed at a space
// (may move across blocks). It will stop by the empty block on the way.
// Forward: wwwwsssss|wwww
@@ -351,7 +296,7 @@ static void insertChangeBlockAfterDeletion(QTextCursor &p_cursor, int p_deletion
}
if (g_config->getAutoIndent()) {
- VEditUtils::indentBlockAsPreviousBlock(p_cursor);
+ VEditUtils::indentBlockAsBlock(p_cursor, false);
}
}
@@ -556,7 +501,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
QChar reg = keyToRegisterName(keyInfo);
if (!reg.isNull()) {
// Insert register content.
- m_editor->insertPlainText(m_registers[reg].read());
+ m_editor->insertPlainText(getRegister(reg).read());
}
goto clear_accept;
@@ -602,11 +547,12 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
QChar reg = keyToRegisterName(keyInfo);
if (!reg.isNull()) {
m_keys.clear();
- setRegister(reg);
- if (m_registers[reg].isNamedRegister()) {
- m_registers[reg].m_append = (modifiers == Qt::ShiftModifier);
+ setCurrentRegisterName(reg);
+ Register &r = getRegister(reg);
+ if (r.isNamedRegister()) {
+ r.m_append = (modifiers == Qt::ShiftModifier);
} else {
- Q_ASSERT(!m_registers[reg].m_append);
+ Q_ASSERT(!r.m_append);
}
goto accept;
@@ -965,7 +911,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
bool textInserted = false;
if (g_config->getAutoIndent()) {
- textInserted = VEditUtils::indentBlockAsPreviousBlock(cursor);
+ textInserted = VEditUtils::indentBlockAsBlock(cursor, false);
if (g_config->getAutoList()) {
textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor)
|| textInserted;
@@ -1115,6 +1061,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
} else {
break;
}
+
+ V_FALLTHROUGH;
}
case Qt::Key_PageUp:
@@ -1381,6 +1329,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
} else {
break;
}
+
+ V_FALLTHROUGH;
}
case Qt::Key_Escape:
@@ -2219,7 +2169,7 @@ void VVim::resetState()
m_keys.clear();
m_tokens.clear();
m_pendingKeys.clear();
- setRegister(c_unnamedRegister);
+ setCurrentRegisterName(c_unnamedRegister);
m_resetPositionInBlock = true;
m_registerPending = false;
}
@@ -2723,7 +2673,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
for (int i = 0; i < p_repeat; ++i) {
int start, end;
// [start, end] is current WORD.
- findCurrentWORD(p_cursor, start, end);
+ VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to end of current WORD.
p_cursor.setPosition(end, p_moveMode);
@@ -2773,7 +2723,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
int start, end;
// [start, end] is current WORD.
- findCurrentWORD(p_cursor, start, end);
+ VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to the end of current WORD.
p_cursor.setPosition(end, p_moveMode);
@@ -2819,7 +2769,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
int start, end;
// [start, end] is current WORD.
- findCurrentWORD(p_cursor, start, end);
+ VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to the start of current WORD.
p_cursor.setPosition(start, p_moveMode);
@@ -2856,7 +2806,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
for (int i = 0; i < p_repeat; ++i) {
int start, end;
- findCurrentWORD(p_cursor, start, end);
+ VEditUtils::findCurrentWORD(p_cursor, start, end);
p_cursor.setPosition(start, p_moveMode);
@@ -3045,7 +2995,7 @@ handle_target:
// Different from Vim:
// We do not recognize a word as strict as Vim.
int start, end;
- findCurrentWord(p_cursor, start, end);
+ VEditUtils::findCurrentWord(p_cursor, start, end);
if (start == end) {
// Spaces, find next word.
QTextCursor cursor = p_cursor;
@@ -3056,7 +3006,7 @@ handle_target:
}
if (!doc->characterAt(cursor.position()).isSpace()) {
- findCurrentWord(cursor, start, end);
+ VEditUtils::findCurrentWord(cursor, start, end);
Q_ASSERT(start != end);
break;
}
@@ -3181,7 +3131,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
Q_ASSERT(p_repeat == -1);
bool spaces = false;
int start, end;
- findCurrentWord(p_cursor, start, end);
+ VEditUtils::findCurrentWord(p_cursor, start, end);
if (start == end) {
// Select the space between previous word and next word.
@@ -3221,7 +3171,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
findCurrentSpace(p_cursor, start, end);
if (start == end) {
- findCurrentWORD(p_cursor, start, end);
+ VEditUtils::findCurrentWORD(p_cursor, start, end);
} else {
// Select the space between previous WORD and next WORD.
spaces = true;
@@ -3240,7 +3190,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
moveCursorAcrossSpaces(p_cursor, moveMode, true);
// [start, end] is current WORD.
- findCurrentWORD(p_cursor, start, end);
+ VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to the end of current WORD.
p_cursor.setPosition(end, moveMode);
@@ -3481,6 +3431,8 @@ void VVim::processDeleteAction(QList &p_tokens)
// Fall through.
mayCrossBlock = true;
+ V_FALLTHROUGH;
+
case Range::WordAround:
// Fall through.
case Range::WordInner:
@@ -3712,6 +3664,8 @@ void VVim::processCopyAction(QList &p_tokens)
// Fall through.
mayCrossBlock = true;
+ V_FALLTHROUGH;
+
case Range::WordAround:
// Fall through.
case Range::WordInner:
@@ -3873,7 +3827,7 @@ void VVim::processPasteAction(QList &p_tokens, bool p_pasteBefore)
repeat = to.m_repeat;
}
- Register ® = m_registers[m_regName];
+ Register ® = getRegister(m_regName);
QString value = reg.read();
bool isBlock = reg.isBlock();
if (value.isEmpty()) {
@@ -4847,14 +4801,17 @@ void VVim::expandSelectionToWholeLines(QTextCursor &p_cursor)
void VVim::initRegisters()
{
- m_registers.clear();
- for (char ch = 'a'; ch <= 'z'; ++ch) {
- m_registers[QChar(ch)] = Register(QChar(ch));
+ if (!s_registers.isEmpty()) {
+ return;
}
- m_registers[c_unnamedRegister] = Register(c_unnamedRegister);
- m_registers[c_blackHoleRegister] = Register(c_blackHoleRegister);
- m_registers[c_selectionRegister] = Register(c_selectionRegister);
+ for (char ch = 'a'; ch <= 'z'; ++ch) {
+ s_registers[QChar(ch)] = Register(QChar(ch));
+ }
+
+ s_registers[c_unnamedRegister] = Register(c_unnamedRegister);
+ s_registers[c_blackHoleRegister] = Register(c_blackHoleRegister);
+ s_registers[c_selectionRegister] = Register(c_selectionRegister);
}
bool VVim::expectingRegisterName() const
@@ -5137,12 +5094,12 @@ void VVim::saveToRegister(const QString &p_text)
qDebug() << QString("save text(%1) to register(%2)").arg(text).arg(m_regName);
- Register ® = m_registers[m_regName];
+ Register ® = getRegister(m_regName);
reg.update(text);
if (!reg.isBlackHoleRegister() && !reg.isUnnamedRegister()) {
// Save it to unnamed register.
- m_registers[c_unnamedRegister].update(reg.m_value);
+ setRegister(c_unnamedRegister, reg.m_value);
}
}
@@ -5249,7 +5206,7 @@ void VVim::message(const QString &p_msg)
const QMap &VVim::getRegisters() const
{
- return m_registers;
+ return s_registers;
}
const VVim::Marks &VVim::getMarks() const
@@ -5272,7 +5229,7 @@ QString VVim::getPendingKeys() const
return str;
}
-void VVim::setRegister(QChar p_reg)
+void VVim::setCurrentRegisterName(QChar p_reg)
{
m_regName = p_reg;
}
@@ -5785,7 +5742,7 @@ QString VVim::readRegister(int p_key, int p_modifiers)
Key keyInfo(p_key, p_modifiers);
QChar reg = keyToRegisterName(keyInfo);
if (!reg.isNull()) {
- return m_registers[reg].read();
+ return getRegister(reg).read();
}
return "";
diff --git a/src/utils/vvim.h b/src/utils/vvim.h
index 69973e8f..7101d7c0 100644
--- a/src/utils/vvim.h
+++ b/src/utils/vvim.h
@@ -166,7 +166,7 @@ public:
void setMode(VimMode p_mode, bool p_clearSelection = true);
// Set current register.
- void setRegister(QChar p_reg);
+ void setCurrentRegisterName(QChar p_reg);
// Get m_registers.
const QMap &getRegisters() const;
@@ -797,6 +797,10 @@ private:
// Clear search highlight.
void clearSearchHighlight();
+ // Function utils for register.
+ Register &getRegister(QChar p_regName) const;
+ void setRegister(QChar p_regName, const QString &p_val);
+
VEdit *m_editor;
const VEditConfig *m_editConfig;
VimMode m_mode;
@@ -814,8 +818,6 @@ private:
// Whether reset the position in block when moving cursor.
bool m_resetPositionInBlock;
- QMap m_registers;
-
// Currently used register.
QChar m_regName;
@@ -843,6 +845,18 @@ private:
static const QChar c_unnamedRegister;
static const QChar c_blackHoleRegister;
static const QChar c_selectionRegister;
+
+ static QMap s_registers;
};
+inline VVim::Register &VVim::getRegister(QChar p_regName) const
+{
+ return s_registers[p_regName];
+}
+
+inline void VVim::setRegister(QChar p_regName, const QString &p_val)
+{
+ s_registers[p_regName].update(p_val);
+}
+
#endif // VVIM_H
diff --git a/src/vattachmentlist.cpp b/src/vattachmentlist.cpp
index 240fa899..8c44044b 100644
--- a/src/vattachmentlist.cpp
+++ b/src/vattachmentlist.cpp
@@ -5,13 +5,12 @@
#include "vconfigmanager.h"
#include "utils/vutils.h"
#include "vbuttonwithwidget.h"
-#include "vnote.h"
#include "vmainwindow.h"
#include "dialog/vconfirmdeletiondialog.h"
#include "dialog/vsortdialog.h"
extern VConfigManager *g_config;
-extern VNote *g_vnote;
+extern VMainWindow *g_mainWin;
VAttachmentList::VAttachmentList(QWidget *p_parent)
: QWidget(p_parent), m_file(NULL)
@@ -53,7 +52,7 @@ void VAttachmentList::setupUI()
.arg(m_file->fetchAttachmentFolderPath()),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok,
- g_vnote->getMainWindow(),
+ g_mainWin,
MessageBoxType::Danger);
if (ret == QMessageBox::Ok) {
if (!m_file->deleteAttachments()) {
@@ -66,7 +65,7 @@ void VAttachmentList::setupUI()
"maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
}
m_attachmentList->clear();
@@ -204,7 +203,7 @@ void VAttachmentList::addAttachment()
}
static QString lastPath = QDir::homePath();
- QStringList files = QFileDialog::getOpenFileNames(g_vnote->getMainWindow(),
+ QStringList files = QFileDialog::getOpenFileNames(g_mainWin,
tr("Select Files As Attachments"),
lastPath);
if (files.isEmpty()) {
@@ -236,16 +235,16 @@ void VAttachmentList::addAttachments(const QStringList &p_files)
"",
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
} else {
++addedFiles;
}
}
if (addedFiles > 0) {
- g_vnote->getMainWindow()->showStatusMessage(tr("%1 %2 added as attachments")
- .arg(addedFiles)
- .arg(addedFiles > 1 ? tr("files") : tr("file")));
+ g_mainWin->showStatusMessage(tr("%1 %2 added as attachments")
+ .arg(addedFiles)
+ .arg(addedFiles > 1 ? tr("files") : tr("file")));
}
}
@@ -330,7 +329,7 @@ void VAttachmentList::deleteSelectedItems()
false,
false,
false,
- g_vnote->getMainWindow());
+ g_mainWin);
if (dialog.exec()) {
items = dialog.getConfirmedItems();
@@ -349,7 +348,7 @@ void VAttachmentList::deleteSelectedItems()
"maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
}
updateButtonState();
@@ -370,7 +369,7 @@ void VAttachmentList::sortItems()
"in the configuration file.")
.arg(g_config->c_dataTextStyle)
.arg(m_file->getName()),
- g_vnote->getMainWindow());
+ g_mainWin);
QTreeWidget *tree = dialog.getTreeWidget();
tree->clear();
tree->setColumnCount(1);
@@ -624,7 +623,7 @@ void VAttachmentList::checkAttachments()
false,
false,
false,
- g_vnote->getMainWindow());
+ g_mainWin);
if (dialog.exec()) {
items = dialog.getConfirmedItems();
@@ -643,7 +642,7 @@ void VAttachmentList::checkAttachments()
"maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
}
updateButtonState();
diff --git a/src/vcaptain.cpp b/src/vcaptain.cpp
index 9fc49504..0bfc6d79 100644
--- a/src/vcaptain.cpp
+++ b/src/vcaptain.cpp
@@ -1,6 +1,5 @@
#include
#include
-#include
#include
#include
#include "vcaptain.h"
@@ -9,38 +8,39 @@
#include "vedittab.h"
#include "vfilelist.h"
#include "vnavigationmode.h"
+#include "vconfigmanager.h"
+
+extern VMainWindow *g_mainWin;
+
+extern VConfigManager *g_config;
// 3s pending time after the leader keys.
const int c_pendingTime = 3 * 1000;
-#if defined(QT_NO_DEBUG)
-extern QFile g_logFile;
-#endif
-
-VCaptain::VCaptain(VMainWindow *p_parent)
- : QWidget(p_parent), m_mainWindow(p_parent), m_mode(VCaptain::Normal),
- m_widgetBeforeCaptain(NULL), m_nextMajorKey('a'), m_ignoreFocusChange(false)
+VCaptain::VCaptain(QWidget *p_parent)
+ : QWidget(p_parent),
+ m_mode(CaptainMode::Normal),
+ m_widgetBeforeNavigation(NULL),
+ m_nextMajorKey('a'),
+ m_ignoreFocusChange(false),
+ m_leaderKey(g_config->getShortcutKeySequence("CaptainMode"))
{
- m_pendingTimer = new QTimer(this);
- m_pendingTimer->setSingleShot(true);
- m_pendingTimer->setInterval(c_pendingTime);
- connect(m_pendingTimer, &QTimer::timeout,
- this, &VCaptain::pendingTimerTimeout);
+ Q_ASSERT(!m_leaderKey.isEmpty());
connect(qApp, &QApplication::focusChanged,
this, &VCaptain::handleFocusChanged);
- QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+E"), this,
- Q_NULLPTR, Q_NULLPTR);
- connect(shortcut, &QShortcut::activated,
- this, &VCaptain::trigger);
-
- qApp->installEventFilter(this);
-
setWindowFlags(Qt::FramelessWindowHint);
+
// Make it as small as possible. This widget will stay at the top-left corner
// of VMainWindow.
resize(1, 1);
+
+ // Register Navigation mode as Captain mode target.
+ registerCaptainTarget(tr("NavigationMode"),
+ g_config->getCaptainShortcutKeySequence("NavigationMode"),
+ this,
+ navigationModeByCaptain);
}
QChar VCaptain::getNextMajorKey()
@@ -59,53 +59,26 @@ void VCaptain::registerNavigationTarget(VNavigationMode *p_target)
QChar key = getNextMajorKey();
if (!key.isNull()) {
p_target->registerNavigation(key);
- m_targets.append(NaviModeTarget(p_target, true));
+ m_naviTargets.push_back(NaviModeTarget(p_target, true));
}
}
-// In pending mode, if user click other widgets, we need to exit Captain mode.
-void VCaptain::handleFocusChanged(QWidget *p_old, QWidget * /* p_now */)
+void VCaptain::handleFocusChanged(QWidget *p_old, QWidget * p_now)
{
- if (!m_ignoreFocusChange && p_old == this) {
- exitCaptainMode();
+ Q_UNUSED(p_now);
+
+ if (!m_ignoreFocusChange
+ && !checkMode(CaptainMode::Normal)
+ && p_old == this) {
+ exitNavigationMode();
}
}
-void VCaptain::pendingTimerTimeout()
-{
- qDebug() << "Captain mode timeout";
- exitCaptainMode();
- restoreFocus();
-}
-
-void VCaptain::trigger()
-{
- if (m_mode != VCaptain::Normal) {
- return;
- }
- qDebug() << "trigger Captain mode";
- // Focus to listen pending key press.
- m_widgetBeforeCaptain = QApplication::focusWidget();
- setFocus();
- m_mode = VCaptain::Pending;
- m_pendingTimer->stop();
- m_pendingTimer->start();
-
- emit captainModeChanged(true);
-}
-
void VCaptain::keyPressEvent(QKeyEvent *p_event)
{
int key = p_event->key();
Qt::KeyboardModifiers modifiers = p_event->modifiers();
- if (m_mode == VCaptain::Normal) {
- // Should not in focus while in Normal mode.
- QWidget::keyPressEvent(p_event);
- m_mainWindow->focusNextChild();
- return;
- }
-
if (key == Qt::Key_Control || key == Qt::Key_Shift) {
QWidget::keyPressEvent(p_event);
return;
@@ -120,230 +93,28 @@ void VCaptain::keyPressEvent(QKeyEvent *p_event)
bool VCaptain::handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers)
{
- bool ret = true;
+ if (!checkMode(CaptainMode::Navigation)) {
+ return false;
+ }
if (p_key == Qt::Key_Escape
|| (p_key == Qt::Key_BracketLeft
&& p_modifiers == Qt::ControlModifier)) {
- goto exit;
+ exitNavigationMode();
+ return true;
}
- m_ignoreFocusChange = true;
-
- if (m_mode == VCaptainMode::Navigation) {
- ret = handleKeyPressNavigationMode(p_key, p_modifiers);
- m_ignoreFocusChange = false;
- return ret;
- }
-
- // In Captain mode, Ctrl key won't make a difference.
- switch (p_key) {
- case Qt::Key_1:
- case Qt::Key_2:
- case Qt::Key_3:
- case Qt::Key_4:
- case Qt::Key_5:
- case Qt::Key_6:
- case Qt::Key_7:
- case Qt::Key_8:
- case Qt::Key_9:
- {
- // Switch to tab .
- VEditWindow *win = m_mainWindow->editArea->getCurrentWindow();
- if (win) {
- int sequence = p_key - Qt::Key_0;
- if (win->activateTab(sequence)) {
- m_widgetBeforeCaptain = NULL;
- }
- }
- break;
- }
-
- case Qt::Key_0:
- {
- // Alternate the tab.
- VEditWindow *win = m_mainWindow->editArea->getCurrentWindow();
- if (win) {
- if (win->alternateTab()) {
- m_widgetBeforeCaptain = NULL;
- }
- }
- break;
- }
-
- case Qt::Key_A:
- {
- // Show attachment list of current note.
- m_mainWindow->showAttachmentList();
- break;
- }
-
- case Qt::Key_D:
- // Locate current tab.
- if (m_mainWindow->locateCurrentFile()) {
- m_widgetBeforeCaptain = NULL;
- }
-
- break;
-
- case Qt::Key_E:
- // Toggle expand view.
- m_mainWindow->expandViewAct->trigger();
- break;
-
- case Qt::Key_F:
- {
- // Show current window's opened file list.
- VEditWindow *win = m_mainWindow->editArea->getCurrentWindow();
- if (win) {
- if (win->showOpenedFileList()) {
- // showOpenedFileList() already focus the right widget.
- m_widgetBeforeCaptain = NULL;
- }
- }
- break;
- }
-
- case Qt::Key_H:
- {
- if (p_modifiers & Qt::ShiftModifier) {
- // Move current tab one split left.
- m_mainWindow->editArea->moveCurrentTabOneSplit(false);
- } else {
- // Focus previous window split.
- int idx = m_mainWindow->editArea->focusNextWindow(-1);
- if (idx > -1) {
- m_widgetBeforeCaptain = NULL;
- }
- }
- break;
- }
-
- case Qt::Key_J:
- {
- // Focus next tab.
- VEditWindow *win = m_mainWindow->editArea->getCurrentWindow();
- if (win) {
- win->focusNextTab(true);
- // focusNextTab() will focus the right widget.
- m_widgetBeforeCaptain = NULL;
- }
- break;
- }
-
- case Qt::Key_K:
- {
- // Focus previous tab.
- VEditWindow *win = m_mainWindow->editArea->getCurrentWindow();
- if (win) {
- win->focusNextTab(false);
- // focusNextTab() will focus the right widget.
- m_widgetBeforeCaptain = NULL;
- }
- break;
- }
-
- case Qt::Key_L:
- {
- if (p_modifiers & Qt::ShiftModifier) {
- // Move current tab one split right.
- m_mainWindow->editArea->moveCurrentTabOneSplit(true);
- } else {
- // Focus next window split.
- int idx = m_mainWindow->editArea->focusNextWindow(1);
- if (idx > -1) {
- m_widgetBeforeCaptain = NULL;
- }
- }
- break;
- }
-
- case Qt::Key_P:
- // Toggle one/two panel view.
- m_mainWindow->toggleOnePanelView();
- break;
-
- case Qt::Key_Q:
- // Discard changes and exit edit mode.
- if (m_mainWindow->m_curFile) {
- m_mainWindow->discardExitAct->trigger();
- }
- break;
-
- case Qt::Key_R:
- {
- // Remove current window split.
- m_mainWindow->editArea->removeCurrentWindow();
-
- QWidget *nextFocus = m_mainWindow->editArea->currentEditTab();
- m_widgetBeforeCaptain = nextFocus ? nextFocus : m_mainWindow->fileList;
- break;
- }
-
- case Qt::Key_T:
- // Toggle the Tools dock.
- m_mainWindow->toolDock->setVisible(!m_mainWindow->toolDock->isVisible());
- break;
-
- case Qt::Key_V:
- // Vertical split current window.
- m_mainWindow->editArea->splitCurrentWindow();
- // Do not restore focus.
- m_widgetBeforeCaptain = NULL;
- break;
-
- case Qt::Key_W:
- // Enter navigation mode.
- triggerNavigationMode();
- m_ignoreFocusChange = false;
- return ret;
-
- case Qt::Key_X:
- {
- // Close current tab.
- m_mainWindow->closeCurrentFile();
-
- // m_widgetBeforeCaptain may be the closed tab which will cause crash.
- QWidget *nextFocus = m_mainWindow->editArea->currentEditTab();
- m_widgetBeforeCaptain = nextFocus ? nextFocus : m_mainWindow->fileList;
- break;
- }
-
- case Qt::Key_Question:
- {
- // Display shortcuts doc.
- m_mainWindow->shortcutHelp();
- m_widgetBeforeCaptain = NULL;
- break;
- }
-
-#if defined(QT_NO_DEBUG)
- case Qt::Key_Comma:
- {
- // Flush g_logFile.
- g_logFile.flush();
- break;
- }
-#endif
-
- default:
- // Not implemented yet. Just exit Captain mode.
- break;
- }
-
-exit:
- exitCaptainMode();
- restoreFocus();
- return ret;
+ return handleKeyPressNavigationMode(p_key, p_modifiers);
}
bool VCaptain::handleKeyPressNavigationMode(int p_key,
Qt::KeyboardModifiers /* p_modifiers */)
{
- Q_ASSERT(m_mode == VCaptainMode::Navigation);
+ Q_ASSERT(m_mode == CaptainMode::Navigation);
bool hasConsumed = false;
bool pending = false;
- for (auto &target : m_targets) {
+ m_ignoreFocusChange = true;
+ for (auto &target : m_naviTargets) {
if (hasConsumed) {
target.m_available = false;
target.m_target->hideNavigation();
@@ -351,12 +122,13 @@ bool VCaptain::handleKeyPressNavigationMode(int p_key,
}
if (target.m_available) {
bool succeed = false;
+ // May change focus, so we need to ignore focus change here.
bool consumed = target.m_target->handleKeyNavigation(p_key, succeed);
if (consumed) {
hasConsumed = true;
if (succeed) {
// Exit.
- m_widgetBeforeCaptain = NULL;
+ m_widgetBeforeNavigation = NULL;
} else {
// Consumed but not succeed. Need more keys.
pending = true;
@@ -368,20 +140,23 @@ bool VCaptain::handleKeyPressNavigationMode(int p_key,
}
}
}
+
+ m_ignoreFocusChange = false;
if (pending) {
return true;
}
- exitCaptainMode();
- restoreFocus();
+
+ exitNavigationMode();
return true;
}
void VCaptain::triggerNavigationMode()
{
- m_pendingTimer->stop();
- m_mode = VCaptainMode::Navigation;
-
- for (auto &target : m_targets) {
+ setMode(CaptainMode::Navigation);
+ m_widgetBeforeNavigation = QApplication::focusWidget();
+ // Focus to listen pending key press.
+ setFocus();
+ for (auto &target : m_naviTargets) {
target.m_available = true;
target.m_target->showNavigation();
}
@@ -389,51 +164,74 @@ void VCaptain::triggerNavigationMode()
void VCaptain::exitNavigationMode()
{
- m_mode = VCaptainMode::Normal;
+ setMode(CaptainMode::Normal);
- for (auto &target : m_targets) {
+ for (auto &target : m_naviTargets) {
target.m_available = true;
target.m_target->hideNavigation();
}
-}
-bool VCaptain::eventFilter(QObject *p_obj, QEvent *p_event)
-{
- if (m_mode != VCaptain::Normal && p_event->type() == QEvent::Shortcut) {
- qDebug() << "filter" << p_event;
- QShortcutEvent *keyEve = dynamic_cast(p_event);
- Q_ASSERT(keyEve);
- const QKeySequence &keys = keyEve->key();
- if (keys.count() == 1) {
- int key = keys[0];
- Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(key & (~0x01FFFFFFU));
- key &= 0x01FFFFFFUL;
- if (handleKeyPress(key, modifiers)) {
- return true;
- }
- }
- exitCaptainMode();
- restoreFocus();
- }
- return QWidget::eventFilter(p_obj, p_event);
+ restoreFocus();
}
void VCaptain::restoreFocus()
{
- if (m_widgetBeforeCaptain) {
- m_widgetBeforeCaptain->setFocus();
+ if (m_widgetBeforeNavigation) {
+ m_widgetBeforeNavigation->setFocus();
}
}
-void VCaptain::exitCaptainMode()
+bool VCaptain::registerCaptainTarget(const QString &p_name,
+ const QString &p_key,
+ void *p_target,
+ CaptainFunc p_func)
{
- if (m_mode == VCaptainMode::Navigation) {
- exitNavigationMode();
+ if (p_key.isEmpty()) {
+ return false;
}
- m_mode = VCaptain::Normal;
- m_pendingTimer->stop();
- m_ignoreFocusChange = false;
- emit captainModeChanged(false);
+ QString lowerKey = p_key.toLower();
+
+ if (m_captainTargets.contains(lowerKey)) {
+ return false;
+ }
+
+ // Register shortcuts.
+ QString sequence = QString("%1,%2").arg(m_leaderKey).arg(p_key);
+ QShortcut *shortcut = new QShortcut(QKeySequence(sequence),
+ this);
+ shortcut->setContext(Qt::ApplicationShortcut);
+
+ connect(shortcut, &QShortcut::activated,
+ this, std::bind(&VCaptain::triggerCaptainTarget, this, p_key));
+
+
+ CaptainModeTarget target(p_name,
+ p_key,
+ p_target,
+ p_func,
+ shortcut);
+ m_captainTargets.insert(lowerKey, target);
+
+ qDebug() << "registered:" << target.toString() << sequence;
+
+ return true;
}
+void VCaptain::triggerCaptainTarget(const QString &p_key)
+{
+ auto it = m_captainTargets.find(p_key.toLower());
+ Q_ASSERT(it != m_captainTargets.end());
+ const CaptainModeTarget &target = it.value();
+
+ qDebug() << "triggered:" << target.toString();
+
+ target.m_function(target.m_target, nullptr);
+}
+
+void VCaptain::navigationModeByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VCaptain *obj = static_cast(p_target);
+ obj->triggerNavigationMode();
+}
diff --git a/src/vcaptain.h b/src/vcaptain.h
index dabb69f8..98bc19d8 100644
--- a/src/vcaptain.h
+++ b/src/vcaptain.h
@@ -1,77 +1,166 @@
#ifndef VCAPTAIN_H
#define VCAPTAIN_H
-#include
-#include
+#include
+
+#include
+#include
+#include
-class QTimer;
class QKeyEvent;
-class VMainWindow;
-class QEvent;
class VNavigationMode;
+class QShortcut;
+
+// void func(void *p_target, void *p_data);
+typedef std::function CaptainFunc;
class VCaptain : public QWidget
{
Q_OBJECT
public:
- explicit VCaptain(VMainWindow *p_parent);
+ explicit VCaptain(QWidget *p_parent);
- // Trigger Captain mode.
- void trigger();
-
- // Register a target for Navigation Mode.
+ // Register a target for Navigation mode.
void registerNavigationTarget(VNavigationMode *p_target);
-signals:
- void captainModeChanged(bool p_enabled);
+ // Register a target for Captain mode.
+ bool registerCaptainTarget(const QString &p_name,
+ const QString &p_key,
+ void *p_target,
+ CaptainFunc p_func);
protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
- bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
-
-public slots:
private slots:
- void pendingTimerTimeout();
+ // Exit Navigation mode if focus lost.
void handleFocusChanged(QWidget *p_old, QWidget *p_new);
private:
- // Restore the focus to m_widgetBeforeCaptain.
- void restoreFocus();
- void exitCaptainMode();
- // Return true if finish handling the event; otherwise, let the base widget
- // to handle it.
- bool handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers);
- bool handleKeyPressNavigationMode(int p_key,
- Qt::KeyboardModifiers p_modifiers);
- QChar getNextMajorKey();
- void triggerNavigationMode();
- void exitNavigationMode();
+ // A widget target for Navigation mode.
+ struct NaviModeTarget {
+ NaviModeTarget()
+ : m_target(nullptr), m_available(false)
+ {
+ }
- enum VCaptainMode {
+ NaviModeTarget(VNavigationMode *p_target, bool p_available)
+ : m_target(p_target), m_available(p_available)
+ {
+ }
+
+ VNavigationMode *m_target;
+ bool m_available;
+ };
+
+ // Modes.
+ enum class CaptainMode {
Normal = 0,
Pending,
Navigation
};
- VMainWindow *m_mainWindow;
- QTimer *m_pendingTimer;
- int m_mode;
- // The widget which has the focus before entering Captain mode.
- QWidget* m_widgetBeforeCaptain;
+ struct CaptainModeTarget {
+ CaptainModeTarget()
+ : m_target(nullptr), m_function(nullptr), m_shortcut(nullptr)
+ {
+ }
- struct NaviModeTarget {
- VNavigationMode *m_target;
- bool m_available;
+ CaptainModeTarget(const QString &p_name,
+ const QString &p_key,
+ void *p_target,
+ CaptainFunc p_func,
+ QShortcut *p_shortcut)
+ : m_name(p_name),
+ m_key(p_key),
+ m_target(p_target),
+ m_function(p_func),
+ m_shortcut(p_shortcut)
+ {
+ }
- NaviModeTarget(VNavigationMode *p_target, bool p_available)
- : m_target(p_target), m_available(p_available) {}
+ QString toString() const
+ {
+ return QString("Captain mode target %1 key[%2]").arg(m_name).arg(m_key);
+ }
+
+ // Name to display.
+ QString m_name;
+
+ // Key sequence to trigger this target.
+ // This is the sub-sequence after leader key.
+ QString m_key;
+
+ // Target.
+ void *m_target;
+
+ // Function to call when this target is trigger.
+ CaptainFunc m_function;
+
+ // Shortcut for this target.
+ QShortcut *m_shortcut;
};
- QList m_targets;
+
+ // Restore the focus to m_widgetBeforeNavigation.
+ void restoreFocus();
+
+ // Return true if finish handling the event; otherwise, let the base widget
+ // to handle it.
+ bool handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers);
+
+ // Handle key press event in Navigation mode.
+ bool handleKeyPressNavigationMode(int p_key,
+ Qt::KeyboardModifiers p_modifiers);
+
+ // Get next major key to use for Navigation mode.
+ QChar getNextMajorKey();
+
+ // Trigger navigation mode to ask all targets show themselves.
+ void triggerNavigationMode();
+
+ // Exit navigation mode to ask all targets hide themselves.
+ void exitNavigationMode();
+
+ // Called to trigger the action of a Captain target which has
+ // registered @p_key.
+ void triggerCaptainTarget(const QString &p_key);
+
+ void setMode(CaptainMode p_mode);
+
+ bool checkMode(CaptainMode p_mode) const;
+
+ static void navigationModeByCaptain(void *p_target, void *p_data);
+
+ // Used to indicate whether we are in Navigation mode.
+ CaptainMode m_mode;
+
+ // The widget which has the focus before entering Navigation mode.
+ QWidget* m_widgetBeforeNavigation;
+
+ // Targets for Navigation mode.
+ QVector m_naviTargets;
+
QChar m_nextMajorKey;
- // Ignore focus change to avoid exiting Captain mode while handling key
- // press.
+
+ // Targets for Captain mode.
+ // Key(lower) -> CaptainModeTarget.
+ QHash m_captainTargets;
+
+ // Ignore focus change during handling Navigation target actions.
bool m_ignoreFocusChange;
+
+ // Leader key sequence for Captain mode.
+ QString m_leaderKey;
};
+inline void VCaptain::setMode(CaptainMode p_mode)
+{
+ m_mode = p_mode;
+}
+
+inline bool VCaptain::checkMode(CaptainMode p_mode) const
+{
+ return m_mode == p_mode;
+}
+
#endif // VCAPTAIN_H
diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp
index 7a9de545..23c257ad 100644
--- a/src/vconfigmanager.cpp
+++ b/src/vconfigmanager.cpp
@@ -8,52 +8,60 @@
#include
#include
#include
+#include
#include "utils/vutils.h"
#include "vstyleparser.h"
const QString VConfigManager::orgName = QString("vnote");
+
const QString VConfigManager::appName = QString("vnote");
+
const QString VConfigManager::c_version = QString("1.9");
+
const QString VConfigManager::c_obsoleteDirConfigFile = QString(".vnote.json");
+
const QString VConfigManager::c_dirConfigFile = QString("_vnote.json");
-const QString VConfigManager::defaultConfigFilePath = QString(":/resources/vnote.ini");
+
+const QString VConfigManager::c_defaultConfigFilePath = QString(":/resources/vnote.ini");
+
+const QString VConfigManager::c_defaultConfigFile = QString("vnote.ini");
+
+const QString VConfigManager::c_sessionConfigFile = QString("session.ini");
+
const QString VConfigManager::c_styleConfigFolder = QString("styles");
+
const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles");
+
const QString VConfigManager::c_defaultCssFile = QString(":/resources/styles/default.css");
+
const QString VConfigManager::c_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.css");
+
const QString VConfigManager::c_defaultMdhlFile = QString(":/resources/styles/default.mdhl");
+
const QString VConfigManager::c_solarizedDarkMdhlFile = QString(":/resources/styles/solarized-dark.mdhl");
+
const QString VConfigManager::c_solarizedLightMdhlFile = QString(":/resources/styles/solarized-light.mdhl");
+
const QString VConfigManager::c_warningTextStyle = QString("color: red; font: bold");
+
const QString VConfigManager::c_dataTextStyle = QString("font: bold");
+
const QString VConfigManager::c_dangerBtnStyle = QString("QPushButton {color: #fff; border-color: #d43f3a; background-color: #d9534f;}"
"QPushButton::hover {color: #fff; border-color: #ac2925; background-color: #c9302c;}");
+
const QString VConfigManager::c_vnoteNotebookFolderName = QString("vnote_notebooks");
VConfigManager::VConfigManager(QObject *p_parent)
- : QObject(p_parent), userSettings(NULL), defaultSettings(NULL)
+ : QObject(p_parent),
+ userSettings(NULL),
+ defaultSettings(NULL),
+ m_sessionSettings(NULL)
{
}
-void VConfigManager::migrateIniFile()
-{
- const QString originalFolder = "tamlok";
- const QString newFolder = orgName;
- QString configFolder = getConfigFolder();
- QDir dir(configFolder);
- dir.cdUp();
- dir.rename(originalFolder, newFolder);
- userSettings->sync();
-}
-
void VConfigManager::initialize()
{
- Q_ASSERT(!userSettings && !defaultSettings);
- userSettings = new QSettings(QSettings::IniFormat, QSettings::UserScope,
- orgName, appName, this);
- defaultSettings = new QSettings(defaultConfigFilePath, QSettings::IniFormat, this);
-
- migrateIniFile();
+ initSettings();
// Override the default css styles on start up.
outputDefaultCssStyle();
@@ -68,7 +76,6 @@ void VConfigManager::initialize()
m_templateCss = getConfigFromSettings("global", "template_css").toString();
m_templateCodeBlockCss = getConfigFromSettings("global", "template_code_block_css").toString();
m_templateCodeBlockCssUrl = getConfigFromSettings("global", "template_code_block_css_url").toString();
- curNotebookIndex = getConfigFromSettings("global", "current_notebook").toInt();
markdownExtensions = hoedown_extensions(HOEDOWN_EXT_TABLES | HOEDOWN_EXT_FENCED_CODE |
HOEDOWN_EXT_HIGHLIGHT | HOEDOWN_EXT_AUTOLINK |
@@ -91,10 +98,7 @@ void VConfigManager::initialize()
curRenderBackgroundColor = getConfigFromSettings("global",
"current_render_background_color").toString();
- m_toolsDockChecked = getConfigFromSettings("session", "tools_dock_checked").toBool();
- m_mainWindowGeometry = getConfigFromSettings("session", "main_window_geometry").toByteArray();
- m_mainWindowState = getConfigFromSettings("session", "main_window_state").toByteArray();
- m_mainSplitterState = getConfigFromSettings("session", "main_splitter_state").toByteArray();
+ m_toolsDockChecked = getConfigFromSettings("global", "tools_dock_checked").toBool();
m_findCaseSensitive = getConfigFromSettings("global",
"find_case_sensitive").toBool();
@@ -168,6 +172,8 @@ void VConfigManager::initialize()
readShortcutsFromSettings();
+ readCaptainShortcutsFromSettings();
+
initDocSuffixes();
m_markdownHighlightInterval = getConfigFromSettings("global",
@@ -187,8 +193,14 @@ void VConfigManager::initialize()
m_noteOpenMode = OpenFileMode::Read;
}
- m_enableHeadingSequence = getConfigFromSettings("global",
- "enable_heading_sequence").toBool();
+ int tmpHeadingSequenceType = getConfigFromSettings("global",
+ "heading_sequence_type").toInt();
+ if (tmpHeadingSequenceType < (int)HeadingSequenceType::Invalid
+ && tmpHeadingSequenceType >= (int)HeadingSequenceType::Disabled) {
+ m_headingSequenceType = (HeadingSequenceType)tmpHeadingSequenceType;
+ } else {
+ m_headingSequenceType = HeadingSequenceType::Disabled;
+ }
m_headingSequenceBaseLevel = getConfigFromSettings("global",
"heading_sequence_base_level").toInt();
@@ -227,6 +239,76 @@ void VConfigManager::initialize()
m_doubleClickCloseTab = getConfigFromSettings("global",
"double_click_close_tab").toBool();
+
+ m_enableCompactMode = getConfigFromSettings("global",
+ "enable_compact_mode").toBool();
+
+ int tmpStartupPageMode = getConfigFromSettings("global",
+ "startup_page_type").toInt();
+ if (tmpStartupPageMode < (int)StartupPageType::Invalid
+ && tmpStartupPageMode >= (int)StartupPageType::None) {
+ m_startupPageType = (StartupPageType)tmpStartupPageMode;
+ } else {
+ m_startupPageType = StartupPageType::None;
+ }
+
+ m_startupPages = getConfigFromSettings("global",
+ "startup_pages").toStringList();
+
+ initFromSessionSettings();
+}
+
+void VConfigManager::initSettings()
+{
+ Q_ASSERT(!userSettings && !defaultSettings && !m_sessionSettings);
+
+ const char *codecForIni = "UTF-8";
+
+ // vnote.ini.
+ // First try to read vnote.ini from the directory of the executable.
+ QString userIniPath = QDir(QCoreApplication::applicationDirPath()).filePath(c_defaultConfigFile);
+ if (QFileInfo::exists(userIniPath)) {
+ userSettings = new QSettings(userIniPath,
+ QSettings::IniFormat,
+ this);
+ } else {
+ userSettings = new QSettings(QSettings::IniFormat,
+ QSettings::UserScope,
+ orgName,
+ appName,
+ this);
+ }
+
+ userSettings->setIniCodec(codecForIni);
+
+ qDebug() << "use user config" << userSettings->fileName();
+
+ // Default vnote.ini from resource file.
+ defaultSettings = new QSettings(c_defaultConfigFilePath, QSettings::IniFormat, this);
+ defaultSettings->setIniCodec(codecForIni);
+
+ // session.ini.
+ m_sessionSettings = new QSettings(QDir(getConfigFolder()).filePath(c_sessionConfigFile),
+ QSettings::IniFormat,
+ this);
+ m_sessionSettings->setIniCodec(codecForIni);
+}
+
+void VConfigManager::initFromSessionSettings()
+{
+ curNotebookIndex = getConfigFromSessionSettings("global", "current_notebook").toInt();
+
+ m_mainWindowGeometry = getConfigFromSessionSettings("geometry",
+ "main_window_geometry").toByteArray();
+
+ m_mainWindowState = getConfigFromSessionSettings("geometry",
+ "main_window_state").toByteArray();
+
+ m_mainSplitterState = getConfigFromSessionSettings("geometry",
+ "main_splitter_state").toByteArray();
+
+ m_naviSplitterState = getConfigFromSessionSettings("geometry",
+ "navi_splitter_state").toByteArray();
}
void VConfigManager::readPredefinedColorsFromSettings()
@@ -245,49 +327,70 @@ void VConfigManager::readPredefinedColorsFromSettings()
<< "pre-defined colors from [predefined_colors] section";
}
-void VConfigManager::readNotebookFromSettings(QVector &p_notebooks, QObject *parent)
+void VConfigManager::readNotebookFromSettings(QSettings *p_settings,
+ QVector &p_notebooks,
+ QObject *parent)
{
Q_ASSERT(p_notebooks.isEmpty());
- int size = userSettings->beginReadArray("notebooks");
+ int size = p_settings->beginReadArray("notebooks");
for (int i = 0; i < size; ++i) {
- userSettings->setArrayIndex(i);
- QString name = userSettings->value("name").toString();
- QString path = userSettings->value("path").toString();
+ p_settings->setArrayIndex(i);
+ QString name = p_settings->value("name").toString();
+ QString path = p_settings->value("path").toString();
VNotebook *notebook = new VNotebook(name, path, parent);
notebook->readConfigNotebook();
p_notebooks.append(notebook);
}
- userSettings->endArray();
+
+ p_settings->endArray();
qDebug() << "read" << p_notebooks.size()
<< "notebook items from [notebooks] section";
}
-void VConfigManager::writeNotebookToSettings(const QVector &p_notebooks)
+void VConfigManager::writeNotebookToSettings(QSettings *p_settings,
+ const QVector &p_notebooks)
{
// Clear it first
- userSettings->beginGroup("notebooks");
- userSettings->remove("");
- userSettings->endGroup();
+ p_settings->beginGroup("notebooks");
+ p_settings->remove("");
+ p_settings->endGroup();
- userSettings->beginWriteArray("notebooks");
+ p_settings->beginWriteArray("notebooks");
for (int i = 0; i < p_notebooks.size(); ++i) {
- userSettings->setArrayIndex(i);
+ p_settings->setArrayIndex(i);
const VNotebook ¬ebook = *p_notebooks[i];
- userSettings->setValue("name", notebook.getName());
- userSettings->setValue("path", notebook.getPath());
+ p_settings->setValue("name", notebook.getName());
+ p_settings->setValue("path", notebook.getPath());
}
- userSettings->endArray();
+
+ p_settings->endArray();
qDebug() << "write" << p_notebooks.size()
<< "notebook items in [notebooks] section";
}
+static QVariant getConfigFromSettingsBySectionKey(const QSettings *p_settings,
+ const QString &p_section,
+ const QString &p_key)
+{
+ QString fullKey = p_section + "/" + p_key;
+ return p_settings->value(fullKey);
+}
+
+static void setConfigToSettingsBySectionKey(QSettings *p_settings,
+ const QString &p_section,
+ const QString &p_key,
+ const QVariant &p_value)
+{
+ QString fullKey = p_section + "/" + p_key;
+ return p_settings->setValue(fullKey, p_value);
+}
+
QVariant VConfigManager::getConfigFromSettings(const QString §ion, const QString &key) const
{
- QString fullKey = section + "/" + key;
// First, look up the user-scoped config file
- QVariant value = userSettings->value(fullKey);
+ QVariant value = getConfigFromSettingsBySectionKey(userSettings, section, key);
if (!value.isNull()) {
- qDebug() << "user config:" << fullKey << value.toString();
+ qDebug() << "user config:" << (section + "/" + key) << value;
return value;
}
@@ -298,18 +401,14 @@ QVariant VConfigManager::getConfigFromSettings(const QString §ion, const QSt
void VConfigManager::setConfigToSettings(const QString §ion, const QString &key, const QVariant &value)
{
// Set the user-scoped config file
- QString fullKey = section + "/" + key;
- userSettings->setValue(fullKey, value);
- qDebug() << "set user config:" << fullKey << value.toString();
+ setConfigToSettingsBySectionKey(userSettings, section, key, value);
+ qDebug() << "set user config:" << (section + "/" + key) << value;
}
QVariant VConfigManager::getDefaultConfig(const QString &p_section, const QString &p_key) const
{
- QString fullKey = p_section + "/" + p_key;
-
- QVariant value = defaultSettings->value(fullKey);
- qDebug() << "default config:" << fullKey << value.toString();
-
+ QVariant value = getConfigFromSettingsBySectionKey(defaultSettings, p_section, p_key);
+ qDebug() << "default config:" << (p_section + "/" + p_key) << value;
return value;
}
@@ -321,6 +420,24 @@ QVariant VConfigManager::resetDefaultConfig(const QString &p_section, const QStr
return defaultValue;
}
+QVariant VConfigManager::getConfigFromSessionSettings(const QString &p_section,
+ const QString &p_key) const
+{
+ return getConfigFromSettingsBySectionKey(m_sessionSettings,
+ p_section,
+ p_key);
+}
+
+void VConfigManager::setConfigToSessionSettings(const QString &p_section,
+ const QString &p_key,
+ const QVariant &p_value)
+{
+ setConfigToSettingsBySectionKey(m_sessionSettings,
+ p_section,
+ p_key,
+ p_value);
+}
+
QString VConfigManager::fetchDirConfigFilePath(const QString &p_path)
{
QDir dir(p_path);
@@ -887,76 +1004,120 @@ QString VConfigManager::getVnoteNotebookFolderPath()
return QDir::home().filePath(c_vnoteNotebookFolderName);
}
+QHash VConfigManager::readShortcutsFromSettings(QSettings *p_settings,
+ const QString &p_group)
+{
+ QHash ret;
+ p_settings->beginGroup(p_group);
+ QStringList keys = p_settings->childKeys();
+ for (auto const & key : keys) {
+ if (key.isEmpty()) {
+ continue;
+ }
+
+ QVariant varVal = p_settings->value(key);
+ QString sequence = varVal.toString();
+ if (varVal.type() == QVariant::StringList) {
+ sequence = varVal.toStringList().join(",");
+ }
+
+ sequence = sequence.trimmed();
+ if (isValidKeySequence(sequence)) {
+ ret.insert(key, sequence);
+ }
+ }
+
+ p_settings->endGroup();
+
+ return ret;
+}
+
bool VConfigManager::isValidKeySequence(const QString &p_seq)
{
- QString lower = p_seq.toLower();
- return lower != "ctrl+q" && lower != "ctrl+e";
+ return p_seq.toLower() != "ctrl+q"
+ && !QKeySequence(p_seq).isEmpty();
}
void VConfigManager::readShortcutsFromSettings()
{
+ const QString group("shortcuts");
+
m_shortcuts.clear();
- int size = defaultSettings->beginReadArray("shortcuts");
- for (int i = 0; i < size; ++i) {
- defaultSettings->setArrayIndex(i);
- QString op = defaultSettings->value("operation").toString();
- QString seq = defaultSettings->value("keysequence").toString().trimmed();
+ m_shortcuts = readShortcutsFromSettings(defaultSettings, group);
- if (isValidKeySequence(seq)) {
- qDebug() << "read shortcut config" << op << seq;
- m_shortcuts[op] = seq;
- }
- }
-
- defaultSettings->endArray();
-
- // Whether we need to update user settings.
- bool needUpdate = false;
- size = userSettings->beginReadArray("shortcuts");
+ // Update default settings according to user settings.
+ QHash userShortcuts = readShortcutsFromSettings(userSettings,
+ group);
QSet matched;
matched.reserve(m_shortcuts.size());
- for (int i = 0; i < size; ++i) {
- userSettings->setArrayIndex(i);
- QString op = userSettings->value("operation").toString();
- QString seq = userSettings->value("keysequence").toString().trimmed();
-
- if (isValidKeySequence(seq)) {
- qDebug() << "read user shortcut config" << op << seq;
- auto it = m_shortcuts.find(op);
- if (it == m_shortcuts.end()) {
- // Could not find this in default settings.
- needUpdate = true;
+ for (auto it = userShortcuts.begin(); it != userShortcuts.end(); ++it) {
+ auto defaultIt = m_shortcuts.find(it.key());
+ if (defaultIt != m_shortcuts.end()) {
+ QString sequence = it.value().trimmed();
+ if (sequence != defaultIt.value()) {
+ if (isValidKeySequence(sequence)) {
+ matched.insert(it.key());
+ *defaultIt = sequence;
+ }
} else {
- matched.insert(op);
- *it = seq;
+ matched.insert(it.key());
}
}
}
- userSettings->endArray();
-
- if (needUpdate || matched.size() < m_shortcuts.size()) {
- // Write the combined config to user settings.
- writeShortcutsToSettings();
+ if (matched.size() < m_shortcuts.size()) {
+ writeShortcutsToSettings(userSettings, group, m_shortcuts);
}
+
+ qDebug() << "shortcuts:" << m_shortcuts;
}
-void VConfigManager::writeShortcutsToSettings()
+void VConfigManager::readCaptainShortcutsFromSettings()
{
- // Clear it first
- userSettings->beginGroup("shortcuts");
- userSettings->remove("");
- userSettings->endGroup();
+ const QString group("captain_mode_shortcuts");
- userSettings->beginWriteArray("shortcuts");
- int idx = 0;
- for (auto it = m_shortcuts.begin(); it != m_shortcuts.end(); ++it, ++idx) {
- userSettings->setArrayIndex(idx);
- userSettings->setValue("operation", it.key());
- userSettings->setValue("keysequence", it.value());
+ m_captainShortcuts.clear();
+ m_captainShortcuts = readShortcutsFromSettings(defaultSettings, group);
+
+ // Update default settings according to user settings.
+ QHash userShortcuts = readShortcutsFromSettings(userSettings,
+ group);
+ QSet matched;
+ matched.reserve(m_captainShortcuts.size());
+ for (auto it = userShortcuts.begin(); it != userShortcuts.end(); ++it) {
+ auto defaultIt = m_captainShortcuts.find(it.key());
+ if (defaultIt != m_captainShortcuts.end()) {
+ QString sequence = it.value().trimmed();
+ if (sequence != defaultIt.value()) {
+ if (isValidKeySequence(sequence)) {
+ matched.insert(it.key());
+ *defaultIt = sequence;
+ }
+ } else {
+ matched.insert(it.key());
+ }
+ }
}
- userSettings->endArray();
+ if (matched.size() < m_captainShortcuts.size()) {
+ writeShortcutsToSettings(userSettings, group, m_captainShortcuts);
+ }
+
+ qDebug() << "captain mode shortcuts:" << m_captainShortcuts;
+}
+
+void VConfigManager::writeShortcutsToSettings(QSettings *p_settings,
+ const QString &p_group,
+ const QHash &p_shortcuts)
+{
+ p_settings->beginGroup(p_group);
+ p_settings->remove("");
+
+ for (auto it = p_shortcuts.begin(); it != p_shortcuts.end(); ++it) {
+ p_settings->setValue(it.key(), it.value());
+ }
+
+ p_settings->endGroup();
}
QString VConfigManager::getShortcutKeySequence(const QString &p_operation) const
@@ -969,6 +1130,16 @@ QString VConfigManager::getShortcutKeySequence(const QString &p_operation) const
return *it;
}
+QString VConfigManager::getCaptainShortcutKeySequence(const QString &p_operation) const
+{
+ auto it = m_captainShortcuts.find(p_operation);
+ if (it == m_captainShortcuts.end()) {
+ return QString();
+ }
+
+ return *it;
+}
+
void VConfigManager::initDocSuffixes()
{
m_docSuffixes.clear();
@@ -999,3 +1170,59 @@ void VConfigManager::initDocSuffixes()
qDebug() << "doc suffixes" << m_docSuffixes;
}
+
+QVector VConfigManager::getLastOpenedFiles()
+{
+ QVector files;
+ int size = m_sessionSettings->beginReadArray("last_opened_files");
+ for (int i = 0; i < size; ++i) {
+ m_sessionSettings->setArrayIndex(i);
+ files.push_back(VFileSessionInfo::fromSettings(m_sessionSettings));
+ }
+
+ m_sessionSettings->endArray();
+ qDebug() << "read" << files.size()
+ << "items from [last_opened_files] section";
+
+ return files;
+}
+
+void VConfigManager::setLastOpenedFiles(const QVector &p_files)
+{
+ const QString section("last_opened_files");
+
+ // Clear it first
+ m_sessionSettings->beginGroup(section);
+ m_sessionSettings->remove("");
+ m_sessionSettings->endGroup();
+
+ m_sessionSettings->beginWriteArray(section);
+ for (int i = 0; i < p_files.size(); ++i) {
+ m_sessionSettings->setArrayIndex(i);
+ const VFileSessionInfo &info = p_files[i];
+ info.toSettings(m_sessionSettings);
+ }
+
+ m_sessionSettings->endArray();
+ qDebug() << "write" << p_files.size()
+ << "items in [last_opened_files] section";
+
+}
+
+QVector VConfigManager::getCustomMagicWords()
+{
+ QVector words;
+ int size = userSettings->beginReadArray("magic_words");
+ for (int i = 0; i < size; ++i) {
+ userSettings->setArrayIndex(i);
+
+ VMagicWord word;
+ word.m_name = userSettings->value("name").toString();
+ word.m_definition = userSettings->value("definition").toString();
+ words.push_back(word);
+ }
+
+ userSettings->endArray();
+
+ return words;
+}
diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h
index 5212c4a0..702f9271 100644
--- a/src/vconfigmanager.h
+++ b/src/vconfigmanager.h
@@ -11,6 +11,9 @@
#include "hgmarkdownhighlighter.h"
#include "vmarkdownconverter.h"
#include "vconstants.h"
+#include "vfilesessioninfo.h"
+#include "utils/vmetawordmanager.h"
+
class QJsonObject;
class QString;
@@ -41,6 +44,18 @@ struct MarkdownitOption
bool m_linkify;
};
+// Type of heading sequence.
+enum class HeadingSequenceType
+{
+ Disabled = 0,
+ Enabled,
+
+ // Enabled only for internal notes.
+ EnabledNoteOnly,
+
+ Invalid
+};
+
class VConfigManager : public QObject
{
public:
@@ -106,7 +121,10 @@ public:
int getCurNotebookIndex() const;
void setCurNotebookIndex(int index);
- void getNotebooks(QVector &p_notebooks, QObject *parent);
+ // Read [notebooks] section from settings into @p_notebooks.
+ void getNotebooks(QVector &p_notebooks, QObject *p_parent);
+
+ // Write @p_notebooks to [notebooks] section into settings.
void setNotebooks(const QVector &p_notebooks);
hoedown_extensions getMarkdownExtensions() const;
@@ -153,6 +171,9 @@ public:
const QByteArray &getMainSplitterState() const;
void setMainSplitterState(const QByteArray &p_state);
+ const QByteArray &getNaviSplitterState() const;
+ void setNaviSplitterState(const QByteArray &p_state);
+
bool getFindCaseSensitive() const;
void setFindCaseSensitive(bool p_enabled);
@@ -254,8 +275,8 @@ public:
OpenFileMode getNoteOpenMode() const;
void setNoteOpenMode(OpenFileMode p_mode);
- bool getEnableHeadingSequence() const;
- void setEnableHeadingSequence(bool p_enabled);
+ HeadingSequenceType getHeadingSequenceType() const;
+ void setHeadingSequenceType(HeadingSequenceType p_type);
int getHeadingSequenceBaseLevel() const;
void setHeadingSequenceBaseLevel(int p_level);
@@ -291,10 +312,32 @@ public:
// Whether user specify template_code_block_css_url directly.
bool getUserSpecifyTemplateCodeBlockCssUrl() const;
+ bool getEnableCompactMode() const;
+ void setEnableCompactMode(bool p_enabled);
+
+ StartupPageType getStartupPageType() const;
+ void setStartupPageType(StartupPageType p_type);
+
+ const QStringList &getStartupPages() const;
+ void setStartupPages(const QStringList &p_pages);
+
+ // Read last opened files from [last_opened_files] of session.ini.
+ QVector getLastOpenedFiles();
+
+ // Write last opened files to [last_opened_files] of session.ini.
+ void setLastOpenedFiles(const QVector &p_files);
+
+ // Read custom magic words from [magic_words] section.
+ QVector getCustomMagicWords();
+
// Return the configured key sequence of @p_operation.
// Return empty if there is no corresponding config.
QString getShortcutKeySequence(const QString &p_operation) const;
+ // Return the configured key sequence in Captain mode.
+ // Return empty if there is no corresponding config.
+ QString getCaptainShortcutKeySequence(const QString &p_operation) const;
+
// Get the folder the ini file exists.
QString getConfigFolder() const;
@@ -317,7 +360,10 @@ public:
QVector getEditorStyles() const;
private:
+ // Look up a config from user and default settings.
QVariant getConfigFromSettings(const QString §ion, const QString &key) const;
+
+ // Set a config to user settings.
void setConfigToSettings(const QString §ion, const QString &key, const QVariant &value);
// Get default config from vnote.ini.
@@ -326,8 +372,29 @@ private:
// Reset user config to default config and return the default config value.
QVariant resetDefaultConfig(const QString &p_section, const QString &p_key);
- void readNotebookFromSettings(QVector &p_notebooks, QObject *parent);
- void writeNotebookToSettings(const QVector &p_notebooks);
+ // Look up a config from session settings.
+ QVariant getConfigFromSessionSettings(const QString &p_section, const QString &p_key) const;
+
+ // Set a config to session settings.
+ void setConfigToSessionSettings(const QString &p_section,
+ const QString &p_key,
+ const QVariant &p_value);
+
+ // Init defaultSettings, userSettings, and m_sessionSettings.
+ void initSettings();
+
+ // Init from m_sessionSettings.
+ void initFromSessionSettings();
+
+ // Read [notebooks] section from @p_settings.
+ void readNotebookFromSettings(QSettings *p_settings,
+ QVector &p_notebooks,
+ QObject *parent);
+
+ // Write to [notebooks] section to @p_settings.
+ void writeNotebookToSettings(QSettings *p_settings,
+ const QVector &p_notebooks);
+
void readPredefinedColorsFromSettings();
// 1. Update styles common in HTML and Markdown;
@@ -336,10 +403,6 @@ private:
void updateMarkdownEditStyle();
- // Migrate ini file from tamlok/vnote.ini to vnote/vnote.ini.
- // This is for the change of org name.
- void migrateIniFile();
-
// Output pre-defined CSS styles to style folder.
bool outputDefaultCssStyle() const;
@@ -359,8 +422,16 @@ private:
// write the combined configs to user settings.
void readShortcutsFromSettings();
- // Write m_shortcuts to the [shortcuts] section in the user settings.
- void writeShortcutsToSettings();
+ // Read the [captain_mode_shortcuts] section in the settings to init
+ // m_captainShortcuts.
+ void readCaptainShortcutsFromSettings();
+
+ QHash readShortcutsFromSettings(QSettings *p_settings,
+ const QString &p_group);
+
+ void writeShortcutsToSettings(QSettings *p_settings,
+ const QString &p_group,
+ const QHash &p_shortcuts);
// Whether @p_seq is a valid key sequence for shortcuts.
bool isValidKeySequence(const QString &p_seq);
@@ -393,6 +464,8 @@ private:
QString m_templateCodeBlockCssUrl;
QString m_editorStyle;
+
+ // Index of current notebook.
int curNotebookIndex;
// Markdown Converter
@@ -429,6 +502,7 @@ private:
QByteArray m_mainWindowGeometry;
QByteArray m_mainWindowState;
QByteArray m_mainSplitterState;
+ QByteArray m_naviSplitterState;
// Find/Replace dialog options
bool m_findCaseSensitive;
@@ -530,6 +604,10 @@ private:
// Operation -> KeySequence.
QHash m_shortcuts;
+ // Shortcuts config in Captain mode.
+ // Operation -> KeySequence.
+ QHash m_captainShortcuts;
+
// Whether minimize to system tray icon when closing the app.
// -1: uninitialized;
// 0: do not minimize to the tay;
@@ -553,7 +631,7 @@ private:
OpenFileMode m_noteOpenMode;
// Whether auto genearte heading sequence.
- bool m_enableHeadingSequence;
+ HeadingSequenceType m_headingSequenceType;
// Heading sequence base level.
int m_headingSequenceBaseLevel;
@@ -600,6 +678,15 @@ private:
// Whether double click on a tab to close it.
bool m_doubleClickCloseTab;
+ // Whether put folder and note panel in one single column.
+ bool m_enableCompactMode;
+
+ // Type of the pages to open on startup.
+ StartupPageType m_startupPageType;
+
+ // File paths to open on startup.
+ QStringList m_startupPages;
+
// The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile;
@@ -607,13 +694,25 @@ private:
// The name of the config file in each directory.
static const QString c_dirConfigFile;
- // The name of the default configuration file
- static const QString defaultConfigFilePath;
+ // The path of the default configuration file
+ static const QString c_defaultConfigFilePath;
+
+ // The name of the config file.
+ static const QString c_defaultConfigFile;
+
+ // The name of the config file for session information.
+ static const QString c_sessionConfigFile;
+
// QSettings for the user configuration
QSettings *userSettings;
- // Qsettings for @defaultConfigFileName
+
+ // Qsettings for @c_defaultConfigFilePath.
QSettings *defaultSettings;
+ // QSettings for the session configuration, such as notebooks,
+ // geometry, last opened files.
+ QSettings *m_sessionSettings;
+
// The folder name of style files.
static const QString c_styleConfigFolder;
@@ -681,18 +780,35 @@ inline void VConfigManager::setCurNotebookIndex(int index)
if (index == curNotebookIndex) {
return;
}
+
curNotebookIndex = index;
- setConfigToSettings("global", "current_notebook", index);
+ setConfigToSessionSettings("global", "current_notebook", index);
}
-inline void VConfigManager::getNotebooks(QVector &p_notebooks, QObject *parent)
+inline void VConfigManager::getNotebooks(QVector &p_notebooks,
+ QObject *p_parent)
{
- readNotebookFromSettings(p_notebooks, parent);
+ // We used to store it in vnote.ini. For now, we store it in session.ini.
+ readNotebookFromSettings(m_sessionSettings, p_notebooks, p_parent);
+
+ // Migration.
+ if (p_notebooks.isEmpty()) {
+ readNotebookFromSettings(userSettings, p_notebooks, p_parent);
+
+ if (!p_notebooks.isEmpty()) {
+ // Clear and save it in another place.
+ userSettings->beginGroup("notebooks");
+ userSettings->remove("");
+ userSettings->endGroup();
+
+ writeNotebookToSettings(m_sessionSettings, p_notebooks);
+ }
+ }
}
inline void VConfigManager::setNotebooks(const QVector &p_notebooks)
{
- writeNotebookToSettings(p_notebooks);
+ writeNotebookToSettings(m_sessionSettings, p_notebooks);
}
inline hoedown_extensions VConfigManager::getMarkdownExtensions() const
@@ -860,7 +976,7 @@ inline bool VConfigManager::getToolsDockChecked() const
inline void VConfigManager::setToolsDockChecked(bool p_checked)
{
m_toolsDockChecked = p_checked;
- setConfigToSettings("session", "tools_dock_checked",
+ setConfigToSettings("global", "tools_dock_checked",
m_toolsDockChecked);
}
@@ -872,8 +988,9 @@ inline const QByteArray& VConfigManager::getMainWindowGeometry() const
inline void VConfigManager::setMainWindowGeometry(const QByteArray &p_geometry)
{
m_mainWindowGeometry = p_geometry;
- setConfigToSettings("session", "main_window_geometry",
- m_mainWindowGeometry);
+ setConfigToSessionSettings("geometry",
+ "main_window_geometry",
+ m_mainWindowGeometry);
}
inline const QByteArray& VConfigManager::getMainWindowState() const
@@ -884,8 +1001,9 @@ inline const QByteArray& VConfigManager::getMainWindowState() const
inline void VConfigManager::setMainWindowState(const QByteArray &p_state)
{
m_mainWindowState = p_state;
- setConfigToSettings("session", "main_window_state",
- m_mainWindowState);
+ setConfigToSessionSettings("geometry",
+ "main_window_state",
+ m_mainWindowState);
}
inline const QByteArray& VConfigManager::getMainSplitterState() const
@@ -896,7 +1014,22 @@ inline const QByteArray& VConfigManager::getMainSplitterState() const
inline void VConfigManager::setMainSplitterState(const QByteArray &p_state)
{
m_mainSplitterState = p_state;
- setConfigToSettings("session", "main_splitter_state", m_mainSplitterState);
+ setConfigToSessionSettings("geometry",
+ "main_splitter_state",
+ m_mainSplitterState);
+}
+
+inline const QByteArray& VConfigManager::getNaviSplitterState() const
+{
+ return m_naviSplitterState;
+}
+
+inline void VConfigManager::setNaviSplitterState(const QByteArray &p_state)
+{
+ m_naviSplitterState = p_state;
+ setConfigToSessionSettings("geometry",
+ "navi_splitter_state",
+ m_naviSplitterState);
}
inline bool VConfigManager::getFindCaseSensitive() const
@@ -1370,20 +1503,21 @@ inline void VConfigManager::setNoteOpenMode(OpenFileMode p_mode)
m_noteOpenMode == OpenFileMode::Read ? 0 : 1);
}
-inline bool VConfigManager::getEnableHeadingSequence() const
+inline HeadingSequenceType VConfigManager::getHeadingSequenceType() const
{
- return m_enableHeadingSequence;
+ return m_headingSequenceType;
}
-inline void VConfigManager::setEnableHeadingSequence(bool p_enabled)
+inline void VConfigManager::setHeadingSequenceType(HeadingSequenceType p_type)
{
- if (m_enableHeadingSequence == p_enabled) {
+ if (m_headingSequenceType == p_type) {
return;
}
- m_enableHeadingSequence = p_enabled;
- setConfigToSettings("global", "enable_heading_sequence",
- m_enableHeadingSequence);
+ m_headingSequenceType = p_type;
+ setConfigToSettings("global",
+ "heading_sequence_type",
+ (int)m_headingSequenceType);
}
inline int VConfigManager::getHeadingSequenceBaseLevel() const
@@ -1570,4 +1704,49 @@ inline bool VConfigManager::getUserSpecifyTemplateCodeBlockCssUrl() const
return !m_templateCodeBlockCssUrl.isEmpty();
}
+inline bool VConfigManager::getEnableCompactMode() const
+{
+ return m_enableCompactMode;
+}
+
+inline void VConfigManager::setEnableCompactMode(bool p_enabled)
+{
+ if (m_enableCompactMode == p_enabled) {
+ return;
+ }
+
+ m_enableCompactMode = p_enabled;
+ setConfigToSettings("global", "enable_compact_mode", m_enableCompactMode);
+}
+
+inline StartupPageType VConfigManager::getStartupPageType() const
+{
+ return m_startupPageType;
+}
+
+inline void VConfigManager::setStartupPageType(StartupPageType p_type)
+{
+ if (m_startupPageType == p_type) {
+ return;
+ }
+
+ m_startupPageType = p_type;
+ setConfigToSettings("global", "startup_page_type", (int)m_startupPageType);
+}
+
+inline const QStringList &VConfigManager::getStartupPages() const
+{
+ return m_startupPages;
+}
+
+inline void VConfigManager::setStartupPages(const QStringList &p_pages)
+{
+ if (m_startupPages == p_pages) {
+ return;
+ }
+
+ m_startupPages = p_pages;
+ setConfigToSettings("global", "startup_pages", m_startupPages);
+}
+
#endif // VCONFIGMANAGER_H
diff --git a/src/vconstants.h b/src/vconstants.h
index ae1a2205..217e7b4b 100644
--- a/src/vconstants.h
+++ b/src/vconstants.h
@@ -1,6 +1,8 @@
#ifndef VCONSTANTS_H
#define VCONSTANTS_H
+#include
+
// Html: rich text file;
// Markdown: Markdown text file;
// List: Infinite list file like WorkFlowy;
@@ -22,7 +24,7 @@ namespace ClipboardConfig
static const QString c_dirs = "dirs";
}
-enum class OpenFileMode {Read = 0, Edit};
+enum class OpenFileMode {Read = 0, Edit, Invalid };
static const qreal c_webZoomFactorMax = 5;
static const qreal c_webZoomFactorMin = 0.25;
@@ -50,12 +52,16 @@ namespace DirConfig
static const QString c_emptyHeaderName = "[EMPTY]";
-enum class TextDecoration { None,
- Bold,
- Italic,
- Underline,
- Strikethrough,
- InlineCode };
+enum class TextDecoration
+{
+ None,
+ Bold,
+ Italic,
+ Underline,
+ Strikethrough,
+ InlineCode,
+ CodeBlock
+};
enum FindOption
{
@@ -97,4 +103,13 @@ enum class LineNumberType
CodeBlock
};
+// Pages to open on start up.
+enum class StartupPageType
+{
+ None = 0,
+ ContinueLeftOff = 1,
+ SpecificPages = 2,
+ Invalid
+};
+
#endif
diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp
index 68ff4f0f..41c78cf1 100644
--- a/src/vdirectorytree.cpp
+++ b/src/vdirectorytree.cpp
@@ -430,7 +430,7 @@ void VDirectoryTree::newSubDirectory()
.arg(g_config->c_dataTextStyle)
.arg(curDir->getName());
QString defaultName("new_folder");
- defaultName = VUtils::getFileNameWithSequence(curDir->fetchPath(), defaultName);
+ defaultName = VUtils::getDirNameWithSequence(curDir->fetchPath(), defaultName);
VNewDirDialog dialog(tr("Create Folder"), info, defaultName, curDir, this);
if (dialog.exec() == QDialog::Accepted) {
QString name = dialog.getNameInput();
@@ -467,7 +467,7 @@ void VDirectoryTree::newRootDirectory()
.arg(g_config->c_dataTextStyle)
.arg(m_notebook->getName());
QString defaultName("new_folder");
- defaultName = VUtils::getFileNameWithSequence(rootDir->fetchPath(), defaultName);
+ defaultName = VUtils::getDirNameWithSequence(rootDir->fetchPath(), defaultName);
VNewDirDialog dialog(tr("Create Root Folder"), info, defaultName, rootDir, this);
if (dialog.exec() == QDialog::Accepted) {
QString name = dialog.getNameInput();
diff --git a/src/vdocument.h b/src/vdocument.h
index 40b00bbb..08b51ed2 100644
--- a/src/vdocument.h
+++ b/src/vdocument.h
@@ -16,9 +16,15 @@ class VDocument : public QObject
public:
// @p_file could be NULL.
VDocument(const VFile *p_file, QObject *p_parent = 0);
+
QString getToc();
+
+ // Scroll to @anchor in the web.
+ // @anchor is the id without '#', like "toc_1". If empty, will scroll to top.
void scrollToAnchor(const QString &anchor);
+
void setHtml(const QString &html);
+
// Request to highlight a segment text.
// Use p_id to identify the result.
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
@@ -35,6 +41,7 @@ public slots:
// When the Web view has been scrolled, it will signal current header anchor.
// Empty @anchor to indicate an invalid header.
+ // The header does not begins with '#'.
void setHeader(const QString &anchor);
void setLog(const QString &p_log);
@@ -49,8 +56,12 @@ public slots:
signals:
void textChanged(const QString &text);
+
void tocChanged(const QString &toc);
+
void requestScrollToAnchor(const QString &anchor);
+
+ // @anchor is the id of that anchor, without '#'.
void headerChanged(const QString &anchor);
void htmlChanged(const QString &html);
void logChanged(const QString &p_log);
diff --git a/src/vedit.cpp b/src/vedit.cpp
index 38cfb259..4d1669f0 100644
--- a/src/vedit.cpp
+++ b/src/vedit.cpp
@@ -4,19 +4,26 @@
#include "vedit.h"
#include "vnote.h"
#include "vconfigmanager.h"
-#include "vtoc.h"
+#include "vtableofcontent.h"
#include "utils/vutils.h"
#include "utils/veditutils.h"
+#include "utils/vmetawordmanager.h"
#include "veditoperations.h"
#include "vedittab.h"
+#include "dialog/vinsertlinkdialog.h"
extern VConfigManager *g_config;
+
extern VNote *g_vnote;
-void VEditConfig::init(const QFontMetrics &p_metric)
+extern VMetaWordManager *g_mwMgr;
+
+void VEditConfig::init(const QFontMetrics &p_metric,
+ bool p_enableHeadingSequence)
{
update(p_metric);
+ // Init configs that do not support update later.
m_enableVimMode = g_config->getEnableVimMode();
if (g_config->getLineDistanceHeight() <= 0) {
@@ -26,6 +33,8 @@ void VEditConfig::init(const QFontMetrics &p_metric)
}
m_highlightWholeBlock = m_enableVimMode;
+
+ m_enableHeadingSequence = p_enableHeadingSequence;
}
void VEditConfig::update(const QFontMetrics &p_metric)
@@ -84,7 +93,7 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
updateFontAndPalette();
- m_config.init(QFontMetrics(font()));
+ m_config.init(QFontMetrics(font()), false);
updateConfig();
connect(this, &VEdit::cursorPositionChanged,
@@ -157,15 +166,16 @@ void VEdit::reloadFile()
setModified(false);
}
-void VEdit::scrollToLine(int p_lineNumber)
+bool VEdit::scrollToBlock(int p_blockNumber)
{
- Q_ASSERT(p_lineNumber >= 0);
-
- QTextBlock block = document()->findBlockByLineNumber(p_lineNumber);
+ QTextBlock block = document()->findBlockByNumber(p_blockNumber);
if (block.isValid()) {
VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
moveCursor(QTextCursor::EndOfBlock);
+ return true;
}
+
+ return false;
}
bool VEdit::isModified() const
@@ -189,6 +199,49 @@ void VEdit::insertImage()
}
}
+void VEdit::insertLink()
+{
+ if (!m_editOps) {
+ return;
+ }
+
+ QString text;
+ QString linkText, linkUrl;
+ QTextCursor cursor = textCursor();
+ if (cursor.hasSelection()) {
+ text = VEditUtils::selectedText(cursor).trimmed();
+ // Only pure space is accepted.
+ QRegExp reg("[\\S ]*");
+ if (reg.exactMatch(text)) {
+ QUrl url = QUrl::fromUserInput(text,
+ m_file->fetchBasePath());
+ QRegExp urlReg("[\\.\\\\/]");
+ if (url.isValid()
+ && text.contains(urlReg)) {
+ // Url.
+ linkUrl = text;
+ } else {
+ // Text.
+ linkText = text;
+ }
+ }
+ }
+
+ VInsertLinkDialog dialog(tr("Insert Link"),
+ "",
+ "",
+ linkText,
+ linkUrl,
+ this);
+ if (dialog.exec() == QDialog::Accepted) {
+ linkText = dialog.getLinkText();
+ linkUrl = dialog.getLinkUrl();
+ Q_ASSERT(!linkText.isEmpty() && !linkUrl.isEmpty());
+
+ m_editOps->insertLink(linkText, linkUrl);
+ }
+}
+
bool VEdit::peekText(const QString &p_text, uint p_options, bool p_forward)
{
if (p_text.isEmpty()) {
@@ -1383,3 +1436,36 @@ void VEdit::updateBlockLineDistanceHeight(int p_pos,
cursor.endEditBlock();
}
}
+
+void VEdit::evaluateMagicWords()
+{
+ QString text;
+ QTextCursor cursor = textCursor();
+ if (!cursor.hasSelection()) {
+ // Get the WORD in current cursor.
+ int start, end;
+ VEditUtils::findCurrentWORD(cursor, start, end);
+
+ if (start == end) {
+ return;
+ } else {
+ cursor.setPosition(start);
+ cursor.setPosition(end, QTextCursor::KeepAnchor);
+ }
+ }
+
+ text = VEditUtils::selectedText(cursor);
+ Q_ASSERT(!text.isEmpty());
+ QString evaText = g_mwMgr->evaluate(text);
+ if (text != evaText) {
+ qDebug() << "evaluateMagicWords" << text << evaText;
+
+ cursor.insertText(evaText);
+
+ if (m_editOps) {
+ m_editOps->setVimMode(VimMode::Insert);
+ }
+
+ setTextCursor(cursor);
+ }
+}
diff --git a/src/vedit.h b/src/vedit.h
index 7c8b4cce..f2d7e4b8 100644
--- a/src/vedit.h
+++ b/src/vedit.h
@@ -10,7 +10,6 @@
#include
#include
#include "vconstants.h"
-#include "vtoc.h"
#include "vnotefile.h"
class VEditOperations;
@@ -38,10 +37,12 @@ public:
m_tabSpaces("\t"),
m_enableVimMode(false),
m_highlightWholeBlock(false),
- m_lineDistanceHeight(0)
+ m_lineDistanceHeight(0),
+ m_enableHeadingSequence(false)
{}
- void init(const QFontMetrics &p_metric);
+ void init(const QFontMetrics &p_metric,
+ bool p_enableHeadingSequence);
// Only update those configs which could be updated online.
void update(const QFontMetrics &p_metric);
@@ -64,6 +65,9 @@ public:
// Line distance height in pixels.
int m_lineDistanceHeight;
+
+ // Whether enable auto heading sequence.
+ bool m_enableHeadingSequence;
};
class LineNumberArea;
@@ -81,10 +85,15 @@ public:
virtual void setModified(bool p_modified);
bool isModified() const;
virtual void reloadFile();
- virtual void scrollToLine(int p_lineNumber);
+
+ virtual bool scrollToBlock(int p_blockNumber);
+
// User requests to insert an image.
virtual void insertImage();
+ // User requests to insert a link.
+ virtual void insertLink();
+
// Used for incremental search.
// User has enter the content to search, but does not enter the "find" button yet.
bool peekText(const QString &p_text, uint p_options, bool p_forward = true);
@@ -135,6 +144,9 @@ public:
bool isBlockVisible(const QTextBlock &p_block);
+ // Evaluate selected text or cursor word as magic words.
+ void evaluateMagicWords();
+
signals:
// Request VEditTab to save and exit edit mode.
void saveAndRead();
@@ -163,6 +175,9 @@ signals:
// Request the edit tab to close find and replace dialog.
void requestCloseFindReplaceDialog();
+ // Emit when all initialization is ready.
+ void ready();
+
public slots:
virtual void highlightCurrentLine();
diff --git a/src/veditarea.cpp b/src/veditarea.cpp
index c92dcb32..2ebe8125 100644
--- a/src/veditarea.cpp
+++ b/src/veditarea.cpp
@@ -7,18 +7,27 @@
#include "vfile.h"
#include "dialog/vfindreplacedialog.h"
#include "utils/vutils.h"
+#include "vfilesessioninfo.h"
+#include "vmainwindow.h"
+#include "vcaptain.h"
extern VConfigManager *g_config;
+
extern VNote *g_vnote;
-VEditArea::VEditArea(VNote *vnote, QWidget *parent)
- : QWidget(parent), VNavigationMode(),
- vnote(vnote), curWindowIndex(-1)
+extern VMainWindow *g_mainWin;
+
+VEditArea::VEditArea(QWidget *parent)
+ : QWidget(parent),
+ VNavigationMode(),
+ curWindowIndex(-1)
{
setupUI();
insertSplitWindow(0);
setCurrentWindow(0, false);
+
+ registerCaptainTargets();
}
void VEditArea::setupUI()
@@ -65,7 +74,7 @@ void VEditArea::setupUI()
void VEditArea::insertSplitWindow(int idx)
{
- VEditWindow *win = new VEditWindow(vnote, this);
+ VEditWindow *win = new VEditWindow(this);
splitter->insertWidget(idx, win);
connect(win, &VEditWindow::tabStatusUpdated,
this, &VEditArea::handleWindowTabStatusUpdated);
@@ -76,9 +85,9 @@ void VEditArea::insertSplitWindow(int idx)
connect(win, &VEditWindow::getFocused,
this, &VEditArea::handleWindowFocused);
connect(win, &VEditWindow::outlineChanged,
- this, &VEditArea::handleOutlineChanged);
- connect(win, &VEditWindow::curHeaderChanged,
- this, &VEditArea::handleCurHeaderChanged);
+ this, &VEditArea::handleWindowOutlineChanged);
+ connect(win, &VEditWindow::currentHeaderChanged,
+ this, &VEditArea::handleWindowCurrentHeaderChanged);
connect(win, &VEditWindow::statusMessage,
this, &VEditArea::handleWindowStatusMessage);
connect(win, &VEditWindow::vimStatusUpdated,
@@ -119,17 +128,17 @@ void VEditArea::removeSplitWindow(VEditWindow *win)
win->deleteLater();
}
-void VEditArea::openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode)
+VEditTab *VEditArea::openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode)
{
if (!p_file) {
- return;
+ return NULL;
}
// If it is DocType::Unknown, open it using system default method.
if (p_file->getDocType() == DocType::Unknown) {
QUrl url = QUrl::fromLocalFile(p_file->fetchPath());
QDesktopServices::openUrl(url);
- return;
+ return NULL;
}
// Find if it has been opened already
@@ -165,6 +174,8 @@ void VEditArea::openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode)
tabIdx = openFileInWindow(winIdx, p_file, p_mode);
out:
+ VEditTab *tab = getTab(winIdx, tabIdx);
+
setCurrentTab(winIdx, tabIdx, setFocus);
if (existFile && p_forceMode) {
@@ -174,6 +185,8 @@ out:
editFile();
}
}
+
+ return tab;
}
QVector > VEditArea::findTabsByFile(const VFile *p_file)
@@ -232,15 +245,13 @@ void VEditArea::updateWindowStatus()
Q_ASSERT(splitter->count() == 0);
emit tabStatusUpdated(VEditTabInfo());
- emit outlineChanged(VToc());
- emit curHeaderChanged(VAnchor());
+ emit outlineChanged(VTableOfContent());
+ emit currentHeaderChanged(VHeaderPointer());
return;
}
VEditWindow *win = getWindow(curWindowIndex);
win->updateTabStatus();
- win->requestUpdateOutline();
- win->requestUpdateCurHeader();
}
bool VEditArea::closeFile(const VFile *p_file, bool p_forced)
@@ -431,26 +442,28 @@ void VEditArea::handleWindowFocused()
}
}
-void VEditArea::handleOutlineChanged(const VToc &toc)
+void VEditArea::handleWindowOutlineChanged(const VTableOfContent &p_outline)
{
QObject *winObject = sender();
if (splitter->widget(curWindowIndex) == winObject) {
- emit outlineChanged(toc);
+ emit outlineChanged(p_outline);
}
}
-void VEditArea::handleCurHeaderChanged(const VAnchor &anchor)
+void VEditArea::handleWindowCurrentHeaderChanged(const VHeaderPointer &p_header)
{
QObject *winObject = sender();
if (splitter->widget(curWindowIndex) == winObject) {
- emit curHeaderChanged(anchor);
+ emit currentHeaderChanged(p_header);
}
}
-void VEditArea::handleOutlineItemActivated(const VAnchor &anchor)
+void VEditArea::scrollToHeader(const VHeaderPointer &p_header)
{
- // Notice current window
- getWindow(curWindowIndex)->scrollCurTab(anchor);
+ VEditWindow *win = getCurrentWindow();
+ if (win) {
+ win->scrollToHeader(p_header);
+ }
}
bool VEditArea::isFileOpened(const VFile *p_file)
@@ -482,13 +495,35 @@ void VEditArea::handleNotebookUpdated(const VNotebook *p_notebook)
}
}
-VEditTab *VEditArea::currentEditTab()
+VEditTab *VEditArea::getCurrentTab() const
{
if (curWindowIndex == -1) {
return NULL;
}
+
VEditWindow *win = getWindow(curWindowIndex);
- return win->currentEditTab();
+ return win->getCurrentTab();
+}
+
+VEditTab *VEditArea::getTab(int p_winIdx, int p_tabIdx) const
+{
+ VEditWindow *win = getWindow(p_winIdx);
+ if (!win) {
+ return NULL;
+ }
+
+ return win->getTab(p_tabIdx);
+}
+
+QVector VEditArea::getAllTabsInfo() const
+{
+ QVector tabs;
+ int nrWin = splitter->count();
+ for (int i = 0; i < nrWin; ++i) {
+ tabs.append(getWindow(i)->getAllTabsInfo());
+ }
+
+ return tabs;
}
int VEditArea::windowIndex(const VEditWindow *p_window) const
@@ -518,7 +553,7 @@ void VEditArea::moveTab(QWidget *p_widget, int p_fromIdx, int p_toIdx)
// Only propogate the search in the IncrementalSearch case.
void VEditArea::handleFindTextChanged(const QString &p_text, uint p_options)
{
- VEditTab *tab = currentEditTab();
+ VEditTab *tab = getCurrentTab();
if (tab) {
if (p_options & FindOption::IncrementalSearch) {
tab->findText(p_text, p_options, true);
@@ -539,7 +574,7 @@ void VEditArea::handleFindNext(const QString &p_text, uint p_options,
bool p_forward)
{
qDebug() << "find next" << p_text << p_options << p_forward;
- VEditTab *tab = currentEditTab();
+ VEditTab *tab = getCurrentTab();
if (tab) {
tab->findText(p_text, p_options, false, p_forward);
}
@@ -550,7 +585,7 @@ void VEditArea::handleReplace(const QString &p_text, uint p_options,
{
qDebug() << "replace" << p_text << p_options << "with" << p_replaceText
<< p_findNext;
- VEditTab *tab = currentEditTab();
+ VEditTab *tab = getCurrentTab();
if (tab) {
tab->replaceText(p_text, p_options, p_replaceText, p_findNext);
}
@@ -560,7 +595,7 @@ void VEditArea::handleReplaceAll(const QString &p_text, uint p_options,
const QString &p_replaceText)
{
qDebug() << "replace all" << p_text << p_options << "with" << p_replaceText;
- VEditTab *tab = currentEditTab();
+ VEditTab *tab = getCurrentTab();
if (tab) {
tab->replaceTextAll(p_text, p_options, p_replaceText);
}
@@ -584,7 +619,7 @@ void VEditArea::handleFindDialogClosed()
QString VEditArea::getSelectedText()
{
- VEditTab *tab = currentEditTab();
+ VEditTab *tab = getCurrentTab();
if (tab) {
return tab->getSelectedText();
} else {
@@ -621,6 +656,7 @@ VEditWindow *VEditArea::getCurrentWindow() const
if (curWindowIndex < 0) {
return NULL;
}
+
return getWindow(curWindowIndex);
}
@@ -694,3 +730,220 @@ bool VEditArea::handleKeyNavigation(int p_key, bool &p_succeed)
return ret;
}
+int VEditArea::openFiles(const QVector &p_files)
+{
+ int nrOpened = 0;
+ for (auto const & info : p_files) {
+ QString filePath = VUtils::validFilePathToOpen(info.m_file);
+ if (filePath.isEmpty()) {
+ continue;
+ }
+
+ VFile *file = g_vnote->getFile(filePath);
+ if (!file) {
+ continue;
+ }
+
+ VEditTab *tab = openFile(file, info.m_mode, true);
+ ++nrOpened;
+
+ VEditTabInfo tabInfo;
+ tabInfo.m_editTab = tab;
+ info.toEditTabInfo(&tabInfo);
+
+ tab->tryRestoreFromTabInfo(tabInfo);
+ }
+
+ return nrOpened;
+}
+
+void VEditArea::registerCaptainTargets()
+{
+ using namespace std::placeholders;
+
+ VCaptain *captain = g_mainWin->getCaptain();
+
+ captain->registerCaptainTarget(tr("ActivateTab1"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab1"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 1));
+ captain->registerCaptainTarget(tr("ActivateTab2"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab2"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 2));
+ captain->registerCaptainTarget(tr("ActivateTab3"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab3"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 3));
+ captain->registerCaptainTarget(tr("ActivateTab4"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab4"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 4));
+ captain->registerCaptainTarget(tr("ActivateTab5"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab5"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 5));
+ captain->registerCaptainTarget(tr("ActivateTab6"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab6"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 6));
+ captain->registerCaptainTarget(tr("ActivateTab7"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab7"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 7));
+ captain->registerCaptainTarget(tr("ActivateTab8"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab8"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 8));
+ captain->registerCaptainTarget(tr("ActivateTab9"),
+ g_config->getCaptainShortcutKeySequence("ActivateTab9"),
+ this,
+ std::bind(activateTabByCaptain, _1, _2, 9));
+ captain->registerCaptainTarget(tr("AlternateTab"),
+ g_config->getCaptainShortcutKeySequence("AlternateTab"),
+ this,
+ alternateTabByCaptain);
+ captain->registerCaptainTarget(tr("OpenedFileList"),
+ g_config->getCaptainShortcutKeySequence("OpenedFileList"),
+ this,
+ showOpenedFileListByCaptain);
+ captain->registerCaptainTarget(tr("ActivateSplitLeft"),
+ g_config->getCaptainShortcutKeySequence("ActivateSplitLeft"),
+ this,
+ activateSplitLeftByCaptain);
+ captain->registerCaptainTarget(tr("ActivateSplitRight"),
+ g_config->getCaptainShortcutKeySequence("ActivateSplitRight"),
+ this,
+ activateSplitRightByCaptain);
+ captain->registerCaptainTarget(tr("MoveTabSplitLeft"),
+ g_config->getCaptainShortcutKeySequence("MoveTabSplitLeft"),
+ this,
+ moveTabSplitLeftByCaptain);
+ captain->registerCaptainTarget(tr("MoveTabSplitRight"),
+ g_config->getCaptainShortcutKeySequence("MoveTabSplitRight"),
+ this,
+ moveTabSplitRightByCaptain);
+ captain->registerCaptainTarget(tr("ActivateNextTab"),
+ g_config->getCaptainShortcutKeySequence("ActivateNextTab"),
+ this,
+ activateNextTabByCaptain);
+ captain->registerCaptainTarget(tr("ActivatePreviousTab"),
+ g_config->getCaptainShortcutKeySequence("ActivatePreviousTab"),
+ this,
+ activatePreviousTabByCaptain);
+ captain->registerCaptainTarget(tr("VerticalSplit"),
+ g_config->getCaptainShortcutKeySequence("VerticalSplit"),
+ this,
+ verticalSplitByCaptain);
+ captain->registerCaptainTarget(tr("RemoveSplit"),
+ g_config->getCaptainShortcutKeySequence("RemoveSplit"),
+ this,
+ removeSplitByCaptain);
+ captain->registerCaptainTarget(tr("MagicWord"),
+ g_config->getCaptainShortcutKeySequence("MagicWord"),
+ this,
+ evaluateMagicWordsByCaptain);
+}
+
+void VEditArea::activateTabByCaptain(void *p_target, void *p_data, int p_idx)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ VEditWindow *win = obj->getCurrentWindow();
+ if (win) {
+ win->activateTab(p_idx);
+ }
+}
+
+void VEditArea::alternateTabByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ VEditWindow *win = obj->getCurrentWindow();
+ if (win) {
+ win->alternateTab();
+ }
+}
+
+void VEditArea::showOpenedFileListByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ VEditWindow *win = obj->getCurrentWindow();
+ if (win) {
+ win->showOpenedFileList();
+ }
+}
+
+void VEditArea::activateSplitLeftByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ obj->focusNextWindow(-1);
+}
+
+void VEditArea::activateSplitRightByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ obj->focusNextWindow(1);
+}
+
+void VEditArea::moveTabSplitLeftByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ obj->moveCurrentTabOneSplit(false);
+}
+
+void VEditArea::moveTabSplitRightByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ obj->moveCurrentTabOneSplit(true);
+}
+
+void VEditArea::activateNextTabByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ VEditWindow *win = obj->getCurrentWindow();
+ if (win) {
+ win->focusNextTab(true);
+ }
+}
+
+void VEditArea::activatePreviousTabByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ VEditWindow *win = obj->getCurrentWindow();
+ if (win) {
+ win->focusNextTab(false);
+ }
+}
+
+void VEditArea::verticalSplitByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ obj->splitCurrentWindow();
+}
+
+void VEditArea::removeSplitByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ obj->removeCurrentWindow();
+}
+
+void VEditArea::evaluateMagicWordsByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VEditArea *obj = static_cast(p_target);
+ VEditTab *tab = obj->getCurrentTab();
+ if (tab && tab->tabHasFocus()) {
+ tab->evaluateMagicWords();
+ }
+}
+
diff --git a/src/veditarea.h b/src/veditarea.h
index b8ee6824..0172f8a0 100644
--- a/src/veditarea.h
+++ b/src/veditarea.h
@@ -12,10 +12,8 @@
#include
#include "vnotebook.h"
#include "veditwindow.h"
-#include "vtoc.h"
#include "vnavigationmode.h"
-class VNote;
class VFile;
class VDirectory;
class VFindReplaceDialog;
@@ -26,7 +24,7 @@ class VEditArea : public QWidget, public VNavigationMode
{
Q_OBJECT
public:
- explicit VEditArea(VNote *vnote, QWidget *parent = 0);
+ explicit VEditArea(QWidget *parent = 0);
// Whether @p_file has been opened in edit area.
bool isFileOpened(const VFile *p_file);
@@ -35,17 +33,31 @@ public:
bool closeFile(const VFile *p_file, bool p_forced);
bool closeFile(const VDirectory *p_dir, bool p_forced);
bool closeFile(const VNotebook *p_notebook, bool p_forced);
- // Returns current edit tab.
- VEditTab *currentEditTab();
- // Returns the count of VEditWindow.
- inline int windowCount() const;
+
+ // Return current edit window.
+ VEditWindow *getCurrentWindow() const;
+
+ // Return current edit tab.
+ VEditTab *getCurrentTab() const;
+
+ // Return the @p_tabIdx tab in the @p_winIdx window.
+ VEditTab *getTab(int p_winIdx, int p_tabIdx) const;
+
+ // Return VEditTabInfo of all edit tabs.
+ QVector getAllTabsInfo() const;
+
+ // Return the count of VEditWindow.
+ int windowCount() const;
+
// Returns the index of @p_window.
int windowIndex(const VEditWindow *p_window) const;
// Move tab widget @p_widget from window @p_fromIdx to @p_toIdx.
// @p_widget has been removed from the original window.
// If fail, just delete the p_widget.
void moveTab(QWidget *p_widget, int p_fromIdx, int p_toIdx);
- inline VFindReplaceDialog *getFindReplaceDialog() const;
+
+ VFindReplaceDialog *getFindReplaceDialog() const;
+
// Return selected text of current edit tab.
QString getSelectedText();
void splitCurrentWindow();
@@ -54,7 +66,6 @@ public:
// Return the new current window index, otherwise, return -1.
int focusNextWindow(int p_biaIdx);
void moveCurrentTabOneSplit(bool p_right);
- VEditWindow *getCurrentWindow() const;
// Implementations for VNavigationMode.
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
@@ -62,12 +73,18 @@ public:
void hideNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
+ // Open files @p_files.
+ int openFiles(const QVector &p_files);
+
signals:
// Emit when current window's tab status updated.
void tabStatusUpdated(const VEditTabInfo &p_info);
- void outlineChanged(const VToc &toc);
- void curHeaderChanged(const VAnchor &anchor);
+ // Emit when current window's tab's outline changed.
+ void outlineChanged(const VTableOfContent &p_outline);
+
+ // Emit when current window's tab's current header changed.
+ void currentHeaderChanged(const VHeaderPointer &p_header);
// Emit when want to show message in status bar.
void statusMessage(const QString &p_msg);
@@ -84,13 +101,16 @@ public slots:
// @p_forceMode is true.
// A given file can be opened in multiple split windows. A given file could be
// opened at most in one tab inside a window.
- void openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode = false);
+ VEditTab *openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode = false);
void editFile();
void saveFile();
void readFile();
void saveAndReadFile();
- void handleOutlineItemActivated(const VAnchor &anchor);
+
+ // Scroll current tab to @p_header.
+ void scrollToHeader(const VHeaderPointer &p_header);
+
void handleFileUpdated(const VFile *p_file);
void handleDirectoryUpdated(const VDirectory *p_dir);
void handleNotebookUpdated(const VNotebook *p_notebook);
@@ -102,8 +122,11 @@ private slots:
void handleRemoveSplitRequest(VEditWindow *curWindow);
void handleWindowFocused();
- void handleOutlineChanged(const VToc &toc);
- void handleCurHeaderChanged(const VAnchor &anchor);
+
+ void handleWindowOutlineChanged(const VTableOfContent &p_outline);
+
+ void handleWindowCurrentHeaderChanged(const VHeaderPointer &p_header);
+
void handleFindTextChanged(const QString &p_text, uint p_options);
void handleFindOptionChanged(uint p_options);
void handleFindNext(const QString &p_text, uint p_options, bool p_forward);
@@ -128,14 +151,48 @@ private:
int openFileInWindow(int windowIndex, VFile *p_file, OpenFileMode p_mode);
void setCurrentTab(int windowIndex, int tabIndex, bool setFocus);
void setCurrentWindow(int windowIndex, bool setFocus);
- inline VEditWindow *getWindow(int windowIndex) const;
+
+ VEditWindow *getWindow(int windowIndex) const;
+
void insertSplitWindow(int idx);
void removeSplitWindow(VEditWindow *win);
// Update status of current window.
void updateWindowStatus();
- VNote *vnote;
+ // Init targets for Captain mode.
+ void registerCaptainTargets();
+
+ // Captain mode functions.
+
+ // Activate tab @p_idx.
+ static void activateTabByCaptain(void *p_target, void *p_data, int p_idx);
+
+ static void alternateTabByCaptain(void *p_target, void *p_data);
+
+ static void showOpenedFileListByCaptain(void *p_target, void *p_data);
+
+ static void activateSplitLeftByCaptain(void *p_target, void *p_data);
+
+ static void activateSplitRightByCaptain(void *p_target, void *p_data);
+
+ static void moveTabSplitLeftByCaptain(void *p_target, void *p_data);
+
+ static void moveTabSplitRightByCaptain(void *p_target, void *p_data);
+
+ static void activateNextTabByCaptain(void *p_target, void *p_data);
+
+ static void activatePreviousTabByCaptain(void *p_target, void *p_data);
+
+ static void verticalSplitByCaptain(void *p_target, void *p_data);
+
+ static void removeSplitByCaptain(void *p_target, void *p_data);
+
+ // Evaluate selected text or the word on cursor as magic words.
+ static void evaluateMagicWordsByCaptain(void *p_target, void *p_data);
+
+ // End Captain mode functions.
+
int curWindowIndex;
// Splitter holding multiple split windows
diff --git a/src/veditoperations.cpp b/src/veditoperations.cpp
index 18c87bb3..74e3183d 100644
--- a/src/veditoperations.cpp
+++ b/src/veditoperations.cpp
@@ -90,3 +90,10 @@ void VEditOperations::requestUpdateVimStatus()
{
emit vimStatusUpdated(m_vim);
}
+
+void VEditOperations::setVimMode(VimMode p_mode)
+{
+ if (m_vim && m_editConfig->m_enableVimMode) {
+ m_vim->setMode(p_mode);
+ }
+}
diff --git a/src/veditoperations.h b/src/veditoperations.h
index cc6b2371..8085e2f1 100644
--- a/src/veditoperations.h
+++ b/src/veditoperations.h
@@ -18,11 +18,18 @@ class VEditOperations: public QObject
Q_OBJECT
public:
VEditOperations(VEdit *p_editor, VFile *p_file);
+
virtual ~VEditOperations();
+
virtual bool insertImageFromMimeData(const QMimeData *source) = 0;
+
virtual bool insertImage() = 0;
+
virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 0;
+ virtual bool insertLink(const QString &p_linkText,
+ const QString &p_linkUrl) = 0;
+
// Return true if @p_event has been handled and no need to be further
// processed.
virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0;
@@ -33,6 +40,9 @@ public:
// Insert decoration markers or decorate selected text.
virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);};
+ // Set Vim mode if not NULL.
+ void setVimMode(VimMode p_mode);
+
signals:
// Want to display a template message in status bar.
void statusMessage(const QString &p_msg);
diff --git a/src/vedittab.cpp b/src/vedittab.cpp
index 0f34297c..db8c4400 100644
--- a/src/vedittab.cpp
+++ b/src/vedittab.cpp
@@ -3,12 +3,13 @@
#include
VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent)
- : QWidget(p_parent), m_file(p_file), m_isEditMode(false),
- m_modified(false), m_editArea(p_editArea)
+ : QWidget(p_parent),
+ m_file(p_file),
+ m_isEditMode(false),
+ m_outline(p_file),
+ m_currentHeader(p_file, -1),
+ m_editArea(p_editArea)
{
- m_toc.m_file = m_file;
- m_curHeader.m_file = m_file;
-
connect(qApp, &QApplication::focusChanged,
this, &VEditTab::handleFocusChanged);
}
@@ -24,6 +25,7 @@ void VEditTab::focusTab()
{
focusChild();
emit getFocused();
+ updateStatus();
}
bool VEditTab::isEditMode() const
@@ -33,7 +35,7 @@ bool VEditTab::isEditMode() const
bool VEditTab::isModified() const
{
- return m_modified;
+ return m_file->isModified();
}
VFile *VEditTab::getFile() const
@@ -48,21 +50,13 @@ void VEditTab::handleFocusChanged(QWidget * /* p_old */, QWidget *p_now)
focusChild();
emit getFocused();
+ updateStatus();
} else if (isAncestorOf(p_now)) {
emit getFocused();
+ updateStatus();
}
}
-void VEditTab::requestUpdateCurHeader()
-{
- emit curHeaderChanged(m_curHeader);
-}
-
-void VEditTab::requestUpdateOutline()
-{
- emit outlineChanged(m_toc);
-}
-
void VEditTab::wheelEvent(QWheelEvent *p_event)
{
QPoint angle = p_event->angleDelta();
@@ -78,17 +72,55 @@ void VEditTab::wheelEvent(QWheelEvent *p_event)
p_event->ignore();
}
-void VEditTab::updateStatus()
-{
- m_modified = m_file->isModified();
-
- emit statusUpdated(createEditTabInfo());
-}
-
-VEditTabInfo VEditTab::createEditTabInfo()
+VEditTabInfo VEditTab::fetchTabInfo() const
{
VEditTabInfo info;
- info.m_editTab = this;
+ info.m_editTab = const_cast(this);
return info;
}
+
+const VHeaderPointer &VEditTab::getCurrentHeader() const
+{
+ return m_currentHeader;
+}
+
+const VTableOfContent &VEditTab::getOutline() const
+{
+ return m_outline;
+}
+
+void VEditTab::tryRestoreFromTabInfo(const VEditTabInfo &p_info)
+{
+ if (p_info.m_editTab != this) {
+ m_infoToRestore.clear();
+ return;
+ }
+
+ if (restoreFromTabInfo(p_info)) {
+ m_infoToRestore.clear();
+ return;
+ }
+
+ // Save it and restore later.
+ m_infoToRestore = p_info;
+}
+
+void VEditTab::updateStatus()
+{
+ emit statusUpdated(fetchTabInfo());
+}
+
+void VEditTab::evaluateMagicWords()
+{
+}
+
+bool VEditTab::tabHasFocus() const
+{
+ QWidget *wid = QApplication::focusWidget();
+ return wid == this || isAncestorOf(wid);
+}
+
+void VEditTab::insertLink()
+{
+}
diff --git a/src/vedittab.h b/src/vedittab.h
index 116c814b..23110fa2 100644
--- a/src/vedittab.h
+++ b/src/vedittab.h
@@ -4,7 +4,7 @@
#include
#include
#include
-#include "vtoc.h"
+#include "vtableofcontent.h"
#include "vfile.h"
#include "utils/vvim.h"
#include "vedittabinfo.h"
@@ -37,18 +37,21 @@ public:
void focusTab();
- virtual void requestUpdateOutline();
+ // Whether this tab has focus.
+ bool tabHasFocus() const;
- virtual void requestUpdateCurHeader();
-
- // Scroll to anchor @p_anchor.
- virtual void scrollToAnchor(const VAnchor& p_anchor) = 0;
+ // Scroll to @p_header.
+ // Will emit currentHeaderChanged() if @p_header is valid.
+ virtual void scrollToHeader(const VHeaderPointer &p_header) { Q_UNUSED(p_header) }
VFile *getFile() const;
// User requests to insert image.
virtual void insertImage() = 0;
+ // User requests to insert link.
+ virtual void insertLink();
+
// Search @p_text in current note.
virtual void findText(const QString &p_text, uint p_options, bool p_peek,
bool p_forward = true) = 0;
@@ -69,15 +72,29 @@ public:
virtual void requestUpdateVimStatus() = 0;
// Insert decoration markers or decorate selected text.
- virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);};
+ virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);}
+
+ // Create a filled VEditTabInfo.
+ virtual VEditTabInfo fetchTabInfo() const;
+
+ const VTableOfContent &getOutline() const;
+
+ const VHeaderPointer &getCurrentHeader() const;
+
+ // Restore status from @p_info.
+ // If this tab is not ready yet, it will restore once it is ready.
+ void tryRestoreFromTabInfo(const VEditTabInfo &p_info);
+
+ // Emit signal to update current status.
+ virtual void updateStatus();
+
+ // Called by evaluateMagicWordsByCaptain() to evaluate the magic words.
+ virtual void evaluateMagicWords();
public slots:
// Enter edit mode
virtual void editFile() = 0;
- // Update status of current tab. Emit statusUpdated().
- virtual void updateStatus();
-
protected:
void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE;
@@ -87,23 +104,32 @@ protected:
// Called to zoom in/out content.
virtual void zoom(bool p_zoomIn, qreal p_step = 0.25) = 0;
- // Create a filled VEditTabInfo.
- virtual VEditTabInfo createEditTabInfo();
+ // Restore from @p_fino.
+ // Return true if succeed.
+ virtual bool restoreFromTabInfo(const VEditTabInfo &p_info) = 0;
// File related to this tab.
QPointer m_file;
+
bool m_isEditMode;
- bool m_modified;
- VToc m_toc;
- VAnchor m_curHeader;
+
+ // Table of content of this tab.
+ VTableOfContent m_outline;
+
+ // Current header in m_outline of this tab.
+ VHeaderPointer m_currentHeader;
+
VEditArea *m_editArea;
+ // Tab info to restore from once ready.
+ VEditTabInfo m_infoToRestore;
+
signals:
void getFocused();
- void outlineChanged(const VToc &p_toc);
+ void outlineChanged(const VTableOfContent &p_outline);
- void curHeaderChanged(const VAnchor &p_anchor);
+ void currentHeaderChanged(const VHeaderPointer &p_header);
// The status of current tab has updates.
void statusUpdated(const VEditTabInfo &p_info);
diff --git a/src/vedittabinfo.h b/src/vedittabinfo.h
index 5fc4003b..f80fd623 100644
--- a/src/vedittabinfo.h
+++ b/src/vedittabinfo.h
@@ -6,8 +6,22 @@ class VEditTab;
struct VEditTabInfo
{
VEditTabInfo()
- : m_editTab(NULL), m_cursorBlockNumber(-1), m_cursorPositionInBlock(-1),
- m_blockCount(-1) {}
+ : m_editTab(NULL),
+ m_cursorBlockNumber(-1),
+ m_cursorPositionInBlock(-1),
+ m_blockCount(-1),
+ m_headerIndex(-1)
+ {
+ }
+
+ void clear()
+ {
+ m_editTab = NULL;
+ m_cursorBlockNumber = -1;
+ m_cursorPositionInBlock = -1;
+ m_blockCount = -1;
+ m_headerIndex = -1;
+ }
VEditTab *m_editTab;
@@ -15,6 +29,9 @@ struct VEditTabInfo
int m_cursorBlockNumber;
int m_cursorPositionInBlock;
int m_blockCount;
+
+ // Header index in outline.
+ int m_headerIndex;
};
#endif // VEDITTABINFO_H
diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp
index 857a2606..bc4a9e3a 100644
--- a/src/veditwindow.cpp
+++ b/src/veditwindow.cpp
@@ -2,7 +2,6 @@
#include
#include "veditwindow.h"
#include "vedittab.h"
-#include "vnote.h"
#include "utils/vutils.h"
#include "vorphanfile.h"
#include "vmainwindow.h"
@@ -13,12 +12,14 @@
#include "vfilelist.h"
#include "vconfigmanager.h"
-extern VNote *g_vnote;
extern VConfigManager *g_config;
+extern VMainWindow *g_mainWin;
-VEditWindow::VEditWindow(VNote *vnote, VEditArea *editArea, QWidget *parent)
- : QTabWidget(parent), vnote(vnote), m_editArea(editArea),
- m_curTabWidget(NULL), m_lastTabWidget(NULL)
+VEditWindow::VEditWindow(VEditArea *editArea, QWidget *parent)
+ : QTabWidget(parent),
+ m_editArea(editArea),
+ m_curTabWidget(NULL),
+ m_lastTabWidget(NULL)
{
setAcceptDrops(true);
initTabActions();
@@ -137,9 +138,9 @@ void VEditWindow::initTabActions()
Q_ASSERT(file);
if (file->getType() == FileType::Note) {
VNoteFile *tmpFile = dynamic_cast((VFile *)file);
- g_vnote->getMainWindow()->getFileList()->fileInfo(tmpFile);
+ g_mainWin->getFileList()->fileInfo(tmpFile);
} else if (file->getType() == FileType::Orphan) {
- g_vnote->getMainWindow()->editOrphanFileInfo(file);
+ g_mainWin->editOrphanFileInfo(file);
}
});
@@ -262,8 +263,10 @@ void VEditWindow::removeEditTab(int p_index)
int VEditWindow::insertEditTab(int p_index, VFile *p_file, QWidget *p_page)
{
- int idx = insertTab(p_index, p_page, p_file->getName());
- setTabToolTip(idx, generateTooltip(p_file));
+ int idx = insertTab(p_index,
+ p_page,
+ p_file->getName());
+ updateTabInfo(idx);
return idx;
}
@@ -466,15 +469,17 @@ void VEditWindow::updateTabStatus(int p_index)
if (p_index == -1) {
emit tabStatusUpdated(VEditTabInfo());
- emit outlineChanged(VToc());
- emit curHeaderChanged(VAnchor());
+ emit outlineChanged(VTableOfContent());
+ emit currentHeaderChanged(VHeaderPointer());
return;
}
VEditTab *tab = getTab(p_index);
- tab->updateStatus();
- tab->requestUpdateOutline();
- tab->requestUpdateCurHeader();
+ emit tabStatusUpdated(tab->fetchTabInfo());
+ emit outlineChanged(tab->getOutline());
+ emit currentHeaderChanged(tab->getCurrentHeader());
+
+ updateTabInfo(p_index);
}
void VEditWindow::updateTabInfo(int p_index)
@@ -483,8 +488,7 @@ void VEditWindow::updateTabInfo(int p_index)
const VFile *file = editor->getFile();
bool editMode = editor->isEditMode();
- setTabText(p_index, generateTabText(p_index, file->getName(),
- file->isModified(), file->isModifiable()));
+ setTabText(p_index, generateTabText(p_index, file));
setTabToolTip(p_index, generateTooltip(file));
QString iconUrl(":/resources/icons/reading.svg");
@@ -501,31 +505,28 @@ void VEditWindow::updateAllTabsSequence()
for (int i = 0; i < count(); ++i) {
VEditTab *editor = getTab(i);
const VFile *file = editor->getFile();
- setTabText(i, generateTabText(i, file->getName(),
- file->isModified(), file->isModifiable()));
+ setTabText(i, generateTabText(i, file));
}
}
-// Be requested to report current outline
-void VEditWindow::requestUpdateOutline()
+VTableOfContent VEditWindow::getOutline() const
{
int idx = currentIndex();
if (idx == -1) {
- emit outlineChanged(VToc());
- return;
+ return VTableOfContent();
}
- getTab(idx)->requestUpdateOutline();
+
+ return getTab(idx)->getOutline();
}
-// Be requested to report current header
-void VEditWindow::requestUpdateCurHeader()
+VHeaderPointer VEditWindow::getCurrentHeader() const
{
int idx = currentIndex();
if (idx == -1) {
- emit curHeaderChanged(VAnchor());
- return;
+ return VHeaderPointer();
}
- getTab(idx)->requestUpdateCurHeader();
+
+ return getTab(idx)->getCurrentHeader();
}
// Focus this windows. Try to focus current tab.
@@ -681,44 +682,39 @@ bool VEditWindow::canRemoveSplit()
return splitter->count() > 1;
}
-void VEditWindow::handleOutlineChanged(const VToc &p_toc)
+void VEditWindow::handleTabOutlineChanged(const VTableOfContent &p_outline)
{
- // Only propagate it if it is current tab
- int idx = currentIndex();
- if (idx == -1) {
- emit outlineChanged(VToc());
+ // Only propagate it if it is current tab.
+ VEditTab *tab = getCurrentTab();
+ if (tab) {
+ if (tab->getFile() == p_outline.getFile()) {
+ emit outlineChanged(p_outline);
+ }
+ } else {
+ emit outlineChanged(VTableOfContent());
return;
}
- const VFile *file = getTab(idx)->getFile();
- if (p_toc.m_file == file) {
- emit outlineChanged(p_toc);
- }
}
-void VEditWindow::handleCurHeaderChanged(const VAnchor &p_anchor)
+void VEditWindow::handleTabCurrentHeaderChanged(const VHeaderPointer &p_header)
{
- // Only propagate it if it is current tab
- int idx = currentIndex();
- if (idx == -1) {
- emit curHeaderChanged(VAnchor());
+ // Only propagate it if it is current tab.
+ VEditTab *tab = getCurrentTab();
+ if (tab) {
+ if (tab->getFile() == p_header.m_file) {
+ emit currentHeaderChanged(p_header);
+ }
+ } else {
+ emit currentHeaderChanged(VHeaderPointer());
return;
}
- const VFile *file = getTab(idx)->getFile();
- if (p_anchor.m_file == file) {
- emit curHeaderChanged(p_anchor);
- }
}
-void VEditWindow::scrollCurTab(const VAnchor &p_anchor)
+void VEditWindow::scrollToHeader(const VHeaderPointer &p_header)
{
- int idx = currentIndex();
- if (idx == -1) {
- emit curHeaderChanged(VAnchor());
- return;
- }
- const VFile *file = getTab(idx)->getFile();
- if (file == p_anchor.m_file) {
- getTab(idx)->scrollToAnchor(p_anchor);
+ VEditTab *tab = getCurrentTab();
+ if (tab) {
+ tab->scrollToHeader(p_header);
}
}
@@ -794,22 +790,37 @@ void VEditWindow::updateNotebookInfo(const VNotebook *p_notebook)
}
}
-VEditTab *VEditWindow::currentEditTab()
+VEditTab *VEditWindow::getCurrentTab() const
{
int idx = currentIndex();
if (idx == -1) {
return NULL;
}
+
return getTab(idx);
}
+QVector VEditWindow::getAllTabsInfo() const
+{
+ int nrTab = count();
+
+ QVector tabs;
+ tabs.reserve(nrTab);
+ for (int i = 0; i < nrTab; ++i) {
+ VEditTab *editTab = getTab(i);
+ tabs.push_back(editTab->fetchTabInfo());
+ }
+
+ return tabs;
+}
+
void VEditWindow::handleLocateAct()
{
int tab = m_locateAct->data().toInt();
VEditTab *editor = getTab(tab);
QPointer file = editor->getFile();
if (file->getType() == FileType::Note) {
- vnote->getMainWindow()->locateFile(file);
+ g_mainWin->locateFile(file);
}
}
@@ -901,9 +912,9 @@ void VEditWindow::connectEditTab(const VEditTab *p_tab)
connect(p_tab, &VEditTab::getFocused,
this, &VEditWindow::getFocused);
connect(p_tab, &VEditTab::outlineChanged,
- this, &VEditWindow::handleOutlineChanged);
- connect(p_tab, &VEditTab::curHeaderChanged,
- this, &VEditWindow::handleCurHeaderChanged);
+ this, &VEditWindow::handleTabOutlineChanged);
+ connect(p_tab, &VEditTab::currentHeaderChanged,
+ this, &VEditWindow::handleTabCurrentHeaderChanged);
connect(p_tab, &VEditTab::statusUpdated,
this, &VEditWindow::handleTabStatusUpdated);
connect(p_tab, &VEditTab::statusMessage,
@@ -1014,7 +1025,7 @@ void VEditWindow::dropEvent(QDropEvent *p_event)
if (!files.isEmpty()) {
focusWindow();
- g_vnote->getMainWindow()->openExternalFiles(files);
+ g_mainWin->openFiles(files);
}
p_event->acceptProposedAction();
diff --git a/src/veditwindow.h b/src/veditwindow.h
index 40e6b176..8e58ebb0 100644
--- a/src/veditwindow.h
+++ b/src/veditwindow.h
@@ -8,11 +8,9 @@
#include
#include "vnotebook.h"
#include "vedittab.h"
-#include "vtoc.h"
#include "vconstants.h"
#include "vnotefile.h"
-class VNote;
class QPushButton;
class QActionGroup;
class VEditArea;
@@ -21,7 +19,7 @@ class VEditWindow : public QTabWidget
{
Q_OBJECT
public:
- explicit VEditWindow(VNote *vnote, VEditArea *editArea, QWidget *parent = 0);
+ explicit VEditWindow(VEditArea *editArea, QWidget *parent = 0);
int findTabByFile(const VFile *p_file) const;
int openFile(VFile *p_file, OpenFileMode p_mode);
bool closeFile(const VFile *p_file, bool p_forced);
@@ -32,15 +30,29 @@ public:
void readFile();
void saveAndReadFile();
bool closeAllFiles(bool p_forced);
- void requestUpdateOutline();
- void requestUpdateCurHeader();
+
+ // Return outline of current tab.
+ VTableOfContent getOutline() const;
+
+ // Return current header of current tab.
+ VHeaderPointer getCurrentHeader() const;
+
// Focus to current tab's editor
void focusWindow();
- void scrollCurTab(const VAnchor &p_anchor);
+
+ // Scroll current tab to header @p_header.
+ void scrollToHeader(const VHeaderPointer &p_header);
+
void updateFileInfo(const VFile *p_file);
void updateDirectoryInfo(const VDirectory *p_dir);
void updateNotebookInfo(const VNotebook *p_notebook);
- VEditTab *currentEditTab();
+
+ VEditTab *getCurrentTab() const;
+
+ VEditTab *getTab(int tabIndex) const;
+
+ QVector getAllTabsInfo() const;
+
// Insert a tab with @p_widget. @p_widget is a fully initialized VEditTab.
bool addEditTab(QWidget *p_widget);
// Set whether it is the current window.
@@ -53,7 +65,6 @@ public:
bool activateTab(int p_sequence);
// Switch to previous activated tab.
bool alternateTab();
- VEditTab *getTab(int tabIndex) const;
// Ask tab @p_index to update its status and propogate.
// The status here means tab status, outline, current header.
@@ -79,8 +90,10 @@ signals:
void requestRemoveSplit(VEditWindow *curWindow);
// This widget or its children get the focus
void getFocused();
- void outlineChanged(const VToc &toc);
- void curHeaderChanged(const VAnchor &anchor);
+
+ void outlineChanged(const VTableOfContent &p_outline);
+
+ void currentHeaderChanged(const VHeaderPointer &p_header);
// Emit when want to show message in status bar.
void statusMessage(const QString &p_msg);
@@ -100,8 +113,11 @@ private slots:
void handleCurrentIndexChanged(int p_index);
void contextMenuRequested(QPoint pos);
void tabListJump(VFile *p_file);
- void handleOutlineChanged(const VToc &p_toc);
- void handleCurHeaderChanged(const VAnchor &p_anchor);
+
+ void handleTabOutlineChanged(const VTableOfContent &p_outline);
+
+ void handleTabCurrentHeaderChanged(const VHeaderPointer &p_header);
+
void updateSplitMenu();
void tabbarContextMenuRequested(QPoint p_pos);
void handleLocateAct();
@@ -124,9 +140,11 @@ private:
int insertEditTab(int p_index, VFile *p_file, QWidget *p_page);
int appendEditTab(VFile *p_file, QWidget *p_page);
int openFileInTab(VFile *p_file, OpenFileMode p_mode);
- inline QString generateTooltip(const VFile *p_file) const;
- inline QString generateTabText(int p_index, const QString &p_name,
- bool p_modified, bool p_modifiable) const;
+
+ QString generateTooltip(const VFile *p_file) const;
+
+ QString generateTabText(int p_index, const VFile *p_file) const;
+
bool canRemoveSplit();
// Move tab at @p_tabIdx one split window.
@@ -135,6 +153,7 @@ private:
// and move the tab to the new split.
void moveTabOneSplit(int p_tabIdx, bool p_right);
+ // Update info of tab @p_idx according to the state of the editor and file.
void updateTabInfo(int p_idx);
// Update the sequence number of all the tabs.
@@ -143,7 +162,6 @@ private:
// Connect the signals of VEditTab to this VEditWindow.
void connectEditTab(const VEditTab *p_tab);
- VNote *vnote;
VEditArea *m_editArea;
// These two members are only used for alternateTab().
@@ -197,11 +215,16 @@ inline QString VEditWindow::generateTooltip(const VFile *p_file) const
}
}
-inline QString VEditWindow::generateTabText(int p_index, const QString &p_name,
- bool p_modified, bool p_modifiable) const
+inline QString VEditWindow::generateTabText(int p_index, const VFile *p_file) const
{
- QString seq = QString::number(p_index + c_tabSequenceBase, 10);
- return seq + ". " + p_name + (p_modifiable ? (p_modified ? "*" : "") : "#");
+ if (!p_file) {
+ return "";
+ }
+
+ return QString("%1.%2%3").arg(QString::number(p_index + c_tabSequenceBase, 10))
+ .arg(p_file->getName())
+ .arg(p_file->isModifiable()
+ ? (p_file->isModified() ? "*" : "") : "#");
}
#endif // VEDITWINDOW_H
diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp
index 5629ad37..54621ab1 100644
--- a/src/vfilelist.cpp
+++ b/src/vfilelist.cpp
@@ -263,7 +263,7 @@ void VFileList::fileInfo(VNoteFile *p_file)
void VFileList::fillItem(QListWidgetItem *p_item, const VNoteFile *p_file)
{
- unsigned long long ptr = (long long)p_file;
+ qulonglong ptr = (qulonglong)p_file;
p_item->setData(Qt::UserRole, ptr);
p_item->setToolTip(p_file->getName());
p_item->setText(p_file->getName());
@@ -330,7 +330,9 @@ void VFileList::newFile()
info = info + "
" + tr("Note with name ending with \"%1\" will be treated as Markdown type.")
.arg(suffixStr);
QString defaultName = QString("new_note.%1").arg(defaultSuf);
- defaultName = VUtils::getFileNameWithSequence(m_directory->fetchPath(), defaultName);
+ defaultName = VUtils::getFileNameWithSequence(m_directory->fetchPath(),
+ defaultName,
+ true);
VNewFileDialog dialog(tr("Create Note"), info, defaultName, m_directory, this);
if (dialog.exec() == QDialog::Accepted) {
VNoteFile *file = m_directory->createFile(dialog.getNameInput());
@@ -349,7 +351,7 @@ void VFileList::newFile()
qWarning() << "fail to open newly-created note" << file->getName();
} else {
Q_ASSERT(file->getContent().isEmpty());
- QString content = QString("# %1\n").arg(QFileInfo(file->getName()).baseName());
+ QString content = QString("# %1\n").arg(QFileInfo(file->getName()).completeBaseName());
file->setContent(content);
if (!file->save()) {
qWarning() << "fail to write to newly-created note" << file->getName();
@@ -372,7 +374,7 @@ void VFileList::newFile()
// Move cursor down if content has been inserted.
if (contentInserted) {
- const VMdTab *tab = dynamic_cast(editArea->currentEditTab());
+ const VMdTab *tab = dynamic_cast(editArea->getCurrentTab());
if (tab) {
VMdEdit *edit = dynamic_cast(tab->getEditor());
if (edit && edit->getFile() == file) {
@@ -621,7 +623,7 @@ bool VFileList::importFiles(const QStringList &p_files, QString *p_errMsg)
QString name = VUtils::fileNameFromPath(file);
Q_ASSERT(!name.isEmpty());
- name = VUtils::getFileNameWithSequence(dirPath, name);
+ name = VUtils::getFileNameWithSequence(dirPath, name, true);
QString targetFilePath = dir.filePath(name);
bool ret = VUtils::copyFile(file, targetFilePath, false);
if (!ret) {
@@ -763,10 +765,14 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
}
// Rename it to xxx_copy.md.
- fileName = VUtils::generateCopiedFileName(file->fetchBasePath(), fileName);
+ fileName = VUtils::generateCopiedFileName(file->fetchBasePath(),
+ fileName,
+ true);
} else {
// Rename it to xxx_copy.md if needed.
- fileName = VUtils::generateCopiedFileName(p_destDir->fetchPath(), fileName);
+ fileName = VUtils::generateCopiedFileName(p_destDir->fetchPath(),
+ fileName,
+ true);
}
QString msg;
diff --git a/src/vfilelist.h b/src/vfilelist.h
index ac24d9eb..6fa0258f 100644
--- a/src/vfilelist.h
+++ b/src/vfilelist.h
@@ -44,7 +44,9 @@ public:
// Locate @p_file in the list widget.
bool locateFile(const VNoteFile *p_file);
- inline const VDirectory *currentDirectory() const;
+ const VDirectory *currentDirectory() const;
+
+ QWidget *getContentWidget() const;
// Implementations for VNavigationMode.
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
@@ -199,4 +201,9 @@ inline const VDirectory *VFileList::currentDirectory() const
return m_directory;
}
+inline QWidget *VFileList::getContentWidget() const
+{
+ return fileList;
+}
+
#endif // VFILELIST_H
diff --git a/src/vfilesessioninfo.cpp b/src/vfilesessioninfo.cpp
new file mode 100644
index 00000000..bdffad1f
--- /dev/null
+++ b/src/vfilesessioninfo.cpp
@@ -0,0 +1,74 @@
+#include "vfilesessioninfo.h"
+
+#include
+
+#include "vedittabinfo.h"
+#include "vtableofcontent.h"
+#include "vedittab.h"
+
+
+VFileSessionInfo::VFileSessionInfo()
+ : m_mode(OpenFileMode::Read),
+ m_headerIndex(-1),
+ m_cursorBlockNumber(-1),
+ m_cursorPositionInBlock(-1)
+{
+}
+
+VFileSessionInfo::VFileSessionInfo(const QString &p_file,
+ OpenFileMode p_mode)
+ : m_file(p_file),
+ m_mode(p_mode),
+ m_headerIndex(-1),
+ m_cursorBlockNumber(-1),
+ m_cursorPositionInBlock(-1)
+{
+}
+
+// Fetch VFileSessionInfo from @p_tabInfo.
+VFileSessionInfo VFileSessionInfo::fromEditTabInfo(const VEditTabInfo *p_tabInfo)
+{
+ Q_ASSERT(p_tabInfo);
+ VEditTab *tab = p_tabInfo->m_editTab;
+ VFileSessionInfo info(tab->getFile()->fetchPath(),
+ tab->isEditMode() ? OpenFileMode::Edit : OpenFileMode::Read);
+ info.m_headerIndex = p_tabInfo->m_headerIndex;
+ info.m_cursorBlockNumber = p_tabInfo->m_cursorBlockNumber;
+ info.m_cursorPositionInBlock = p_tabInfo->m_cursorPositionInBlock;
+
+ return info;
+}
+
+void VFileSessionInfo::toEditTabInfo(VEditTabInfo *p_tabInfo) const
+{
+ p_tabInfo->m_headerIndex = m_headerIndex;
+ p_tabInfo->m_cursorBlockNumber = m_cursorBlockNumber;
+ p_tabInfo->m_cursorPositionInBlock = m_cursorPositionInBlock;
+}
+
+VFileSessionInfo VFileSessionInfo::fromSettings(const QSettings *p_settings)
+{
+ VFileSessionInfo info;
+ info.m_file = p_settings->value(FileSessionConfig::c_file).toString();
+ int tmpMode = p_settings->value(FileSessionConfig::c_mode).toInt();
+ if (tmpMode >= (int)OpenFileMode::Read && tmpMode < (int)OpenFileMode::Invalid) {
+ info.m_mode = (OpenFileMode)tmpMode;
+ } else {
+ info.m_mode = OpenFileMode::Read;
+ }
+
+ info.m_headerIndex = p_settings->value(FileSessionConfig::c_headerIndex).toInt();
+ info.m_cursorBlockNumber = p_settings->value(FileSessionConfig::c_cursorBlockNumber).toInt();
+ info.m_cursorPositionInBlock = p_settings->value(FileSessionConfig::c_cursorPositionInBlock).toInt();
+
+ return info;
+}
+
+void VFileSessionInfo::toSettings(QSettings *p_settings) const
+{
+ p_settings->setValue(FileSessionConfig::c_file, m_file);
+ p_settings->setValue(FileSessionConfig::c_mode, (int)m_mode);
+ p_settings->setValue(FileSessionConfig::c_headerIndex, m_headerIndex);
+ p_settings->setValue(FileSessionConfig::c_cursorBlockNumber, m_cursorBlockNumber);
+ p_settings->setValue(FileSessionConfig::c_cursorPositionInBlock, m_cursorPositionInBlock);
+}
diff --git a/src/vfilesessioninfo.h b/src/vfilesessioninfo.h
new file mode 100644
index 00000000..f0fd8673
--- /dev/null
+++ b/src/vfilesessioninfo.h
@@ -0,0 +1,57 @@
+#ifndef VFILESESSIONINFO_H
+#define VFILESESSIONINFO_H
+
+#include "vconstants.h"
+
+struct VEditTabInfo;
+class QSettings;
+
+namespace FileSessionConfig
+{
+ static const QString c_file = "file";
+ static const QString c_mode = "mode";
+
+ // Index in outline of the anchor.
+ static const QString c_headerIndex = "header_index";
+
+ static const QString c_cursorBlockNumber = "cursor_block_number";
+ static const QString c_cursorPositionInBlock = "cursor_position_in_block";
+}
+
+// Information about an opened file (session).
+class VFileSessionInfo
+{
+public:
+ VFileSessionInfo();
+
+ VFileSessionInfo(const QString &p_file,
+ OpenFileMode p_mode);
+
+ // Fetch VFileSessionInfo from @p_tabInfo.
+ static VFileSessionInfo fromEditTabInfo(const VEditTabInfo *p_tabInfo);
+
+ // Fill corresponding fields of @p_tabInfo.
+ void toEditTabInfo(VEditTabInfo *p_tabInfo) const;
+
+ // Fetch VFileSessionInfo from @p_settings.
+ static VFileSessionInfo fromSettings(const QSettings *p_settings);
+
+ void toSettings(QSettings *p_settings) const;
+
+ // Absolute path of the file.
+ QString m_file;
+
+ // Mode of this file in this session.
+ OpenFileMode m_mode;
+
+ // Index in outline of the header.
+ int m_headerIndex;
+
+ // Block number of cursor block.
+ int m_cursorBlockNumber;
+
+ // Position in block of cursor.
+ int m_cursorPositionInBlock;
+};
+
+#endif // VFILESESSIONINFO_H
diff --git a/src/vhtmltab.cpp b/src/vhtmltab.cpp
index d41c32d3..6c8fb40c 100644
--- a/src/vhtmltab.cpp
+++ b/src/vhtmltab.cpp
@@ -32,7 +32,7 @@ void VHtmlTab::setupUI()
{
m_editor = new VEdit(m_file, this);
connect(m_editor, &VEdit::textChanged,
- this, &VHtmlTab::handleTextChanged);
+ this, &VHtmlTab::updateStatus);
connect(m_editor, &VEdit::saveAndRead,
this, &VHtmlTab::saveAndRead);
connect(m_editor, &VEdit::discardAndRead,
@@ -52,17 +52,6 @@ void VHtmlTab::setupUI()
setLayout(mainLayout);
}
-void VHtmlTab::handleTextChanged()
-{
- V_ASSERT(m_file->isModifiable());
-
- if (m_modified) {
- return;
- }
-
- updateStatus();
-}
-
void VHtmlTab::showFileReadMode()
{
m_isEditMode = false;
@@ -194,10 +183,6 @@ void VHtmlTab::discardAndRead()
readFile();
}
-void VHtmlTab::scrollToAnchor(const VAnchor & /* p_anchor */)
-{
-}
-
void VHtmlTab::insertImage()
{
}
@@ -252,3 +237,12 @@ void VHtmlTab::requestUpdateVimStatus()
{
m_editor->requestUpdateVimStatus();
}
+
+bool VHtmlTab::restoreFromTabInfo(const VEditTabInfo &p_info)
+{
+ if (p_info.m_editTab != this) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/vhtmltab.h b/src/vhtmltab.h
index 9024dfd6..80471fa5 100644
--- a/src/vhtmltab.h
+++ b/src/vhtmltab.h
@@ -26,9 +26,6 @@ public:
// Save file.
bool saveFile() Q_DECL_OVERRIDE;
- // Scroll to anchor @p_anchor.
- void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE;
-
void insertImage() Q_DECL_OVERRIDE;
// Search @p_text in current note.
@@ -53,9 +50,6 @@ public slots:
void editFile() Q_DECL_OVERRIDE;
private slots:
- // Handle text changed in m_editor.
- void handleTextChanged();
-
// m_editor requests to save changes and enter read mode.
void saveAndRead();
@@ -78,6 +72,10 @@ private:
// Focus the proper child widget.
void focusChild() Q_DECL_OVERRIDE;
+ // Restore from @p_fino.
+ // Return true if succeed.
+ bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
+
VEdit *m_editor;
};
#endif // VHTMLTAB_H
diff --git a/src/vlineedit.cpp b/src/vlineedit.cpp
new file mode 100644
index 00000000..ea716ce0
--- /dev/null
+++ b/src/vlineedit.cpp
@@ -0,0 +1,48 @@
+#include "vlineedit.h"
+
+#include
+#include
+
+#include "utils/vmetawordmanager.h"
+
+
+extern VMetaWordManager *g_mwMgr;
+
+VLineEdit::VLineEdit(QWidget *p_parent)
+ : QLineEdit(p_parent)
+{
+ init();
+}
+
+VLineEdit::VLineEdit(const QString &p_contents, QWidget *p_parent)
+ : QLineEdit(p_contents, p_parent)
+{
+ init();
+}
+
+void VLineEdit::handleTextChanged(const QString &p_text)
+{
+ m_evaluatedText = g_mwMgr->evaluate(p_text);
+ qDebug() << "evaluate text:" << m_evaluatedText;
+
+ if (m_evaluatedText == p_text) {
+ return;
+ }
+
+ // Display tooltip at bottom-left.
+ QPoint pos = mapToGlobal(QPoint(0, height()));
+ QToolTip::showText(pos, m_evaluatedText, this);
+}
+
+void VLineEdit::init()
+{
+ m_evaluatedText = g_mwMgr->evaluate(text());
+
+ connect(this, &QLineEdit::textChanged,
+ this, &VLineEdit::handleTextChanged);
+}
+
+const QString VLineEdit::getEvaluatedText() const
+{
+ return m_evaluatedText;
+}
diff --git a/src/vlineedit.h b/src/vlineedit.h
new file mode 100644
index 00000000..81a8a86c
--- /dev/null
+++ b/src/vlineedit.h
@@ -0,0 +1,29 @@
+#ifndef VLINEEDIT_H
+#define VLINEEDIT_H
+
+#include
+
+
+// QLineEdit with meta word support.
+class VLineEdit : public QLineEdit
+{
+ Q_OBJECT
+public:
+ explicit VLineEdit(QWidget *p_parent = nullptr);
+
+ VLineEdit(const QString &p_contents, QWidget *p_parent = Q_NULLPTR);
+
+ // Return the evaluated text.
+ const QString getEvaluatedText() const;
+
+private slots:
+ void handleTextChanged(const QString &p_text);
+
+private:
+ void init();
+
+ // We should keep the evaluated text identical with what's displayed.
+ QString m_evaluatedText;
+};
+
+#endif // VLINEEDIT_H
diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp
index fe1a0eec..068c2440 100644
--- a/src/vmainwindow.cpp
+++ b/src/vmainwindow.cpp
@@ -29,6 +29,9 @@
#include "vnotefile.h"
#include "vbuttonwithwidget.h"
#include "vattachmentlist.h"
+#include "vfilesessioninfo.h"
+
+VMainWindow *g_mainWin;
extern VConfigManager *g_config;
@@ -40,16 +43,29 @@ const int VMainWindow::c_sharedMemTimerInterval = 1000;
extern QFile g_logFile;
#endif
+
VMainWindow::VMainWindow(VSingleInstanceGuard *p_guard, QWidget *p_parent)
- : QMainWindow(p_parent), m_onePanel(false), m_guard(p_guard),
+ : QMainWindow(p_parent), m_guard(p_guard),
m_windowOldState(Qt::WindowNoState), m_requestQuit(false)
{
+ qsrand(QDateTime::currentDateTime().toTime_t());
+
+ g_mainWin = this;
+
setWindowIcon(QIcon(":/resources/icons/vnote.ico"));
vnote = new VNote(this);
g_vnote = vnote;
vnote->initPalette(palette());
initPredefinedColorPixmaps();
+ if (g_config->getEnableCompactMode()) {
+ m_panelViewState = PanelViewState::CompactMode;
+ } else {
+ m_panelViewState = PanelViewState::TwoPanels;
+ }
+
+ initCaptain();
+
setupUI();
initMenuBar();
@@ -58,13 +74,16 @@ VMainWindow::VMainWindow(VSingleInstanceGuard *p_guard, QWidget *p_parent)
initAvatar();
restoreStateAndGeometry();
+
+ changePanelView(m_panelViewState);
+
setContextMenuPolicy(Qt::NoContextMenu);
notebookSelector->update();
- initCaptain();
-
initSharedMemoryWatcher();
+
+ registerCaptainAndNavigationTargets();
}
void VMainWindow::initSharedMemoryWatcher()
@@ -83,43 +102,82 @@ void VMainWindow::initCaptain()
// VCaptain should be visible to accpet key focus. But VCaptain
// may hide other widgets.
m_captain = new VCaptain(this);
- connect(m_captain, &VCaptain::captainModeChanged,
- this, &VMainWindow::handleCaptainModeChanged);
+}
+void VMainWindow::registerCaptainAndNavigationTargets()
+{
m_captain->registerNavigationTarget(notebookSelector);
m_captain->registerNavigationTarget(directoryTree);
- m_captain->registerNavigationTarget(fileList);
+ m_captain->registerNavigationTarget(m_fileList);
m_captain->registerNavigationTarget(editArea);
m_captain->registerNavigationTarget(outline);
+
+ // Register Captain mode targets.
+ m_captain->registerCaptainTarget(tr("AttachmentList"),
+ g_config->getCaptainShortcutKeySequence("AttachmentList"),
+ this,
+ showAttachmentListByCaptain);
+ m_captain->registerCaptainTarget(tr("LocateCurrentFile"),
+ g_config->getCaptainShortcutKeySequence("LocateCurrentFile"),
+ this,
+ locateCurrentFileByCaptain);
+ m_captain->registerCaptainTarget(tr("ExpandMode"),
+ g_config->getCaptainShortcutKeySequence("ExpandMode"),
+ this,
+ toggleExpandModeByCaptain);
+ m_captain->registerCaptainTarget(tr("OnePanelView"),
+ g_config->getCaptainShortcutKeySequence("OnePanelView"),
+ this,
+ toggleOnePanelViewByCaptain);
+ m_captain->registerCaptainTarget(tr("DiscardAndRead"),
+ g_config->getCaptainShortcutKeySequence("DiscardAndRead"),
+ this,
+ discardAndReadByCaptain);
+ m_captain->registerCaptainTarget(tr("ToolsDock"),
+ g_config->getCaptainShortcutKeySequence("ToolsDock"),
+ this,
+ toggleToolsDockByCaptain);
+ m_captain->registerCaptainTarget(tr("CloseNote"),
+ g_config->getCaptainShortcutKeySequence("CloseNote"),
+ this,
+ closeFileByCaptain);
+ m_captain->registerCaptainTarget(tr("ShortcutsHelp"),
+ g_config->getCaptainShortcutKeySequence("ShortcutsHelp"),
+ this,
+ shortcutsHelpByCaptain);
+ m_captain->registerCaptainTarget(tr("FlushLogFile"),
+ g_config->getCaptainShortcutKeySequence("FlushLogFile"),
+ this,
+ flushLogFileByCaptain);
}
void VMainWindow::setupUI()
{
QWidget *directoryPanel = setupDirectoryPanel();
- fileList = new VFileList();
- fileList->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
+ m_fileList = new VFileList();
+ m_fileList->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
- editArea = new VEditArea(vnote);
+ editArea = new VEditArea();
editArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_findReplaceDialog = editArea->getFindReplaceDialog();
- fileList->setEditArea(editArea);
+ m_fileList->setEditArea(editArea);
directoryTree->setEditArea(editArea);
- notebookSelector->setEditArea(editArea);
// Main Splitter
- mainSplitter = new QSplitter();
- mainSplitter->setObjectName("MainSplitter");
- mainSplitter->addWidget(directoryPanel);
- mainSplitter->addWidget(fileList);
- mainSplitter->addWidget(editArea);
- mainSplitter->setStretchFactor(0, 0);
- mainSplitter->setStretchFactor(1, 0);
- mainSplitter->setStretchFactor(2, 1);
+ m_mainSplitter = new QSplitter();
+ m_mainSplitter->setObjectName("MainSplitter");
+ m_mainSplitter->addWidget(directoryPanel);
+ m_mainSplitter->addWidget(m_fileList);
+ setTabOrder(directoryTree, m_fileList->getContentWidget());
+ m_mainSplitter->addWidget(editArea);
+ m_mainSplitter->setStretchFactor(0, 0);
+ m_mainSplitter->setStretchFactor(1, 0);
+ m_mainSplitter->setStretchFactor(2, 1);
// Signals
connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
- fileList, &VFileList::setDirectory);
+ m_fileList, &VFileList::setDirectory);
connect(directoryTree, &VDirectoryTree::directoryUpdated,
editArea, &VEditArea::handleDirectoryUpdated);
@@ -133,11 +191,11 @@ void VMainWindow::setupUI()
}
});
- connect(fileList, &VFileList::fileClicked,
+ connect(m_fileList, &VFileList::fileClicked,
editArea, &VEditArea::openFile);
- connect(fileList, &VFileList::fileCreated,
+ connect(m_fileList, &VFileList::fileCreated,
editArea, &VEditArea::openFile);
- connect(fileList, &VFileList::fileUpdated,
+ connect(m_fileList, &VFileList::fileUpdated,
editArea, &VEditArea::handleFileUpdated);
connect(editArea, &VEditArea::tabStatusUpdated,
this, &VMainWindow::handleAreaTabStatusUpdated);
@@ -148,7 +206,7 @@ void VMainWindow::setupUI()
connect(m_findReplaceDialog, &VFindReplaceDialog::findTextChanged,
this, &VMainWindow::handleFindDialogTextChanged);
- setCentralWidget(mainSplitter);
+ setCentralWidget(m_mainSplitter);
m_vimIndicator = new VVimIndicator(this);
m_vimIndicator->hide();
@@ -165,26 +223,49 @@ void VMainWindow::setupUI()
QWidget *VMainWindow::setupDirectoryPanel()
{
+ // Notebook selector.
notebookLabel = new QLabel(tr("Notebooks"));
notebookLabel->setProperty("TitleLabel", true);
notebookLabel->setProperty("NotebookPanel", true);
- directoryLabel = new QLabel(tr("Folders"));
- directoryLabel->setProperty("TitleLabel", true);
- directoryLabel->setProperty("NotebookPanel", true);
- notebookSelector = new VNotebookSelector(vnote);
+ notebookSelector = new VNotebookSelector();
notebookSelector->setObjectName("NotebookSelector");
notebookSelector->setProperty("NotebookPanel", true);
notebookSelector->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
+ // Navigation panel.
+ directoryLabel = new QLabel(tr("Folders"));
+ directoryLabel->setProperty("TitleLabel", true);
+ directoryLabel->setProperty("NotebookPanel", true);
+
directoryTree = new VDirectoryTree;
directoryTree->setProperty("NotebookPanel", true);
+ QVBoxLayout *naviLayout = new QVBoxLayout;
+ naviLayout->addWidget(directoryLabel);
+ naviLayout->addWidget(directoryTree);
+ naviLayout->setContentsMargins(0, 0, 0, 0);
+ naviLayout->setSpacing(0);
+ QWidget *naviWidget = new QWidget();
+ naviWidget->setLayout(naviLayout);
+
+ QWidget *tmpWidget = new QWidget();
+
+ // Compact splitter.
+ m_naviSplitter = new QSplitter();
+ m_naviSplitter->setOrientation(Qt::Vertical);
+ m_naviSplitter->setObjectName("NaviSplitter");
+ m_naviSplitter->addWidget(naviWidget);
+ m_naviSplitter->addWidget(tmpWidget);
+ m_naviSplitter->setStretchFactor(0, 0);
+ m_naviSplitter->setStretchFactor(1, 1);
+
+ tmpWidget->hide();
+
QVBoxLayout *nbLayout = new QVBoxLayout;
nbLayout->addWidget(notebookLabel);
nbLayout->addWidget(notebookSelector);
- nbLayout->addWidget(directoryLabel);
- nbLayout->addWidget(directoryTree);
+ nbLayout->addWidget(m_naviSplitter);
nbLayout->setContentsMargins(0, 0, 0, 0);
nbLayout->setSpacing(0);
QWidget *nbContainer = new QWidget();
@@ -203,6 +284,7 @@ QWidget *VMainWindow::setupDirectoryPanel()
connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
this, &VMainWindow::handleCurrentDirectoryChanged);
+
return nbContainer;
}
@@ -226,24 +308,60 @@ void VMainWindow::initViewToolBar(QSize p_iconSize)
viewToolBar->setIconSize(p_iconSize);
}
+ m_viewActGroup = new QActionGroup(this);
QAction *onePanelViewAct = new QAction(QIcon(":/resources/icons/one_panel.svg"),
- tr("&Single Panel"), this);
+ tr("&Single Panel"),
+ m_viewActGroup);
onePanelViewAct->setStatusTip(tr("Display only the notes list panel"));
onePanelViewAct->setToolTip(tr("Single Panel (Ctrl+E P)"));
- connect(onePanelViewAct, &QAction::triggered,
- this, &VMainWindow::onePanelView);
+ onePanelViewAct->setCheckable(true);
+ onePanelViewAct->setData((int)PanelViewState::SinglePanel);
QAction *twoPanelViewAct = new QAction(QIcon(":/resources/icons/two_panels.svg"),
- tr("&Two Panels"), this);
+ tr("&Two Panels"),
+ m_viewActGroup);
twoPanelViewAct->setStatusTip(tr("Display both the folders and notes list panel"));
twoPanelViewAct->setToolTip(tr("Two Panels (Ctrl+E P)"));
- connect(twoPanelViewAct, &QAction::triggered,
- this, &VMainWindow::twoPanelView);
+ twoPanelViewAct->setCheckable(true);
+ twoPanelViewAct->setData((int)PanelViewState::TwoPanels);
+
+ QAction *compactViewAct = new QAction(QIcon(":/resources/icons/compact_mode.svg"),
+ tr("&Compact Mode"),
+ m_viewActGroup);
+ compactViewAct->setStatusTip(tr("Integrate the folders and notes list panel in one column"));
+ compactViewAct->setCheckable(true);
+ compactViewAct->setData((int)PanelViewState::CompactMode);
+
+ connect(m_viewActGroup, &QActionGroup::triggered,
+ this, [this](QAction *p_action) {
+ if (!p_action) {
+ return;
+ }
+
+ int act = p_action->data().toInt();
+ switch (act) {
+ case (int)PanelViewState::SinglePanel:
+ onePanelView();
+ break;
+
+ case (int)PanelViewState::TwoPanels:
+ twoPanelView();
+ break;
+
+ case (int)PanelViewState::CompactMode:
+ compactModeView();
+ break;
+
+ default:
+ break;
+ }
+ });
QMenu *panelMenu = new QMenu(this);
panelMenu->setToolTipsVisible(true);
panelMenu->addAction(onePanelViewAct);
panelMenu->addAction(twoPanelViewAct);
+ panelMenu->addAction(compactViewAct);
expandViewAct = new QAction(QIcon(":/resources/icons/expand.svg"),
tr("Expand (Ctrl+E E)"), this);
@@ -251,11 +369,16 @@ void VMainWindow::initViewToolBar(QSize p_iconSize)
expandViewAct->setCheckable(true);
expandViewAct->setMenu(panelMenu);
connect(expandViewAct, &QAction::triggered,
- this, &VMainWindow::expandPanelView);
+ this, [this](bool p_checked) {
+ // Recover m_panelViewState or change to expand mode.
+ changePanelView(p_checked ? PanelViewState::ExpandMode
+ : m_panelViewState);
+ });
viewToolBar->addAction(expandViewAct);
}
+// Enable/disable all actions of @p_widget.
static void setActionsEnabled(QWidget *p_widget, bool p_enabled)
{
Q_ASSERT(p_widget);
@@ -276,6 +399,22 @@ void VMainWindow::initEditToolBar(QSize p_iconSize)
m_editToolBar->addSeparator();
+ m_headingSequenceAct = new QAction(QIcon(":/resources/icons/heading_sequence.svg"),
+ tr("Heading Sequence"),
+ this);
+ m_headingSequenceAct->setStatusTip(tr("Enable heading sequence in current note in edit mode"));
+ m_headingSequenceAct->setCheckable(true);
+ connect(m_headingSequenceAct, &QAction::triggered,
+ this, [this](bool p_checked){
+ if (isHeadingSequenceApplicable()) {
+ VMdTab *tab = dynamic_cast(m_curTab.data());
+ Q_ASSERT(tab);
+ tab->enableHeadingSequence(p_checked);
+ }
+ });
+
+ m_editToolBar->addAction(m_headingSequenceAct);
+
QAction *boldAct = new QAction(QIcon(":/resources/icons/bold.svg"),
tr("Bold (Ctrl+B)"), this);
boldAct->setStatusTip(tr("Insert bold text or change selected text to bold"));
@@ -324,6 +463,48 @@ void VMainWindow::initEditToolBar(QSize p_iconSize)
m_editToolBar->addAction(inlineCodeAct);
+ QAction *codeBlockAct = new QAction(QIcon(":/resources/icons/code_block.svg"),
+ tr("Code Block (Ctrl+M)"),
+ this);
+ codeBlockAct->setStatusTip(tr("Insert fenced code block text or wrap selected text into a fenced code block"));
+ connect(codeBlockAct, &QAction::triggered,
+ this, [this](){
+ if (m_curTab) {
+ m_curTab->decorateText(TextDecoration::CodeBlock);
+ }
+ });
+
+ m_editToolBar->addAction(codeBlockAct);
+
+ m_editToolBar->addSeparator();
+
+ // Insert link.
+ QAction *insetLinkAct = new QAction(QIcon(":/resources/icons/link.svg"),
+ tr("Insert Link (Ctrl+L)"), this);
+ insetLinkAct->setStatusTip(tr("Insert a link"));
+ connect(insetLinkAct, &QAction::triggered,
+ this, [this]() {
+ if (m_curTab) {
+ m_curTab->insertLink();
+ }
+ });
+
+ m_editToolBar->addAction(insetLinkAct);
+
+ // Insert image.
+ QAction *insertImageAct = new QAction(QIcon(":/resources/icons/insert_image.svg"),
+ tr("Insert Image"),
+ this);
+ insertImageAct->setStatusTip(tr("Insert an image from file or URL"));
+ connect(insertImageAct, &QAction::triggered,
+ this, [this]() {
+ if (m_curTab) {
+ m_curTab->insertImage();
+ }
+ });
+
+ m_editToolBar->addAction(insertImageAct);
+
QAction *toggleAct = m_editToolBar->toggleViewAction();
toggleAct->setToolTip(tr("Toggle the edit toolbar"));
viewMenu->addAction(toggleAct);
@@ -378,7 +559,7 @@ void VMainWindow::initFileToolBar(QSize p_iconSize)
qDebug() << "set NewNote shortcut to" << keySeq;
newNoteAct->setShortcut(QKeySequence(keySeq));
connect(newNoteAct, &QAction::triggered,
- fileList, &VFileList::newFile);
+ m_fileList, &VFileList::newFile);
noteInfoAct = new QAction(QIcon(":/resources/icons/note_info_tb.svg"),
tr("Note &Info"), this);
@@ -397,11 +578,7 @@ void VMainWindow::initFileToolBar(QSize p_iconSize)
m_closeNoteShortcut = new QShortcut(QKeySequence(keySeq), this);
m_closeNoteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
connect(m_closeNoteShortcut, &QShortcut::activated,
- this, [this](){
- if (m_curFile) {
- editArea->closeFile(m_curFile, false);
- }
- });
+ this, &VMainWindow::closeCurrentFile);
editNoteAct = new QAction(QIcon(":/resources/icons/edit_note.svg"),
tr("&Edit"), this);
@@ -489,7 +666,7 @@ void VMainWindow::initHelpMenu()
QAction *shortcutAct = new QAction(tr("&Shortcuts Help"), this);
shortcutAct->setToolTip(tr("View information about shortcut keys"));
connect(shortcutAct, &QAction::triggered,
- this, &VMainWindow::shortcutHelp);
+ this, &VMainWindow::shortcutsHelp);
QAction *mdGuideAct = new QAction(tr("&Markdown Guide"), this);
mdGuideAct->setToolTip(tr("A quick guide of Markdown syntax"));
@@ -692,7 +869,7 @@ void VMainWindow::initFileMenu()
// Update lastPath
lastPath = QFileInfo(files[0]).path();
- openExternalFiles(files);
+ openFiles(VUtils::filterFilePathsToOpen(files));
});
fileMenu->addAction(openAct);
@@ -738,20 +915,15 @@ void VMainWindow::initFileMenu()
fileMenu->addAction(settingsAct);
- QAction *editConfigAct = new QAction(tr("Edit Configuration File"), this);
- editConfigAct->setToolTip(tr("View and edit configuration file of VNote (vnote.ini)"));
- connect(editConfigAct, &QAction::triggered,
+ QAction *openConfigAct = new QAction(tr("Open Configuration Folder"), this);
+ openConfigAct->setToolTip(tr("Open configuration folder of VNote"));
+ connect(openConfigAct, &QAction::triggered,
this, [this](){
-#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);
});
- fileMenu->addAction(editConfigAct);
+ fileMenu->addAction(openConfigAct);
QAction *customShortcutAct = new QAction(tr("Custom Shortcuts"), this);
customShortcutAct->setToolTip(tr("Custom some standard shortcuts"));
@@ -805,13 +977,6 @@ void VMainWindow::initEditMenu()
QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
editMenu->setToolTipsVisible(true);
- // Insert image.
- m_insertImageAct = newAction(QIcon(":/resources/icons/insert_image.svg"),
- tr("Insert &Image"), this);
- m_insertImageAct->setToolTip(tr("Insert an image from file into current note"));
- connect(m_insertImageAct, &QAction::triggered,
- this, &VMainWindow::insertImage);
-
// Find/Replace.
m_findReplaceAct = newAction(QIcon(":/resources/icons/find_replace.svg"),
tr("Find/Replace"), this);
@@ -934,10 +1099,6 @@ void VMainWindow::initEditMenu()
connect(trailingSapceAct, &QAction::triggered,
this, &VMainWindow::changeHighlightTrailingSapce);
- editMenu->addAction(m_insertImageAct);
- editMenu->addSeparator();
- m_insertImageAct->setEnabled(false);
-
QMenu *findReplaceMenu = editMenu->addMenu(tr("Find/Replace"));
findReplaceMenu->setToolTipsVisible(true);
findReplaceMenu->addAction(m_findReplaceAct);
@@ -1022,16 +1183,19 @@ void VMainWindow::initEditMenu()
void VMainWindow::initDockWindows()
{
toolDock = new QDockWidget(tr("Tools"), this);
- toolDock->setObjectName("tools_dock");
+ toolDock->setObjectName("ToolsDock");
toolDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
toolBox = new QToolBox(this);
+
+ // Outline tree.
outline = new VOutline(this);
connect(editArea, &VEditArea::outlineChanged,
outline, &VOutline::updateOutline);
+ connect(editArea, &VEditArea::currentHeaderChanged,
+ outline, &VOutline::updateCurrentHeader);
connect(outline, &VOutline::outlineItemActivated,
- editArea, &VEditArea::handleOutlineItemActivated);
- connect(editArea, &VEditArea::curHeaderChanged,
- outline, &VOutline::updateCurHeader);
+ editArea, &VEditArea::scrollToHeader);
+
toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline"));
toolDock->setWidget(toolBox);
addDockWidget(Qt::RightDockWidgetArea, toolDock);
@@ -1064,7 +1228,7 @@ void VMainWindow::importNoteFromFile()
lastPath = QFileInfo(files[0]).path();
QString msg;
- if (!fileList->importFiles(files, &msg)) {
+ if (!m_fileList->importFiles(files, &msg)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to create notes for all the files."),
@@ -1616,61 +1780,64 @@ void VMainWindow::setCodeBlockStyle(QAction *p_action)
}
}
-void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
- bool p_editMode)
+void VMainWindow::updateActionsStateFromTab(const VEditTab *p_tab)
{
- bool systemFile = p_file
- && p_file->getType() == FileType::Orphan
- && dynamic_cast(p_file)->isSystemFile();
+ const VFile *file = p_tab ? p_tab->getFile() : NULL;
+ bool editMode = p_tab ? p_tab->isEditMode() : false;
+ bool systemFile = file
+ && file->getType() == FileType::Orphan
+ && dynamic_cast(file)->isSystemFile();
- m_printAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
- m_exportAsPDFAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
+ m_printAct->setEnabled(file && file->getDocType() == DocType::Markdown);
+ m_exportAsPDFAct->setEnabled(file && file->getDocType() == DocType::Markdown);
- discardExitAct->setVisible(p_file && p_editMode);
- saveExitAct->setVisible(p_file && p_editMode);
- editNoteAct->setEnabled(p_file && p_file->isModifiable() && !p_editMode);
+ discardExitAct->setVisible(file && editMode);
+ saveExitAct->setVisible(file && editMode);
+ editNoteAct->setEnabled(file && file->isModifiable() && !editMode);
editNoteAct->setVisible(!saveExitAct->isVisible());
- saveNoteAct->setEnabled(p_file && p_editMode);
- deleteNoteAct->setEnabled(p_file && p_file->getType() == FileType::Note);
- noteInfoAct->setEnabled(p_file && !systemFile);
+ saveNoteAct->setEnabled(file && editMode);
+ deleteNoteAct->setEnabled(file && file->getType() == FileType::Note);
+ noteInfoAct->setEnabled(file && !systemFile);
- m_attachmentBtn->setEnabled(p_file && p_file->getType() == FileType::Note);
+ m_attachmentBtn->setEnabled(file && file->getType() == FileType::Note);
- m_insertImageAct->setEnabled(p_file && p_editMode);
+ setActionsEnabled(m_editToolBar, file && editMode);
- setActionsEnabled(m_editToolBar, p_file && p_editMode);
+ // Handle heading sequence act independently.
+ m_headingSequenceAct->setEnabled(isHeadingSequenceApplicable());
+ const VMdTab *mdTab = dynamic_cast(p_tab);
+ m_headingSequenceAct->setChecked(mdTab && mdTab->isHeadingSequenceEnabled());
// Find/Replace
- m_findReplaceAct->setEnabled(p_file);
- m_findNextAct->setEnabled(p_file);
- m_findPreviousAct->setEnabled(p_file);
- m_replaceAct->setEnabled(p_file && p_editMode);
- m_replaceFindAct->setEnabled(p_file && p_editMode);
- m_replaceAllAct->setEnabled(p_file && p_editMode);
+ m_findReplaceAct->setEnabled(file);
+ m_findNextAct->setEnabled(file);
+ m_findPreviousAct->setEnabled(file);
+ m_replaceAct->setEnabled(file && editMode);
+ m_replaceFindAct->setEnabled(file && editMode);
+ m_replaceAllAct->setEnabled(file && editMode);
- if (!p_file) {
+ if (!file) {
m_findReplaceDialog->closeDialog();
}
}
void VMainWindow::handleAreaTabStatusUpdated(const VEditTabInfo &p_info)
{
- bool editMode = false;
m_curTab = p_info.m_editTab;
if (m_curTab) {
m_curFile = m_curTab->getFile();
- editMode = m_curTab->isEditMode();
} else {
m_curFile = NULL;
}
- updateActionStateFromTabStatusChange(m_curFile, editMode);
+ updateActionsStateFromTab(m_curTab);
m_attachmentList->setFile(dynamic_cast(m_curFile.data()));
QString title;
if (m_curFile) {
- m_findReplaceDialog->updateState(m_curFile->getDocType(), editMode);
+ m_findReplaceDialog->updateState(m_curFile->getDocType(),
+ m_curTab->isEditMode());
if (m_curFile->getType() == FileType::Note) {
const VNoteFile *tmpFile = dynamic_cast((VFile *)m_curFile);
@@ -1694,65 +1861,120 @@ void VMainWindow::handleAreaTabStatusUpdated(const VEditTabInfo &p_info)
void VMainWindow::onePanelView()
{
- changeSplitterView(1);
- expandViewAct->setChecked(false);
- m_onePanel = true;
+ m_panelViewState = PanelViewState::SinglePanel;
+ g_config->setEnableCompactMode(false);
+ changePanelView(m_panelViewState);
}
void VMainWindow::twoPanelView()
{
- changeSplitterView(2);
- expandViewAct->setChecked(false);
- m_onePanel = false;
+ m_panelViewState = PanelViewState::TwoPanels;
+ g_config->setEnableCompactMode(false);
+ changePanelView(m_panelViewState);
}
-void VMainWindow::toggleOnePanelView()
+void VMainWindow::compactModeView()
{
- if (m_onePanel) {
- twoPanelView();
+ m_panelViewState = PanelViewState::CompactMode;
+ g_config->setEnableCompactMode(true);
+ changePanelView(m_panelViewState);
+}
+
+void VMainWindow::enableCompactMode(bool p_enabled)
+{
+ const int fileListIdx = 1;
+ bool isCompactMode = m_naviSplitter->indexOf(m_fileList) != -1;
+ if (p_enabled) {
+ // Change to compact mode.
+ if (isCompactMode) {
+ return;
+ }
+
+ // Take m_fileList out of m_mainSplitter.
+ QWidget *tmpWidget = new QWidget(this);
+ Q_ASSERT(fileListIdx == m_mainSplitter->indexOf(m_fileList));
+ m_fileList->hide();
+ m_mainSplitter->replaceWidget(fileListIdx, tmpWidget);
+ tmpWidget->hide();
+
+ // Insert m_fileList into m_naviSplitter.
+ QWidget *wid = m_naviSplitter->replaceWidget(fileListIdx, m_fileList);
+ delete wid;
+
+ m_fileList->show();
} else {
- onePanelView();
+ // Disable compact mode and go back to two panels view.
+ if (!isCompactMode) {
+ return;
+ }
+
+ // Take m_fileList out of m_naviSplitter.
+ Q_ASSERT(fileListIdx == m_naviSplitter->indexOf(m_fileList));
+ QWidget *tmpWidget = new QWidget(this);
+ m_fileList->hide();
+ m_naviSplitter->replaceWidget(fileListIdx, tmpWidget);
+ tmpWidget->hide();
+
+ // Insert m_fileList into m_mainSplitter.
+ QWidget *wid = m_mainSplitter->replaceWidget(fileListIdx, m_fileList);
+ delete wid;
+
+ m_fileList->show();
}
+
+ // Set Tab order.
+ setTabOrder(directoryTree, m_fileList->getContentWidget());
}
-void VMainWindow::expandPanelView(bool p_checked)
+void VMainWindow::changePanelView(PanelViewState p_state)
{
- int nrSplits = 0;
- if (p_checked) {
- nrSplits = 0;
- } else {
- if (m_onePanel) {
- nrSplits = 1;
+ switch (p_state) {
+ case PanelViewState::ExpandMode:
+ m_mainSplitter->widget(0)->hide();
+ m_mainSplitter->widget(1)->hide();
+ m_mainSplitter->widget(2)->show();
+ break;
+
+ case PanelViewState::SinglePanel:
+ enableCompactMode(false);
+
+ m_mainSplitter->widget(0)->hide();
+ m_mainSplitter->widget(1)->show();
+ m_mainSplitter->widget(2)->show();
+ break;
+
+ case PanelViewState::TwoPanels:
+ enableCompactMode(false);
+
+ m_mainSplitter->widget(0)->show();
+ m_mainSplitter->widget(1)->show();
+ m_mainSplitter->widget(2)->show();
+ break;
+
+ case PanelViewState::CompactMode:
+ m_mainSplitter->widget(0)->show();
+ m_mainSplitter->widget(1)->hide();
+ m_mainSplitter->widget(2)->show();
+
+ enableCompactMode(true);
+ break;
+
+ default:
+ break;
+ }
+
+ // Change the action state.
+ QList acts = m_viewActGroup->actions();
+ for (auto & act : acts) {
+ if (act->data().toInt() == (int)p_state) {
+ act->setChecked(true);
} else {
- nrSplits = 2;
+ act->setChecked(false);
}
}
- changeSplitterView(nrSplits);
-}
-void VMainWindow::changeSplitterView(int nrPanel)
-{
- switch (nrPanel) {
- case 0:
- // Expand
- mainSplitter->widget(0)->hide();
- mainSplitter->widget(1)->hide();
- mainSplitter->widget(2)->show();
- break;
- case 1:
- // Single panel
- mainSplitter->widget(0)->hide();
- mainSplitter->widget(1)->show();
- mainSplitter->widget(2)->show();
- break;
- case 2:
- // Two panels
- mainSplitter->widget(0)->show();
- mainSplitter->widget(1)->show();
- mainSplitter->widget(2)->show();
- break;
- default:
- qWarning() << "invalid panel number" << nrPanel;
+ if (p_state != PanelViewState::ExpandMode) {
+ expandViewAct->setChecked(false);
}
}
@@ -1772,7 +1994,7 @@ void VMainWindow::curEditFileInfo()
if (m_curFile->getType() == FileType::Note) {
VNoteFile *file = dynamic_cast((VFile *)m_curFile);
Q_ASSERT(file);
- fileList->fileInfo(file);
+ m_fileList->fileInfo(file);
} else if (m_curFile->getType() == FileType::Orphan) {
VOrphanFile *file = dynamic_cast((VFile *)m_curFile);
Q_ASSERT(file);
@@ -1789,7 +2011,7 @@ void VMainWindow::deleteCurNote()
}
VNoteFile *file = dynamic_cast((VFile *)m_curFile);
- fileList->deleteFile(file);
+ m_fileList->deleteFile(file);
}
void VMainWindow::closeEvent(QCloseEvent *event)
@@ -1828,12 +2050,40 @@ void VMainWindow::closeEvent(QCloseEvent *event)
}
if (isExit || !m_trayIcon->isVisible()) {
+ // Get all the opened tabs.
+ bool saveOpenedNotes = g_config->getStartupPageType() == StartupPageType::ContinueLeftOff;
+ QVector fileInfos;
+ QVector tabs;
+ if (saveOpenedNotes) {
+ tabs = editArea->getAllTabsInfo();
+
+ fileInfos.reserve(tabs.size());
+
+ for (auto const & tab : tabs) {
+ // Skip system file.
+ VFile *file = tab.m_editTab->getFile();
+ if (file->getType() == FileType::Orphan
+ && dynamic_cast(file)->isSystemFile()) {
+ continue;
+ }
+
+ VFileSessionInfo info = VFileSessionInfo::fromEditTabInfo(&tab);
+ fileInfos.push_back(info);
+
+ qDebug() << "file session:" << info.m_file << (info.m_mode == OpenFileMode::Edit);
+ }
+ }
+
if (!editArea->closeAllFiles(false)) {
// Fail to close all the opened files, cancel closing app.
event->ignore();
return;
}
+ if (saveOpenedNotes) {
+ g_config->setLastOpenedFiles(fileInfos);
+ }
+
QMainWindow::closeEvent(event);
} else {
hide();
@@ -1843,14 +2093,17 @@ void VMainWindow::closeEvent(QCloseEvent *event)
void VMainWindow::saveStateAndGeometry()
{
- // In one panel view, it will save the wrong state that the directory tree
- // panel has a width of zero.
- twoPanelView();
-
g_config->setMainWindowGeometry(saveGeometry());
g_config->setMainWindowState(saveState());
g_config->setToolsDockChecked(toolDock->isVisible());
- g_config->setMainSplitterState(mainSplitter->saveState());
+
+ // In one panel view, it will save the wrong state that the directory tree
+ // panel has a width of zero.
+ changePanelView(PanelViewState::TwoPanels);
+ g_config->setMainSplitterState(m_mainSplitter->saveState());
+
+ changePanelView(PanelViewState::CompactMode);
+ g_config->setNaviSplitterState(m_naviSplitter->saveState());
}
void VMainWindow::restoreStateAndGeometry()
@@ -1864,9 +2117,15 @@ void VMainWindow::restoreStateAndGeometry()
restoreState(state);
}
toolDock->setVisible(g_config->getToolsDockChecked());
+
const QByteArray &splitterState = g_config->getMainSplitterState();
if (!splitterState.isEmpty()) {
- mainSplitter->restoreState(splitterState);
+ m_mainSplitter->restoreState(splitterState);
+ }
+
+ const QByteArray &naviSplitterState = g_config->getNaviSplitterState();
+ if (!naviSplitterState.isEmpty()) {
+ m_naviSplitter->restoreState(naviSplitterState);
}
}
@@ -1908,7 +2167,7 @@ void VMainWindow::keyPressEvent(QKeyEvent *event)
void VMainWindow::repositionAvatar()
{
- int diameter = mainSplitter->pos().y();
+ int diameter = m_mainSplitter->pos().y();
int x = width() - diameter - 5;
int y = 0;
qDebug() << "avatar:" << diameter << x << y;
@@ -1917,15 +2176,6 @@ void VMainWindow::repositionAvatar()
m_avatar->show();
}
-void VMainWindow::insertImage()
-{
- if (!m_curTab) {
- return;
- }
- Q_ASSERT(m_curTab == editArea->currentEditTab());
- m_curTab->insertImage();
-}
-
bool VMainWindow::locateFile(VFile *p_file)
{
bool ret = false;
@@ -1942,30 +2192,25 @@ bool VMainWindow::locateFile(VFile *p_file)
VDirectory *dir = file->getDirectory();
if (directoryTree->locateDirectory(dir)) {
- while (fileList->currentDirectory() != dir) {
+ while (m_fileList->currentDirectory() != dir) {
QCoreApplication::sendPostedEvents();
}
- if (fileList->locateFile(file)) {
+ if (m_fileList->locateFile(file)) {
ret = true;
- fileList->setFocus();
+ m_fileList->setFocus();
}
}
}
// Open the directory and file panels after location.
- twoPanelView();
-
- return ret;
-}
-
-bool VMainWindow::locateCurrentFile()
-{
- if (m_curFile) {
- return locateFile(m_curFile);
+ if (m_panelViewState == PanelViewState::CompactMode) {
+ compactModeView();
+ } else {
+ twoPanelView();
}
- return false;
+ return ret;
}
void VMainWindow::handleFindDialogTextChanged(const QString &p_text, uint /* p_options */)
@@ -1992,18 +2237,6 @@ void VMainWindow::viewSettings()
settingsDialog.exec();
}
-void VMainWindow::handleCaptainModeChanged(bool p_enabled)
-{
- static QString normalBaseColor = m_avatar->getBaseColor();
- static QString captainModeColor = vnote->getColorFromPalette("Purple5");
-
- if (p_enabled) {
- m_avatar->updateBaseColor(captainModeColor);
- } else {
- m_avatar->updateBaseColor(normalBaseColor);
- }
-}
-
void VMainWindow::closeCurrentFile()
{
if (m_curFile) {
@@ -2061,7 +2294,7 @@ void VMainWindow::enableImageCaption(bool p_checked)
g_config->setEnableImageCaption(p_checked);
}
-void VMainWindow::shortcutHelp()
+void VMainWindow::shortcutsHelp()
{
QString locale = VUtils::getLocale();
QString docName = VNote::c_shortcutsDocFile_en;
@@ -2173,9 +2406,8 @@ bool VMainWindow::tryOpenInternalFile(const QString &p_filePath)
return false;
}
-void VMainWindow::openExternalFiles(const QStringList &p_files, bool p_forceOrphan)
+void VMainWindow::openFiles(const QStringList &p_files, bool p_forceOrphan)
{
- qDebug() << "open external files" << p_files;
for (int i = 0; i < p_files.size(); ++i) {
VFile *file = NULL;
if (!p_forceOrphan) {
@@ -2207,7 +2439,7 @@ void VMainWindow::checkSharedMemory()
QStringList files = m_guard->fetchFilesToOpen();
if (!files.isEmpty()) {
qDebug() << "shared memory fetch files" << files;
- openExternalFiles(files);
+ openFiles(files);
// Eliminate the signal.
m_guard->fetchAskedToShow();
@@ -2273,9 +2505,128 @@ void VMainWindow::showMainWindow()
this->activateWindow();
}
-void VMainWindow::showAttachmentList()
+void VMainWindow::openStartupPages()
{
- if (m_attachmentBtn->isEnabled()) {
- m_attachmentBtn->showPopupWidget();
+ StartupPageType type = g_config->getStartupPageType();
+ switch (type) {
+ case StartupPageType::ContinueLeftOff:
+ {
+ QVector files = g_config->getLastOpenedFiles();
+ qDebug() << "open" << files.size() << "last opened files";
+ editArea->openFiles(files);
+ break;
+ }
+
+ case StartupPageType::SpecificPages:
+ {
+ QStringList pagesToOpen = VUtils::filterFilePathsToOpen(g_config->getStartupPages());
+ qDebug() << "open startup pages" << pagesToOpen;
+ openFiles(pagesToOpen);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+bool VMainWindow::isHeadingSequenceApplicable() const
+{
+ if (!m_curTab) {
+ return false;
+ }
+
+ Q_ASSERT(m_curFile);
+
+ if (!m_curFile->isModifiable()
+ || m_curFile->getDocType() != DocType::Markdown) {
+ return false;
+ }
+
+ return true;
+}
+
+// Popup the attachment list if it is enabled.
+void VMainWindow::showAttachmentListByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ if (obj->m_attachmentBtn->isEnabled()) {
+ obj->m_attachmentBtn->showPopupWidget();
+ }
+}
+
+void VMainWindow::locateCurrentFileByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ if (obj->m_curFile) {
+ obj->locateFile(obj->m_curFile);
+ }
+}
+
+void VMainWindow::toggleExpandModeByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ obj->expandViewAct->trigger();
+}
+
+void VMainWindow::toggleOnePanelViewByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ if (obj->m_panelViewState == PanelViewState::TwoPanels) {
+ obj->onePanelView();
+ } else {
+ obj->twoPanelView();
+ }
+}
+
+void VMainWindow::discardAndReadByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ if (obj->m_curFile) {
+ obj->discardExitAct->trigger();
+ }
+}
+
+void VMainWindow::toggleToolsDockByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ obj->toolDock->setVisible(!obj->toolDock->isVisible());
+}
+
+void VMainWindow::closeFileByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ obj->closeCurrentFile();
+}
+
+void VMainWindow::shortcutsHelpByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_data);
+ VMainWindow *obj = static_cast(p_target);
+ obj->shortcutsHelp();
+}
+
+void VMainWindow::flushLogFileByCaptain(void *p_target, void *p_data)
+{
+ Q_UNUSED(p_target);
+ Q_UNUSED(p_data);
+
+#if defined(QT_NO_DEBUG)
+ // Flush g_logFile.
+ g_logFile.flush();
+#endif
+}
+
+void VMainWindow::promptNewNotebookIfEmpty()
+{
+ if (vnote->getNotebooks().isEmpty()) {
+ notebookSelector->newNotebook();
}
}
diff --git a/src/vmainwindow.h b/src/vmainwindow.h
index a0e1bc61..799eb0a6 100644
--- a/src/vmainwindow.h
+++ b/src/vmainwindow.h
@@ -38,6 +38,15 @@ class QShortcut;
class VButtonWithWidget;
class VAttachmentList;
+enum class PanelViewState
+{
+ ExpandMode,
+ SinglePanel,
+ TwoPanels,
+ CompactMode,
+ Invalid
+};
+
class VMainWindow : public QMainWindow
{
Q_OBJECT
@@ -51,18 +60,17 @@ public:
// Returns true if the location succeeds.
bool locateFile(VFile *p_file);
- // Returns true if the location succeeds.
- bool locateCurrentFile();
-
VFileList *getFileList() const;
+ VEditArea *getEditArea() const;
+
// View and edit the information of @p_file, which is an orphan file.
void editOrphanFileInfo(VFile *p_file);
- // Open external files @p_files as orphan files.
+ // Open files @p_files as orphan files or internal note files.
// If @p_forceOrphan is false, for each file, VNote will try to find out if
// it is a note inside VNote. If yes, VNote will open it as internal file.
- void openExternalFiles(const QStringList &p_files, bool p_forceOrphan = false);
+ void openFiles(const QStringList &p_files, bool p_forceOrphan = false);
// Try to open @p_filePath as internal note.
bool tryOpenInternalFile(const QString &p_filePath);
@@ -70,15 +78,23 @@ public:
// Show a temporary message in status bar.
void showStatusMessage(const QString &p_msg);
- // Popup the attachment list if it is enabled.
- void showAttachmentList();
+ // Open startup pages according to configuration.
+ void openStartupPages();
+
+ VCaptain *getCaptain() const;
+
+ // Prompt user for new notebook if there is no notebook.
+ void promptNewNotebookIfEmpty();
private slots:
void importNoteFromFile();
void viewSettings();
void changeMarkdownConverter(QAction *action);
void aboutMessage();
- void shortcutHelp();
+
+ // Display shortcuts help.
+ void shortcutsHelp();
+
void changeExpandTab(bool checked);
void setTabStopWidth(QAction *action);
void setEditorBackgroundColor(QAction *action);
@@ -105,17 +121,15 @@ private slots:
void changeHighlightTrailingSapce(bool p_checked);
void onePanelView();
void twoPanelView();
- void expandPanelView(bool p_checked);
+ void compactModeView();
void curEditFileInfo();
void deleteCurNote();
void handleCurrentDirectoryChanged(const VDirectory *p_dir);
void handleCurrentNotebookChanged(const VNotebook *p_notebook);
- void insertImage();
void handleFindDialogTextChanged(const QString &p_text, uint p_options);
void openFindDialog();
void enableMermaid(bool p_checked);
void enableMathjax(bool p_checked);
- void handleCaptainModeChanged(bool p_enabled);
void changeAutoIndent(bool p_checked);
void changeAutoList(bool p_checked);
void changeVimMode(bool p_checked);
@@ -127,10 +141,14 @@ private slots:
void printNote();
void exportAsPDF();
+ // Set the panel view properly.
+ void enableCompactMode(bool p_enabled);
+
// Handle Vim status updated.
void handleVimStatusUpdated(const VVim *p_vim);
// Handle the status update of the current tab of VEditArea.
+ // Will be called frequently.
void handleAreaTabStatusUpdated(const VEditTabInfo &p_info);
// Check the shared memory between different instances to see if we have
@@ -142,6 +160,9 @@ private slots:
// Restore main window.
void showMainWindow();
+ // Close current note.
+ void closeCurrentFile();
+
protected:
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
@@ -186,17 +207,19 @@ private:
void initEditorLineNumberMenu(QMenu *p_menu);
void initEditorStyleMenu(QMenu *p_emnu);
- void changeSplitterView(int nrPanel);
void updateWindowTitle(const QString &str);
- void updateActionStateFromTabStatusChange(const VFile *p_file,
- bool p_editMode);
+
+ // Update state of actions according to @p_tab.
+ void updateActionsStateFromTab(const VEditTab *p_tab);
+
void saveStateAndGeometry();
void restoreStateAndGeometry();
void repositionAvatar();
+ // Should init VCaptain before setupUI().
void initCaptain();
- void toggleOnePanelView();
- void closeCurrentFile();
+
+ void registerCaptainAndNavigationTargets();
// Update status bar information.
void updateStatusInfo(const VEditTabInfo &p_info);
@@ -213,6 +236,37 @@ private:
// Init system tray icon and correspondign context menu.
void initTrayIcon();
+ // Change the panel view according to @p_state.
+ // Will not change m_panelViewState.
+ void changePanelView(PanelViewState p_state);
+
+ // Whether heading sequence is applicable to current tab.
+ // Only available for writable Markdown file.
+ bool isHeadingSequenceApplicable() const;
+
+ // Captain mode functions.
+
+ // Popup the attachment list if it is enabled.
+ static void showAttachmentListByCaptain(void *p_target, void *p_data);
+
+ static void locateCurrentFileByCaptain(void *p_target, void *p_data);
+
+ static void toggleExpandModeByCaptain(void *p_target, void *p_data);
+
+ static void toggleOnePanelViewByCaptain(void *p_target, void *p_data);
+
+ static void discardAndReadByCaptain(void *p_target, void *p_data);
+
+ static void toggleToolsDockByCaptain(void *p_target, void *p_data);
+
+ static void closeFileByCaptain(void *p_target, void *p_data);
+
+ static void shortcutsHelpByCaptain(void *p_target, void *p_data);
+
+ static void flushLogFileByCaptain(void *p_target, void *p_data);
+
+ // End Captain mode functions.
+
VNote *vnote;
QPointer m_curFile;
QPointer m_curTab;
@@ -222,10 +276,18 @@ private:
QLabel *notebookLabel;
QLabel *directoryLabel;
VNotebookSelector *notebookSelector;
- VFileList *fileList;
+ VFileList *m_fileList;
VDirectoryTree *directoryTree;
- QSplitter *mainSplitter;
+
+ // Splitter for directory | files | edit.
+ QSplitter *m_mainSplitter;
+
+ // Splitter for directory | files.
+ // Move directory and file panel in one compact vertical split.
+ QSplitter *m_naviSplitter;
+
VEditArea *editArea;
+
QDockWidget *toolDock;
QToolBox *toolBox;
VOutline *outline;
@@ -234,8 +296,8 @@ private:
VVimIndicator *m_vimIndicator;
VTabIndicator *m_tabIndicator;
- // Whether it is one panel or two panles.
- bool m_onePanel;
+ // SinglePanel, TwoPanels, CompactMode.
+ PanelViewState m_panelViewState;
// Actions
QAction *newRootDirAct;
@@ -251,7 +313,6 @@ private:
QAction *m_printAct;
QAction *m_exportAsPDFAct;
- QAction *m_insertImageAct;
QAction *m_findReplaceAct;
QAction *m_findNextAct;
QAction *m_findPreviousAct;
@@ -261,6 +322,9 @@ private:
QAction *m_autoIndentAct;
+ // Enable heading sequence for current note.
+ QAction *m_headingSequenceAct;
+
// Act group for render styles.
QActionGroup *m_renderStyleActs;
@@ -269,6 +333,9 @@ private:
// Act group for code block render styles.
QActionGroup *m_codeBlockStyleActs;
+ // Act group for panel view actions.
+ QActionGroup *m_viewActGroup;
+
QShortcut *m_closeNoteShortcut;
// Menus
@@ -306,7 +373,17 @@ private:
inline VFileList *VMainWindow::getFileList() const
{
- return fileList;
+ return m_fileList;
+}
+
+inline VEditArea *VMainWindow::getEditArea() const
+{
+ return editArea;
+}
+
+inline VCaptain *VMainWindow::getCaptain() const
+{
+ return m_captain;
}
#endif // VMAINWINDOW_H
diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp
index 0bb54218..2ca757a0 100644
--- a/src/vmdedit.cpp
+++ b/src/vmdedit.cpp
@@ -5,7 +5,7 @@
#include "vmdeditoperations.h"
#include "vnote.h"
#include "vconfigmanager.h"
-#include "vtoc.h"
+#include "vtableofcontent.h"
#include "utils/vutils.h"
#include "utils/veditutils.h"
#include "utils/vpreviewutils.h"
@@ -35,7 +35,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
document());
connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
- this, &VMdEdit::updateOutline);
+ this, &VMdEdit::updateHeaders);
// After highlight, the cursor may trun into non-visible. We should make it visible
// in this case.
@@ -74,7 +74,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
this, &VEdit::vimStatusUpdated);
connect(this, &VMdEdit::cursorPositionChanged,
- this, &VMdEdit::updateCurHeader);
+ this, &VMdEdit::updateCurrentHeader);
connect(QApplication::clipboard(), &QClipboard::changed,
this, &VMdEdit::handleClipboardChanged);
@@ -111,7 +111,7 @@ void VMdEdit::beginEdit()
setReadOnly(false);
}
- updateOutline(m_mdHighlighter->getHeaderRegions());
+ updateHeaders(m_mdHighlighter->getHeaderRegions());
}
void VMdEdit::endEdit()
@@ -345,43 +345,9 @@ void VMdEdit::clearUnusedImages()
m_initImages.clear();
}
-int VMdEdit::currentCursorHeader() const
+void VMdEdit::updateCurrentHeader()
{
- if (m_headers.isEmpty()) {
- return -1;
- }
-
- int curLine = textCursor().block().firstLineNumber();
- int i = 0;
- for (i = m_headers.size() - 1; i >= 0; --i) {
- if (!m_headers[i].isEmpty()) {
- if (m_headers[i].lineNumber <= curLine) {
- break;
- }
- }
- }
-
- if (i == -1) {
- return -1;
- } else {
- Q_ASSERT(m_headers[i].index == i);
- return i;
- }
-}
-
-void VMdEdit::updateCurHeader()
-{
- if (m_headers.isEmpty()) {
- return;
- }
-
- int idx = currentCursorHeader();
- if (idx == -1) {
- emit curHeaderChanged(VAnchor(m_file, "", -1, -1));
- return;
- }
-
- emit curHeaderChanged(VAnchor(m_file, "", m_headers[idx].lineNumber, m_headers[idx].index));
+ emit currentHeaderChanged(textCursor().block().blockNumber());
}
static void addHeaderSequence(QVector &p_sequence, int p_level, int p_baseLevel)
@@ -448,11 +414,11 @@ static void insertSequenceToHeader(QTextBlock p_block,
}
}
-void VMdEdit::updateOutline(const QVector &p_headerRegions)
+void VMdEdit::updateHeaders(const QVector &p_headerRegions)
{
QTextDocument *doc = document();
- QVector headers;
+ QVector headers;
QVector headerBlockNumbers;
QVector headerSequences;
if (!p_headerRegions.isEmpty()) {
@@ -480,8 +446,10 @@ void VMdEdit::updateOutline(const QVector &p_headerRegions)
if ((block.userState() == HighlightBlockState::Normal) &&
headerReg.exactMatch(block.text())) {
int level = headerReg.cap(1).length();
- VHeader header(level, headerReg.cap(2).trimmed(),
- "", block.firstLineNumber(), headers.size());
+ VTableOfContentItem header(headerReg.cap(2).trimmed(),
+ level,
+ block.blockNumber(),
+ headers.size());
headers.append(header);
headerBlockNumbers.append(block.blockNumber());
headerSequences.append(headerReg.cap(3));
@@ -496,7 +464,7 @@ void VMdEdit::updateOutline(const QVector &p_headerRegions)
m_headers.clear();
- bool autoSequence = g_config->getEnableHeadingSequence() && !isReadOnly();
+ bool autoSequence = m_config.m_enableHeadingSequence && !isReadOnly();
int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel();
if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) {
headingSequenceBaseLevel = 1;
@@ -506,22 +474,25 @@ void VMdEdit::updateOutline(const QVector &p_headerRegions)
QRegExp preReg(VUtils::c_headerPrefixRegExp);
int curLevel = baseLevel - 1;
for (int i = 0; i < headers.size(); ++i) {
- VHeader &item = headers[i];
- while (item.level > curLevel + 1) {
+ VTableOfContentItem &item = headers[i];
+ while (item.m_level > curLevel + 1) {
curLevel += 1;
// Insert empty level which is an invalid header.
- m_headers.append(VHeader(curLevel, c_emptyHeaderName, "", -1, m_headers.size()));
+ m_headers.append(VTableOfContentItem(c_emptyHeaderName,
+ curLevel,
+ -1,
+ m_headers.size()));
if (autoSequence) {
addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
}
}
- item.index = m_headers.size();
+ item.m_index = m_headers.size();
m_headers.append(item);
- curLevel = item.level;
+ curLevel = item.m_level;
if (autoSequence) {
- addHeaderSequence(seqs, item.level, headingSequenceBaseLevel);
+ addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
QString seqStr = headerSequenceStr(seqs);
if (headerSequences[i] != seqStr) {
@@ -536,25 +507,16 @@ void VMdEdit::updateOutline(const QVector &p_headerRegions)
emit headersChanged(m_headers);
- updateCurHeader();
+ updateCurrentHeader();
}
-void VMdEdit::scrollToHeader(const VAnchor &p_anchor)
+bool VMdEdit::scrollToHeader(int p_blockNumber)
{
- if (p_anchor.lineNumber == -1
- || p_anchor.m_outlineIndex < 0) {
- // Move to the start of document if m_headers is not empty.
- // Otherwise, there is no outline, so just let it be.
- if (!m_headers.isEmpty()) {
- moveCursor(QTextCursor::Start);
- }
-
- return;
- } else if (p_anchor.m_outlineIndex >= m_headers.size()) {
- return;
+ if (p_blockNumber < 0) {
+ return false;
}
- scrollToLine(p_anchor.lineNumber);
+ return scrollToBlock(p_blockNumber);
}
QString VMdEdit::toPlainTextWithoutImg()
@@ -728,9 +690,21 @@ void VMdEdit::resizeEvent(QResizeEvent *p_event)
VEdit::resizeEvent(p_event);
}
-const QVector &VMdEdit::getHeaders() const
+int VMdEdit::indexOfCurrentHeader() const
{
- return m_headers;
+ if (m_headers.isEmpty()) {
+ return -1;
+ }
+
+ int blockNumber = textCursor().block().blockNumber();
+ for (int i = m_headers.size() - 1; i >= 0; --i) {
+ if (!m_headers[i].isEmpty()
+ && m_headers[i].m_blockNumber <= blockNumber) {
+ return i;
+ }
+ }
+
+ return -1;
}
bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
@@ -740,11 +714,11 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
}
QTextCursor cursor = textCursor();
- int cursorLine = cursor.block().firstLineNumber();
+ int cursorLine = cursor.block().blockNumber();
int targetIdx = -1;
// -1: skip level check.
int targetLevel = 0;
- int idx = currentCursorHeader();
+ int idx = indexOfCurrentHeader();
if (idx == -1) {
// Cursor locates at the beginning, before any headers.
if (p_relativeLevel < 0 || !p_forward) {
@@ -761,7 +735,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
for (targetIdx = idx == -1 ? 0 : idx;
targetIdx >= 0 && targetIdx < m_headers.size();
targetIdx += delta) {
- const VHeader &header = m_headers[targetIdx];
+ const VTableOfContentItem &header = m_headers[targetIdx];
if (header.isEmpty()) {
continue;
}
@@ -769,7 +743,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
if (targetLevel == 0) {
// The target level has not been init yet.
Q_ASSERT(firstHeader);
- targetLevel = header.level;
+ targetLevel = header.m_level;
if (p_relativeLevel < 0) {
targetLevel += p_relativeLevel;
if (targetLevel < 1) {
@@ -781,9 +755,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
}
}
- if (targetLevel == -1 || header.level == targetLevel) {
+ if (targetLevel == -1 || header.m_level == targetLevel) {
if (firstHeader
- && (cursorLine == header.lineNumber
+ && (cursorLine == header.m_blockNumber
|| p_forward)
&& idx != -1) {
// This header is not counted for the repeat.
@@ -795,7 +769,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
// Found.
break;
}
- } else if (header.level < targetLevel) {
+ } else if (header.m_level < targetLevel) {
// Stop by higher level.
return false;
}
@@ -808,9 +782,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
}
// Jump to target header.
- int line = m_headers[targetIdx].lineNumber;
+ int line = m_headers[targetIdx].m_blockNumber;
if (line > -1) {
- QTextBlock block = document()->findBlockByLineNumber(line);
+ QTextBlock block = document()->findBlockByNumber(line);
if (block.isValid()) {
cursor.setPosition(block.position());
setTextCursor(cursor);
@@ -837,6 +811,8 @@ void VMdEdit::finishOneAsyncJob(int p_idx)
m_freshEdit = false;
emit statusChanged();
- updateOutline(m_mdHighlighter->getHeaderRegions());
+ updateHeaders(m_mdHighlighter->getHeaderRegions());
+
+ emit ready();
}
}
diff --git a/src/vmdedit.h b/src/vmdedit.h
index 026bd449..231d58c0 100644
--- a/src/vmdedit.h
+++ b/src/vmdedit.h
@@ -7,7 +7,7 @@
#include
#include
#include
-#include "vtoc.h"
+#include "vtableofcontent.h"
#include "veditoperations.h"
#include "vconfigmanager.h"
#include "utils/vutils.h"
@@ -32,31 +32,34 @@ public:
// @p_path is the absolute path of the inserted image.
void imageInserted(const QString &p_path);
- void scrollToHeader(const VAnchor &p_anchor);
+ // Scroll to header @p_blockNumber.
+ // Return true if @p_blockNumber is valid to scroll to.
+ bool scrollToHeader(int p_blockNumber);
// Like toPlainText(), but remove image preview characters.
QString toPlainTextWithoutImg();
- const QVector &getHeaders() const;
-
public slots:
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
signals:
- void headersChanged(const QVector &headers);
+ // Signal when headers change.
+ void headersChanged(const QVector &p_headers);
// Signal when current header change.
- void curHeaderChanged(VAnchor p_anchor);
+ void currentHeaderChanged(int p_blockNumber);
// Signal when the status of VMdEdit changed.
// Will be emitted by VImagePreviewer for now.
void statusChanged();
private slots:
- void updateOutline(const QVector &p_headerRegions);
+ // Update m_headers according to elements.
+ void updateHeaders(const QVector &p_headerRegions);
+ // Update current header according to cursor position.
// When there is no header in current cursor, will signal an invalid header.
- void updateCurHeader();
+ void updateCurrentHeader();
void handleClipboardChanged(QClipboard::Mode p_mode);
@@ -95,9 +98,6 @@ private:
// in the selection. Get the QImage.
QImage tryGetSelectedImage();
- // Return the header index in m_headers where current cursor locates.
- int currentCursorHeader() const;
-
QString getPlainTextWithoutPreviewImage() const;
// Try to get all the regions of preview image within @p_block.
@@ -107,6 +107,9 @@ private:
void finishOneAsyncJob(int p_idx);
+ // Index in m_headers of current header which contains the cursor.
+ int indexOfCurrentHeader() const;
+
HGMarkdownHighlighter *m_mdHighlighter;
VCodeBlockHighlightHelper *m_cbHighlighter;
VImagePreviewer *m_imagePreviewer;
@@ -117,7 +120,8 @@ private:
// Image links right at the beginning of the edit.
QVector m_initImages;
- QVector m_headers;
+ // Mainly used for title jump.
+ QVector m_headers;
bool m_freshEdit;
diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp
index bb80e883..0f73de01 100644
--- a/src/vmdeditoperations.cpp
+++ b/src/vmdeditoperations.cpp
@@ -26,7 +26,7 @@
extern VConfigManager *g_config;
-const QString VMdEditOperations::c_defaultImageTitle = "image";
+const QString VMdEditOperations::c_defaultImageTitle = "";
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
: VEditOperations(p_editor, p_file), m_autoIndentPos(-1)
@@ -278,6 +278,28 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
break;
}
+ case Qt::Key_L:
+ {
+ if (modifiers == Qt::ControlModifier) {
+ m_editor->insertLink();
+ p_event->accept();
+ ret = true;
+ }
+
+ break;
+ }
+
+ case Qt::Key_M:
+ {
+ if (modifiers == Qt::ControlModifier) {
+ decorateCodeBlock();
+ p_event->accept();
+ ret = true;
+ }
+
+ break;
+ }
+
case Qt::Key_O:
{
if (modifiers == Qt::ControlModifier) {
@@ -626,33 +648,25 @@ void VMdEditOperations::changeListBlockSeqNumber(QTextBlock &p_block, int p_seq)
bool VMdEditOperations::insertTitle(int p_level)
{
- Q_ASSERT(p_level > 0 && p_level < 7);
QTextDocument *doc = m_editor->document();
- QString titleMark(p_level, '#');
QTextCursor cursor = m_editor->textCursor();
+ int firstBlock = cursor.block().blockNumber();
+ int lastBlock = firstBlock;
+
if (cursor.hasSelection()) {
- // Insert title # in front of the selected lines.
+ // Insert title # in front of the selected blocks.
int start = cursor.selectionStart();
int end = cursor.selectionEnd();
- int startBlock = doc->findBlock(start).blockNumber();
- int endBlock = doc->findBlock(end).blockNumber();
- cursor.beginEditBlock();
- cursor.clearSelection();
- for (int i = startBlock; i <= endBlock; ++i) {
- QTextBlock block = doc->findBlockByNumber(i);
- cursor.setPosition(block.position(), QTextCursor::MoveAnchor);
- cursor.insertText(titleMark + " ");
- }
- cursor.movePosition(QTextCursor::EndOfBlock);
- cursor.endEditBlock();
- } else {
- // Insert title # in front of current block.
- cursor.beginEditBlock();
- cursor.movePosition(QTextCursor::StartOfBlock);
- cursor.insertText(titleMark + " ");
- cursor.movePosition(QTextCursor::EndOfBlock);
- cursor.endEditBlock();
+ firstBlock = doc->findBlock(start).blockNumber();
+ lastBlock = doc->findBlock(end).blockNumber();
}
+
+ cursor.beginEditBlock();
+ for (int i = firstBlock; i <= lastBlock; ++i) {
+ VEditUtils::insertTitleMark(cursor, doc->findBlockByNumber(i), p_level);
+ }
+
+ cursor.endEditBlock();
m_editor->setTextCursor(cursor);
return true;
}
@@ -681,6 +695,10 @@ void VMdEditOperations::decorateText(TextDecoration p_decoration)
decorateInlineCode();
break;
+ case TextDecoration::CodeBlock:
+ decorateCodeBlock();
+ break;
+
default:
validDecoration = false;
qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
@@ -804,6 +822,86 @@ void VMdEditOperations::decorateInlineCode()
m_editor->setTextCursor(cursor);
}
+void VMdEditOperations::decorateCodeBlock()
+{
+ const QString marker("```");
+
+ QTextCursor cursor = m_editor->textCursor();
+ cursor.beginEditBlock();
+ if (cursor.hasSelection()) {
+ // Insert ``` around the selected text.
+ int start = cursor.selectionStart();
+ int end = cursor.selectionEnd();
+
+ QString indentation = VEditUtils::fetchIndentSpaces(cursor.block());
+
+ // Insert the end marker first.
+ cursor.setPosition(end, QTextCursor::MoveAnchor);
+ VEditUtils::insertBlock(cursor, false);
+ VEditUtils::indentBlock(cursor, indentation);
+ cursor.insertText(marker);
+
+ // Insert the start marker.
+ cursor.setPosition(start, QTextCursor::MoveAnchor);
+ VEditUtils::insertBlock(cursor, true);
+ VEditUtils::indentBlock(cursor, indentation);
+ cursor.insertText(marker);
+ } else {
+ // Insert ``` ``` and place cursor after the first marker.
+ // Or if current block or next block is ```, we will skip it.
+ QTextBlock block = cursor.block();
+ int state = block.userState();
+ if (state == HighlightBlockState::CodeBlock
+ || state == HighlightBlockState::CodeBlockStart
+ || state == HighlightBlockState::CodeBlockEnd) {
+ // Find the block end.
+ while (block.isValid()) {
+ if (block.userState() == HighlightBlockState::CodeBlockEnd) {
+ break;
+ }
+
+ block = block.next();
+ }
+
+ if (block.isValid()) {
+ // It is CodeBlockEnd.
+ cursor.setPosition(block.position());
+ if (block.next().isValid()) {
+ cursor.movePosition(QTextCursor::NextBlock);
+ cursor.movePosition(QTextCursor::StartOfBlock);
+ } else {
+ cursor.movePosition(QTextCursor::EndOfBlock);
+ }
+ } else {
+ // Reach the end of the document.
+ cursor.movePosition(QTextCursor::End);
+ }
+ } else {
+ bool insertInline = false;
+ if (!cursor.atBlockEnd()) {
+ cursor.insertBlock();
+ cursor.movePosition(QTextCursor::PreviousBlock);
+ } else if (cursor.atBlockStart()) {
+ insertInline = true;
+ }
+
+ if (!insertInline) {
+ VEditUtils::insertBlock(cursor, false);
+ VEditUtils::indentBlockAsBlock(cursor, false);
+ }
+
+ cursor.insertText(marker);
+
+ VEditUtils::insertBlock(cursor, true);
+ VEditUtils::indentBlockAsBlock(cursor, true);
+ cursor.insertText(marker);
+ }
+ }
+
+ cursor.endEditBlock();
+ m_editor->setTextCursor(cursor);
+}
+
void VMdEditOperations::decorateStrikethrough()
{
QTextCursor cursor = m_editor->textCursor();
@@ -840,3 +938,16 @@ void VMdEditOperations::decorateStrikethrough()
cursor.endEditBlock();
m_editor->setTextCursor(cursor);
}
+
+bool VMdEditOperations::insertLink(const QString &p_linkText,
+ const QString &p_linkUrl)
+{
+ QString link = QString("[%1](%2)").arg(p_linkText).arg(p_linkUrl);
+ QTextCursor cursor = m_editor->textCursor();
+ cursor.insertText(link);
+ m_editor->setTextCursor(cursor);
+
+ setVimMode(VimMode::Insert);
+
+ return true;
+}
diff --git a/src/vmdeditoperations.h b/src/vmdeditoperations.h
index faeb791f..d8609339 100644
--- a/src/vmdeditoperations.h
+++ b/src/vmdeditoperations.h
@@ -16,11 +16,18 @@ class VMdEditOperations : public VEditOperations
Q_OBJECT
public:
VMdEditOperations(VEdit *p_editor, VFile *p_file);
+
bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
+
bool insertImage() Q_DECL_OVERRIDE;
+
bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
+
bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE;
+ bool insertLink(const QString &p_linkText,
+ const QString &p_linkUrl);
+
// Insert decoration markers or decorate selected text.
// If it is Vim Normal mode, change to Insert mode first.
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
@@ -47,6 +54,10 @@ private:
bool handleKeyEsc(QKeyEvent *p_event);
bool handleKeyReturn(QKeyEvent *p_event);
bool handleKeyBracketLeft(QKeyEvent *p_event);
+
+ // Insert title of level @p_level.
+ // Will detect if current block already has some leading #s. If yes,
+ // will delete it and insert the correct #s.
bool insertTitle(int p_level);
// Change the sequence number of a list block.
@@ -61,6 +72,9 @@ private:
// Insert inline-code marker or set selected text inline-coded.
void decorateInlineCode();
+ // Insert inline-code marker or set selected text inline-coded.
+ void decorateCodeBlock();
+
// Insert strikethrough marker or set selected text strikethrough.
void decorateStrikethrough();
diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp
index 69d79642..658d5d07 100644
--- a/src/vmdtab.cpp
+++ b/src/vmdtab.cpp
@@ -11,7 +11,7 @@
#include "vconfigmanager.h"
#include "vmarkdownconverter.h"
#include "vnotebook.h"
-#include "vtoc.h"
+#include "vtableofcontent.h"
#include "vmdedit.h"
#include "dialog/vfindreplacedialog.h"
#include "veditarea.h"
@@ -22,13 +22,25 @@ extern VConfigManager *g_config;
VMdTab::VMdTab(VFile *p_file, VEditArea *p_editArea,
OpenFileMode p_mode, QWidget *p_parent)
- : VEditTab(p_file, p_editArea, p_parent), m_editor(NULL), m_webViewer(NULL),
- m_document(NULL), m_mdConType(g_config->getMdConverterType())
+ : VEditTab(p_file, p_editArea, p_parent),
+ m_editor(NULL),
+ m_webViewer(NULL),
+ m_document(NULL),
+ m_mdConType(g_config->getMdConverterType()),
+ m_enableHeadingSequence(false)
{
V_ASSERT(m_file->getDocType() == DocType::Markdown);
m_file->open();
+ HeadingSequenceType headingSequenceType = g_config->getHeadingSequenceType();
+ if (headingSequenceType == HeadingSequenceType::Enabled) {
+ m_enableHeadingSequence = true;
+ } else if (headingSequenceType == HeadingSequenceType::EnabledNoteOnly
+ && m_file->getType() == FileType::Note) {
+ m_enableHeadingSequence = true;
+ }
+
setupUI();
if (p_mode == OpenFileMode::Edit) {
@@ -50,54 +62,110 @@ void VMdTab::setupUI()
setLayout(m_stacks);
}
-void VMdTab::handleTextChanged()
-{
- V_ASSERT(m_file->isModifiable());
-
- if (m_modified) {
- return;
- }
-
- updateStatus();
-}
-
void VMdTab::showFileReadMode()
{
m_isEditMode = false;
- int outlineIndex = m_curHeader.m_outlineIndex;
+ VHeaderPointer header(m_currentHeader);
if (m_mdConType == MarkdownConverterType::Hoedown) {
viewWebByConverter();
} else {
m_document->updateText();
- updateTocFromHtml(m_document->getToc());
+ updateOutlineFromHtml(m_document->getToc());
}
m_stacks->setCurrentWidget(m_webViewer);
clearSearchedWordHighlight();
- scrollWebViewToHeader(outlineIndex);
+ scrollWebViewToHeader(header);
updateStatus();
}
-void VMdTab::scrollWebViewToHeader(int p_outlineIndex)
+bool VMdTab::scrollWebViewToHeader(const VHeaderPointer &p_header)
{
- QString anchor;
-
- m_curHeader = VAnchor(m_file, anchor, -1, p_outlineIndex);
-
- if (p_outlineIndex < m_toc.headers.size() && p_outlineIndex >= 0) {
- QString tmp = m_toc.headers[p_outlineIndex].anchor;
- V_ASSERT(!tmp.isEmpty());
- m_curHeader.anchor = tmp;
- anchor = tmp.mid(1);
+ if (!m_outline.isMatched(p_header)
+ || m_outline.getType() != VTableOfContentType::Anchor) {
+ return false;
}
- m_document->scrollToAnchor(anchor);
+ if (p_header.isValid()) {
+ const VTableOfContentItem *item = m_outline.getItem(p_header);
+ if (item) {
+ if (item->m_anchor.isEmpty()) {
+ return false;
+ }
- emit curHeaderChanged(m_curHeader);
+ m_currentHeader = p_header;
+ m_document->scrollToAnchor(item->m_anchor);
+ } else {
+ return false;
+ }
+ } else {
+ if (m_outline.isEmpty()) {
+ // Let it be.
+ m_currentHeader = p_header;
+ } else {
+ // Scroll to top.
+ m_currentHeader = p_header;
+ m_document->scrollToAnchor("");
+ }
+ }
+
+ emit currentHeaderChanged(m_currentHeader);
+ return true;
+}
+
+bool VMdTab::scrollEditorToHeader(const VHeaderPointer &p_header)
+{
+ if (!m_outline.isMatched(p_header)
+ || m_outline.getType() != VTableOfContentType::BlockNumber) {
+ return false;
+ }
+
+ VMdEdit *mdEdit = dynamic_cast(getEditor());
+
+ int blockNumber = -1;
+ if (p_header.isValid()) {
+ const VTableOfContentItem *item = m_outline.getItem(p_header);
+ if (item) {
+ blockNumber = item->m_blockNumber;
+ if (blockNumber == -1) {
+ // Empty item.
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else {
+ if (m_outline.isEmpty()) {
+ // No outline and scroll to -1 index.
+ // Just let it be.
+ m_currentHeader = p_header;
+ return true;
+ } else {
+ // Has outline and scroll to -1 index.
+ // Scroll to top.
+ blockNumber = 0;
+ }
+ }
+
+ if (mdEdit->scrollToHeader(blockNumber)) {
+ m_currentHeader = p_header;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool VMdTab::scrollToHeaderInternal(const VHeaderPointer &p_header)
+{
+ if (m_isEditMode) {
+ return scrollEditorToHeader(p_header);
+ } else {
+ return scrollWebViewToHeader(p_header);
+ }
}
void VMdTab::viewWebByConverter()
@@ -108,7 +176,7 @@ void VMdTab::viewWebByConverter()
g_config->getMarkdownExtensions(),
toc);
m_document->setHtml(html);
- updateTocFromHtml(toc);
+ updateOutlineFromHtml(toc);
}
void VMdTab::showFileEditMode()
@@ -117,42 +185,28 @@ void VMdTab::showFileEditMode()
return;
}
+ VHeaderPointer header(m_currentHeader);
+
m_isEditMode = true;
VMdEdit *mdEdit = dynamic_cast(getEditor());
V_ASSERT(mdEdit);
- // beginEdit() may change m_curHeader.
- int outlineIndex = m_curHeader.m_outlineIndex;
-
mdEdit->beginEdit();
m_stacks->setCurrentWidget(mdEdit);
- int lineNumber = -1;
- const QVector &headers = mdEdit->getHeaders();
// If editor is not init, we need to wait for it to init headers.
// Generally, beginEdit() will generate the headers. Wait is needed when
// highlight completion is going to re-generate the headers.
int nrRetry = 5;
- while (outlineIndex > -1 && headers.isEmpty() && nrRetry-- > 0) {
+ while (header.m_index > -1 && m_outline.isEmpty() && nrRetry-- > 0) {
qDebug() << "wait another 100 ms for editor's headers ready";
VUtils::sleepWait(100);
}
- if (outlineIndex < 0 || outlineIndex >= headers.size()) {
- lineNumber = -1;
- outlineIndex = -1;
- } else {
- lineNumber = headers[outlineIndex].lineNumber;
- }
-
- VAnchor anchor(m_file, "", lineNumber, outlineIndex);
-
- mdEdit->scrollToHeader(anchor);
+ scrollEditorToHeader(header);
mdEdit->setFocus();
-
- updateStatus();
}
bool VMdTab::closeFile(bool p_forced)
@@ -232,7 +286,7 @@ bool VMdTab::saveFile()
return true;
}
- bool ret;
+ bool ret = true;
// Make sure the file already exists. Temporary deal with cases when user delete or move
// a file.
QString filePath = m_file->fetchPath();
@@ -242,16 +296,16 @@ bool VMdTab::saveFile()
tr("File %2 being written has been removed.")
.arg(g_config->c_dataTextStyle).arg(filePath),
QMessageBox::Ok, QMessageBox::Ok, this);
- return false;
- }
-
- m_editor->saveFile();
- ret = m_file->save();
- if (!ret) {
- VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note."),
- tr("Fail to write to disk when saving a note. Please try it again."),
- QMessageBox::Ok, QMessageBox::Ok, this);
- m_editor->setModified(true);
+ ret = false;
+ } else {
+ m_editor->saveFile();
+ ret = m_file->save();
+ if (!ret) {
+ VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note."),
+ tr("Fail to write to disk when saving a note. Please try it again."),
+ QMessageBox::Ok, QMessageBox::Ok, this);
+ m_editor->setModified(true);
+ }
}
updateStatus();
@@ -285,11 +339,13 @@ void VMdTab::setupMarkdownViewer()
QWebChannel *channel = new QWebChannel(m_webViewer);
channel->registerObject(QStringLiteral("content"), m_document);
connect(m_document, &VDocument::tocChanged,
- this, &VMdTab::updateTocFromHtml);
- connect(m_document, SIGNAL(headerChanged(const QString&)),
- this, SLOT(updateCurHeader(const QString &)));
+ this, &VMdTab::updateOutlineFromHtml);
+ connect(m_document, SIGNAL(headerChanged(const QString &)),
+ this, SLOT(updateCurrentHeader(const QString &)));
connect(m_document, &VDocument::keyPressed,
this, &VMdTab::handleWebKeyPressed);
+ connect(m_document, SIGNAL(logicsFinished(void)),
+ this, SLOT(restoreFromTabInfo(void)));
page->setWebChannel(channel);
m_webViewer->setHtml(VUtils::generateHtmlTemplate(m_mdConType, false),
@@ -302,15 +358,16 @@ void VMdTab::setupMarkdownEditor()
{
Q_ASSERT(m_file->isModifiable() && !m_editor);
qDebug() << "create Markdown editor";
+
m_editor = new VMdEdit(m_file, m_document, m_mdConType, this);
connect(dynamic_cast(m_editor), &VMdEdit::headersChanged,
- this, &VMdTab::updateTocFromHeaders);
+ this, &VMdTab::updateOutlineFromHeaders);
+ connect(dynamic_cast(m_editor), SIGNAL(currentHeaderChanged(int)),
+ this, SLOT(updateCurrentHeader(int)));
connect(dynamic_cast(m_editor), &VMdEdit::statusChanged,
this, &VMdTab::updateStatus);
- connect(m_editor, SIGNAL(curHeaderChanged(VAnchor)),
- this, SLOT(updateCurHeader(VAnchor)));
connect(m_editor, &VEdit::textChanged,
- this, &VMdTab::handleTextChanged);
+ this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::cursorPositionChanged,
this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::saveAndRead,
@@ -324,193 +381,82 @@ void VMdTab::setupMarkdownEditor()
connect(m_editor, &VEdit::vimStatusUpdated,
this, &VEditTab::vimStatusUpdated);
connect(m_editor, &VEdit::requestCloseFindReplaceDialog,
- this, [this](){
+ this, [this]() {
this->m_editArea->getFindReplaceDialog()->closeDialog();
});
+ connect(m_editor, SIGNAL(ready(void)),
+ this, SLOT(restoreFromTabInfo(void)));
+ enableHeadingSequence(m_enableHeadingSequence);
m_editor->reloadFile();
m_stacks->addWidget(m_editor);
}
-static void parseTocUl(QXmlStreamReader &p_xml, QVector &p_headers,
- int p_level);
-
-static void parseTocLi(QXmlStreamReader &p_xml, QVector &p_headers, int p_level)
-{
- Q_ASSERT(p_xml.isStartElement() && p_xml.name() == "li");
-
- if (p_xml.readNextStartElement()) {
- if (p_xml.name() == "a") {
- QString anchor = p_xml.attributes().value("href").toString();
- QString name;
- if (p_xml.readNext()) {
- if (p_xml.tokenString() == "Characters") {
- name = p_xml.text().toString();
- } else if (!p_xml.isEndElement()) {
- qWarning() << "TOC HTML should be ended by " << p_xml.name();
- return;
- }
-
- VHeader header(p_level, name, anchor, -1, p_headers.size());
- p_headers.append(header);
- } else {
- // Error
- return;
- }
- } else if (p_xml.name() == "ul") {
- // Such as header 3 under header 1 directly
- VHeader header(p_level, c_emptyHeaderName, "#", -1, p_headers.size());
- p_headers.append(header);
- parseTocUl(p_xml, p_headers, p_level + 1);
- } else {
- qWarning() << "TOC HTML should contain or " << p_xml.name();
- return;
- }
- }
-
- while (p_xml.readNext()) {
- if (p_xml.isEndElement()) {
- if (p_xml.name() == "li") {
- return;
- }
- continue;
- }
- if (p_xml.name() == "ul") {
- // Nested unordered list
- parseTocUl(p_xml, p_headers, p_level + 1);
- } else {
- return;
- }
- }
-}
-
-static void parseTocUl(QXmlStreamReader &p_xml, QVector &p_headers,
- int p_level)
-{
- Q_ASSERT(p_xml.isStartElement() && p_xml.name() == "ul");
-
- while (p_xml.readNextStartElement()) {
- if (p_xml.name() == "li") {
- parseTocLi(p_xml, p_headers, p_level);
- } else {
- qWarning() << "TOC HTML should contain - " << p_xml.name();
- break;
- }
- }
-}
-
-static bool parseTocHtml(const QString &p_tocHtml,
- QVector &p_headers)
-{
- if (!p_tocHtml.isEmpty()) {
- QXmlStreamReader xml(p_tocHtml);
- if (xml.readNextStartElement()) {
- if (xml.name() == "ul") {
- parseTocUl(xml, p_headers, 1);
- } else {
- qWarning() << "TOC HTML does not start with
";
- }
- }
-
- if (xml.hasError()) {
- qWarning() << "fail to parse TOC in HTML";
- return false;
- }
- }
-
- return true;
-}
-
-void VMdTab::updateTocFromHtml(const QString &p_tocHtml)
+void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml)
{
if (m_isEditMode) {
return;
}
- m_toc.type = VHeaderType::Anchor;
- m_toc.headers.clear();
+ m_outline.clear();
- if (!parseTocHtml(p_tocHtml, m_toc.headers)) {
- return;
+ if (m_outline.parseTableFromHtml(p_tocHtml)) {
+ m_outline.setFile(m_file);
+ m_outline.setType(VTableOfContentType::Anchor);
}
- m_toc.m_file = m_file;
- m_toc.valid = true;
+ m_currentHeader.reset();
- emit outlineChanged(m_toc);
+ emit outlineChanged(m_outline);
}
-void VMdTab::updateTocFromHeaders(const QVector &p_headers)
+void VMdTab::updateOutlineFromHeaders(const QVector &p_headers)
{
if (!m_isEditMode) {
return;
}
- m_toc.type = VHeaderType::LineNumber;
- m_toc.headers = p_headers;
- m_toc.m_file = m_file;
- m_toc.valid = true;
+ m_outline.update(m_file,
+ p_headers,
+ VTableOfContentType::BlockNumber);
- // Clear current header.
- m_curHeader = VAnchor(m_file, "", -1, -1);
- emit curHeaderChanged(m_curHeader);
+ m_currentHeader.reset();
- emit outlineChanged(m_toc);
+ emit outlineChanged(m_outline);
}
-void VMdTab::scrollToAnchor(const VAnchor &p_anchor)
+void VMdTab::scrollToHeader(const VHeaderPointer &p_header)
{
- if (p_anchor == m_curHeader) {
+ if (m_outline.isMatched(p_header)) {
+ // Scroll only when @p_header is valid.
+ scrollToHeaderInternal(p_header);
+ }
+}
+
+void VMdTab::updateCurrentHeader(const QString &p_anchor)
+{
+ if (m_isEditMode) {
return;
}
- m_curHeader = p_anchor;
+ // Find the index of the anchor in outline.
+ int idx = m_outline.indexOfItemByAnchor(p_anchor);
+ m_currentHeader.update(m_file, idx);
- if (m_isEditMode) {
- dynamic_cast(getEditor())->scrollToHeader(p_anchor);
- } else {
- if (!p_anchor.anchor.isEmpty()) {
- m_document->scrollToAnchor(p_anchor.anchor.mid(1));
- }
- }
+ emit currentHeaderChanged(m_currentHeader);
}
-void VMdTab::updateCurHeader(const QString &p_anchor)
+void VMdTab::updateCurrentHeader(int p_blockNumber)
{
- if (m_isEditMode || m_curHeader.anchor.mid(1) == p_anchor) {
+ if (!m_isEditMode) {
return;
}
- m_curHeader = VAnchor(m_file, "#" + p_anchor, -1);
- if (!p_anchor.isEmpty()) {
- const QVector &headers = m_toc.headers;
- for (int i = 0; i < headers.size(); ++i) {
- if (headers[i].anchor == m_curHeader.anchor) {
- V_ASSERT(headers[i].index == i);
- m_curHeader.m_outlineIndex = headers[i].index;
- break;
- }
- }
- }
+ // Find the index of the block number in outline.
+ int idx = m_outline.indexOfItemByBlockNumber(p_blockNumber);
+ m_currentHeader.update(m_file, idx);
- emit curHeaderChanged(m_curHeader);
-}
-
-void VMdTab::updateCurHeader(VAnchor p_anchor)
-{
- if (m_isEditMode) {
- if (!p_anchor.anchor.isEmpty() || p_anchor.lineNumber == m_curHeader.lineNumber) {
- return;
- }
- } else {
- if (p_anchor.lineNumber != -1 || p_anchor.anchor == m_curHeader.anchor) {
- return;
- }
- }
-
- m_curHeader = p_anchor;
-
- emit curHeaderChanged(m_curHeader);
+ emit currentHeaderChanged(m_currentHeader);
}
void VMdTab::insertImage()
@@ -523,6 +469,16 @@ void VMdTab::insertImage()
m_editor->insertImage();
}
+void VMdTab::insertLink()
+{
+ if (!m_isEditMode) {
+ return;
+ }
+
+ Q_ASSERT(m_editor);
+ m_editor->insertLink();
+}
+
void VMdTab::findText(const QString &p_text, uint p_options, bool p_peek,
bool p_forward)
{
@@ -682,9 +638,9 @@ void VMdTab::requestUpdateVimStatus()
}
}
-VEditTabInfo VMdTab::createEditTabInfo()
+VEditTabInfo VMdTab::fetchTabInfo() const
{
- VEditTabInfo info = VEditTab::createEditTabInfo();
+ VEditTabInfo info = VEditTab::fetchTabInfo();
if (m_editor) {
QTextCursor cursor = m_editor->textCursor();
@@ -693,6 +649,8 @@ VEditTabInfo VMdTab::createEditTabInfo()
info.m_blockCount = m_editor->document()->blockCount();
}
+ info.m_headerIndex = m_currentHeader.m_index;
+
return info;
}
@@ -702,3 +660,45 @@ void VMdTab::decorateText(TextDecoration p_decoration)
m_editor->decorateText(p_decoration);
}
}
+
+bool VMdTab::restoreFromTabInfo(const VEditTabInfo &p_info)
+{
+ if (p_info.m_editTab != this) {
+ return false;
+ }
+
+ // Restore header.
+ VHeaderPointer header(m_file, p_info.m_headerIndex);
+ bool ret = scrollToHeaderInternal(header);
+ return ret;
+}
+
+void VMdTab::restoreFromTabInfo()
+{
+ restoreFromTabInfo(m_infoToRestore);
+
+ // Clear it anyway.
+ m_infoToRestore.clear();
+}
+
+void VMdTab::enableHeadingSequence(bool p_enabled)
+{
+ m_enableHeadingSequence = p_enabled;
+
+ if (m_editor) {
+ VEditConfig &config = m_editor->getConfig();
+ config.m_enableHeadingSequence = m_enableHeadingSequence;
+ }
+}
+
+bool VMdTab::isHeadingSequenceEnabled() const
+{
+ return m_enableHeadingSequence;
+}
+
+void VMdTab::evaluateMagicWords()
+{
+ if (isEditMode() && m_file->isModifiable()) {
+ getEditor()->evaluateMagicWords();
+ }
+}
diff --git a/src/vmdtab.h b/src/vmdtab.h
index ab7a5fd6..267df642 100644
--- a/src/vmdtab.h
+++ b/src/vmdtab.h
@@ -31,11 +31,13 @@ public:
// Save file.
bool saveFile() Q_DECL_OVERRIDE;
- // Scroll to anchor @p_anchor.
- void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE;
+ // Scroll to @p_header.
+ void scrollToHeader(const VHeaderPointer &p_header) Q_DECL_OVERRIDE;
void insertImage() Q_DECL_OVERRIDE;
+ void insertLink() Q_DECL_OVERRIDE;
+
// Search @p_text in current note.
void findText(const QString &p_text, uint p_options, bool p_peek,
bool p_forward = true) Q_DECL_OVERRIDE;
@@ -62,25 +64,34 @@ public:
// Insert decoration markers or decorate selected text.
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
+ // Create a filled VEditTabInfo.
+ VEditTabInfo fetchTabInfo() const Q_DECL_OVERRIDE;
+
+ // Enable or disable heading sequence.
+ void enableHeadingSequence(bool p_enabled);
+
+ bool isHeadingSequenceEnabled() const;
+
+ // Evaluate magic words.
+ void evaluateMagicWords() Q_DECL_OVERRIDE;
+
public slots:
// Enter edit mode.
void editFile() Q_DECL_OVERRIDE;
private slots:
- // Handle text changed in m_editor.
- void handleTextChanged();
+ // Update m_outline according to @p_tocHtml for read mode.
+ void updateOutlineFromHtml(const QString &p_tocHtml);
- // Update m_toc according to @p_tocHtml for read mode.
- void updateTocFromHtml(const QString &p_tocHtml);
-
- // Update m_toc accroding to @p_headers for edit mode.
- void updateTocFromHeaders(const QVector &p_headers);
+ // Update m_outline accroding to @p_headers for edit mode.
+ void updateOutlineFromHeaders(const QVector &p_headers);
// Web viewer requests to update current header.
- void updateCurHeader(const QString &p_anchor);
+ // @p_anchor is the anchor of the header, like "toc_1".
+ void updateCurrentHeader(const QString &p_anchor);
// Editor requests to update current header.
- void updateCurHeader(VAnchor p_anchor);
+ void updateCurrentHeader(int p_blockNumber);
// Handle key press event in Web view.
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift);
@@ -91,6 +102,9 @@ private slots:
// m_editor requests to discard changes and enter read mode.
void discardAndRead();
+ // Restore from m_infoToRestore.
+ void restoreFromTabInfo();
+
private:
// Setup UI.
void setupUI();
@@ -111,8 +125,14 @@ private:
void viewWebByConverter();
// Scroll Web view to given header.
- // @p_outlineIndex is the index in m_toc.headers.
- void scrollWebViewToHeader(int p_outlineIndex);
+ // Return true if scroll was made.
+ bool scrollWebViewToHeader(const VHeaderPointer &p_header);
+
+ bool scrollEditorToHeader(const VHeaderPointer &p_header);
+
+ // Scroll web/editor to given header.
+ // Return true if scroll was made.
+ bool scrollToHeaderInternal(const VHeaderPointer &p_header);
// Search text in Web view.
void findTextInWebView(const QString &p_text, uint p_options, bool p_peek,
@@ -127,17 +147,21 @@ private:
// Focus the proper child widget.
void focusChild() Q_DECL_OVERRIDE;
- // Create a filled VEditTabInfo.
- VEditTabInfo createEditTabInfo() Q_DECL_OVERRIDE;
-
// Get the markdown editor. If not init yet, init and return it.
VEdit *getEditor();
+ // Restore from @p_fino.
+ // Return true if succeed.
+ bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
+
VEdit *m_editor;
VWebView *m_webViewer;
VDocument *m_document;
MarkdownConverterType m_mdConType;
+ // Whether heading sequence is enabled.
+ bool m_enableHeadingSequence;
+
QStackedLayout *m_stacks;
};
diff --git a/src/vnote.cpp b/src/vnote.cpp
index 1c318527..48d2e6d9 100644
--- a/src/vnote.cpp
+++ b/src/vnote.cpp
@@ -16,6 +16,10 @@
extern VConfigManager *g_config;
+// Meta word manager.
+VMetaWordManager *g_mwMgr;
+
+
QString VNote::s_markdownTemplate;
QString VNote::s_markdownTemplatePDF;
@@ -53,10 +57,15 @@ const QString VNote::c_markdownGuideDocFile_en = ":/resources/docs/markdown_guid
const QString VNote::c_markdownGuideDocFile_zh = ":/resources/docs/markdown_guide_zh.md";
VNote::VNote(QObject *parent)
- : QObject(parent), m_mainWindow(dynamic_cast(parent))
+ : QObject(parent)
{
initTemplate();
+
g_config->getNotebooks(m_notebooks, this);
+
+ m_metaWordMgr.init();
+
+ g_mwMgr = &m_metaWordMgr;
}
void VNote::initPalette(QPalette palette)
@@ -330,6 +339,16 @@ VNoteFile *VNote::getInternalFile(const QString &p_path)
return file;
}
+VFile *VNote::getFile(const QString &p_path)
+{
+ VFile *file = getInternalFile(p_path);
+ if (!file) {
+ file = getOrphanFile(p_path, true, false);
+ }
+
+ return file;
+}
+
VDirectory *VNote::getInternalDirectory(const QString &p_path)
{
VDirectory *dir = NULL;
diff --git a/src/vnote.h b/src/vnote.h
index dd320ce5..11f4ebd6 100644
--- a/src/vnote.h
+++ b/src/vnote.h
@@ -12,11 +12,12 @@
#include
#include "vnotebook.h"
#include "vconstants.h"
+#include "utils/vmetawordmanager.h"
-class VMainWindow;
class VOrphanFile;
class VNoteFile;
+
class VNote : public QObject
{
Q_OBJECT
@@ -74,10 +75,13 @@ public:
const QVector > &getPalette() const;
void initPalette(QPalette palette);
QString getColorFromPalette(const QString &p_name) const;
- VMainWindow *getMainWindow() const;
QString getNavigationLabelStyle(const QString &p_str) const;
+ // Given the path of a file, first try to open it as note file,
+ // then try to open it as orphan file.
+ VFile *getFile(const QString &p_path);
+
// Given the path of an external file, create a VOrphanFile struct.
VOrphanFile *getOrphanFile(const QString &p_path,
bool p_modifiable,
@@ -102,7 +106,8 @@ private:
// Maintain all the notebooks. Other holder should use QPointer.
QVector m_notebooks;
QVector > m_palette;
- VMainWindow *m_mainWindow;
+
+ VMetaWordManager m_metaWordMgr;
// Hold all external file: Orphan File.
// Need to clean up periodly.
@@ -114,9 +119,4 @@ inline const QVector >& VNote::getPalette() const
return m_palette;
}
-inline VMainWindow *VNote::getMainWindow() const
-{
- return m_mainWindow;
-}
-
#endif // VNOTE_H
diff --git a/src/vnote.qrc b/src/vnote.qrc
index 5783fc0b..80d83209 100644
--- a/src/vnote.qrc
+++ b/src/vnote.qrc
@@ -130,5 +130,9 @@
resources/icons/delete_attachment.svg
resources/icons/sort.svg
resources/icons/create_subdir.svg
+ resources/icons/compact_mode.svg
+ resources/icons/heading_sequence.svg
+ resources/icons/link.svg
+ resources/icons/code_block.svg
diff --git a/src/vnotebook.cpp b/src/vnotebook.cpp
index 43b9ba22..a4ecb3ae 100644
--- a/src/vnotebook.cpp
+++ b/src/vnotebook.cpp
@@ -9,7 +9,7 @@
extern VConfigManager *g_config;
VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
- : QObject(parent), m_name(name)
+ : QObject(parent), m_name(name), m_valid(false)
{
m_path = QDir::cleanPath(path);
m_recycleBinFolder = g_config->getRecycleBinFolder();
@@ -29,6 +29,7 @@ bool VNotebook::readConfigNotebook()
QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path);
if (configJson.isEmpty()) {
qWarning() << "fail to read notebook configuration" << m_path;
+ m_valid = false;
return false;
}
@@ -58,6 +59,7 @@ bool VNotebook::readConfigNotebook()
writeConfigNotebook();
}
+ m_valid = true;
return true;
}
diff --git a/src/vnotebook.h b/src/vnotebook.h
index 80bf9585..316ca109 100644
--- a/src/vnotebook.h
+++ b/src/vnotebook.h
@@ -93,6 +93,8 @@ public:
// Need to check if this notebook has been opened.
QDateTime getCreatedTimeUtc();
+ bool isValid() const;
+
private:
// Serialize current instance to json.
QJsonObject toConfigJson() const;
@@ -118,6 +120,10 @@ private:
// Parent is NULL for root directory
VDirectory *m_rootDir;
+
+ // Whether this notebook is valid.
+ // Will set to true after readConfigNotebook().
+ bool m_valid;
};
inline VDirectory *VNotebook::getRootDir() const
@@ -130,4 +136,9 @@ inline const QString &VNotebook::getRecycleBinFolder() const
return m_recycleBinFolder;
}
+inline bool VNotebook::isValid() const
+{
+ return m_valid;
+}
+
#endif // VNOTEBOOK_H
diff --git a/src/vnotebookselector.cpp b/src/vnotebookselector.cpp
index 00605e80..0a0696e4 100644
--- a/src/vnotebookselector.cpp
+++ b/src/vnotebookselector.cpp
@@ -20,22 +20,27 @@
#include "vnote.h"
#include "veditarea.h"
#include "vnofocusitemdelegate.h"
+#include "vmainwindow.h"
extern VConfigManager *g_config;
+
extern VNote *g_vnote;
-const int VNotebookSelector::c_notebookStartIdx = 1;
+extern VMainWindow *g_mainWin;
-VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent)
- : QComboBox(p_parent), VNavigationMode(),
- m_vnote(vnote), m_notebooks(m_vnote->getNotebooks()),
- m_editArea(NULL), m_lastValidIndex(-1), m_naviLabel(NULL)
+VNotebookSelector::VNotebookSelector(QWidget *p_parent)
+ : QComboBox(p_parent),
+ VNavigationMode(),
+ m_notebooks(g_vnote->getNotebooks()),
+ m_lastValidIndex(-1),
+ m_muted(false),
+ m_naviLabel(NULL)
{
m_listWidget = new QListWidget(this);
m_listWidget->setItemDelegate(new VNoFocusItemDelegate(this));
m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_listWidget, &QListWidget::customContextMenuRequested,
- this, &VNotebookSelector::requestPopupListContextMenu);
+ this, &VNotebookSelector::popupListContextMenuRequested);
setModel(m_listWidget->model());
setView(m_listWidget);
@@ -47,8 +52,6 @@ VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent)
connect(this, SIGNAL(currentIndexChanged(int)),
this, SLOT(handleCurIndexChanged(int)));
- connect(this, SIGNAL(activated(int)),
- this, SLOT(handleItemActivated(int)));
}
void VNotebookSelector::initActions()
@@ -75,9 +78,7 @@ void VNotebookSelector::initActions()
}
Q_ASSERT(items.size() == 1);
- QListWidgetItem *item = items[0];
- int index = this->indexOfListItem(item);
- VNotebook *notebook = this->getNotebookFromComboIndex(index);
+ VNotebook *notebook = getNotebook(items[0]);
QUrl url = QUrl::fromLocalFile(notebook->getPath());
QDesktopServices::openUrl(url);
});
@@ -93,9 +94,7 @@ void VNotebookSelector::initActions()
}
Q_ASSERT(items.size() == 1);
- QListWidgetItem *item = items[0];
- int index = this->indexOfListItem(item);
- VNotebook *notebook = this->getNotebookFromComboIndex(index);
+ VNotebook *notebook = getNotebook(items[0]);
QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
QDesktopServices::openUrl(url);
});
@@ -111,9 +110,7 @@ void VNotebookSelector::initActions()
}
Q_ASSERT(items.size() == 1);
- QListWidgetItem *item = items[0];
- int index = this->indexOfListItem(item);
- VNotebook *notebook = this->getNotebookFromComboIndex(index);
+ VNotebook *notebook = getNotebook(items[0]);
QString binPath = notebook->getRecycleBinFolderPath();
int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
@@ -129,7 +126,9 @@ void VNotebookSelector::initActions()
.arg(g_config->c_dataTextStyle)
.arg(binPath),
QMessageBox::Ok | QMessageBox::Cancel,
- QMessageBox::Ok, this, MessageBoxType::Danger);
+ QMessageBox::Ok,
+ this,
+ MessageBoxType::Danger);
if (ret == QMessageBox::Ok) {
QString info;
if (VUtils::emptyDirectory(notebook, binPath, true)) {
@@ -157,37 +156,60 @@ void VNotebookSelector::initActions()
void VNotebookSelector::updateComboBox()
{
+ m_muted = true;
+
int index = g_config->getCurNotebookIndex();
- disconnect(this, SIGNAL(currentIndexChanged(int)),
- this, SLOT(handleCurIndexChanged(int)));
clear();
+
m_listWidget->clear();
insertAddNotebookItem();
for (int i = 0; i < m_notebooks.size(); ++i) {
- addNotebookItem(m_notebooks[i]->getName());
+ addNotebookItem(m_notebooks[i]);
}
+
setCurrentIndex(-1);
- connect(this, SIGNAL(currentIndexChanged(int)),
- this, SLOT(handleCurIndexChanged(int)));
+
+ m_muted = false;
if (m_notebooks.isEmpty()) {
g_config->setCurNotebookIndex(-1);
setCurrentIndex(0);
} else {
- setCurrentIndexNotebook(index);
+ const VNotebook *nb = NULL;
+ if (index >= 0 && index < m_notebooks.size()) {
+ nb = m_notebooks[index];
+ }
+
+ setCurrentItemToNotebook(nb);
}
+
qDebug() << "notebooks" << m_notebooks.size() << "current index" << index;
}
-void VNotebookSelector::setCurrentIndexNotebook(int p_index)
+void VNotebookSelector::setCurrentItemToNotebook(const VNotebook *p_notebook)
{
- if (p_index > -1) {
- p_index += c_notebookStartIdx;
+ setCurrentIndex(itemIndexOfNotebook(p_notebook));
+}
+
+int VNotebookSelector::itemIndexOfNotebook(const VNotebook *p_notebook) const
+{
+ if (!p_notebook) {
+ return -1;
}
- setCurrentIndex(p_index);
+
+ qulonglong ptr = (qulonglong)p_notebook;
+ int cnt = m_listWidget->count();
+ for (int i = 0; i < cnt; ++i) {
+ QListWidgetItem *item = m_listWidget->item(i);
+ if (item->data(Qt::UserRole).toULongLong() == ptr) {
+ return i;
+ }
+ }
+
+ return -1;
}
void VNotebookSelector::insertAddNotebookItem()
@@ -199,49 +221,51 @@ void VNotebookSelector::insertAddNotebookItem()
font.setItalic(true);
item->setData(Qt::FontRole, font);
item->setToolTip(tr("Create or import a notebook"));
+
m_listWidget->insertItem(0, item);
}
void VNotebookSelector::handleCurIndexChanged(int p_index)
{
- qDebug() << "current index changed" << p_index << "startIdx" << c_notebookStartIdx;
+ if (m_muted) {
+ return;
+ }
+
+ QString tooltip = tr("View and edit notebooks");
VNotebook *nb = NULL;
if (p_index > -1) {
- if (p_index < c_notebookStartIdx) {
- // Click a special action item.
- if (m_listWidget->count() == c_notebookStartIdx) {
- // There is no regular notebook item. Just let it be selected.
- p_index = -1;
- } else {
- // handleItemActivated() will handle the logics.
- return;
- }
- } else {
- int nbIdx = p_index - c_notebookStartIdx;
- Q_ASSERT(nbIdx >= 0);
- nb = m_notebooks[nbIdx];
- }
- }
- m_lastValidIndex = p_index;
- QString tooltip;
- if (p_index > -1) {
- p_index -= c_notebookStartIdx;
- tooltip = nb->getName();
- }
- setToolTip(tooltip);
- g_config->setCurNotebookIndex(p_index);
- emit curNotebookChanged(nb);
-}
+ nb = getNotebook(p_index);
+ if (!nb) {
+ // Add notebook.
+ setToolTip(tooltip);
-void VNotebookSelector::handleItemActivated(int p_index)
-{
- if (p_index > -1 && p_index < c_notebookStartIdx) {
- // Click a special action item
- if (m_lastValidIndex > -1) {
- setCurrentIndex(m_lastValidIndex);
+ if (m_lastValidIndex != p_index && m_lastValidIndex > -1) {
+ setCurrentIndex(m_lastValidIndex);
+ }
+
+ if (!m_notebooks.isEmpty()) {
+ newNotebook();
+ }
+
+ return;
}
- newNotebook();
}
+
+ m_lastValidIndex = p_index;
+
+ int nbIdx = -1;
+ if (nb) {
+ tooltip = nb->getName();
+
+ nbIdx = m_notebooks.indexOf(nb);
+ Q_ASSERT(nbIdx > -1);
+ }
+
+ g_config->setCurNotebookIndex(nbIdx);
+
+ setToolTip(tooltip);
+
+ emit curNotebookChanged(nb);
}
void VNotebookSelector::update()
@@ -260,13 +284,14 @@ bool VNotebookSelector::newNotebook()
info += tr("* A previously created notebook could be imported into VNote "
"by choosing its root folder.");
- QString defaultName;
- QString defaultPath;
-
// Use empty default name and path to let the dialog to auto generate a name
// under the default VNote notebook folder.
- VNewNotebookDialog dialog(tr("Add Notebook"), info, defaultName,
- defaultPath, m_notebooks, this);
+ VNewNotebookDialog dialog(tr("Add Notebook"),
+ info,
+ "",
+ "",
+ m_notebooks,
+ this);
if (dialog.exec() == QDialog::Accepted) {
createNotebook(dialog.getNameInput(),
dialog.getPathInput(),
@@ -289,7 +314,7 @@ void VNotebookSelector::createNotebook(const QString &p_name,
{
VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import,
p_imageFolder, p_attachmentFolder,
- m_vnote);
+ g_vnote);
if (!nb) {
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
tr("Fail to create notebook "
@@ -302,8 +327,8 @@ void VNotebookSelector::createNotebook(const QString &p_name,
m_notebooks.append(nb);
g_config->setNotebooks(m_notebooks);
- addNotebookItem(nb->getName());
- setCurrentIndexNotebook(m_notebooks.size() - 1);
+ addNotebookItem(nb);
+ setCurrentItemToNotebook(nb);
}
void VNotebookSelector::deleteNotebook()
@@ -312,86 +337,83 @@ void VNotebookSelector::deleteNotebook()
if (items.isEmpty()) {
return;
}
- Q_ASSERT(items.size() == 1);
- QListWidgetItem *item = items[0];
- int index = indexOfListItem(item);
- VNotebook *notebook = getNotebookFromComboIndex(index);
+ Q_ASSERT(items.size() == 1);
+
+ VNotebook *notebook = getNotebook(items[0]);
Q_ASSERT(notebook);
VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook, this);
if (dialog.exec() == QDialog::Accepted) {
bool deleteFiles = dialog.getDeleteFiles();
- m_editArea->closeFile(notebook, true);
+ g_mainWin->getEditArea()->closeFile(notebook, true);
deleteNotebook(notebook, deleteFiles);
}
}
void VNotebookSelector::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles)
{
- V_ASSERT(p_notebook);
+ Q_ASSERT(p_notebook);
- int idx = indexOfNotebook(p_notebook);
-
- m_notebooks.remove(idx);
+ m_notebooks.removeOne(p_notebook);
g_config->setNotebooks(m_notebooks);
- removeNotebookItem(idx);
+ int idx = itemIndexOfNotebook(p_notebook);
+ QListWidgetItem *item = m_listWidget->takeItem(idx);
+ Q_ASSERT(item);
+ delete item;
QString name(p_notebook->getName());
QString path(p_notebook->getPath());
bool ret = VNotebook::deleteNotebook(p_notebook, p_deleteFiles);
if (!ret) {
// Notebook could not be deleted completely.
- int cho = VUtils::showMessage(QMessageBox::Information, tr("Delete Notebook Folder From Disk"),
+ int cho = VUtils::showMessage(QMessageBox::Information,
+ tr("Delete Notebook Folder From Disk"),
tr("Fail to delete the root folder of notebook "
"%2 from disk. You may open "
"the folder and check it manually.")
- .arg(g_config->c_dataTextStyle).arg(name), "",
+ .arg(g_config->c_dataTextStyle).arg(name),
+ "",
QMessageBox::Open | QMessageBox::Ok,
- QMessageBox::Ok, this);
+ QMessageBox::Ok,
+ this);
if (cho == QMessageBox::Open) {
// Open the notebook location.
QUrl url = QUrl::fromLocalFile(path);
QDesktopServices::openUrl(url);
}
}
-}
-int VNotebookSelector::indexOfNotebook(const VNotebook *p_notebook)
-{
- for (int i = 0; i < m_notebooks.size(); ++i) {
- if (m_notebooks[i] == p_notebook) {
- return i;
- }
+ if (m_notebooks.isEmpty()) {
+ m_muted = true;
+ setCurrentIndex(0);
+ m_muted = false;
}
- return -1;
}
-
void VNotebookSelector::editNotebookInfo()
{
QList items = m_listWidget->selectedItems();
if (items.isEmpty()) {
return;
}
+
Q_ASSERT(items.size() == 1);
- QListWidgetItem *item = items[0];
- int index = indexOfListItem(item);
- VNotebook *notebook = getNotebookFromComboIndex(index);
- QString curName = notebook->getName();
-
- VNotebookInfoDialog dialog(tr("Notebook Information"), "", notebook,
- m_notebooks, this);
+ VNotebook *notebook = getNotebook(items[0]);
+ VNotebookInfoDialog dialog(tr("Notebook Information"),
+ "",
+ notebook,
+ m_notebooks,
+ this);
if (dialog.exec() == QDialog::Accepted) {
bool updated = false;
bool configUpdated = false;
QString name = dialog.getName();
- if (name != curName) {
+ if (name != notebook->getName()) {
updated = true;
notebook->rename(name);
- updateComboBoxItem(index, name);
g_config->setNotebooks(m_notebooks);
}
@@ -407,54 +429,58 @@ void VNotebookSelector::editNotebookInfo()
}
if (updated) {
+ fillItem(items[0], notebook);
emit notebookUpdated(notebook);
}
}
}
-void VNotebookSelector::addNotebookItem(const QString &p_name)
+void VNotebookSelector::addNotebookItem(const VNotebook *p_notebook)
{
QListWidgetItem *item = new QListWidgetItem(m_listWidget);
- item->setText(p_name);
- item->setToolTip(p_name);
- item->setIcon(QIcon(":/resources/icons/notebook_item.svg"));
+ fillItem(item, p_notebook);
}
-void VNotebookSelector::removeNotebookItem(int p_index)
+void VNotebookSelector::fillItem(QListWidgetItem *p_item,
+ const VNotebook *p_notebook) const
{
- QListWidgetItem *item = m_listWidget->item(p_index + c_notebookStartIdx);
- m_listWidget->removeItemWidget(item);
- delete item;
+ p_item->setText(p_notebook->getName());
+ p_item->setToolTip(p_notebook->getName());
+ p_item->setIcon(QIcon(":/resources/icons/notebook_item.svg"));
+ p_item->setData(Qt::UserRole, (qulonglong)p_notebook);
}
-void VNotebookSelector::updateComboBoxItem(int p_index, const QString &p_name)
-{
- QListWidgetItem *item = m_listWidget->item(p_index);
- item->setText(p_name);
- item->setToolTip(p_name);
-}
-void VNotebookSelector::requestPopupListContextMenu(QPoint p_pos)
+void VNotebookSelector::popupListContextMenuRequested(QPoint p_pos)
{
- QModelIndex index = m_listWidget->indexAt(p_pos);
- if (!index.isValid() || index.row() < c_notebookStartIdx) {
+ QListWidgetItem *item = m_listWidget->itemAt(p_pos);
+ if (!item) {
+ return;
+ }
+
+ const VNotebook *nb = getNotebook(item);
+ if (!nb) {
return;
}
- QListWidgetItem *item = m_listWidget->itemAt(p_pos);
- Q_ASSERT(item);
m_listWidget->clearSelection();
item->setSelected(true);
QMenu menu(this);
menu.setToolTipsVisible(true);
menu.addAction(m_deleteNotebookAct);
- menu.addSeparator();
- menu.addAction(m_recycleBinAct);
- menu.addAction(m_emptyRecycleBinAct);
+ if (nb->isValid()) {
+ menu.addSeparator();
+ menu.addAction(m_recycleBinAct);
+ menu.addAction(m_emptyRecycleBinAct);
+ }
+
menu.addSeparator();
menu.addAction(m_openLocationAct);
- menu.addAction(m_notebookInfoAct);
+
+ if (nb->isValid()) {
+ menu.addAction(m_notebookInfoAct);
+ }
menu.exec(m_listWidget->mapToGlobal(p_pos));
}
@@ -471,40 +497,30 @@ bool VNotebookSelector::eventFilter(QObject *watched, QEvent *event)
return true;
}
}
- return QComboBox::eventFilter(watched, event);
-}
-int VNotebookSelector::indexOfListItem(const QListWidgetItem *p_item)
-{
- int nrItems = m_listWidget->count();
- for (int i = 0; i < nrItems; ++i) {
- if (m_listWidget->item(i) == p_item) {
- return i;
- }
- }
- return -1;
+ return QComboBox::eventFilter(watched, event);
}
bool VNotebookSelector::locateNotebook(const VNotebook *p_notebook)
{
- if (p_notebook) {
- for (int i = 0; i < m_notebooks.size(); ++i) {
- if (m_notebooks[i] == p_notebook) {
- setCurrentIndexNotebook(i);
- return true;
- }
- }
+ bool ret = false;
+ int index = itemIndexOfNotebook(p_notebook);
+ if (index > -1) {
+ setCurrentIndex(index);
+ ret = true;
}
- return false;
+
+ return ret;
}
void VNotebookSelector::showPopup()
{
- if (count() <= c_notebookStartIdx) {
+ if (m_notebooks.isEmpty()) {
// No normal notebook items. Just add notebook.
newNotebook();
return;
}
+
resizeListWidgetToContent();
QComboBox::showPopup();
}
@@ -526,6 +542,7 @@ void VNotebookSelector::resizeListWidgetToContent()
minHeight = m_listWidget->sizeHintForRow(0) * m_listWidget->count() + 10;
minHeight = qMin(minHeight, maxMinHeight);
}
+
m_listWidget->setMinimumSize(minWidth, minHeight);
}
@@ -616,3 +633,23 @@ bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event)
return false;
}
+
+VNotebook *VNotebookSelector::getNotebook(int p_itemIdx) const
+{
+ VNotebook *nb = NULL;
+ QListWidgetItem *item = m_listWidget->item(p_itemIdx);
+ if (item) {
+ nb = (VNotebook *)item->data(Qt::UserRole).toULongLong();
+ }
+
+ return nb;
+}
+
+VNotebook *VNotebookSelector::getNotebook(const QListWidgetItem *p_item) const
+{
+ if (p_item) {
+ return (VNotebook *)p_item->data(Qt::UserRole).toULongLong();
+ }
+
+ return NULL;
+}
diff --git a/src/vnotebookselector.h b/src/vnotebookselector.h
index 9f5797b5..cfb875d4 100644
--- a/src/vnotebookselector.h
+++ b/src/vnotebookselector.h
@@ -7,8 +7,6 @@
#include "vnavigationmode.h"
class VNotebook;
-class VNote;
-class VEditArea;
class QListWidget;
class QAction;
class QListWidgetItem;
@@ -18,11 +16,15 @@ class VNotebookSelector : public QComboBox, public VNavigationMode
{
Q_OBJECT
public:
- explicit VNotebookSelector(VNote *vnote, QWidget *p_parent = 0);
+ explicit VNotebookSelector(QWidget *p_parent = 0);
+
+ // Update Combox from m_notebooks.
void update();
- inline void setEditArea(VEditArea *p_editArea);
+
// Select notebook @p_notebook.
bool locateNotebook(const VNotebook *p_notebook);
+
+ // Add notebook on popup if no notebooks currently.
void showPopup() Q_DECL_OVERRIDE;
// Implementations for VNavigationMode.
@@ -41,24 +43,32 @@ signals:
void notebookCreated(const QString &p_name, bool p_import);
public slots:
+ // Popup a dialog to prompt user to create a notebook.
bool newNotebook();
protected:
bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE;
private slots:
+ // Act to currentIndexChanged() signal if m_muted is false.
void handleCurIndexChanged(int p_index);
- void handleItemActivated(int p_index);
- void requestPopupListContextMenu(QPoint p_pos);
+
+ void popupListContextMenuRequested(QPoint p_pos);
+
+ // Delete currently selected notebook.
void deleteNotebook();
+
+ // View and edit notebook information of selected notebook.
void editNotebookInfo();
private:
void initActions();
+
+ // Update Combox from m_notebooks.
void updateComboBox();
- // Return the index of @p_notebook in m_noteboks.
- int indexOfNotebook(const VNotebook *p_notebook);
+ // Return the item index of @p_notebook.
+ int itemIndexOfNotebook(const VNotebook *p_notebook) const;
// If @p_import is true, we will use the existing config file.
// If @p_imageFolder is empty, we will use the global one.
@@ -68,26 +78,37 @@ private:
const QString &p_attachmentFolder);
void deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles);
- void addNotebookItem(const QString &p_name);
- // @p_index is the index of m_notebooks, NOT of QComboBox.
- void removeNotebookItem(int p_index);
- // @p_index is the index of QComboBox.
- void updateComboBoxItem(int p_index, const QString &p_name);
+
+ // Add an item corresponding to @p_notebook to combo box.
+ void addNotebookItem(const VNotebook *p_notebook);
+
+ void fillItem(QListWidgetItem *p_item, const VNotebook *p_notebook) const;
+
+ // Insert "Add Notebook" item to combo box.
void insertAddNotebookItem();
- // @p_index is the index of m_notebooks.
- void setCurrentIndexNotebook(int p_index);
- int indexOfListItem(const QListWidgetItem *p_item);
- // @p_index is the idnex of QComboBox.
- inline VNotebook *getNotebookFromComboIndex(int p_index);
+
+ // Set current item corresponding to @p_notebook.
+ void setCurrentItemToNotebook(const VNotebook *p_notebook);
+
+ // Get VNotebook from @p_itemIdx, the index of m_listWidget.
+ VNotebook *getNotebook(int p_itemIdx) const;
+
+ VNotebook *getNotebook(const QListWidgetItem *p_item) const;
+
void resizeListWidgetToContent();
+
bool handlePopupKeyPress(QKeyEvent *p_event);
- VNote *m_vnote;
QVector &m_notebooks;
- VEditArea *m_editArea;
+
QListWidget *m_listWidget;
+
+ // Used to restore after clicking Add Notebook item.
int m_lastValidIndex;
+ // Whether it is muted from currentIndexChanged().
+ bool m_muted;
+
// Actions
QAction *m_deleteNotebookAct;
QAction *m_notebookInfoAct;
@@ -95,28 +116,7 @@ private:
QAction *m_recycleBinAct;
QAction *m_emptyRecycleBinAct;
- // We will add several special action item in the combobox. This is the start index
- // of the real notebook items related to m_notebooks.
- static const int c_notebookStartIdx;
-
QLabel *m_naviLabel;
};
-inline void VNotebookSelector::setEditArea(VEditArea *p_editArea)
-{
- m_editArea = p_editArea;
-}
-
-inline VNotebook *VNotebookSelector::getNotebookFromComboIndex(int p_index)
-{
- if (p_index < c_notebookStartIdx) {
- return NULL;
- }
- int nbIdx = p_index - c_notebookStartIdx;
- if (nbIdx >= m_notebooks.size()) {
- return NULL;
- }
- return m_notebooks[nbIdx];
-}
-
#endif // VNOTEBOOKSELECTOR_H
diff --git a/src/vnotefile.cpp b/src/vnotefile.cpp
index 0cbb46f8..ea85a7fb 100644
--- a/src/vnotefile.cpp
+++ b/src/vnotefile.cpp
@@ -233,7 +233,9 @@ bool VNoteFile::addAttachment(const QString &p_file)
QString folderPath = fetchAttachmentFolderPath();
QString name = VUtils::fileNameFromPath(p_file);
Q_ASSERT(!name.isEmpty());
- name = VUtils::getFileNameWithSequence(folderPath, name);
+ // For attachments, we do not use complete base name.
+ // abc.tar.gz should be abc_001.tar.gz instead of abc.tar_001.gz.
+ name = VUtils::getFileNameWithSequence(folderPath, name, false);
QString destPath = QDir(folderPath).filePath(name);
if (!VUtils::copyFile(p_file, destPath, false)) {
return false;
@@ -543,7 +545,7 @@ bool VNoteFile::copyFile(VDirectory *p_destDir,
if (!attaFolderPath.isEmpty()) {
QDir dir(destFile->fetchBasePath());
QString folderPath = dir.filePath(destFile->getNotebook()->getAttachmentFolder());
- attaFolder = VUtils::getFileNameWithSequence(folderPath, attaFolder);
+ attaFolder = VUtils::getDirNameWithSequence(folderPath, attaFolder);
folderPath = QDir(folderPath).filePath(attaFolder);
// Copy attaFolderPath to folderPath.
diff --git a/src/voutline.cpp b/src/voutline.cpp
index dd1f7f31..c4a6c299 100644
--- a/src/voutline.cpp
+++ b/src/voutline.cpp
@@ -1,44 +1,39 @@
-#include
#include
#include
#include
#include
#include
#include "voutline.h"
-#include "vtoc.h"
#include "utils/vutils.h"
#include "vnote.h"
+#include "vfile.h"
extern VNote *g_vnote;
VOutline::VOutline(QWidget *parent)
- : QTreeWidget(parent), VNavigationMode()
+ : QTreeWidget(parent),
+ VNavigationMode(),
+ m_muted(false)
{
setColumnCount(1);
setHeaderHidden(true);
setSelectionMode(QAbstractItemView::SingleSelection);
+ // TODO: jump to the header when user click the same item twice.
connect(this, &VOutline::currentItemChanged,
- this, &VOutline::handleCurItemChanged);
+ this, &VOutline::handleCurrentItemChanged);
}
-void VOutline::checkOutline(const VToc &p_toc) const
+void VOutline::updateOutline(const VTableOfContent &p_outline)
{
- const QVector &headers = p_toc.headers;
-
- for (int i = 0; i < headers.size(); ++i) {
- V_ASSERT(headers[i].index == i);
+ if (p_outline == m_outline) {
+ return;
}
-}
-void VOutline::updateOutline(const VToc &toc)
-{
// Clear current header
- curHeader = VAnchor();
+ m_currentHeader.clear();
- checkOutline(toc);
-
- outline = toc;
+ m_outline = p_outline;
updateTreeFromOutline();
@@ -49,22 +44,25 @@ void VOutline::updateTreeFromOutline()
{
clear();
- if (!outline.valid) {
+ if (m_outline.isEmpty()) {
return;
}
- const QVector &headers = outline.headers;
+ const QVector &headers = m_outline.getTable();
int idx = 0;
updateTreeByLevel(headers, idx, NULL, NULL, 1);
}
-void VOutline::updateTreeByLevel(const QVector &headers, int &index,
- QTreeWidgetItem *parent, QTreeWidgetItem *last, int level)
+void VOutline::updateTreeByLevel(const QVector &headers,
+ int &index,
+ QTreeWidgetItem *parent,
+ QTreeWidgetItem *last,
+ int level)
{
while (index < headers.size()) {
- const VHeader &header = headers[index];
+ const VTableOfContentItem &header = headers[index];
QTreeWidgetItem *item;
- if (header.level == level) {
+ if (header.m_level == level) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
@@ -75,7 +73,7 @@ void VOutline::updateTreeByLevel(const QVector &headers, int &index,
last = item;
++index;
- } else if (header.level < level) {
+ } else if (header.m_level < level) {
return;
} else {
updateTreeByLevel(headers, index, last, NULL, level + 1);
@@ -83,11 +81,11 @@ void VOutline::updateTreeByLevel(const QVector &headers, int &index,
}
}
-void VOutline::fillItem(QTreeWidgetItem *p_item, const VHeader &p_header)
+void VOutline::fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header)
{
- p_item->setData(0, Qt::UserRole, p_header.index);
- p_item->setText(0, p_header.name);
- p_item->setToolTip(0, p_header.name);
+ p_item->setData(0, Qt::UserRole, p_header.m_index);
+ p_item->setText(0, p_header.m_name);
+ p_item->setToolTip(0, p_header.m_name);
if (p_header.isEmpty()) {
p_item->setForeground(0, QColor("grey"));
@@ -99,127 +97,81 @@ void VOutline::expandTree()
if (topLevelItemCount() == 0) {
return;
}
+
expandAll();
}
-void VOutline::handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem * /*p_preItem*/)
+void VOutline::handleCurrentItemChanged(QTreeWidgetItem *p_curItem,
+ QTreeWidgetItem * p_preItem)
{
+ Q_UNUSED(p_preItem);
+
if (!p_curItem) {
return;
}
- const VHeader *header = getHeaderFromItem(p_curItem);
- if (!header) {
- return;
- }
+ const VTableOfContentItem *header = getHeaderFromItem(p_curItem);
+ Q_ASSERT(header);
+ m_currentHeader.update(m_outline.getFile(), header->m_index);
- VAnchor tmp(outline.m_file, header->anchor, header->lineNumber, header->index);
- if (tmp == curHeader) {
- return;
- }
-
- curHeader = tmp;
-
- if (!header->isEmpty()) {
- emit outlineItemActivated(curHeader);
+ if (!header->isEmpty() && !m_muted) {
+ emit outlineItemActivated(m_currentHeader);
}
}
-void VOutline::updateCurHeader(const VAnchor &anchor)
+void VOutline::updateCurrentHeader(const VHeaderPointer &p_header)
{
- if (anchor == curHeader) {
+ if (p_header == m_currentHeader
+ || !m_outline.isMatched(p_header)) {
return;
}
- curHeader = anchor;
- if (outline.type == VHeaderType::Anchor) {
- selectAnchor(anchor.anchor);
- } else {
- // Select by lineNumber.
- selectLineNumber(anchor.lineNumber);
- }
+ // Item change should not emit the signal.
+ m_muted = true;
+ m_currentHeader = p_header;
+ selectHeader(m_currentHeader);
+ m_muted = false;
}
-void VOutline::selectAnchor(const QString &anchor)
+void VOutline::selectHeader(const VHeaderPointer &p_header)
{
setCurrentItem(NULL);
- if (anchor.isEmpty()) {
+ if (!m_outline.getItem(p_header)) {
return;
}
int nrTop = topLevelItemCount();
for (int i = 0; i < nrTop; ++i) {
- if (selectAnchorOne(topLevelItem(i), anchor)) {
+ if (selectHeaderOne(topLevelItem(i), p_header)) {
return;
}
}
}
-bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor)
+bool VOutline::selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header)
{
- if (!item) {
+ if (!p_item) {
return false;
}
- const VHeader *header = getHeaderFromItem(item);
+ const VTableOfContentItem *header = getHeaderFromItem(p_item);
if (!header) {
return false;
}
- if (header->anchor == anchor) {
- setCurrentItem(item);
+ if (header->isMatched(p_header)) {
+ setCurrentItem(p_item);
return true;
}
- int nrChild = item->childCount();
+ int nrChild = p_item->childCount();
for (int i = 0; i < nrChild; ++i) {
- if (selectAnchorOne(item->child(i), anchor)) {
+ if (selectHeaderOne(p_item->child(i), p_header)) {
return true;
}
}
- return false;
-}
-void VOutline::selectLineNumber(int lineNumber)
-{
- setCurrentItem(NULL);
-
- if (lineNumber == -1) {
- return;
- }
-
- int nrTop = topLevelItemCount();
- for (int i = 0; i < nrTop; ++i) {
- if (selectLineNumberOne(topLevelItem(i), lineNumber)) {
- return;
- }
- }
-}
-
-bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber)
-{
- if (!item) {
- return false;
- }
-
- const VHeader *header = getHeaderFromItem(item);
- if (!header) {
- return false;
- }
-
- if (header->lineNumber == lineNumber) {
- // Select this item
- setCurrentItem(item);
- return true;
- }
-
- int nrChild = item->childCount();
- for (int i = 0; i < nrChild; ++i) {
- if (selectLineNumberOne(item->child(i), lineNumber)) {
- return true;
- }
- }
return false;
}
@@ -373,17 +325,8 @@ QList VOutline::getVisibleChildItems(const QTreeWidgetItem *p
return items;
}
-const VHeader *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const
+const VTableOfContentItem *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const
{
- const VHeader *header = NULL;
-
int index = p_item->data(0, Qt::UserRole).toInt();
- if (index < 0 || index >= outline.headers.size()) {
- return header;
- }
-
- header = &(outline.headers[index]);
- Q_ASSERT(header->index == index);
-
- return header;
+ return m_outline.getItem(index);
}
diff --git a/src/voutline.h b/src/voutline.h
index 6867e1c0..b7c6ff5e 100644
--- a/src/voutline.h
+++ b/src/voutline.h
@@ -5,11 +5,13 @@
#include
#include
#include
-#include "vtoc.h"
+#include "vtableofcontent.h"
#include "vnavigationmode.h"
class QLabel;
+// Display table of content as a tree and enable user to click an item to
+// jump to that header.
class VOutline : public QTreeWidget, public VNavigationMode
{
Q_OBJECT
@@ -23,45 +25,60 @@ public:
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
signals:
- void outlineItemActivated(const VAnchor &anchor);
+ // Emit when current item changed by user and header of that item is not empty.
+ // Do not worry about infinite recursion.
+ void outlineItemActivated(const VHeaderPointer &p_header);
public slots:
- void updateOutline(const VToc &toc);
- void updateCurHeader(const VAnchor &anchor);
+ // Called to update outline and the tree.
+ // Just clear the tree if @p_outline is empty.
+ void updateOutline(const VTableOfContent &p_outline);
+
+ // Called to update current header in the tree.
+ // Will not emit outlineItemActivated().
+ void updateCurrentHeader(const VHeaderPointer &p_header);
protected:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
private slots:
- void handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
+ // Handle current item change even of the tree.
+ // Do not response if m_muted is true.
+ void handleCurrentItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
private:
// Update tree according to outline.
void updateTreeFromOutline();
// @index: the index in @headers.
- void updateTreeByLevel(const QVector &headers, int &index, QTreeWidgetItem *parent,
- QTreeWidgetItem *last, int level);
+ void updateTreeByLevel(const QVector &headers,
+ int &index,
+ QTreeWidgetItem *parent,
+ QTreeWidgetItem *last,
+ int level);
void expandTree();
- void selectAnchor(const QString &anchor);
- bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
- void selectLineNumber(int lineNumber);
- bool selectLineNumberOne(QTreeWidgetItem *item, int lineNumber);
+
+ // Set the item corresponding to @p_header as current item.
+ void selectHeader(const VHeaderPointer &p_header);
+
+ bool selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header);
+
QList getVisibleItems() const;
QList getVisibleChildItems(const QTreeWidgetItem *p_item) const;
// Fill the info of @p_item.
- void fillItem(QTreeWidgetItem *p_item, const VHeader &p_header);
-
- // Check if @p_toc is valid.
- void checkOutline(const VToc &p_toc) const;
+ void fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header);
// Return NULL if no corresponding header in outline.
- const VHeader *getHeaderFromItem(QTreeWidgetItem *p_item) const;
+ const VTableOfContentItem *getHeaderFromItem(QTreeWidgetItem *p_item) const;
- VToc outline;
- VAnchor curHeader;
+ VTableOfContent m_outline;
+
+ VHeaderPointer m_currentHeader;
+
+ // When true, won't emit outlineItemActivated().
+ bool m_muted;
// Navigation Mode.
// Map second key to QTreeWidgetItem.
diff --git a/src/vpreviewpage.cpp b/src/vpreviewpage.cpp
index 524ae109..d87859d7 100644
--- a/src/vpreviewpage.cpp
+++ b/src/vpreviewpage.cpp
@@ -2,10 +2,9 @@
#include
-#include "vnote.h"
#include "vmainwindow.h"
-extern VNote *g_vnote;
+extern VMainWindow *g_mainWin;
VPreviewPage::VPreviewPage(QWidget *parent) : QWebEnginePage(parent)
{
@@ -21,7 +20,7 @@ bool VPreviewPage::acceptNavigationRequest(const QUrl &p_url,
if (p_url.isLocalFile()) {
QString filePath = p_url.toLocalFile();
- if (g_vnote->getMainWindow()->tryOpenInternalFile(filePath)) {
+ if (g_mainWin->tryOpenInternalFile(filePath)) {
qDebug() << "internal notes jump" << filePath;
return false;
}
diff --git a/src/vtableofcontent.cpp b/src/vtableofcontent.cpp
new file mode 100644
index 00000000..54954991
--- /dev/null
+++ b/src/vtableofcontent.cpp
@@ -0,0 +1,176 @@
+#include "vtableofcontent.h"
+#include "vconstants.h"
+
+#include
+#include
+
+
+VTableOfContent::VTableOfContent()
+ : m_file(NULL), m_type(VTableOfContentType::Anchor)
+{
+}
+
+VTableOfContent::VTableOfContent(const VFile *p_file)
+ : m_file(p_file), m_type(VTableOfContentType::Anchor)
+{
+}
+
+void VTableOfContent::update(const VFile *p_file,
+ const QVector &p_table,
+ VTableOfContentType p_type)
+{
+ m_file = p_file;
+ m_table = p_table;
+ m_type = p_type;
+}
+
+static bool parseTocUl(QXmlStreamReader &p_xml,
+ QVector &p_table,
+ int p_level);
+
+static bool parseTocLi(QXmlStreamReader &p_xml,
+ QVector &p_table,
+ int p_level)
+{
+ Q_ASSERT(p_xml.isStartElement() && p_xml.name() == "li");
+
+ if (p_xml.readNextStartElement()) {
+ if (p_xml.name() == "a") {
+ QString anchor = p_xml.attributes().value("href").toString().mid(1);
+ QString name;
+ if (p_xml.readNext()) {
+ if (p_xml.tokenString() == "Characters") {
+ name = p_xml.text().toString();
+ } else if (!p_xml.isEndElement()) {
+ qWarning() << "TOC HTML