Merge remote-tracking branch 'origin/dev'

This commit is contained in:
Le Tan 2017-10-22 11:31:19 +08:00
commit d7224380d1
104 changed files with 5871 additions and 2125 deletions

View File

@ -1,11 +1,11 @@
#!/bin/bash #!/bin/bash
project_dir=$(pwd) 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: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 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 source /opt/qt*/bin/qt*-env.sh
# Compile newer version fcitx-qt5 # 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/libFcitxQt5DBusAddons.so* /opt/qt*/lib/
sudo cp /usr/local/lib/libFcitxQt5WidgetsAddons.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} cd ${project_dir}
mkdir build 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/iconengines
mkdir -p ./dist/usr/plugins/imageformats mkdir -p ./dist/usr/plugins/imageformats
mkdir -p ./dist/usr/plugins/platforminputcontexts mkdir -p ./dist/usr/plugins/platforminputcontexts
cp /opt/qt57/plugins/iconengines/* ./dist/usr/plugins/iconengines/ cp /opt/qt59/plugins/iconengines/* ./dist/usr/plugins/iconengines/
cp /opt/qt57/plugins/imageformats/* ./dist/usr/plugins/imageformats/ cp /opt/qt59/plugins/imageformats/* ./dist/usr/plugins/imageformats/
cp /opt/qt57/plugins/platforminputcontexts/* ./dist/usr/plugins/platforminputcontexts/ cp /opt/qt59/plugins/platforminputcontexts/* ./dist/usr/plugins/platforminputcontexts/
# Copy other project files # Copy other project files
cp "${project_dir}/README.md" "dist/README.md" 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 ./linuxdeployqt*.AppImage ./dist/usr/share/applications/*.desktop -bundle-non-qt-libs
# Copy translations # 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. # Package it for the second time.
./linuxdeployqt*.AppImage ./dist/usr/share/applications/*.desktop -appimage ./linuxdeployqt*.AppImage ./dist/usr/share/applications/*.desktop -appimage

View File

@ -2,8 +2,8 @@
project_dir=$(pwd) project_dir=$(pwd)
brew update > /dev/null brew update > /dev/null
brew install qt@5.7 brew install qt@5.9
QTDIR="/usr/local/opt/qt@5.7" QTDIR="/usr/local/opt/qt@5.9"
PATH="$QTDIR/bin:$PATH" PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include CPPFLAGS=-I$QTDIR/include

View File

@ -54,7 +54,8 @@ Utilizing Qt, VNote could run on **Linux**, **Windows**, and **macOS** (due to t
- Supports infinite levels of folders; - Supports infinite levels of folders;
- Supports multiple tabs and splitting windows; - 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 [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.
![VNote Edit](screenshots/vnote_edit.gif) ![VNote Edit](screenshots/vnote_edit.gif)
@ -136,7 +137,7 @@ VNote also supports many other features, like:
- Auto indent and auto list; - Auto indent and auto list;
# Build & Development # Build & Development
VNote needs Qt 5.7 or above to build. VNote needs Qt 5.9.1 or above to build.
1. Clone & Init 1. Clone & Init
``` ```
@ -145,15 +146,15 @@ VNote needs Qt 5.7 or above to build.
git submodule update --init git submodule update --init
``` ```
2. Download Qt & Have Fun 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 ## 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 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 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. If you prefer command line on macOS, you could follow these steps.
1. Install Xcode and Homebrew; 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: 3. In the project directory, create `build_macos.sh` like this:
```sh ```sh
QTDIR="/usr/local/opt/qt@5.7" QTDIR="/usr/local/opt/qt@5.9.1"
PATH="$QTDIR/bin:$PATH" PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include 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! 5. Now you got the bundle `path/to/project/build/src/VNote.app`. Enjoy yourself!
# Dependencies # 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) - [PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/) (MIT License)
- [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License) - [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License)
- [Marked](https://github.com/chjj/marked) (MIT 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) - [MathJax](https://www.mathjax.org/) (Apache-2.0)
- [showdown](https://github.com/showdownjs/showdown) (Unknown) - [showdown](https://github.com/showdownjs/showdown) (Unknown)
- [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License) - [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License)
- Icons made by a326703305@qq.com
# License # License
VNote is licensed under the [MIT license](http://opensource.org/licenses/MIT). VNote is licensed under the [MIT license](http://opensource.org/licenses/MIT).

View File

@ -54,8 +54,9 @@ VNote不是一个简单的Markdown编辑器。通过提供笔记管理功能V
- 支持Vim模式以及一系列强大的快捷键 - 支持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/)
- 支持高分辨率; - 支持高分辨率;
- 支持笔记附件。
![VNote Edit](screenshots/vnote_edit.gif) ![VNote Edit](screenshots/vnote_edit.gif)
@ -137,7 +138,7 @@ VNote还支持其他很多的功能比如
- 自动缩进和自动列表; - 自动缩进和自动列表;
# 构建与开发 # 构建与开发
VNote需要5.7版本以上的Qt进行构建。 VNote需要5.9.1版本以上的Qt进行构建。
1. 克隆代码仓库 1. 克隆代码仓库
``` ```
@ -146,15 +147,15 @@ VNote需要5.7版本以上的Qt进行构建。
git submodule update --init git submodule update --init
``` ```
2. 下载Qt 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
如果您的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 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 source /opt/qt*/bin/qt*-env.sh
``` ```
@ -175,15 +176,15 @@ sudo make install
在macOS下您可以执行以下步骤来编译 在macOS下您可以执行以下步骤来编译
1. 安装Xcode和Homebrew 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` 3. 在VNote源码根目录下新建一个文件`build_macos.sh`
```sh ```sh
QTDIR="/usr/local/opt/qt@5.7" QTDIR="/usr/local/opt/qt@5.9.1"
PATH="$QTDIR/bin:$PATH" PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include CPPFLAGS=-I$QTDIR/include
@ -203,7 +204,7 @@ sudo make install
5. 此时得到VNote的Bundle `path/to/project/build/src/VNote.app`,打开即可。 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) - [PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/) (MIT License)
- [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License) - [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License)
- [Marked](https://github.com/chjj/marked) (MIT License) - [Marked](https://github.com/chjj/marked) (MIT License)
@ -216,6 +217,7 @@ sudo make install
- [MathJax](https://www.mathjax.org/) (Apache-2.0) - [MathJax](https://www.mathjax.org/) (Apache-2.0)
- [showdown](https://github.com/showdownjs/showdown) (Unknown) - [showdown](https://github.com/showdownjs/showdown) (Unknown)
- [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License) - [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License)
- 图标由九梦岛主(a326703305@qq.com)制作
# 代码许可 # 代码许可
VNote使用[MIT许可](http://opensource.org/licenses/MIT)。 VNote使用[MIT许可](http://opensource.org/licenses/MIT)。

View File

@ -11,9 +11,9 @@ environment:
VSVER: 14 VSVER: 14
matrix: matrix:
- QT: C:\Qt\5.7\msvc2015_64 - QT: C:\Qt\5.9.1\msvc2015_64
PLATFORM: amd64 PLATFORM: amd64
- QT: C:\Qt\5.7\msvc2015 - QT: C:\Qt\5.9.1\msvc2015
PLATFORM: x86 PLATFORM: x86
clone_depth: 1 clone_depth: 1
@ -29,8 +29,8 @@ before_build:
# After calling vcvarsall.bat, %PLATFORM% will be X64 or x86 # After calling vcvarsall.bat, %PLATFORM% will be X64 or x86
- mkdir build - mkdir build
- cd build - cd build
- if "%PLATFORM%" EQU "X64" (qmake -r -spec win32-msvc2015 CONFIG+=x86_64 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-msvc2015 CONFIG+=Win32 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 # custom build scripts
build_script: build_script:

View File

@ -2,6 +2,7 @@
#include "vdirinfodialog.h" #include "vdirinfodialog.h"
#include "vdirectory.h" #include "vdirectory.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vlineedit.h"
#include "utils/vutils.h" #include "utils/vutils.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -16,7 +17,7 @@ VDirInfoDialog::VDirInfoDialog(const QString &title,
{ {
setupUI(); setupUI();
connect(nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged); connect(m_nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
handleInputChanged(); handleInputChanged();
} }
@ -28,15 +29,18 @@ void VDirInfoDialog::setupUI()
infoLabel = new QLabel(info); infoLabel = new QLabel(info);
} }
nameEdit = new QLineEdit(m_directory->getName()); m_nameEdit = new VLineEdit(m_directory->getName());
nameEdit->selectAll(); QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
m_nameEdit);
m_nameEdit->setValidator(validator);
m_nameEdit->selectAll();
// Created time. // Created time.
QString createdTimeStr = VUtils::displayDateTime(m_directory->getCreatedTimeUtc().toLocalTime()); QString createdTimeStr = VUtils::displayDateTime(m_directory->getCreatedTimeUtc().toLocalTime());
QLabel *createdTimeLabel = new QLabel(createdTimeStr); QLabel *createdTimeLabel = new QLabel(createdTimeStr);
QFormLayout *topLayout = new QFormLayout(); QFormLayout *topLayout = new QFormLayout();
topLayout->addRow(tr("Folder &name:"), nameEdit); topLayout->addRow(tr("Folder &name:"), m_nameEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel); topLayout->addRow(tr("Created time:"), createdTimeLabel);
m_warnLabel = new QLabel(); m_warnLabel = new QLabel();
@ -49,7 +53,7 @@ void VDirInfoDialog::setupUI()
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout(); QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) { if (infoLabel) {
@ -67,19 +71,35 @@ void VDirInfoDialog::setupUI()
void VDirInfoDialog::handleInputChanged() void VDirInfoDialog::handleInputChanged()
{ {
bool showWarnLabel = false; bool showWarnLabel = false;
QString name = nameEdit->text(); QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty(); bool nameOk = !name.isEmpty();
if (nameOk && name != m_directory->getName()) { if (nameOk && name != m_directory->getName()) {
// Check if the name conflicts with existing directory name. // Check if the name conflicts with existing directory name.
// Case-insensitive when creating note. // Case-insensitive when creating note.
const VDirectory *directory = m_parentDirectory->findSubDirectory(name, false); const VDirectory *directory = m_parentDirectory->findSubDirectory(name, false);
QString warnText;
if (directory && directory != m_directory) { if (directory && directory != m_directory) {
nameOk = false; nameOk = false;
showWarnLabel = true; warnText = tr("<span style=\"%1\">WARNING</span>: "
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. " "Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
"Please choose another name.") "Please choose another name.")
.arg(g_config->c_warningTextStyle); .arg(g_config->c_warningTextStyle)
m_warnLabel->setText(nameConflictText); .arg(g_config->c_dataTextStyle)
.arg(name);
} else if (!VUtils::checkFileNameLegal(name)) {
// Check if evaluated name contains illegal characters.
nameOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> contains illegal characters "
"(after magic word evaluation).")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(name);
}
if (!nameOk) {
showWarnLabel = true;
m_warnLabel->setText(warnText);
} }
} }
@ -91,5 +111,5 @@ void VDirInfoDialog::handleInputChanged()
QString VDirInfoDialog::getNameInput() const QString VDirInfoDialog::getNameInput() const
{ {
return nameEdit->text(); return m_nameEdit->getEvaluatedText();
} }

View File

@ -4,7 +4,7 @@
#include <QDialog> #include <QDialog>
class QLabel; class QLabel;
class QLineEdit; class VLineEdit;
class QDialogButtonBox; class QDialogButtonBox;
class QString; class QString;
class VDirectory; class VDirectory;
@ -27,7 +27,7 @@ private slots:
private: private:
void setupUI(); void setupUI();
QLineEdit *nameEdit; VLineEdit *m_nameEdit;
QLabel *m_warnLabel; QLabel *m_warnLabel;
QDialogButtonBox *m_btnBox; QDialogButtonBox *m_btnBox;

View File

@ -4,6 +4,7 @@
#include "vnotefile.h" #include "vnotefile.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vlineedit.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -16,7 +17,7 @@ VFileInfoDialog::VFileInfoDialog(const QString &title,
{ {
setupUI(title, info); setupUI(title, info);
connect(nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged); connect(m_nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
handleInputChanged(); handleInputChanged();
} }
@ -30,7 +31,10 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
// File name. // File name.
QString name = m_file->getName(); 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 baseStart = 0, baseLength = name.size();
int dotIdx = name.lastIndexOf('.'); int dotIdx = name.lastIndexOf('.');
if (dotIdx != -1) { if (dotIdx != -1) {
@ -38,7 +42,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
} }
// Select without suffix. // Select without suffix.
nameEdit->setSelection(baseStart, baseLength); m_nameEdit->setSelection(baseStart, baseLength);
// Attachment folder. // Attachment folder.
QLineEdit *attachmentFolderEdit = new QLineEdit(m_file->getAttachmentFolder()); 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")); modifiedTimeLabel->setToolTip(tr("Last modified time within VNote"));
QFormLayout *topLayout = new QFormLayout(); 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("Attachment folder:"), attachmentFolderEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel); topLayout->addRow(tr("Created time:"), createdTimeLabel);
topLayout->addRow(tr("Modified time:"), modifiedTimeLabel); 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); connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout(); QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) { if (infoLabel) {
@ -90,28 +94,43 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
void VFileInfoDialog::handleInputChanged() void VFileInfoDialog::handleInputChanged()
{ {
bool showWarnLabel = false; bool showWarnLabel = false;
QString name = nameEdit->text(); QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty(); bool nameOk = !name.isEmpty();
if (nameOk && name != m_file->getName()) { if (nameOk && name != m_file->getName()) {
// Check if the name conflicts with existing note name. // Check if the name conflicts with existing note name.
// Case-insensitive when creating note. // Case-insensitive when creating note.
const VNoteFile *file = m_directory->findFile(name, false); const VNoteFile *file = m_directory->findFile(name, false);
QString warnText;
if (file && file != m_file) { if (file && file != m_file) {
nameOk = false; nameOk = false;
showWarnLabel = true; warnText = tr("<span style=\"%1\">WARNING</span>: "
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. " "Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
"Please choose another name.") "Please choose another name.")
.arg(g_config->c_warningTextStyle); .arg(g_config->c_warningTextStyle)
m_warnLabel->setText(nameConflictText); .arg(g_config->c_dataTextStyle)
.arg(name);
} else if (m_file->getDocType() != DocType::Unknown } else if (m_file->getDocType() != DocType::Unknown
&& VUtils::docTypeFromName(name) != m_file->getDocType()) { && VUtils::docTypeFromName(name) != m_file->getDocType()) {
// Check if the name change the doc type. // Check if the name change the doc type.
nameOk = false; nameOk = false;
showWarnLabel = true; warnText = tr("<span style=\"%1\">WARNING</span>: "
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Changing type of the note is not supported. " "Changing type of the note is not supported. "
"Please use the same suffix as the old one.") "Please use the same suffix as the old one.")
.arg(g_config->c_warningTextStyle); .arg(g_config->c_warningTextStyle);
m_warnLabel->setText(nameConflictText); } else if (!VUtils::checkFileNameLegal(name)) {
// Check if evaluated name contains illegal characters.
nameOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> contains illegal characters "
"(after magic word evaluation).")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(name);
}
if (!nameOk) {
showWarnLabel = true;
m_warnLabel->setText(warnText);
} }
} }
@ -123,5 +142,5 @@ void VFileInfoDialog::handleInputChanged()
QString VFileInfoDialog::getNameInput() const QString VFileInfoDialog::getNameInput() const
{ {
return nameEdit->text(); return m_nameEdit->getEvaluatedText();
} }

View File

@ -4,7 +4,7 @@
#include <QDialog> #include <QDialog>
class QLabel; class QLabel;
class QLineEdit; class VLineEdit;
class QDialogButtonBox; class QDialogButtonBox;
class QString; class QString;
class VDirectory; class VDirectory;
@ -29,7 +29,7 @@ private slots:
private: private:
void setupUI(const QString &p_title, const QString &p_info); void setupUI(const QString &p_title, const QString &p_info);
QLineEdit *nameEdit; VLineEdit *m_nameEdit;
QLabel *m_warnLabel; QLabel *m_warnLabel;
QDialogButtonBox *m_btnBox; QDialogButtonBox *m_btnBox;

View File

@ -3,6 +3,7 @@
#include <QRegExp> #include <QRegExp>
#include "vinsertimagedialog.h" #include "vinsertimagedialog.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vlineedit.h"
VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle, VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
const QString &defaultPath, QWidget *parent) const QString &defaultPath, QWidget *parent)
@ -11,11 +12,14 @@ VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defa
{ {
setupUI(); setupUI();
connect(imageTitleEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton); connect(m_imageTitleEdit, &QLineEdit::textChanged,
connect(pathEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton); this, &VInsertImageDialog::handleInputChanged);
connect(browseBtn, &QPushButton::clicked, this, &VInsertImageDialog::handleBrowseBtnClicked); connect(pathEdit, &QLineEdit::textChanged,
this, &VInsertImageDialog::handleInputChanged);
connect(browseBtn, &QPushButton::clicked,
this, &VInsertImageDialog::handleBrowseBtnClicked);
enableOkButton(); handleInputChanged();
} }
VInsertImageDialog::~VInsertImageDialog() VInsertImageDialog::~VInsertImageDialog()
@ -34,19 +38,19 @@ void VInsertImageDialog::setupUI()
browseBtn = new QPushButton(tr("&Browse")); browseBtn = new QPushButton(tr("&Browse"));
imageTitleLabel = new QLabel(tr("&Image title:")); imageTitleLabel = new QLabel(tr("&Image title:"));
imageTitleEdit = new QLineEdit(defaultImageTitle); m_imageTitleEdit = new VLineEdit(defaultImageTitle);
imageTitleEdit->selectAll(); m_imageTitleEdit->selectAll();
imageTitleLabel->setBuddy(imageTitleEdit); imageTitleLabel->setBuddy(m_imageTitleEdit);
QRegExp regExp("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]+"); QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp),
QValidator *validator = new QRegExpValidator(regExp, this); m_imageTitleEdit);
imageTitleEdit->setValidator(validator); m_imageTitleEdit->setValidator(validator);
QGridLayout *topLayout = new QGridLayout(); QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(pathLabel, 0, 0); topLayout->addWidget(pathLabel, 0, 0);
topLayout->addWidget(pathEdit, 0, 1); topLayout->addWidget(pathEdit, 0, 1);
topLayout->addWidget(browseBtn, 0, 2); topLayout->addWidget(browseBtn, 0, 2);
topLayout->addWidget(imageTitleLabel, 1, 0); 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(0, 0);
topLayout->setColumnStretch(1, 1); topLayout->setColumnStretch(1, 1);
topLayout->setColumnStretch(2, 0); topLayout->setColumnStretch(2, 0);
@ -67,22 +71,31 @@ void VInsertImageDialog::setupUI()
mainLayout->setSizeConstraint(QLayout::SetFixedSize); mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(title); setWindowTitle(title);
imageTitleEdit->setFocus(); m_imageTitleEdit->setFocus();
} }
void VInsertImageDialog::enableOkButton() void VInsertImageDialog::handleInputChanged()
{ {
bool enabled = true; bool pathOk = true;
if (imageTitleEdit->text().isEmpty() || !image) { if (pathEdit->isVisible() && !pathEdit->isReadOnly()) {
enabled = false; 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); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
okBtn->setEnabled(enabled); okBtn->setEnabled(pathOk && titleOk);
} }
QString VInsertImageDialog::getImageTitleInput() const QString VInsertImageDialog::getImageTitleInput() const
{ {
return imageTitleEdit->text(); return m_imageTitleEdit->getEvaluatedText();
} }
QString VInsertImageDialog::getPathInput() const QString VInsertImageDialog::getPathInput() const
@ -130,7 +143,8 @@ void VInsertImageDialog::setImage(const QImage &image)
imagePreviewLabel->setPixmap(pixmap); imagePreviewLabel->setPixmap(pixmap);
imagePreviewLabel->setVisible(true); imagePreviewLabel->setVisible(true);
enableOkButton();
handleInputChanged();
} }
void VInsertImageDialog::setBrowseable(bool browseable, bool visible) void VInsertImageDialog::setBrowseable(bool browseable, bool visible)
@ -141,6 +155,8 @@ void VInsertImageDialog::setBrowseable(bool browseable, bool visible)
pathLabel->setVisible(visible); pathLabel->setVisible(visible);
pathEdit->setVisible(visible); pathEdit->setVisible(visible);
browseBtn->setVisible(visible); browseBtn->setVisible(visible);
handleInputChanged();
} }
void VInsertImageDialog::imageDownloaded(const QByteArray &data) void VInsertImageDialog::imageDownloaded(const QByteArray &data)

View File

@ -8,6 +8,7 @@
class QLabel; class QLabel;
class QLineEdit; class QLineEdit;
class VLineEdit;
class QPushButton; class QPushButton;
class QDialogButtonBox; class QDialogButtonBox;
@ -30,14 +31,14 @@ public slots:
void imageDownloaded(const QByteArray &data); void imageDownloaded(const QByteArray &data);
private slots: private slots:
void enableOkButton(); void handleInputChanged();
void handleBrowseBtnClicked(); void handleBrowseBtnClicked();
private: private:
void setupUI(); void setupUI();
QLabel *imageTitleLabel; QLabel *imageTitleLabel;
QLineEdit *imageTitleEdit; VLineEdit *m_imageTitleEdit;
QLabel *pathLabel; QLabel *pathLabel;
QLineEdit *pathEdit; QLineEdit *pathEdit;
QPushButton *browseBtn; QPushButton *browseBtn;

View File

@ -0,0 +1,132 @@
#include "vinsertlinkdialog.h"
#include <QtWidgets>
#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();
}

View File

@ -0,0 +1,46 @@
#ifndef VINSERTLINKDIALOG_H
#define VINSERTLINKDIALOG_H
#include <QDialog>
#include <QString>
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

View File

@ -2,6 +2,8 @@
#include "vnewdirdialog.h" #include "vnewdirdialog.h"
#include "vdirectory.h" #include "vdirectory.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vlineedit.h"
#include "utils/vutils.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -15,7 +17,7 @@ VNewDirDialog::VNewDirDialog(const QString &title,
{ {
setupUI(); setupUI();
connect(nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged); connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
handleInputChanged(); handleInputChanged();
} }
@ -29,9 +31,12 @@ void VNewDirDialog::setupUI()
} }
QLabel *nameLabel = new QLabel(tr("Folder &name:")); QLabel *nameLabel = new QLabel(tr("Folder &name:"));
nameEdit = new QLineEdit(defaultName); m_nameEdit = new VLineEdit(defaultName);
nameEdit->selectAll(); QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
nameLabel->setBuddy(nameEdit); m_nameEdit);
m_nameEdit->setValidator(validator);
m_nameEdit->selectAll();
nameLabel->setBuddy(m_nameEdit);
m_warnLabel = new QLabel(); m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true); m_warnLabel->setWordWrap(true);
@ -44,10 +49,10 @@ void VNewDirDialog::setupUI()
QHBoxLayout *topLayout = new QHBoxLayout(); QHBoxLayout *topLayout = new QHBoxLayout();
topLayout->addWidget(nameLabel); topLayout->addWidget(nameLabel);
topLayout->addWidget(nameEdit); topLayout->addWidget(m_nameEdit);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout(); QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) { if (infoLabel) {
@ -64,18 +69,34 @@ void VNewDirDialog::setupUI()
void VNewDirDialog::handleInputChanged() void VNewDirDialog::handleInputChanged()
{ {
bool showWarnLabel = false; bool showWarnLabel = false;
QString name = nameEdit->text(); QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty(); bool nameOk = !name.isEmpty();
if (nameOk) { if (nameOk) {
// Check if the name conflicts with existing directory name. // Check if the name conflicts with existing directory name.
// Case-insensitive when creating folder. // Case-insensitive when creating folder.
QString warnText;
if (m_directory->findSubDirectory(name, false)) { if (m_directory->findSubDirectory(name, false)) {
nameOk = false; nameOk = false;
showWarnLabel = true; warnText = tr("<span style=\"%1\">WARNING</span>: "
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. " "Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
"Please choose another name.") "Please choose another name.")
.arg(g_config->c_warningTextStyle); .arg(g_config->c_warningTextStyle)
m_warnLabel->setText(nameConflictText); .arg(g_config->c_dataTextStyle)
.arg(name);
} else if (!VUtils::checkFileNameLegal(name)) {
// Check if evaluated name contains illegal characters.
nameOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> contains illegal characters "
"(after magic word evaluation).")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(name);
}
if (!nameOk) {
showWarnLabel = true;
m_warnLabel->setText(warnText);
} }
} }
@ -87,5 +108,5 @@ void VNewDirDialog::handleInputChanged()
QString VNewDirDialog::getNameInput() const QString VNewDirDialog::getNameInput() const
{ {
return nameEdit->text(); return m_nameEdit->getEvaluatedText();
} }

View File

@ -4,7 +4,7 @@
#include <QDialog> #include <QDialog>
class QLabel; class QLabel;
class QLineEdit; class VLineEdit;
class QDialogButtonBox; class QDialogButtonBox;
class QString; class QString;
class VDirectory; class VDirectory;
@ -27,7 +27,7 @@ private slots:
private: private:
void setupUI(); void setupUI();
QLineEdit *nameEdit; VLineEdit *m_nameEdit;
QDialogButtonBox *m_btnBox; QDialogButtonBox *m_btnBox;
QLabel *m_warnLabel; QLabel *m_warnLabel;

View File

@ -2,6 +2,8 @@
#include "vnewfiledialog.h" #include "vnewfiledialog.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vdirectory.h" #include "vdirectory.h"
#include "vlineedit.h"
#include "utils/vutils.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -13,7 +15,7 @@ VNewFileDialog::VNewFileDialog(const QString &title, const QString &info,
{ {
setupUI(); setupUI();
connect(nameEdit, &QLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged); connect(m_nameEdit, &VLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
handleInputChanged(); handleInputChanged();
} }
@ -27,10 +29,13 @@ void VNewFileDialog::setupUI()
// Name. // Name.
QLabel *nameLabel = new QLabel(tr("Note &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('.'); int dotIndex = defaultName.lastIndexOf('.');
nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex); m_nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex);
nameLabel->setBuddy(nameEdit); nameLabel->setBuddy(m_nameEdit);
// InsertTitle. // InsertTitle.
m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)")); m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)"));
@ -42,10 +47,10 @@ void VNewFileDialog::setupUI()
}); });
QFormLayout *topLayout = new QFormLayout(); QFormLayout *topLayout = new QFormLayout();
topLayout->addRow(nameLabel, nameEdit); topLayout->addRow(nameLabel, m_nameEdit);
topLayout->addWidget(m_insertTitleCB); topLayout->addWidget(m_insertTitleCB);
nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width()); m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
m_warnLabel = new QLabel(); m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true); m_warnLabel->setWordWrap(true);
@ -73,18 +78,34 @@ void VNewFileDialog::setupUI()
void VNewFileDialog::handleInputChanged() void VNewFileDialog::handleInputChanged()
{ {
bool showWarnLabel = false; bool showWarnLabel = false;
QString name = nameEdit->text(); QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty(); bool nameOk = !name.isEmpty();
if (nameOk) { if (nameOk) {
// Check if the name conflicts with existing note name. // Check if the name conflicts with existing note name.
// Case-insensitive when creating note. // Case-insensitive when creating note.
QString warnText;
if (m_directory->findFile(name, false)) { if (m_directory->findFile(name, false)) {
nameOk = false; nameOk = false;
showWarnLabel = true; warnText = tr("<span style=\"%1\">WARNING</span>: "
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. " "Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
"Please choose another name.") "Please choose another name.")
.arg(g_config->c_warningTextStyle); .arg(g_config->c_warningTextStyle)
m_warnLabel->setText(nameConflictText); .arg(g_config->c_dataTextStyle)
.arg(name);
} else if (!VUtils::checkFileNameLegal(name)) {
// Check if evaluated name contains illegal characters.
nameOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> contains illegal characters "
"(after magic word evaluation).")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(name);
}
if (!nameOk) {
showWarnLabel = true;
m_warnLabel->setText(warnText);
} }
} }
@ -96,7 +117,7 @@ void VNewFileDialog::handleInputChanged()
QString VNewFileDialog::getNameInput() const QString VNewFileDialog::getNameInput() const
{ {
return nameEdit->text(); return m_nameEdit->getEvaluatedText();
} }
bool VNewFileDialog::getInsertTitleInput() const bool VNewFileDialog::getInsertTitleInput() const

View File

@ -4,7 +4,7 @@
#include <QDialog> #include <QDialog>
class QLabel; class QLabel;
class QLineEdit; class VLineEdit;
class QDialogButtonBox; class QDialogButtonBox;
class QCheckBox; class QCheckBox;
class VDirectory; class VDirectory;
@ -27,7 +27,7 @@ private slots:
private: private:
void setupUI(); void setupUI();
QLineEdit *nameEdit; VLineEdit *m_nameEdit;
QCheckBox *m_insertTitleCB; QCheckBox *m_insertTitleCB;
QPushButton *okBtn; QPushButton *okBtn;

View File

@ -4,6 +4,7 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vnotebook.h" #include "vnotebook.h"
#include "vlineedit.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -18,7 +19,7 @@ VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info
{ {
setupUI(title, 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(pathEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
connect(browseBtn, &QPushButton::clicked, this, &VNewNotebookDialog::handleBrowseBtnClicked); 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:")); QLabel *nameLabel = new QLabel(tr("Notebook &name:"));
nameEdit = new QLineEdit(defaultName); m_nameEdit = new VLineEdit(defaultName);
nameLabel->setBuddy(nameEdit); 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:")); QLabel *pathLabel = new QLabel(tr("Notebook &root folder:"));
pathEdit = new QLineEdit(defaultPath); 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 " 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)")); "(empty to use global configuration)"));
imageFolderLabel->setToolTip(m_imageFolderEdit->toolTip()); 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); m_imageFolderEdit->setValidator(validator);
QLabel *attachmentFolderLabel = new QLabel(tr("&Attachment folder:")); 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(); QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(nameLabel, 0, 0); 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(pathLabel, 1, 0);
topLayout->addWidget(pathEdit, 1, 1); topLayout->addWidget(pathEdit, 1, 1);
topLayout->addWidget(browseBtn, 1, 2); topLayout->addWidget(browseBtn, 1, 2);
@ -104,7 +108,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
QString VNewNotebookDialog::getNameInput() const QString VNewNotebookDialog::getNameInput() const
{ {
return nameEdit->text(); return m_nameEdit->getEvaluatedText();
} }
QString VNewNotebookDialog::getPathInput() const 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, defaultPath,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
@ -160,7 +165,7 @@ bool VNewNotebookDialog::isImportExistingNotebook() const
void VNewNotebookDialog::showEvent(QShowEvent *event) void VNewNotebookDialog::showEvent(QShowEvent *event)
{ {
nameEdit->setFocus(); m_nameEdit->setFocus();
QDialog::showEvent(event); QDialog::showEvent(event);
} }
@ -182,7 +187,7 @@ void VNewNotebookDialog::handleInputChanged()
m_manualPath = true; m_manualPath = true;
} }
if (nameEdit->isModified()) { if (m_nameEdit->isModified()) {
m_manualName = true; m_manualName = true;
} }
@ -247,7 +252,7 @@ void VNewNotebookDialog::handleInputChanged()
} }
} }
QString name = nameEdit->text(); QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty(); bool nameOk = !name.isEmpty();
if (pathOk && nameOk) { if (pathOk && nameOk) {
// Check if the name conflicts with existing notebook name. // Check if the name conflicts with existing notebook name.
@ -259,13 +264,29 @@ void VNewNotebookDialog::handleInputChanged()
} }
} }
QString warnText;
if (idx < m_notebooks.size()) { if (idx < m_notebooks.size()) {
nameOk = false; nameOk = false;
showWarnLabel = true; warnText = tr("<span style=\"%1\">WARNING</span>: "
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. " "Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
"Please choose another name.") "Please choose another name.")
.arg(g_config->c_warningTextStyle); .arg(g_config->c_warningTextStyle)
m_warnLabel->setText(nameConflictText); .arg(g_config->c_dataTextStyle)
.arg(name);
} else if (!VUtils::checkFileNameLegal(name)) {
// Check if evaluated name contains illegal characters.
nameOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> contains illegal characters "
"(after magic word evaluation).")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(name);
}
if (!nameOk) {
showWarnLabel = true;
m_warnLabel->setText(warnText);
} }
} }
@ -280,6 +301,8 @@ void VNewNotebookDialog::handleInputChanged()
bool VNewNotebookDialog::autoComplete() bool VNewNotebookDialog::autoComplete()
{ {
QString nameText = m_nameEdit->getEvaluatedText();
if (m_manualPath) { if (m_manualPath) {
if (m_manualName) { if (m_manualName) {
return false; return false;
@ -289,8 +312,8 @@ bool VNewNotebookDialog::autoComplete()
QString pathText = pathEdit->text(); QString pathText = pathEdit->text();
if (!pathText.isEmpty()) { if (!pathText.isEmpty()) {
QString autoName = VUtils::directoryNameFromPath(pathText); QString autoName = VUtils::directoryNameFromPath(pathText);
if (autoName != nameEdit->text()) { if (autoName != nameText) {
nameEdit->setText(autoName); m_nameEdit->setText(autoName);
return true; return true;
} }
} }
@ -306,7 +329,6 @@ bool VNewNotebookDialog::autoComplete()
} }
bool ret = false; bool ret = false;
QString nameText = nameEdit->text();
if (nameText.isEmpty()) { if (nameText.isEmpty()) {
if (m_manualName) { if (m_manualName) {
return false; return false;
@ -314,8 +336,8 @@ bool VNewNotebookDialog::autoComplete()
// Get a folder name under vnoteFolder and set it as the name of the notebook. // Get a folder name under vnoteFolder and set it as the name of the notebook.
QString name = "vnotebook"; QString name = "vnotebook";
name = VUtils::getFileNameWithSequence(vnoteFolder, name); name = VUtils::getDirNameWithSequence(vnoteFolder, name);
nameEdit->setText(name); m_nameEdit->setText(name);
ret = true; ret = true;
} else { } else {
// Use the name as the folder name under vnoteFolder. // Use the name as the folder name under vnoteFolder.

View File

@ -6,6 +6,7 @@
class QLabel; class QLabel;
class QLineEdit; class QLineEdit;
class VLineEdit;
class QPushButton; class QPushButton;
class QDialogButtonBox; class QDialogButtonBox;
class VNotebook; class VNotebook;
@ -52,7 +53,7 @@ private:
// Returns true if name or path is modified. // Returns true if name or path is modified.
bool autoComplete(); bool autoComplete();
QLineEdit *nameEdit; VLineEdit *m_nameEdit;
QLineEdit *pathEdit; QLineEdit *pathEdit;
QPushButton *browseBtn; QPushButton *browseBtn;
QLabel *m_warnLabel; QLabel *m_warnLabel;

View File

@ -3,6 +3,7 @@
#include "vnotebook.h" #include "vnotebook.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vlineedit.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -29,7 +30,10 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
infoLabel = new QLabel(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_nameEdit->selectAll();
m_pathEdit = new QLineEdit(m_notebook->getPath()); 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())); .arg(g_config->getImageFolder()));
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook " 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)")); "(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); m_imageFolderEdit->setValidator(validator);
// Attachment folder. // Attachment folder.
@ -98,7 +102,7 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
void VNotebookInfoDialog::handleInputChanged() void VNotebookInfoDialog::handleInputChanged()
{ {
QString name = m_nameEdit->text(); QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty(); bool nameOk = !name.isEmpty();
bool showWarnLabel = false; bool showWarnLabel = false;
@ -112,13 +116,29 @@ void VNotebookInfoDialog::handleInputChanged()
} }
} }
QString warnText;
if (idx < m_notebooks.size() && m_notebooks[idx] != m_notebook) { if (idx < m_notebooks.size() && m_notebooks[idx] != m_notebook) {
nameOk = false; nameOk = false;
showWarnLabel = true; warnText = tr("<span style=\"%1\">WARNING</span>: "
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. " "Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
"Please choose another name.") "Please choose another name.")
.arg(g_config->c_warningTextStyle); .arg(g_config->c_warningTextStyle)
m_warnLabel->setText(nameConflictText); .arg(g_config->c_dataTextStyle)
.arg(name);
} else if (!VUtils::checkFileNameLegal(name)) {
// Check if evaluated name contains illegal characters.
nameOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> contains illegal characters "
"(after magic word evaluation).")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(name);
}
if (!nameOk) {
showWarnLabel = true;
m_warnLabel->setText(warnText);
} }
} }
@ -130,7 +150,7 @@ void VNotebookInfoDialog::handleInputChanged()
QString VNotebookInfoDialog::getName() const QString VNotebookInfoDialog::getName() const
{ {
return m_nameEdit->text(); return m_nameEdit->getEvaluatedText();
} }
QString VNotebookInfoDialog::getImageFolder() const QString VNotebookInfoDialog::getImageFolder() const

View File

@ -6,6 +6,7 @@
class QLabel; class QLabel;
class QLineEdit; class QLineEdit;
class VLineEdit;
class QDialogButtonBox; class QDialogButtonBox;
class QString; class QString;
class VNotebook; class VNotebook;
@ -38,7 +39,7 @@ private:
const VNotebook *m_notebook; const VNotebook *m_notebook;
QLineEdit *m_nameEdit; VLineEdit *m_nameEdit;
QLineEdit *m_pathEdit; QLineEdit *m_pathEdit;
QLineEdit *m_imageFolderEdit; QLineEdit *m_imageFolderEdit;
// Read-only. // Read-only.

View File

@ -25,7 +25,6 @@ void VOrphanFileInfoDialog::setupUI()
QLabel *fileLabel = new QLabel(m_file->fetchPath()); QLabel *fileLabel = new QLabel(m_file->fetchPath());
topLayout->addRow(tr("File:"), fileLabel); topLayout->addRow(tr("File:"), fileLabel);
QLabel *imageFolderLabel = new QLabel(tr("Image folder:"));
m_imageFolderEdit = new QLineEdit(m_file->getImageFolder()); m_imageFolderEdit = new QLineEdit(m_file->getImageFolder());
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)") m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
.arg(g_config->getImageFolderExt())); .arg(g_config->getImageFolderExt()));
@ -33,9 +32,8 @@ void VOrphanFileInfoDialog::setupUI()
"of this file.\nIf absolute path is used, " "of this file.\nIf absolute path is used, "
"VNote will not manage those images." "VNote will not manage those images."
"(empty to use global configuration)"); "(empty to use global configuration)");
imageFolderLabel->setToolTip(imgFolderTip);
m_imageFolderEdit->setToolTip(imgFolderTip); m_imageFolderEdit->setToolTip(imgFolderTip);
topLayout->addRow(imageFolderLabel, m_imageFolderEdit); topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
// Ok is the default button. // Ok is the default button.
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);

View File

@ -169,9 +169,6 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
m_langCombo->addItem(lang.second, lang.first); m_langCombo->addItem(lang.second, lang.first);
} }
QLabel *langLabel = new QLabel(tr("Language:"), this);
langLabel->setToolTip(m_langCombo->toolTip());
// System tray checkbox. // System tray checkbox.
m_systemTray = new QCheckBox(tr("System tray"), this); m_systemTray = new QCheckBox(tr("System tray"), this);
m_systemTray->setToolTip(tr("Minimized to the system tray after closing VNote" 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); m_systemTray->setEnabled(false);
#endif #endif
// Startup pages.
QLayout *startupLayout = setupStartupPagesLayout();
QFormLayout *optionLayout = new QFormLayout(); QFormLayout *optionLayout = new QFormLayout();
optionLayout->addRow(langLabel, m_langCombo); optionLayout->addRow(tr("Language:"), m_langCombo);
optionLayout->addRow(m_systemTray); optionLayout->addRow(m_systemTray);
optionLayout->addRow(tr("Startup pages:"), startupLayout);
QVBoxLayout *mainLayout = new QVBoxLayout(); QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addLayout(optionLayout); mainLayout->addLayout(optionLayout);
@ -191,6 +192,60 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
setLayout(mainLayout); 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<void(QComboBox::*)(int)>(&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() bool VGeneralTab::loadConfiguration()
{ {
if (!loadLanguage()) { if (!loadLanguage()) {
@ -201,6 +256,10 @@ bool VGeneralTab::loadConfiguration()
return false; return false;
} }
if (!loadStartupPageType()) {
return false;
}
return true; return true;
} }
@ -214,6 +273,10 @@ bool VGeneralTab::saveConfiguration()
return false; return false;
} }
if (!saveStartupPageType()) {
return false;
}
return true; return true;
} }
@ -264,6 +327,42 @@ bool VGeneralTab::saveSystemTray()
return true; 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) VReadEditTab::VReadEditTab(QWidget *p_parent)
: 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("Read Mode"), (int)OpenFileMode::Read);
m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit); m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
QLabel *openModeLabel = new QLabel(tr("Note open mode:"));
openModeLabel->setToolTip(m_openModeCombo->toolTip());
// Heading sequence. // Heading sequence.
m_headingSequence = new QCheckBox(tr("Heading sequence")); m_headingSequenceTypeCombo = new QComboBox();
m_headingSequence->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)")); m_headingSequenceTypeCombo->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)"));
m_headingSequenceCombo = new QComboBox(); m_headingSequenceTypeCombo->addItem(tr("Disabled"), (int)HeadingSequenceType::Disabled);
m_headingSequenceCombo->setToolTip(tr("Base level to start heading sequence")); m_headingSequenceTypeCombo->addItem(tr("Enabled"), (int)HeadingSequenceType::Enabled);
m_headingSequenceCombo->addItem(tr("1"), 1); m_headingSequenceTypeCombo->addItem(tr("Enabled for notes only"), (int)HeadingSequenceType::EnabledNoteOnly);
m_headingSequenceCombo->addItem(tr("2"), 2);
m_headingSequenceCombo->addItem(tr("3"), 3); m_headingSequenceLevelCombo = new QComboBox();
m_headingSequenceCombo->addItem(tr("4"), 4); m_headingSequenceLevelCombo->setToolTip(tr("Base level to start heading sequence"));
m_headingSequenceCombo->addItem(tr("5"), 5); m_headingSequenceLevelCombo->addItem(tr("1"), 1);
m_headingSequenceCombo->addItem(tr("6"), 6); m_headingSequenceLevelCombo->addItem(tr("2"), 2);
m_headingSequenceCombo->setEnabled(false); m_headingSequenceLevelCombo->addItem(tr("3"), 3);
connect(m_headingSequence, &QCheckBox::stateChanged, m_headingSequenceLevelCombo->addItem(tr("4"), 4);
this, [this](int p_state){ m_headingSequenceLevelCombo->addItem(tr("5"), 5);
this->m_headingSequenceCombo->setEnabled(p_state == Qt::Checked); m_headingSequenceLevelCombo->addItem(tr("6"), 6);
m_headingSequenceLevelCombo->setEnabled(false);
connect(m_headingSequenceTypeCombo, static_cast<void(QComboBox::*)(int)>(&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(); QHBoxLayout *headingSequenceLayout = new QHBoxLayout();
headingSequenceLayout->addWidget(m_headingSequence); headingSequenceLayout->addWidget(m_headingSequenceTypeCombo);
headingSequenceLayout->addWidget(m_headingSequenceCombo); headingSequenceLayout->addWidget(m_headingSequenceLevelCombo);
// Web Zoom Factor. // Web Zoom Factor.
m_customWebZoom = new QCheckBox(tr("Custom Web zoom factor"), this); m_customWebZoom = new QCheckBox(tr("Custom Web zoom factor"), this);
@ -553,8 +658,8 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
colorColumnLabel->setToolTip(m_colorColumnEdit->toolTip()); colorColumnLabel->setToolTip(m_colorColumnEdit->toolTip());
QFormLayout *mainLayout = new QFormLayout(); QFormLayout *mainLayout = new QFormLayout();
mainLayout->addRow(openModeLabel, m_openModeCombo); mainLayout->addRow(tr("Note open mode:"), m_openModeCombo);
mainLayout->addRow(headingSequenceLayout); mainLayout->addRow(tr("Heading sequence:"), headingSequenceLayout);
mainLayout->addRow(zoomFactorLayout); mainLayout->addRow(zoomFactorLayout);
mainLayout->addRow(colorColumnLabel, m_colorColumnEdit); mainLayout->addRow(colorColumnLabel, m_colorColumnEdit);
@ -628,21 +733,28 @@ bool VMarkdownTab::saveOpenMode()
bool VMarkdownTab::loadHeadingSequence() bool VMarkdownTab::loadHeadingSequence()
{ {
bool enabled = g_config->getEnableHeadingSequence(); HeadingSequenceType type = g_config->getHeadingSequenceType();
int level = g_config->getHeadingSequenceBaseLevel(); int level = g_config->getHeadingSequenceBaseLevel();
if (level < 1 || level > 6) { if (level < 1 || level > 6) {
level = 1; level = 1;
} }
m_headingSequence->setChecked(enabled); int idx = m_headingSequenceTypeCombo->findData((int)type);
m_headingSequenceCombo->setCurrentIndex(level - 1); Q_ASSERT(idx > -1);
m_headingSequenceTypeCombo->setCurrentIndex(idx);
m_headingSequenceLevelCombo->setCurrentIndex(level - 1);
m_headingSequenceLevelCombo->setEnabled(type != HeadingSequenceType::Disabled);
return true; return true;
} }
bool VMarkdownTab::saveHeadingSequence() bool VMarkdownTab::saveHeadingSequence()
{ {
g_config->setEnableHeadingSequence(m_headingSequence->isChecked()); QVariant typeData = m_headingSequenceTypeCombo->currentData();
g_config->setHeadingSequenceBaseLevel(m_headingSequenceCombo->currentData().toInt()); Q_ASSERT(typeData.isValid());
g_config->setHeadingSequenceType((HeadingSequenceType)typeData.toInt());
g_config->setHeadingSequenceBaseLevel(m_headingSequenceLevelCombo->currentData().toInt());
return true; return true;
} }

View File

@ -13,6 +13,8 @@ class QCheckBox;
class QLineEdit; class QLineEdit;
class QStackedLayout; class QStackedLayout;
class QListWidget; class QListWidget;
class QPlainTextEdit;
class QVBoxLayout;
class VGeneralTab : public QWidget class VGeneralTab : public QWidget
{ {
@ -23,18 +25,32 @@ public:
bool saveConfiguration(); bool saveConfiguration();
private: private:
QLayout *setupStartupPagesLayout();
bool loadLanguage(); bool loadLanguage();
bool saveLanguage(); bool saveLanguage();
bool loadSystemTray(); bool loadSystemTray();
bool saveSystemTray(); bool saveSystemTray();
bool loadStartupPageType();
bool saveStartupPageType();
// Language // Language
QComboBox *m_langCombo; QComboBox *m_langCombo;
// System tray // System tray
QCheckBox *m_systemTray; 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<QString> c_availableLangs; static const QVector<QString> c_availableLangs;
}; };
@ -101,8 +117,8 @@ public:
QComboBox *m_openModeCombo; QComboBox *m_openModeCombo;
// Whether enable heading sequence. // Whether enable heading sequence.
QCheckBox *m_headingSequence; QComboBox *m_headingSequenceTypeCombo;
QComboBox *m_headingSequenceCombo; QComboBox *m_headingSequenceLevelCombo;
// Web zoom factor. // Web zoom factor.
QCheckBox *m_customWebZoom; QCheckBox *m_customWebZoom;

View File

@ -13,7 +13,6 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
VConfigManager *g_config; VConfigManager *g_config;
VMainWindow *g_mainWin;
#if defined(QT_NO_DEBUG) #if defined(QT_NO_DEBUG)
QFile g_logFile; QFile g_logFile;
@ -116,22 +115,10 @@ int main(int argc, char *argv[])
QApplication app(argc, argv); QApplication app(argc, argv);
// The file path passed via command line arguments. // The file path passed via command line arguments.
QStringList filePaths; QStringList filePaths = VUtils::filterFilePathsToOpen(app.arguments().mid(1));
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);
}
}
}
qDebug() << "command line arguments" << args; qDebug() << "command line arguments" << app.arguments();
qDebug() << "files to open from arguments" << filePaths;
if (!canRun) { if (!canRun) {
// Ask another instance to open files passed in. // Ask another instance to open files passed in.
@ -169,7 +156,6 @@ int main(int argc, char *argv[])
} }
VMainWindow w(&guard); VMainWindow w(&guard);
g_mainWin = &w;
QString style = VUtils::readFileFromDisk(":/resources/vnote.qss"); QString style = VUtils::readFileFromDisk(":/resources/vnote.qss");
if (!style.isEmpty()) { if (!style.isEmpty()) {
VUtils::processStyle(style, w.getPalette()); VUtils::processStyle(style, w.getPalette());
@ -178,7 +164,11 @@ int main(int argc, char *argv[])
w.show(); w.show();
w.openExternalFiles(filePaths); w.openStartupPages();
w.openFiles(filePaths);
w.promptNewNotebookIfEmpty();
return app.exec(); return app.exec();
} }

View File

@ -49,13 +49,17 @@ Save current changes and exit edit mode.
#### Text Editing #### Text Editing
- `Ctrl+B` - `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` - `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` - `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` - `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` - `Ctrl+H`
Backspace. Delete a character backward. Backspace. Delete a character backward.
- `Ctrl+W` - `Ctrl+W`
@ -63,7 +67,7 @@ Delete all the characters from current cursor to the first space backward.
- `Ctrl+U` - `Ctrl+U`
Delete all the characters from current cursor to the beginning of current line. Delete all the characters from current cursor to the beginning of current line.
- `Ctrl+<Num>` - `Ctrl+<Num>`
Insert title at level `<Num>`. `<Num>` should be 1 to 6. Current selected text will be changed to title if exist. Insert title at level `<Num>`. `<Num>` should be 1 to 6. Current selected text will be changed to title if exists.
- `Tab`/`Shift+Tab` - `Tab`/`Shift+Tab`
Increase or decrease the indentation. If any text is selected, the indentation will operate on all these selected lines. Increase or decrease the indentation. If any text is selected, the indentation will operate on all these selected lines.
- `Shift+Enter` - `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. Expand the selection to the beginning or end of current note.
## Custom Shortcuts ## 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: For example, the default configruation may look like this:
```ini ```ini
[shortcuts] [shortcuts]
1\operation=NewNote ; Define shortcuts here, with each item in the form "operation=keysequence".
1\keysequence=Ctrl+N ; Leave keysequence empty to disable the shortcut of an operation.
2\operation=SaveNote ; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
2\keysequence=Ctrl+S ; Ctrl+Q is reserved for quitting VNote.
3\operation=SaveAndRead
3\keysequence=Ctrl+T ; Leader key of Captain mode
4\operation=EditNote CaptainMode=Ctrl+E
4\keysequence=Ctrl+W ; Create a note in current folder
5\operation=CloseNote NewNote=Ctrl+Alt+N
5\keysequence= ; Save current note
6\operation=Find SaveNote=Ctrl+S
6\keysequence=Ctrl+F ; Save changes and enter read mode
7\operation=FindNext SaveAndRead=Ctrl+T
7\keysequence=F3 ; Edit current note
8\operation=FindPrevious EditNote=Ctrl+W
8\keysequence=Shift+F3 ; Close current note
size=8 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 # Captain Mode
To efficiently utilize the shortcuts, VNote supports the **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. 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` - `E`
Toggle expanding the edit area. Toggle expanding the edit area.
- `P` - `P`

View File

@ -56,6 +56,10 @@
插入删除线;再次按`Ctrl+D`退出。如果已经选择文本,则将当前选择文本改为删除线。 插入删除线;再次按`Ctrl+D`退出。如果已经选择文本,则将当前选择文本改为删除线。
- `Ctrl+O` - `Ctrl+O`
插入行内代码;再次按`Ctrl+O`退出。如果已经选择文本,则将当前选择文本改为行内代码。 插入行内代码;再次按`Ctrl+O`退出。如果已经选择文本,则将当前选择文本改为行内代码。
- `Ctrl+M`
插入代码块;再次按`Ctrl+M`退出。如果已经选择文本,则将当前选择文本嵌入到代码块中。
- `Ctrl+L`
插入链接。
- `Ctrl+H` - `Ctrl+H`
退格键,向前删除一个字符。 退格键,向前删除一个字符。
- `Ctrl+W` - `Ctrl+W`
@ -80,43 +84,103 @@
扩展选定到笔记开始或结尾处。 扩展选定到笔记开始或结尾处。
## 自定义快捷键 ## 自定义快捷键
VNote支持自定义部分标准快捷键但并不建议这么做。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]`小节。 VNote支持自定义部分标准快捷键但并不建议这么做。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]``[captain_mode_shortcuts]`两个小节。
例如,默认的配置可能是这样子的: 例如,默认的配置可能是这样子的:
```ini ```ini
[shortcuts] [shortcuts]
1\operation=NewNote ; Define shortcuts here, with each item in the form "operation=keysequence".
1\keysequence=Ctrl+N ; Leave keysequence empty to disable the shortcut of an operation.
2\operation=SaveNote ; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
2\keysequence=Ctrl+S ; Ctrl+Q is reserved for quitting VNote.
3\operation=SaveAndRead
3\keysequence=Ctrl+T ; Leader key of Captain mode
4\operation=EditNote CaptainMode=Ctrl+E
4\keysequence=Ctrl+W ; Create a note in current folder
5\operation=CloseNote NewNote=Ctrl+Alt+N
5\keysequence= ; Save current note
6\operation=Find SaveNote=Ctrl+S
6\keysequence=Ctrl+F ; Save changes and enter read mode
7\operation=FindNext SaveAndRead=Ctrl+T
7\keysequence=F3 ; Edit current note
8\operation=FindPrevious EditNote=Ctrl+W
8\keysequence=Shift+F3 ; Close current note
size=8 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支持 **舰长模式** 为了更有效地利用快捷键VNote支持 **舰长模式**
按前导键`Ctrl+E`VNote会进入舰长模式。在舰长模式中VNote会支持更多高效的快捷操作。 按前导键`Ctrl+E`VNote会进入舰长模式。在舰长模式中VNote会支持更多高效的快捷操作。
另外,在该模式中,`Ctrl+W``W`是等效的,因此,可以`Ctrl+E+W`来实现`Ctrl+E W`的操作。
- `E` - `E`
是否扩展编辑区域。 是否扩展编辑区域。
- `P` - `P`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,6 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<text style="cursor: move;" fill="#000000" stroke-width="0" x="-146.75684" y="248.49038" id="svg_4" font-size="24" font-family="serif" text-anchor="middle" xml:space="preserve" font-weight="bold" transform="matrix(16.72881317138672,0,0,16.72881317138672,2707.567729830742,-3759.186347961426) " stroke="#000000">#</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 470 B

View File

@ -0,0 +1,10 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<title>one_panel</title>
<g>
<title>Layer 1</title>
<rect fill="none" stroke="#000000" stroke-width="40" x="58" y="57" width="395" height="395" id="svg_1"/>
<line fill="none" stroke="#000000" stroke-width="40" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" x1="193" y1="61" x2="193" y2="455" id="svg_4"/>
<line stroke="#000000" transform="rotate(-90 125.49999237060548,220.1999969482422) " id="svg_2" fill="none" stroke-width="40" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" x1="125.49999" y1="169.7" x2="125.49999" y2="270.7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 780 B

View File

@ -0,0 +1,7 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<g>
<title>Layer 2</title>
<text stroke="#000000" transform="matrix(11.892995768998892,0,0,12.532065948318316,-399.2455647895517,-874.5950374851516) " xml:space="preserve" text-anchor="middle" font-family="Sans-serif" font-size="24" id="svg_1" y="98.48198" x="55.1146" stroke-width="0" fill="#000000">1.2.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 502 B

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<path d="M256.5,208H256v0C256.2,208,256.3,208,256.5,208z"/>
<path d="M368.5,160H320c0,0,26,17,31.6,48H368h0.5c17.6,0,31.5,13.9,31.5,31.5v32c0,17.6-13.9,32.5-31.5,32.5h-112
c-17.6,0-32.5-14.9-32.5-32.5V240h-48v31.5c0,11.5,2.5,22.5,6.9,32.5c12.6,28.2,40.9,48,73.6,48h112c44.2,0,79.5-36.3,79.5-80.5
v-32C448,195.3,412.7,160,368.5,160z"/>
<path d="M329.6,208c-12.1-28.3-40.1-48-73.1-48h-112c-44.2,0-80.5,35.3-80.5,79.5v32c0,44.2,36.3,80.5,80.5,80.5H192
c0,0-25.8-17-32.1-48h-15.4c-17.6,0-32.5-14.9-32.5-32.5v-32c0-17.6,14.9-31.5,32.5-31.5H256h0.5c17.6,0,31.5,13.9,31.5,31.5v32
c0,0.2,0,0.3,0,0.5h48c0-0.2,0-0.3,0-0.5v-32C336,228.3,333.7,217.6,329.6,208z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,17 +1 @@
<?xml version="1.0"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 82 82"><title>vnote</title><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><rect width="82" height="82" rx="11.76" ry="11.76" fill="#f2f2f2"/><path d="M50.29,12.74a1.52,1.52,0,0,1,1.52-1.52H62.73a.54.54,0,0,1,.38.92L51.21,24a.54.54,0,0,1-.92-.38Z" fill="#4d4d4d"/><path d="M19.23,28.27a1.52,1.52,0,0,1,1.52-1.52H31.66a.54.54,0,0,1,.38.92l-11.9,11.9a.54.54,0,0,1-.92-.38Z" fill="#4d4d4d"/><path d="M19.23,42.6a1.52,1.52,0,0,1,.45-1.08L32.79,28.41a.54.54,0,0,1,.92.38V40.9A1.52,1.52,0,0,1,33.26,42L20.15,55.1a.54.54,0,0,1-.92-.38Z" fill="#4d4d4d"/><rect x="19.23" y="11.21" width="14.48" height="14.48" rx="1.44" ry="1.44" fill="#c69c6d"/><path d="M34.76,43.8a1.52,1.52,0,0,1,1.52-1.52H62.73a.54.54,0,0,1,.38.92L35.68,70.63a.54.54,0,0,1-.92-.38Z" fill="#4d4d4d"/><path d="M36.8,41.23a.54.54,0,0,1-.38-.92L63.85,12.88a.54.54,0,0,1,.92.38V39.7a1.52,1.52,0,0,1-1.52,1.52Z" fill="#4d4d4d"/><path d="M20.52,58.36a1.52,1.52,0,0,1,0-2.16L32.79,43.94a.54.54,0,0,1,.92.38V70.25a.54.54,0,0,1-.92.38Z" fill="#4d4d4d"/></g></g></svg>
<svg width="704" height="704" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<title>vnote</title>
<g>
<title>Layer 3</title>
<rect stroke="#000000" id="svg_11" fill="#d6eace" stroke-width="5" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" x="-0.242409" y="-0.033089" width="704.290516" height="704.010575" stroke-opacity="0" rx="40" ry="40"/>
</g>
<g>
<title>Layer 1</title>
<path id="svg_7" d="m160.375009,100.656259l-0.250009,389.843741l129.9375,129.9375l260.15625,-260.4375l0,-259.875l-260.15625,259.875l0,-259.59375l-129.687491,0.250009z" stroke-opacity="0" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="null" stroke="#000000" fill="#15ae67"/>
</g>
<g>
<title>Layer 2</title>
<path id="svg_8" d="m550.43365,100.304726l-130.205135,-0.003944l0,130.042969" stroke-opacity="0" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="null" stroke="#000000" fill="#75c5b5"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -1,17 +1 @@
<?xml version="1.0"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 142 142"><title>vnote_cube</title><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><rect width="142" height="142" rx="11.76" ry="11.76" fill="#f2f2f2"/><path d="M12.6,13.8a1.88,1.88,0,0,1,1.88-1.87H64.66A1.87,1.87,0,0,1,66,15.12L15.8,65.31A1.88,1.88,0,0,1,12.6,64Z" fill="#4d4d4d"/><path d="M75.59,14.3a1.27,1.27,0,0,1,.9-2.18h48.42a1.27,1.27,0,0,1,.9,2.18L103.14,37a3.45,3.45,0,0,1-4.87,0Z" fill="#4d4d4d"/><path d="M42.1,46.41a1.27,1.27,0,0,1,2.18-.9L67,68.18a3.45,3.45,0,0,1,0,4.87L44.28,95.73a1.27,1.27,0,0,1-2.18-.9Z" fill="#4d4d4d"/><path d="M12.6,72.56a3.45,3.45,0,0,1,1-2.44L38.13,45.62a1.27,1.27,0,0,1,2.18.9V98.58a3.45,3.45,0,0,1-1,2.44L14.78,125.53a1.27,1.27,0,0,1-2.18-.9Z" fill="#4d4d4d"/><rect x="50.8" y="20.92" width="40" height="40" rx="3.45" ry="3.45" transform="translate(-8.2 62.05) rotate(-45)" fill="#c69c6d"/><path d="M17.09,128.92a1.27,1.27,0,0,1-.9-2.18L68.56,74.38a3.45,3.45,0,0,1,4.87,0l52.37,52.37a1.27,1.27,0,0,1-.9,2.18Z" fill="#4d4d4d"/><path d="M74.85,73a3.45,3.45,0,0,1,0-4.87l52.37-52.37a1.27,1.27,0,0,1,2.18.9V124.43a1.27,1.27,0,0,1-2.18.9Z" fill="#4d4d4d"/></g></g></svg>
<svg width="704" height="704" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<title>vnote</title>
<g>
<title>Layer 3</title>
<rect ry="40" rx="40" stroke-opacity="0" height="704.010575" width="704.290516" y="-0.033089" x="-0.242409" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" fill="#d6eace" id="svg_11" stroke="#000000"/>
</g>
<g>
<title>Layer 1</title>
<path fill="#15ae67" stroke="#000000" stroke-width="null" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" stroke-opacity="0" d="m160.37501,100.65626l-0.25001,389.84374l129.9375,129.9375l260.15625,-260.4375l0,-259.875l-260.15625,259.875l0,-259.59375l-129.68749,0.25001z" id="svg_7"/>
</g>
<g>
<title>Layer 2</title>
<path fill="#ffffff" stroke="#000000" stroke-width="null" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" stroke-opacity="0" d="m550.43365,100.30473l-130.20514,-0.00395l0,130.04297" id="svg_8"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -97,7 +97,8 @@ insert_title_from_note_name=true
note_open_mode=0 note_open_mode=0
; Whether auto generate heading sequence ; 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
heading_sequence_base_level=1 heading_sequence_base_level=1
@ -135,13 +136,26 @@ confirm_reload_folder=true
; Whether double click on a tab to close it ; Whether double click on a tab to close it
double_click_close_tab=true 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] [web]
; Location and configuration for Mathjax ; Location and configuration for Mathjax
mathjax_javascript=https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML 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] [predefined_colors]
1\name=White 1\name=White
1\rgb=EEEEEE 1\rgb=EEEEEE
@ -154,25 +168,84 @@ tools_dock_checked=true
size=4 size=4
[shortcuts] [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. ; Leave keysequence empty to disable the shortcut of an operation.
; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode. ; 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. ; Ctrl+Q is reserved for quitting VNote.
1\operation=NewNote
1\keysequence=Ctrl+Alt+N ; Leader key of Captain mode
2\operation=SaveNote CaptainMode=Ctrl+E
2\keysequence=Ctrl+S ; Create a note in current folder
3\operation=SaveAndRead NewNote=Ctrl+Alt+N
3\keysequence=Ctrl+T ; Save current note
4\operation=EditNote SaveNote=Ctrl+S
4\keysequence=Ctrl+W ; Save changes and enter read mode
5\operation=CloseNote SaveAndRead=Ctrl+T
5\keysequence= ; Edit current note
6\operation=Find EditNote=Ctrl+W
6\keysequence=Ctrl+F ; Close current note
7\operation=FindNext CloseNote=
7\keysequence=F3 ; Open file/replace dialog
8\operation=FindPrevious Find=Ctrl+F
8\keysequence=Shift+F3 ; Find next occurence
size=8 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

View File

@ -9,11 +9,11 @@ QPushButton[CornerBtn="true"]::menu-indicator {
image: none; image: none;
} }
QPushButton[CornerBtn="true"]::hover { QPushButton[CornerBtn="true"]:hover {
background-color: @hover-color; background-color: @hover-color;
} }
QPushButton[CornerBtn="true"]::focus { QPushButton[CornerBtn="true"]:focus {
background-color: @focus-color; background-color: @focus-color;
} }
@ -25,11 +25,11 @@ QPushButton[StatusBtn="true"] {
background-color: transparent; background-color: transparent;
} }
QPushButton[StatusBtn="true"]::hover { QPushButton[StatusBtn="true"]:hover {
background-color: @hover-color; background-color: @hover-color;
} }
QPushButton[StatusBtn="true"]::focus { QPushButton[StatusBtn="true"]:focus {
background-color: @focus-color; background-color: @focus-color;
} }
@ -40,11 +40,11 @@ QPushButton[FlatBtn="true"] {
background-color: transparent; background-color: transparent;
} }
QPushButton[FlatBtn="true"]::hover { QPushButton[FlatBtn="true"]:hover {
background-color: @hover-color; background-color: @hover-color;
} }
QPushButton[FlatBtn="true"]::focus { QPushButton[FlatBtn="true"]:focus {
background-color: @focus-color; background-color: @focus-color;
} }
@ -56,11 +56,11 @@ QPushButton[SelectionBtn="true"] {
text-align: left; text-align: left;
} }
QPushButton[SelectionBtn="true"]::hover { QPushButton[SelectionBtn="true"]:hover {
background-color: @hover-color; background-color: @hover-color;
} }
QPushButton[SelectionBtn="true"]::focus { QPushButton[SelectionBtn="true"]:focus {
background-color: @focus-color; background-color: @focus-color;
} }
@ -71,11 +71,11 @@ QPushButton[TitleBtn="true"] {
background-color: @base-color; background-color: @base-color;
} }
QPushButton[TitleBtn="true"]::hover { QPushButton[TitleBtn="true"]:hover {
background-color: @hover-color; background-color: @hover-color;
} }
QPushButton[TitleBtn="true"]::focus { QPushButton[TitleBtn="true"]:focus {
background-color: @focus-color; background-color: @focus-color;
} }
@ -85,7 +85,7 @@ QPushButton[DangerBtn="true"] {
background-color: #d9534f; background-color: #d9534f;
} }
QPushButton[DangerBtn="true"]::hover { QPushButton[DangerBtn="true"]:hover {
color: #fff; color: #fff;
border-color: #ac2925; border-color: #ac2925;
background-color: #c9302c; background-color: #c9302c;
@ -95,10 +95,14 @@ QToolBar {
border: none; border: none;
} }
QToolButton::hover { QToolButton:hover {
background-color: @hover-color; background-color: @hover-color;
} }
/* Override default shift behavior */
QToolButton::menu-arrow:open {
}
QMenuBar { QMenuBar {
border: none; border: none;
} }
@ -141,7 +145,7 @@ QComboBox#NotebookSelector {
icon-size: 30px; icon-size: 30px;
} }
QComboBox#NotebookSelector::focus { QComboBox#NotebookSelector:focus {
background-color: @focus-color; background-color: @focus-color;
} }

View File

@ -44,7 +44,6 @@ SOURCES += main.cpp\
veditwindow.cpp \ veditwindow.cpp \
vedittab.cpp \ vedittab.cpp \
voutline.cpp \ voutline.cpp \
vtoc.cpp \
vsingleinstanceguard.cpp \ vsingleinstanceguard.cpp \
vdirectory.cpp \ vdirectory.cpp \
vfile.cpp \ vfile.cpp \
@ -77,7 +76,12 @@ SOURCES += main.cpp\
dialog/vconfirmdeletiondialog.cpp \ dialog/vconfirmdeletiondialog.cpp \
vnotefile.cpp \ vnotefile.cpp \
vattachmentlist.cpp \ vattachmentlist.cpp \
dialog/vsortdialog.cpp dialog/vsortdialog.cpp \
vfilesessioninfo.cpp \
vtableofcontent.cpp \
utils/vmetawordmanager.cpp \
vlineedit.cpp \
dialog/vinsertlinkdialog.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -107,7 +111,6 @@ HEADERS += vmainwindow.h \
veditwindow.h \ veditwindow.h \
vedittab.h \ vedittab.h \
voutline.h \ voutline.h \
vtoc.h \
vsingleinstanceguard.h \ vsingleinstanceguard.h \
vdirectory.h \ vdirectory.h \
vfile.h \ vfile.h \
@ -142,7 +145,12 @@ HEADERS += vmainwindow.h \
dialog/vconfirmdeletiondialog.h \ dialog/vconfirmdeletiondialog.h \
vnotefile.h \ vnotefile.h \
vattachmentlist.h \ vattachmentlist.h \
dialog/vsortdialog.h dialog/vsortdialog.h \
vfilesessioninfo.h \
vtableofcontent.h \
utils/vmetawordmanager.h \
vlineedit.h \
dialog/vinsertlinkdialog.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

View File

@ -48,7 +48,7 @@ bool VEditUtils::insertBlockWithIndent(QTextCursor &p_cursor)
{ {
V_ASSERT(!p_cursor.hasSelection()); V_ASSERT(!p_cursor.hasSelection());
p_cursor.insertBlock(); p_cursor.insertBlock();
return indentBlockAsPreviousBlock(p_cursor); return indentBlockAsBlock(p_cursor, false);
} }
bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor) 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; bool changed = false;
QTextBlock block = p_cursor.block(); QTextBlock block = p_cursor.block();
if (block.blockNumber() == 0) { QTextBlock refBlock = p_next ? block.next() : block.previous();
// The first block. if (!refBlock.isValid()) {
return false; return false;
} }
QTextBlock preBlock = block.previous(); QString leadingSpaces = fetchIndentSpaces(refBlock);
QString text = preBlock.text();
QRegExp regExp("(^\\s*)");
regExp.indexIn(text);
V_ASSERT(regExp.captureCount() == 1);
QString leadingSpaces = regExp.capturedTexts()[1];
moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::MoveAnchor); moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::MoveAnchor);
if (!p_cursor.atBlockStart()) { if (!p_cursor.atBlockStart()) {
@ -689,3 +684,130 @@ bool VEditUtils::needToCancelAutoIndent(int p_autoIndentPos, const QTextCursor &
return false; 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);
}

View File

@ -24,10 +24,11 @@ public:
// Need to call setTextCursor() to make it take effect. // Need to call setTextCursor() to make it take effect.
static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor, static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode); 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. // Return true if some changes have been made.
// @p_cursor will be placed at the position after inserting leading spaces. // @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. // Returns true if two blocks has the same indent.
static bool hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb); static bool hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb);
@ -132,7 +133,38 @@ public:
// Check if we need to cancel auto indent. // Check if we need to cancel auto indent.
// @p_autoIndentPos: the position of the cursor after 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: private:
VEditUtils() {} VEditUtils() {}

View File

@ -0,0 +1,596 @@
#include "vmetawordmanager.h"
#include <QDebug>
#include <QWidget>
#include <QApplication>
#include <QToolTip>
#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<QString> 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<VMagicWord> 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<VMetaWordManager *>(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<QString, QString> 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::Token> VMetaWord::parseToTokens(const QString &p_text)
{
QVector<Token> 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<VMetaWord::Token> &p_tokens,
QHash<QString, QString> &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;
}

View File

@ -0,0 +1,205 @@
#ifndef VMETAWORDMANAGER_H
#define VMETAWORDMANAGER_H
#include <functional>
#include <QObject>
#include <QString>
#include <QVector>
#include <QHash>
#include <QDateTime>
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<QString(const VMetaWord *)> 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<VMetaWord::Token> 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<VMetaWord::Token> &p_tokens,
QHash<QString, QString> &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<Token> 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<QString, VMetaWord> &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<QString, VMetaWord> 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<QString, VMetaWord> &VMetaWordManager::getAllMetaWords() const
{
return m_metaWords;
}
#endif // VMETAWORDMANAGER_H

View File

@ -33,6 +33,8 @@ QVector<QPair<QString, QString>> VUtils::s_availableLanguages;
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)"); 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_fileNameRegExp = QString("[^\\\\/:\\*\\?\"<>\\|]*");
const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$"); 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_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() void VUtils::initAvailableLanguage()
{ {
@ -104,10 +106,10 @@ QRgb VUtils::QRgbFromString(const QString &str)
return QRgb(); return QRgb();
} }
QString VUtils::generateImageFileName(const QString &path, const QString &title, QString VUtils::generateImageFileName(const QString &path,
const QString &title,
const QString &format) const QString &format)
{ {
Q_ASSERT(!title.isEmpty());
QRegExp regExp("\\W"); QRegExp regExp("\\W");
QString baseName(title.toLower()); QString baseName(title.toLower());
@ -117,7 +119,9 @@ QString VUtils::generateImageFileName(const QString &path, const QString &title,
// Constrain the length of the name. // Constrain the length of the name.
baseName.truncate(10); baseName.truncate(10);
if (!baseName.isEmpty()) {
baseName.prepend('_'); baseName.prepend('_');
}
// Add current time and random number to make the name be most likely unique // Add current time and random number to make the name be most likely unique
baseName = baseName + '_' + QString::number(QDateTime::currentDateTime().toTime_t()); 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(); 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; QDir dir(p_dirPath);
QString base = p_fileName; if (!dir.exists() || !dir.exists(p_fileName)) {
int dotIdx = p_fileName.lastIndexOf('.'); return p_fileName;
if (dotIdx != -1) {
// .md
suffix = p_fileName.right(p_fileName.size() - dotIdx);
base = p_fileName.left(dotIdx);
} }
QDir dir(p_dirPath); QFileInfo fi(p_fileName);
QString name = p_fileName; QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
int index = 0; int index = 0;
while (dir.exists(name)) { QString fileName;
do {
QString seq; QString seq;
if (index > 0) { if (index > 0) {
seq = QString::number(index); seq = QString("%1").arg(QString::number(index), 3, '0');
} }
index++; 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) 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, QString VUtils::getFileNameWithSequence(const QString &p_directory,
const QString &p_baseFileName) const QString &p_baseFileName,
bool p_completeBaseName)
{ {
QDir dir(p_directory); QDir dir(p_directory);
if (!dir.exists() || !dir.exists(p_baseFileName)) { if (!dir.exists() || !dir.exists(p_baseFileName)) {
@ -623,8 +632,8 @@ QString VUtils::getFileNameWithSequence(const QString &p_directory,
// Append a sequence. // Append a sequence.
QFileInfo fi(p_baseFileName); QFileInfo fi(p_baseFileName);
QString baseName = fi.baseName(); QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
QString suffix = fi.completeSuffix(); QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
int seq = 1; int seq = 1;
QString fileName; QString fileName;
do { do {
@ -637,6 +646,24 @@ QString VUtils::getFileNameWithSequence(const QString &p_directory,
return fileName; 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) QString VUtils::getRandomFileName(const QString &p_directory)
{ {
Q_ASSERT(!p_directory.isEmpty()); Q_ASSERT(!p_directory.isEmpty());
@ -702,6 +729,16 @@ bool VUtils::checkPathLegal(const QString &p_path)
return ret; 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) bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
{ {
QString a = QDir::cleanPath(p_patha); QString a = QDir::cleanPath(p_patha);
@ -850,7 +887,8 @@ bool VUtils::deleteFile(const QString &p_recycleBinFolderPath,
} }
QString destName = getFileNameWithSequence(binPath, QString destName = getFileNameWithSequence(binPath,
fileNameFromPath(p_path)); fileNameFromPath(p_path),
true);
qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName; qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
if (!binDir.rename(p_path, binDir.filePath(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; *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();
}

View File

@ -20,6 +20,32 @@ class VNotebook;
#define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop()) #define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
#endif #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 enum class MessageBoxType
{ {
Normal = 0, Normal = 0,
@ -54,9 +80,19 @@ public:
// Given the file name @p_fileName and directory path @p_dirPath, generate // 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. // 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<QPair<QString, QString> > &varMap); static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
// Return the last directory name of @p_path. // 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. // 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 // If there already exists a file named @p_baseFileName, try to add sequence
// suffix to the name, such as _001. // 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, 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. // Get an available random file name in @p_directory.
static QString getRandomFileName(const QString &p_directory); static QString getRandomFileName(const QString &p_directory);
@ -131,6 +177,9 @@ public:
// Try to check if @p_path is legal. // Try to check if @p_path is legal.
static bool checkPathLegal(const QString &p_path); 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. // 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); 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. // Assign @p_str to @p_msg if it is not NULL.
static void addErrMsg(QString *p_msg, const QString &p_str); 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. // Regular expression for image link.
// ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" ) // ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" )
// Captured texts (need to be trimmed): // Captured texts (need to be trimmed):
@ -195,6 +251,9 @@ public:
// 4. Unused; // 4. Unused;
static const QString c_imageLinkRegExp; static const QString c_imageLinkRegExp;
// Regular expression for image title.
static const QString c_imageTitleRegExp;
// Regular expression for file/directory name. // Regular expression for file/directory name.
// Forbidden char: \/:*?"<>| // Forbidden char: \/:*?"<>|
static const QString c_fileNameRegExp; static const QString c_fileNameRegExp;

View File

@ -18,6 +18,7 @@ extern VConfigManager *g_config;
const QChar VVim::c_unnamedRegister = QChar('"'); const QChar VVim::c_unnamedRegister = QChar('"');
const QChar VVim::c_blackHoleRegister = QChar('_'); const QChar VVim::c_blackHoleRegister = QChar('_');
const QChar VVim::c_selectionRegister = QChar('+'); const QChar VVim::c_selectionRegister = QChar('+');
QMap<QChar, VVim::Register> VVim::s_registers;
const int VVim::SearchHistory::c_capacity = 50; 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(); 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 // 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. // (may move across blocks). It will stop by the empty block on the way.
// Forward: wwwwsssss|wwww // Forward: wwwwsssss|wwww
@ -351,7 +296,7 @@ static void insertChangeBlockAfterDeletion(QTextCursor &p_cursor, int p_deletion
} }
if (g_config->getAutoIndent()) { 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); QChar reg = keyToRegisterName(keyInfo);
if (!reg.isNull()) { if (!reg.isNull()) {
// Insert register content. // Insert register content.
m_editor->insertPlainText(m_registers[reg].read()); m_editor->insertPlainText(getRegister(reg).read());
} }
goto clear_accept; goto clear_accept;
@ -602,11 +547,12 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
QChar reg = keyToRegisterName(keyInfo); QChar reg = keyToRegisterName(keyInfo);
if (!reg.isNull()) { if (!reg.isNull()) {
m_keys.clear(); m_keys.clear();
setRegister(reg); setCurrentRegisterName(reg);
if (m_registers[reg].isNamedRegister()) { Register &r = getRegister(reg);
m_registers[reg].m_append = (modifiers == Qt::ShiftModifier); if (r.isNamedRegister()) {
r.m_append = (modifiers == Qt::ShiftModifier);
} else { } else {
Q_ASSERT(!m_registers[reg].m_append); Q_ASSERT(!r.m_append);
} }
goto accept; goto accept;
@ -965,7 +911,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
bool textInserted = false; bool textInserted = false;
if (g_config->getAutoIndent()) { if (g_config->getAutoIndent()) {
textInserted = VEditUtils::indentBlockAsPreviousBlock(cursor); textInserted = VEditUtils::indentBlockAsBlock(cursor, false);
if (g_config->getAutoList()) { if (g_config->getAutoList()) {
textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor) textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor)
|| textInserted; || textInserted;
@ -1115,6 +1061,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
} else { } else {
break; break;
} }
V_FALLTHROUGH;
} }
case Qt::Key_PageUp: case Qt::Key_PageUp:
@ -1381,6 +1329,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
} else { } else {
break; break;
} }
V_FALLTHROUGH;
} }
case Qt::Key_Escape: case Qt::Key_Escape:
@ -2219,7 +2169,7 @@ void VVim::resetState()
m_keys.clear(); m_keys.clear();
m_tokens.clear(); m_tokens.clear();
m_pendingKeys.clear(); m_pendingKeys.clear();
setRegister(c_unnamedRegister); setCurrentRegisterName(c_unnamedRegister);
m_resetPositionInBlock = true; m_resetPositionInBlock = true;
m_registerPending = false; m_registerPending = false;
} }
@ -2723,7 +2673,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
for (int i = 0; i < p_repeat; ++i) { for (int i = 0; i < p_repeat; ++i) {
int start, end; int start, end;
// [start, end] is current WORD. // [start, end] is current WORD.
findCurrentWORD(p_cursor, start, end); VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to end of current WORD. // Move cursor to end of current WORD.
p_cursor.setPosition(end, p_moveMode); p_cursor.setPosition(end, p_moveMode);
@ -2773,7 +2723,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
int start, end; int start, end;
// [start, end] is current WORD. // [start, end] is current WORD.
findCurrentWORD(p_cursor, start, end); VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to the end of current WORD. // Move cursor to the end of current WORD.
p_cursor.setPosition(end, p_moveMode); p_cursor.setPosition(end, p_moveMode);
@ -2819,7 +2769,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
int start, end; int start, end;
// [start, end] is current WORD. // [start, end] is current WORD.
findCurrentWORD(p_cursor, start, end); VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to the start of current WORD. // Move cursor to the start of current WORD.
p_cursor.setPosition(start, p_moveMode); p_cursor.setPosition(start, p_moveMode);
@ -2856,7 +2806,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
for (int i = 0; i < p_repeat; ++i) { for (int i = 0; i < p_repeat; ++i) {
int start, end; int start, end;
findCurrentWORD(p_cursor, start, end); VEditUtils::findCurrentWORD(p_cursor, start, end);
p_cursor.setPosition(start, p_moveMode); p_cursor.setPosition(start, p_moveMode);
@ -3045,7 +2995,7 @@ handle_target:
// Different from Vim: // Different from Vim:
// We do not recognize a word as strict as Vim. // We do not recognize a word as strict as Vim.
int start, end; int start, end;
findCurrentWord(p_cursor, start, end); VEditUtils::findCurrentWord(p_cursor, start, end);
if (start == end) { if (start == end) {
// Spaces, find next word. // Spaces, find next word.
QTextCursor cursor = p_cursor; QTextCursor cursor = p_cursor;
@ -3056,7 +3006,7 @@ handle_target:
} }
if (!doc->characterAt(cursor.position()).isSpace()) { if (!doc->characterAt(cursor.position()).isSpace()) {
findCurrentWord(cursor, start, end); VEditUtils::findCurrentWord(cursor, start, end);
Q_ASSERT(start != end); Q_ASSERT(start != end);
break; break;
} }
@ -3181,7 +3131,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
Q_ASSERT(p_repeat == -1); Q_ASSERT(p_repeat == -1);
bool spaces = false; bool spaces = false;
int start, end; int start, end;
findCurrentWord(p_cursor, start, end); VEditUtils::findCurrentWord(p_cursor, start, end);
if (start == end) { if (start == end) {
// Select the space between previous word and next word. // 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); findCurrentSpace(p_cursor, start, end);
if (start == end) { if (start == end) {
findCurrentWORD(p_cursor, start, end); VEditUtils::findCurrentWORD(p_cursor, start, end);
} else { } else {
// Select the space between previous WORD and next WORD. // Select the space between previous WORD and next WORD.
spaces = true; spaces = true;
@ -3240,7 +3190,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
moveCursorAcrossSpaces(p_cursor, moveMode, true); moveCursorAcrossSpaces(p_cursor, moveMode, true);
// [start, end] is current WORD. // [start, end] is current WORD.
findCurrentWORD(p_cursor, start, end); VEditUtils::findCurrentWORD(p_cursor, start, end);
// Move cursor to the end of current WORD. // Move cursor to the end of current WORD.
p_cursor.setPosition(end, moveMode); p_cursor.setPosition(end, moveMode);
@ -3481,6 +3431,8 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
// Fall through. // Fall through.
mayCrossBlock = true; mayCrossBlock = true;
V_FALLTHROUGH;
case Range::WordAround: case Range::WordAround:
// Fall through. // Fall through.
case Range::WordInner: case Range::WordInner:
@ -3712,6 +3664,8 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
// Fall through. // Fall through.
mayCrossBlock = true; mayCrossBlock = true;
V_FALLTHROUGH;
case Range::WordAround: case Range::WordAround:
// Fall through. // Fall through.
case Range::WordInner: case Range::WordInner:
@ -3873,7 +3827,7 @@ void VVim::processPasteAction(QList<Token> &p_tokens, bool p_pasteBefore)
repeat = to.m_repeat; repeat = to.m_repeat;
} }
Register &reg = m_registers[m_regName]; Register &reg = getRegister(m_regName);
QString value = reg.read(); QString value = reg.read();
bool isBlock = reg.isBlock(); bool isBlock = reg.isBlock();
if (value.isEmpty()) { if (value.isEmpty()) {
@ -4847,14 +4801,17 @@ void VVim::expandSelectionToWholeLines(QTextCursor &p_cursor)
void VVim::initRegisters() void VVim::initRegisters()
{ {
m_registers.clear(); if (!s_registers.isEmpty()) {
for (char ch = 'a'; ch <= 'z'; ++ch) { return;
m_registers[QChar(ch)] = Register(QChar(ch));
} }
m_registers[c_unnamedRegister] = Register(c_unnamedRegister); for (char ch = 'a'; ch <= 'z'; ++ch) {
m_registers[c_blackHoleRegister] = Register(c_blackHoleRegister); s_registers[QChar(ch)] = Register(QChar(ch));
m_registers[c_selectionRegister] = Register(c_selectionRegister); }
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 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); qDebug() << QString("save text(%1) to register(%2)").arg(text).arg(m_regName);
Register &reg = m_registers[m_regName]; Register &reg = getRegister(m_regName);
reg.update(text); reg.update(text);
if (!reg.isBlackHoleRegister() && !reg.isUnnamedRegister()) { if (!reg.isBlackHoleRegister() && !reg.isUnnamedRegister()) {
// Save it to unnamed register. // 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<QChar, VVim::Register> &VVim::getRegisters() const const QMap<QChar, VVim::Register> &VVim::getRegisters() const
{ {
return m_registers; return s_registers;
} }
const VVim::Marks &VVim::getMarks() const const VVim::Marks &VVim::getMarks() const
@ -5272,7 +5229,7 @@ QString VVim::getPendingKeys() const
return str; return str;
} }
void VVim::setRegister(QChar p_reg) void VVim::setCurrentRegisterName(QChar p_reg)
{ {
m_regName = 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); Key keyInfo(p_key, p_modifiers);
QChar reg = keyToRegisterName(keyInfo); QChar reg = keyToRegisterName(keyInfo);
if (!reg.isNull()) { if (!reg.isNull()) {
return m_registers[reg].read(); return getRegister(reg).read();
} }
return ""; return "";

View File

@ -166,7 +166,7 @@ public:
void setMode(VimMode p_mode, bool p_clearSelection = true); void setMode(VimMode p_mode, bool p_clearSelection = true);
// Set current register. // Set current register.
void setRegister(QChar p_reg); void setCurrentRegisterName(QChar p_reg);
// Get m_registers. // Get m_registers.
const QMap<QChar, Register> &getRegisters() const; const QMap<QChar, Register> &getRegisters() const;
@ -797,6 +797,10 @@ private:
// Clear search highlight. // Clear search highlight.
void clearSearchHighlight(); void clearSearchHighlight();
// Function utils for register.
Register &getRegister(QChar p_regName) const;
void setRegister(QChar p_regName, const QString &p_val);
VEdit *m_editor; VEdit *m_editor;
const VEditConfig *m_editConfig; const VEditConfig *m_editConfig;
VimMode m_mode; VimMode m_mode;
@ -814,8 +818,6 @@ private:
// Whether reset the position in block when moving cursor. // Whether reset the position in block when moving cursor.
bool m_resetPositionInBlock; bool m_resetPositionInBlock;
QMap<QChar, Register> m_registers;
// Currently used register. // Currently used register.
QChar m_regName; QChar m_regName;
@ -843,6 +845,18 @@ private:
static const QChar c_unnamedRegister; static const QChar c_unnamedRegister;
static const QChar c_blackHoleRegister; static const QChar c_blackHoleRegister;
static const QChar c_selectionRegister; static const QChar c_selectionRegister;
static QMap<QChar, VVim::Register> 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 #endif // VVIM_H

View File

@ -5,13 +5,12 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vbuttonwithwidget.h" #include "vbuttonwithwidget.h"
#include "vnote.h"
#include "vmainwindow.h" #include "vmainwindow.h"
#include "dialog/vconfirmdeletiondialog.h" #include "dialog/vconfirmdeletiondialog.h"
#include "dialog/vsortdialog.h" #include "dialog/vsortdialog.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VNote *g_vnote; extern VMainWindow *g_mainWin;
VAttachmentList::VAttachmentList(QWidget *p_parent) VAttachmentList::VAttachmentList(QWidget *p_parent)
: QWidget(p_parent), m_file(NULL) : QWidget(p_parent), m_file(NULL)
@ -53,7 +52,7 @@ void VAttachmentList::setupUI()
.arg(m_file->fetchAttachmentFolderPath()), .arg(m_file->fetchAttachmentFolderPath()),
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok, QMessageBox::Ok,
g_vnote->getMainWindow(), g_mainWin,
MessageBoxType::Danger); MessageBoxType::Danger);
if (ret == QMessageBox::Ok) { if (ret == QMessageBox::Ok) {
if (!m_file->deleteAttachments()) { if (!m_file->deleteAttachments()) {
@ -66,7 +65,7 @@ void VAttachmentList::setupUI()
"maintain the configuration file manually."), "maintain the configuration file manually."),
QMessageBox::Ok, QMessageBox::Ok,
QMessageBox::Ok, QMessageBox::Ok,
g_vnote->getMainWindow()); g_mainWin);
} }
m_attachmentList->clear(); m_attachmentList->clear();
@ -204,7 +203,7 @@ void VAttachmentList::addAttachment()
} }
static QString lastPath = QDir::homePath(); static QString lastPath = QDir::homePath();
QStringList files = QFileDialog::getOpenFileNames(g_vnote->getMainWindow(), QStringList files = QFileDialog::getOpenFileNames(g_mainWin,
tr("Select Files As Attachments"), tr("Select Files As Attachments"),
lastPath); lastPath);
if (files.isEmpty()) { if (files.isEmpty()) {
@ -236,14 +235,14 @@ void VAttachmentList::addAttachments(const QStringList &p_files)
"", "",
QMessageBox::Ok, QMessageBox::Ok,
QMessageBox::Ok, QMessageBox::Ok,
g_vnote->getMainWindow()); g_mainWin);
} else { } else {
++addedFiles; ++addedFiles;
} }
} }
if (addedFiles > 0) { if (addedFiles > 0) {
g_vnote->getMainWindow()->showStatusMessage(tr("%1 %2 added as attachments") g_mainWin->showStatusMessage(tr("%1 %2 added as attachments")
.arg(addedFiles) .arg(addedFiles)
.arg(addedFiles > 1 ? tr("files") : tr("file"))); .arg(addedFiles > 1 ? tr("files") : tr("file")));
} }
@ -330,7 +329,7 @@ void VAttachmentList::deleteSelectedItems()
false, false,
false, false,
false, false,
g_vnote->getMainWindow()); g_mainWin);
if (dialog.exec()) { if (dialog.exec()) {
items = dialog.getConfirmedItems(); items = dialog.getConfirmedItems();
@ -349,7 +348,7 @@ void VAttachmentList::deleteSelectedItems()
"maintain the configuration file manually."), "maintain the configuration file manually."),
QMessageBox::Ok, QMessageBox::Ok,
QMessageBox::Ok, QMessageBox::Ok,
g_vnote->getMainWindow()); g_mainWin);
} }
updateButtonState(); updateButtonState();
@ -370,7 +369,7 @@ void VAttachmentList::sortItems()
"in the configuration file.") "in the configuration file.")
.arg(g_config->c_dataTextStyle) .arg(g_config->c_dataTextStyle)
.arg(m_file->getName()), .arg(m_file->getName()),
g_vnote->getMainWindow()); g_mainWin);
QTreeWidget *tree = dialog.getTreeWidget(); QTreeWidget *tree = dialog.getTreeWidget();
tree->clear(); tree->clear();
tree->setColumnCount(1); tree->setColumnCount(1);
@ -624,7 +623,7 @@ void VAttachmentList::checkAttachments()
false, false,
false, false,
false, false,
g_vnote->getMainWindow()); g_mainWin);
if (dialog.exec()) { if (dialog.exec()) {
items = dialog.getConfirmedItems(); items = dialog.getConfirmedItems();
@ -643,7 +642,7 @@ void VAttachmentList::checkAttachments()
"maintain the configuration file manually."), "maintain the configuration file manually."),
QMessageBox::Ok, QMessageBox::Ok,
QMessageBox::Ok, QMessageBox::Ok,
g_vnote->getMainWindow()); g_mainWin);
} }
updateButtonState(); updateButtonState();

View File

@ -1,6 +1,5 @@
#include <QtWidgets> #include <QtWidgets>
#include <QString> #include <QString>
#include <QTimer>
#include <QDebug> #include <QDebug>
#include <QShortcut> #include <QShortcut>
#include "vcaptain.h" #include "vcaptain.h"
@ -9,38 +8,39 @@
#include "vedittab.h" #include "vedittab.h"
#include "vfilelist.h" #include "vfilelist.h"
#include "vnavigationmode.h" #include "vnavigationmode.h"
#include "vconfigmanager.h"
extern VMainWindow *g_mainWin;
extern VConfigManager *g_config;
// 3s pending time after the leader keys. // 3s pending time after the leader keys.
const int c_pendingTime = 3 * 1000; const int c_pendingTime = 3 * 1000;
#if defined(QT_NO_DEBUG) VCaptain::VCaptain(QWidget *p_parent)
extern QFile g_logFile; : QWidget(p_parent),
#endif m_mode(CaptainMode::Normal),
m_widgetBeforeNavigation(NULL),
VCaptain::VCaptain(VMainWindow *p_parent) m_nextMajorKey('a'),
: QWidget(p_parent), m_mainWindow(p_parent), m_mode(VCaptain::Normal), m_ignoreFocusChange(false),
m_widgetBeforeCaptain(NULL), m_nextMajorKey('a'), m_ignoreFocusChange(false) m_leaderKey(g_config->getShortcutKeySequence("CaptainMode"))
{ {
m_pendingTimer = new QTimer(this); Q_ASSERT(!m_leaderKey.isEmpty());
m_pendingTimer->setSingleShot(true);
m_pendingTimer->setInterval(c_pendingTime);
connect(m_pendingTimer, &QTimer::timeout,
this, &VCaptain::pendingTimerTimeout);
connect(qApp, &QApplication::focusChanged, connect(qApp, &QApplication::focusChanged,
this, &VCaptain::handleFocusChanged); 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); setWindowFlags(Qt::FramelessWindowHint);
// Make it as small as possible. This widget will stay at the top-left corner // Make it as small as possible. This widget will stay at the top-left corner
// of VMainWindow. // of VMainWindow.
resize(1, 1); resize(1, 1);
// Register Navigation mode as Captain mode target.
registerCaptainTarget(tr("NavigationMode"),
g_config->getCaptainShortcutKeySequence("NavigationMode"),
this,
navigationModeByCaptain);
} }
QChar VCaptain::getNextMajorKey() QChar VCaptain::getNextMajorKey()
@ -59,39 +59,19 @@ void VCaptain::registerNavigationTarget(VNavigationMode *p_target)
QChar key = getNextMajorKey(); QChar key = getNextMajorKey();
if (!key.isNull()) { if (!key.isNull()) {
p_target->registerNavigation(key); 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) { Q_UNUSED(p_now);
exitCaptainMode();
}
}
void VCaptain::pendingTimerTimeout() if (!m_ignoreFocusChange
{ && !checkMode(CaptainMode::Normal)
qDebug() << "Captain mode timeout"; && p_old == this) {
exitCaptainMode(); exitNavigationMode();
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) void VCaptain::keyPressEvent(QKeyEvent *p_event)
@ -99,13 +79,6 @@ void VCaptain::keyPressEvent(QKeyEvent *p_event)
int key = p_event->key(); int key = p_event->key();
Qt::KeyboardModifiers modifiers = p_event->modifiers(); 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) { if (key == Qt::Key_Control || key == Qt::Key_Shift) {
QWidget::keyPressEvent(p_event); QWidget::keyPressEvent(p_event);
return; return;
@ -120,230 +93,28 @@ void VCaptain::keyPressEvent(QKeyEvent *p_event)
bool VCaptain::handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers) 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 if (p_key == Qt::Key_Escape
|| (p_key == Qt::Key_BracketLeft || (p_key == Qt::Key_BracketLeft
&& p_modifiers == Qt::ControlModifier)) { && p_modifiers == Qt::ControlModifier)) {
goto exit; exitNavigationMode();
return true;
} }
m_ignoreFocusChange = true; return handleKeyPressNavigationMode(p_key, p_modifiers);
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 <i>.
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;
} }
bool VCaptain::handleKeyPressNavigationMode(int p_key, bool VCaptain::handleKeyPressNavigationMode(int p_key,
Qt::KeyboardModifiers /* p_modifiers */) Qt::KeyboardModifiers /* p_modifiers */)
{ {
Q_ASSERT(m_mode == VCaptainMode::Navigation); Q_ASSERT(m_mode == CaptainMode::Navigation);
bool hasConsumed = false; bool hasConsumed = false;
bool pending = false; bool pending = false;
for (auto &target : m_targets) { m_ignoreFocusChange = true;
for (auto &target : m_naviTargets) {
if (hasConsumed) { if (hasConsumed) {
target.m_available = false; target.m_available = false;
target.m_target->hideNavigation(); target.m_target->hideNavigation();
@ -351,12 +122,13 @@ bool VCaptain::handleKeyPressNavigationMode(int p_key,
} }
if (target.m_available) { if (target.m_available) {
bool succeed = false; bool succeed = false;
// May change focus, so we need to ignore focus change here.
bool consumed = target.m_target->handleKeyNavigation(p_key, succeed); bool consumed = target.m_target->handleKeyNavigation(p_key, succeed);
if (consumed) { if (consumed) {
hasConsumed = true; hasConsumed = true;
if (succeed) { if (succeed) {
// Exit. // Exit.
m_widgetBeforeCaptain = NULL; m_widgetBeforeNavigation = NULL;
} else { } else {
// Consumed but not succeed. Need more keys. // Consumed but not succeed. Need more keys.
pending = true; pending = true;
@ -368,20 +140,23 @@ bool VCaptain::handleKeyPressNavigationMode(int p_key,
} }
} }
} }
m_ignoreFocusChange = false;
if (pending) { if (pending) {
return true; return true;
} }
exitCaptainMode();
restoreFocus(); exitNavigationMode();
return true; return true;
} }
void VCaptain::triggerNavigationMode() void VCaptain::triggerNavigationMode()
{ {
m_pendingTimer->stop(); setMode(CaptainMode::Navigation);
m_mode = VCaptainMode::Navigation; m_widgetBeforeNavigation = QApplication::focusWidget();
// Focus to listen pending key press.
for (auto &target : m_targets) { setFocus();
for (auto &target : m_naviTargets) {
target.m_available = true; target.m_available = true;
target.m_target->showNavigation(); target.m_target->showNavigation();
} }
@ -389,51 +164,74 @@ void VCaptain::triggerNavigationMode()
void VCaptain::exitNavigationMode() 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_available = true;
target.m_target->hideNavigation(); 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<QShortcutEvent *>(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(); restoreFocus();
} }
return QWidget::eventFilter(p_obj, p_event);
}
void VCaptain::restoreFocus() void VCaptain::restoreFocus()
{ {
if (m_widgetBeforeCaptain) { if (m_widgetBeforeNavigation) {
m_widgetBeforeCaptain->setFocus(); 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) { if (p_key.isEmpty()) {
exitNavigationMode(); 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<VCaptain *>(p_target);
obj->triggerNavigationMode();
}

View File

@ -1,77 +1,166 @@
#ifndef VCAPTAIN_H #ifndef VCAPTAIN_H
#define VCAPTAIN_H #define VCAPTAIN_H
#include <QWidget> #include <functional>
#include <QList>
#include <QWidget>
#include <QVector>
#include <QHash>
class QTimer;
class QKeyEvent; class QKeyEvent;
class VMainWindow;
class QEvent;
class VNavigationMode; class VNavigationMode;
class QShortcut;
// void func(void *p_target, void *p_data);
typedef std::function<void(void *, void *)> CaptainFunc;
class VCaptain : public QWidget class VCaptain : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit VCaptain(VMainWindow *p_parent); explicit VCaptain(QWidget *p_parent);
// Trigger Captain mode. // Register a target for Navigation mode.
void trigger();
// Register a target for Navigation Mode.
void registerNavigationTarget(VNavigationMode *p_target); void registerNavigationTarget(VNavigationMode *p_target);
signals: // Register a target for Captain mode.
void captainModeChanged(bool p_enabled); bool registerCaptainTarget(const QString &p_name,
const QString &p_key,
void *p_target,
CaptainFunc p_func);
protected: protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
public slots:
private slots: private slots:
void pendingTimerTimeout(); // Exit Navigation mode if focus lost.
void handleFocusChanged(QWidget *p_old, QWidget *p_new); void handleFocusChanged(QWidget *p_old, QWidget *p_new);
private: private:
// Restore the focus to m_widgetBeforeCaptain. // A widget target for Navigation mode.
void restoreFocus(); struct NaviModeTarget {
void exitCaptainMode(); NaviModeTarget()
// Return true if finish handling the event; otherwise, let the base widget : m_target(nullptr), m_available(false)
// 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();
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, Normal = 0,
Pending, Pending,
Navigation Navigation
}; };
VMainWindow *m_mainWindow; struct CaptainModeTarget {
QTimer *m_pendingTimer; CaptainModeTarget()
int m_mode; : m_target(nullptr), m_function(nullptr), m_shortcut(nullptr)
// The widget which has the focus before entering Captain mode. {
QWidget* m_widgetBeforeCaptain; }
struct NaviModeTarget { CaptainModeTarget(const QString &p_name,
VNavigationMode *m_target; const QString &p_key,
bool m_available; 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) QString toString() const
: m_target(p_target), m_available(p_available) {} {
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<NaviModeTarget> 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<NaviModeTarget> m_naviTargets;
QChar m_nextMajorKey; QChar m_nextMajorKey;
// Ignore focus change to avoid exiting Captain mode while handling key
// press. // Targets for Captain mode.
// Key(lower) -> CaptainModeTarget.
QHash<QString, CaptainModeTarget> m_captainTargets;
// Ignore focus change during handling Navigation target actions.
bool m_ignoreFocusChange; 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 #endif // VCAPTAIN_H

View File

@ -8,52 +8,60 @@
#include <QtDebug> #include <QtDebug>
#include <QTextEdit> #include <QTextEdit>
#include <QStandardPaths> #include <QStandardPaths>
#include <QCoreApplication>
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vstyleparser.h" #include "vstyleparser.h"
const QString VConfigManager::orgName = QString("vnote"); const QString VConfigManager::orgName = QString("vnote");
const QString VConfigManager::appName = QString("vnote"); const QString VConfigManager::appName = QString("vnote");
const QString VConfigManager::c_version = QString("1.9"); const QString VConfigManager::c_version = QString("1.9");
const QString VConfigManager::c_obsoleteDirConfigFile = QString(".vnote.json"); const QString VConfigManager::c_obsoleteDirConfigFile = QString(".vnote.json");
const QString VConfigManager::c_dirConfigFile = 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_styleConfigFolder = QString("styles");
const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles"); const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles");
const QString VConfigManager::c_defaultCssFile = QString(":/resources/styles/default.css"); 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_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.css");
const QString VConfigManager::c_defaultMdhlFile = QString(":/resources/styles/default.mdhl"); 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_solarizedDarkMdhlFile = QString(":/resources/styles/solarized-dark.mdhl");
const QString VConfigManager::c_solarizedLightMdhlFile = QString(":/resources/styles/solarized-light.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_warningTextStyle = QString("color: red; font: bold");
const QString VConfigManager::c_dataTextStyle = QString("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;}" 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;}"); "QPushButton::hover {color: #fff; border-color: #ac2925; background-color: #c9302c;}");
const QString VConfigManager::c_vnoteNotebookFolderName = QString("vnote_notebooks"); const QString VConfigManager::c_vnoteNotebookFolderName = QString("vnote_notebooks");
VConfigManager::VConfigManager(QObject *p_parent) 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() void VConfigManager::initialize()
{ {
Q_ASSERT(!userSettings && !defaultSettings); initSettings();
userSettings = new QSettings(QSettings::IniFormat, QSettings::UserScope,
orgName, appName, this);
defaultSettings = new QSettings(defaultConfigFilePath, QSettings::IniFormat, this);
migrateIniFile();
// Override the default css styles on start up. // Override the default css styles on start up.
outputDefaultCssStyle(); outputDefaultCssStyle();
@ -68,7 +76,6 @@ void VConfigManager::initialize()
m_templateCss = getConfigFromSettings("global", "template_css").toString(); m_templateCss = getConfigFromSettings("global", "template_css").toString();
m_templateCodeBlockCss = getConfigFromSettings("global", "template_code_block_css").toString(); m_templateCodeBlockCss = getConfigFromSettings("global", "template_code_block_css").toString();
m_templateCodeBlockCssUrl = getConfigFromSettings("global", "template_code_block_css_url").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 | markdownExtensions = hoedown_extensions(HOEDOWN_EXT_TABLES | HOEDOWN_EXT_FENCED_CODE |
HOEDOWN_EXT_HIGHLIGHT | HOEDOWN_EXT_AUTOLINK | HOEDOWN_EXT_HIGHLIGHT | HOEDOWN_EXT_AUTOLINK |
@ -91,10 +98,7 @@ void VConfigManager::initialize()
curRenderBackgroundColor = getConfigFromSettings("global", curRenderBackgroundColor = getConfigFromSettings("global",
"current_render_background_color").toString(); "current_render_background_color").toString();
m_toolsDockChecked = getConfigFromSettings("session", "tools_dock_checked").toBool(); m_toolsDockChecked = getConfigFromSettings("global", "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_findCaseSensitive = getConfigFromSettings("global", m_findCaseSensitive = getConfigFromSettings("global",
"find_case_sensitive").toBool(); "find_case_sensitive").toBool();
@ -168,6 +172,8 @@ void VConfigManager::initialize()
readShortcutsFromSettings(); readShortcutsFromSettings();
readCaptainShortcutsFromSettings();
initDocSuffixes(); initDocSuffixes();
m_markdownHighlightInterval = getConfigFromSettings("global", m_markdownHighlightInterval = getConfigFromSettings("global",
@ -187,8 +193,14 @@ void VConfigManager::initialize()
m_noteOpenMode = OpenFileMode::Read; m_noteOpenMode = OpenFileMode::Read;
} }
m_enableHeadingSequence = getConfigFromSettings("global", int tmpHeadingSequenceType = getConfigFromSettings("global",
"enable_heading_sequence").toBool(); "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", m_headingSequenceBaseLevel = getConfigFromSettings("global",
"heading_sequence_base_level").toInt(); "heading_sequence_base_level").toInt();
@ -227,6 +239,76 @@ void VConfigManager::initialize()
m_doubleClickCloseTab = getConfigFromSettings("global", m_doubleClickCloseTab = getConfigFromSettings("global",
"double_click_close_tab").toBool(); "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() void VConfigManager::readPredefinedColorsFromSettings()
@ -245,49 +327,70 @@ void VConfigManager::readPredefinedColorsFromSettings()
<< "pre-defined colors from [predefined_colors] section"; << "pre-defined colors from [predefined_colors] section";
} }
void VConfigManager::readNotebookFromSettings(QVector<VNotebook *> &p_notebooks, QObject *parent) void VConfigManager::readNotebookFromSettings(QSettings *p_settings,
QVector<VNotebook *> &p_notebooks,
QObject *parent)
{ {
Q_ASSERT(p_notebooks.isEmpty()); Q_ASSERT(p_notebooks.isEmpty());
int size = userSettings->beginReadArray("notebooks"); int size = p_settings->beginReadArray("notebooks");
for (int i = 0; i < size; ++i) { for (int i = 0; i < size; ++i) {
userSettings->setArrayIndex(i); p_settings->setArrayIndex(i);
QString name = userSettings->value("name").toString(); QString name = p_settings->value("name").toString();
QString path = userSettings->value("path").toString(); QString path = p_settings->value("path").toString();
VNotebook *notebook = new VNotebook(name, path, parent); VNotebook *notebook = new VNotebook(name, path, parent);
notebook->readConfigNotebook(); notebook->readConfigNotebook();
p_notebooks.append(notebook); p_notebooks.append(notebook);
} }
userSettings->endArray();
p_settings->endArray();
qDebug() << "read" << p_notebooks.size() qDebug() << "read" << p_notebooks.size()
<< "notebook items from [notebooks] section"; << "notebook items from [notebooks] section";
} }
void VConfigManager::writeNotebookToSettings(const QVector<VNotebook *> &p_notebooks) void VConfigManager::writeNotebookToSettings(QSettings *p_settings,
const QVector<VNotebook *> &p_notebooks)
{ {
// Clear it first // Clear it first
userSettings->beginGroup("notebooks"); p_settings->beginGroup("notebooks");
userSettings->remove(""); p_settings->remove("");
userSettings->endGroup(); p_settings->endGroup();
userSettings->beginWriteArray("notebooks"); p_settings->beginWriteArray("notebooks");
for (int i = 0; i < p_notebooks.size(); ++i) { for (int i = 0; i < p_notebooks.size(); ++i) {
userSettings->setArrayIndex(i); p_settings->setArrayIndex(i);
const VNotebook &notebook = *p_notebooks[i]; const VNotebook &notebook = *p_notebooks[i];
userSettings->setValue("name", notebook.getName()); p_settings->setValue("name", notebook.getName());
userSettings->setValue("path", notebook.getPath()); p_settings->setValue("path", notebook.getPath());
} }
userSettings->endArray();
p_settings->endArray();
qDebug() << "write" << p_notebooks.size() qDebug() << "write" << p_notebooks.size()
<< "notebook items in [notebooks] section"; << "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 &section, const QString &key) const QVariant VConfigManager::getConfigFromSettings(const QString &section, const QString &key) const
{ {
QString fullKey = section + "/" + key;
// First, look up the user-scoped config file // First, look up the user-scoped config file
QVariant value = userSettings->value(fullKey); QVariant value = getConfigFromSettingsBySectionKey(userSettings, section, key);
if (!value.isNull()) { if (!value.isNull()) {
qDebug() << "user config:" << fullKey << value.toString(); qDebug() << "user config:" << (section + "/" + key) << value;
return value; return value;
} }
@ -298,18 +401,14 @@ QVariant VConfigManager::getConfigFromSettings(const QString &section, const QSt
void VConfigManager::setConfigToSettings(const QString &section, const QString &key, const QVariant &value) void VConfigManager::setConfigToSettings(const QString &section, const QString &key, const QVariant &value)
{ {
// Set the user-scoped config file // Set the user-scoped config file
QString fullKey = section + "/" + key; setConfigToSettingsBySectionKey(userSettings, section, key, value);
userSettings->setValue(fullKey, value); qDebug() << "set user config:" << (section + "/" + key) << value;
qDebug() << "set user config:" << fullKey << value.toString();
} }
QVariant VConfigManager::getDefaultConfig(const QString &p_section, const QString &p_key) const QVariant VConfigManager::getDefaultConfig(const QString &p_section, const QString &p_key) const
{ {
QString fullKey = p_section + "/" + p_key; QVariant value = getConfigFromSettingsBySectionKey(defaultSettings, p_section, p_key);
qDebug() << "default config:" << (p_section + "/" + p_key) << value;
QVariant value = defaultSettings->value(fullKey);
qDebug() << "default config:" << fullKey << value.toString();
return value; return value;
} }
@ -321,6 +420,24 @@ QVariant VConfigManager::resetDefaultConfig(const QString &p_section, const QStr
return defaultValue; 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) QString VConfigManager::fetchDirConfigFilePath(const QString &p_path)
{ {
QDir dir(p_path); QDir dir(p_path);
@ -887,76 +1004,120 @@ QString VConfigManager::getVnoteNotebookFolderPath()
return QDir::home().filePath(c_vnoteNotebookFolderName); return QDir::home().filePath(c_vnoteNotebookFolderName);
} }
QHash<QString, QString> VConfigManager::readShortcutsFromSettings(QSettings *p_settings,
const QString &p_group)
{
QHash<QString, QString> 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) bool VConfigManager::isValidKeySequence(const QString &p_seq)
{ {
QString lower = p_seq.toLower(); return p_seq.toLower() != "ctrl+q"
return lower != "ctrl+q" && lower != "ctrl+e"; && !QKeySequence(p_seq).isEmpty();
} }
void VConfigManager::readShortcutsFromSettings() void VConfigManager::readShortcutsFromSettings()
{ {
const QString group("shortcuts");
m_shortcuts.clear(); m_shortcuts.clear();
int size = defaultSettings->beginReadArray("shortcuts"); m_shortcuts = readShortcutsFromSettings(defaultSettings, group);
for (int i = 0; i < size; ++i) {
defaultSettings->setArrayIndex(i);
QString op = defaultSettings->value("operation").toString();
QString seq = defaultSettings->value("keysequence").toString().trimmed();
if (isValidKeySequence(seq)) { // Update default settings according to user settings.
qDebug() << "read shortcut config" << op << seq; QHash<QString, QString> userShortcuts = readShortcutsFromSettings(userSettings,
m_shortcuts[op] = seq; group);
}
}
defaultSettings->endArray();
// Whether we need to update user settings.
bool needUpdate = false;
size = userSettings->beginReadArray("shortcuts");
QSet<QString> matched; QSet<QString> matched;
matched.reserve(m_shortcuts.size()); matched.reserve(m_shortcuts.size());
for (int i = 0; i < size; ++i) { for (auto it = userShortcuts.begin(); it != userShortcuts.end(); ++it) {
userSettings->setArrayIndex(i); auto defaultIt = m_shortcuts.find(it.key());
QString op = userSettings->value("operation").toString(); if (defaultIt != m_shortcuts.end()) {
QString seq = userSettings->value("keysequence").toString().trimmed(); QString sequence = it.value().trimmed();
if (sequence != defaultIt.value()) {
if (isValidKeySequence(seq)) { if (isValidKeySequence(sequence)) {
qDebug() << "read user shortcut config" << op << seq; matched.insert(it.key());
auto it = m_shortcuts.find(op); *defaultIt = sequence;
if (it == m_shortcuts.end()) { }
// Could not find this in default settings.
needUpdate = true;
} else { } else {
matched.insert(op); matched.insert(it.key());
*it = seq;
} }
} }
} }
userSettings->endArray(); if (matched.size() < m_shortcuts.size()) {
writeShortcutsToSettings(userSettings, group, m_shortcuts);
if (needUpdate || matched.size() < m_shortcuts.size()) {
// Write the combined config to user settings.
writeShortcutsToSettings();
}
} }
void VConfigManager::writeShortcutsToSettings() qDebug() << "shortcuts:" << m_shortcuts;
}
void VConfigManager::readCaptainShortcutsFromSettings()
{ {
// Clear it first const QString group("captain_mode_shortcuts");
userSettings->beginGroup("shortcuts");
userSettings->remove("");
userSettings->endGroup();
userSettings->beginWriteArray("shortcuts"); m_captainShortcuts.clear();
int idx = 0; m_captainShortcuts = readShortcutsFromSettings(defaultSettings, group);
for (auto it = m_shortcuts.begin(); it != m_shortcuts.end(); ++it, ++idx) {
userSettings->setArrayIndex(idx); // Update default settings according to user settings.
userSettings->setValue("operation", it.key()); QHash<QString, QString> userShortcuts = readShortcutsFromSettings(userSettings,
userSettings->setValue("keysequence", it.value()); group);
QSet<QString> 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<QString, QString> &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 QString VConfigManager::getShortcutKeySequence(const QString &p_operation) const
@ -969,6 +1130,16 @@ QString VConfigManager::getShortcutKeySequence(const QString &p_operation) const
return *it; 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() void VConfigManager::initDocSuffixes()
{ {
m_docSuffixes.clear(); m_docSuffixes.clear();
@ -999,3 +1170,59 @@ void VConfigManager::initDocSuffixes()
qDebug() << "doc suffixes" << m_docSuffixes; qDebug() << "doc suffixes" << m_docSuffixes;
} }
QVector<VFileSessionInfo> VConfigManager::getLastOpenedFiles()
{
QVector<VFileSessionInfo> 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<VFileSessionInfo> &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<VMagicWord> VConfigManager::getCustomMagicWords()
{
QVector<VMagicWord> 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;
}

View File

@ -11,6 +11,9 @@
#include "hgmarkdownhighlighter.h" #include "hgmarkdownhighlighter.h"
#include "vmarkdownconverter.h" #include "vmarkdownconverter.h"
#include "vconstants.h" #include "vconstants.h"
#include "vfilesessioninfo.h"
#include "utils/vmetawordmanager.h"
class QJsonObject; class QJsonObject;
class QString; class QString;
@ -41,6 +44,18 @@ struct MarkdownitOption
bool m_linkify; bool m_linkify;
}; };
// Type of heading sequence.
enum class HeadingSequenceType
{
Disabled = 0,
Enabled,
// Enabled only for internal notes.
EnabledNoteOnly,
Invalid
};
class VConfigManager : public QObject class VConfigManager : public QObject
{ {
public: public:
@ -106,7 +121,10 @@ public:
int getCurNotebookIndex() const; int getCurNotebookIndex() const;
void setCurNotebookIndex(int index); void setCurNotebookIndex(int index);
void getNotebooks(QVector<VNotebook *> &p_notebooks, QObject *parent); // Read [notebooks] section from settings into @p_notebooks.
void getNotebooks(QVector<VNotebook *> &p_notebooks, QObject *p_parent);
// Write @p_notebooks to [notebooks] section into settings.
void setNotebooks(const QVector<VNotebook *> &p_notebooks); void setNotebooks(const QVector<VNotebook *> &p_notebooks);
hoedown_extensions getMarkdownExtensions() const; hoedown_extensions getMarkdownExtensions() const;
@ -153,6 +171,9 @@ public:
const QByteArray &getMainSplitterState() const; const QByteArray &getMainSplitterState() const;
void setMainSplitterState(const QByteArray &p_state); void setMainSplitterState(const QByteArray &p_state);
const QByteArray &getNaviSplitterState() const;
void setNaviSplitterState(const QByteArray &p_state);
bool getFindCaseSensitive() const; bool getFindCaseSensitive() const;
void setFindCaseSensitive(bool p_enabled); void setFindCaseSensitive(bool p_enabled);
@ -254,8 +275,8 @@ public:
OpenFileMode getNoteOpenMode() const; OpenFileMode getNoteOpenMode() const;
void setNoteOpenMode(OpenFileMode p_mode); void setNoteOpenMode(OpenFileMode p_mode);
bool getEnableHeadingSequence() const; HeadingSequenceType getHeadingSequenceType() const;
void setEnableHeadingSequence(bool p_enabled); void setHeadingSequenceType(HeadingSequenceType p_type);
int getHeadingSequenceBaseLevel() const; int getHeadingSequenceBaseLevel() const;
void setHeadingSequenceBaseLevel(int p_level); void setHeadingSequenceBaseLevel(int p_level);
@ -291,10 +312,32 @@ public:
// Whether user specify template_code_block_css_url directly. // Whether user specify template_code_block_css_url directly.
bool getUserSpecifyTemplateCodeBlockCssUrl() const; 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<VFileSessionInfo> getLastOpenedFiles();
// Write last opened files to [last_opened_files] of session.ini.
void setLastOpenedFiles(const QVector<VFileSessionInfo> &p_files);
// Read custom magic words from [magic_words] section.
QVector<VMagicWord> getCustomMagicWords();
// Return the configured key sequence of @p_operation. // Return the configured key sequence of @p_operation.
// Return empty if there is no corresponding config. // Return empty if there is no corresponding config.
QString getShortcutKeySequence(const QString &p_operation) const; 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. // Get the folder the ini file exists.
QString getConfigFolder() const; QString getConfigFolder() const;
@ -317,7 +360,10 @@ public:
QVector<QString> getEditorStyles() const; QVector<QString> getEditorStyles() const;
private: private:
// Look up a config from user and default settings.
QVariant getConfigFromSettings(const QString &section, const QString &key) const; QVariant getConfigFromSettings(const QString &section, const QString &key) const;
// Set a config to user settings.
void setConfigToSettings(const QString &section, const QString &key, const QVariant &value); void setConfigToSettings(const QString &section, const QString &key, const QVariant &value);
// Get default config from vnote.ini. // Get default config from vnote.ini.
@ -326,8 +372,29 @@ private:
// Reset user config to default config and return the default config value. // Reset user config to default config and return the default config value.
QVariant resetDefaultConfig(const QString &p_section, const QString &p_key); QVariant resetDefaultConfig(const QString &p_section, const QString &p_key);
void readNotebookFromSettings(QVector<VNotebook *> &p_notebooks, QObject *parent); // Look up a config from session settings.
void writeNotebookToSettings(const QVector<VNotebook *> &p_notebooks); 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<VNotebook *> &p_notebooks,
QObject *parent);
// Write to [notebooks] section to @p_settings.
void writeNotebookToSettings(QSettings *p_settings,
const QVector<VNotebook *> &p_notebooks);
void readPredefinedColorsFromSettings(); void readPredefinedColorsFromSettings();
// 1. Update styles common in HTML and Markdown; // 1. Update styles common in HTML and Markdown;
@ -336,10 +403,6 @@ private:
void updateMarkdownEditStyle(); 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. // Output pre-defined CSS styles to style folder.
bool outputDefaultCssStyle() const; bool outputDefaultCssStyle() const;
@ -359,8 +422,16 @@ private:
// write the combined configs to user settings. // write the combined configs to user settings.
void readShortcutsFromSettings(); void readShortcutsFromSettings();
// Write m_shortcuts to the [shortcuts] section in the user settings. // Read the [captain_mode_shortcuts] section in the settings to init
void writeShortcutsToSettings(); // m_captainShortcuts.
void readCaptainShortcutsFromSettings();
QHash<QString, QString> readShortcutsFromSettings(QSettings *p_settings,
const QString &p_group);
void writeShortcutsToSettings(QSettings *p_settings,
const QString &p_group,
const QHash<QString, QString> &p_shortcuts);
// Whether @p_seq is a valid key sequence for shortcuts. // Whether @p_seq is a valid key sequence for shortcuts.
bool isValidKeySequence(const QString &p_seq); bool isValidKeySequence(const QString &p_seq);
@ -393,6 +464,8 @@ private:
QString m_templateCodeBlockCssUrl; QString m_templateCodeBlockCssUrl;
QString m_editorStyle; QString m_editorStyle;
// Index of current notebook.
int curNotebookIndex; int curNotebookIndex;
// Markdown Converter // Markdown Converter
@ -429,6 +502,7 @@ private:
QByteArray m_mainWindowGeometry; QByteArray m_mainWindowGeometry;
QByteArray m_mainWindowState; QByteArray m_mainWindowState;
QByteArray m_mainSplitterState; QByteArray m_mainSplitterState;
QByteArray m_naviSplitterState;
// Find/Replace dialog options // Find/Replace dialog options
bool m_findCaseSensitive; bool m_findCaseSensitive;
@ -530,6 +604,10 @@ private:
// Operation -> KeySequence. // Operation -> KeySequence.
QHash<QString, QString> m_shortcuts; QHash<QString, QString> m_shortcuts;
// Shortcuts config in Captain mode.
// Operation -> KeySequence.
QHash<QString, QString> m_captainShortcuts;
// Whether minimize to system tray icon when closing the app. // Whether minimize to system tray icon when closing the app.
// -1: uninitialized; // -1: uninitialized;
// 0: do not minimize to the tay; // 0: do not minimize to the tay;
@ -553,7 +631,7 @@ private:
OpenFileMode m_noteOpenMode; OpenFileMode m_noteOpenMode;
// Whether auto genearte heading sequence. // Whether auto genearte heading sequence.
bool m_enableHeadingSequence; HeadingSequenceType m_headingSequenceType;
// Heading sequence base level. // Heading sequence base level.
int m_headingSequenceBaseLevel; int m_headingSequenceBaseLevel;
@ -600,6 +678,15 @@ private:
// Whether double click on a tab to close it. // Whether double click on a tab to close it.
bool m_doubleClickCloseTab; 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. // The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead. // Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile; static const QString c_obsoleteDirConfigFile;
@ -607,13 +694,25 @@ private:
// The name of the config file in each directory. // The name of the config file in each directory.
static const QString c_dirConfigFile; static const QString c_dirConfigFile;
// The name of the default configuration file // The path of the default configuration file
static const QString defaultConfigFilePath; 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 for the user configuration
QSettings *userSettings; QSettings *userSettings;
// Qsettings for @defaultConfigFileName
// Qsettings for @c_defaultConfigFilePath.
QSettings *defaultSettings; QSettings *defaultSettings;
// QSettings for the session configuration, such as notebooks,
// geometry, last opened files.
QSettings *m_sessionSettings;
// The folder name of style files. // The folder name of style files.
static const QString c_styleConfigFolder; static const QString c_styleConfigFolder;
@ -681,18 +780,35 @@ inline void VConfigManager::setCurNotebookIndex(int index)
if (index == curNotebookIndex) { if (index == curNotebookIndex) {
return; return;
} }
curNotebookIndex = index; curNotebookIndex = index;
setConfigToSettings("global", "current_notebook", index); setConfigToSessionSettings("global", "current_notebook", index);
} }
inline void VConfigManager::getNotebooks(QVector<VNotebook *> &p_notebooks, QObject *parent) inline void VConfigManager::getNotebooks(QVector<VNotebook *> &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<VNotebook *> &p_notebooks) inline void VConfigManager::setNotebooks(const QVector<VNotebook *> &p_notebooks)
{ {
writeNotebookToSettings(p_notebooks); writeNotebookToSettings(m_sessionSettings, p_notebooks);
} }
inline hoedown_extensions VConfigManager::getMarkdownExtensions() const inline hoedown_extensions VConfigManager::getMarkdownExtensions() const
@ -860,7 +976,7 @@ inline bool VConfigManager::getToolsDockChecked() const
inline void VConfigManager::setToolsDockChecked(bool p_checked) inline void VConfigManager::setToolsDockChecked(bool p_checked)
{ {
m_toolsDockChecked = p_checked; m_toolsDockChecked = p_checked;
setConfigToSettings("session", "tools_dock_checked", setConfigToSettings("global", "tools_dock_checked",
m_toolsDockChecked); m_toolsDockChecked);
} }
@ -872,7 +988,8 @@ inline const QByteArray& VConfigManager::getMainWindowGeometry() const
inline void VConfigManager::setMainWindowGeometry(const QByteArray &p_geometry) inline void VConfigManager::setMainWindowGeometry(const QByteArray &p_geometry)
{ {
m_mainWindowGeometry = p_geometry; m_mainWindowGeometry = p_geometry;
setConfigToSettings("session", "main_window_geometry", setConfigToSessionSettings("geometry",
"main_window_geometry",
m_mainWindowGeometry); m_mainWindowGeometry);
} }
@ -884,7 +1001,8 @@ inline const QByteArray& VConfigManager::getMainWindowState() const
inline void VConfigManager::setMainWindowState(const QByteArray &p_state) inline void VConfigManager::setMainWindowState(const QByteArray &p_state)
{ {
m_mainWindowState = p_state; m_mainWindowState = p_state;
setConfigToSettings("session", "main_window_state", setConfigToSessionSettings("geometry",
"main_window_state",
m_mainWindowState); m_mainWindowState);
} }
@ -896,7 +1014,22 @@ inline const QByteArray& VConfigManager::getMainSplitterState() const
inline void VConfigManager::setMainSplitterState(const QByteArray &p_state) inline void VConfigManager::setMainSplitterState(const QByteArray &p_state)
{ {
m_mainSplitterState = 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 inline bool VConfigManager::getFindCaseSensitive() const
@ -1370,20 +1503,21 @@ inline void VConfigManager::setNoteOpenMode(OpenFileMode p_mode)
m_noteOpenMode == OpenFileMode::Read ? 0 : 1); 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; return;
} }
m_enableHeadingSequence = p_enabled; m_headingSequenceType = p_type;
setConfigToSettings("global", "enable_heading_sequence", setConfigToSettings("global",
m_enableHeadingSequence); "heading_sequence_type",
(int)m_headingSequenceType);
} }
inline int VConfigManager::getHeadingSequenceBaseLevel() const inline int VConfigManager::getHeadingSequenceBaseLevel() const
@ -1570,4 +1704,49 @@ inline bool VConfigManager::getUserSpecifyTemplateCodeBlockCssUrl() const
return !m_templateCodeBlockCssUrl.isEmpty(); 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 #endif // VCONFIGMANAGER_H

View File

@ -1,6 +1,8 @@
#ifndef VCONSTANTS_H #ifndef VCONSTANTS_H
#define VCONSTANTS_H #define VCONSTANTS_H
#include <QString>
// Html: rich text file; // Html: rich text file;
// Markdown: Markdown text file; // Markdown: Markdown text file;
// List: Infinite list file like WorkFlowy; // List: Infinite list file like WorkFlowy;
@ -22,7 +24,7 @@ namespace ClipboardConfig
static const QString c_dirs = "dirs"; 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_webZoomFactorMax = 5;
static const qreal c_webZoomFactorMin = 0.25; static const qreal c_webZoomFactorMin = 0.25;
@ -50,12 +52,16 @@ namespace DirConfig
static const QString c_emptyHeaderName = "[EMPTY]"; static const QString c_emptyHeaderName = "[EMPTY]";
enum class TextDecoration { None, enum class TextDecoration
{
None,
Bold, Bold,
Italic, Italic,
Underline, Underline,
Strikethrough, Strikethrough,
InlineCode }; InlineCode,
CodeBlock
};
enum FindOption enum FindOption
{ {
@ -97,4 +103,13 @@ enum class LineNumberType
CodeBlock CodeBlock
}; };
// Pages to open on start up.
enum class StartupPageType
{
None = 0,
ContinueLeftOff = 1,
SpecificPages = 2,
Invalid
};
#endif #endif

View File

@ -430,7 +430,7 @@ void VDirectoryTree::newSubDirectory()
.arg(g_config->c_dataTextStyle) .arg(g_config->c_dataTextStyle)
.arg(curDir->getName()); .arg(curDir->getName());
QString defaultName("new_folder"); 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); VNewDirDialog dialog(tr("Create Folder"), info, defaultName, curDir, this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
QString name = dialog.getNameInput(); QString name = dialog.getNameInput();
@ -467,7 +467,7 @@ void VDirectoryTree::newRootDirectory()
.arg(g_config->c_dataTextStyle) .arg(g_config->c_dataTextStyle)
.arg(m_notebook->getName()); .arg(m_notebook->getName());
QString defaultName("new_folder"); 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); VNewDirDialog dialog(tr("Create Root Folder"), info, defaultName, rootDir, this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
QString name = dialog.getNameInput(); QString name = dialog.getNameInput();

View File

@ -16,9 +16,15 @@ class VDocument : public QObject
public: public:
// @p_file could be NULL. // @p_file could be NULL.
VDocument(const VFile *p_file, QObject *p_parent = 0); VDocument(const VFile *p_file, QObject *p_parent = 0);
QString getToc(); 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 scrollToAnchor(const QString &anchor);
void setHtml(const QString &html); void setHtml(const QString &html);
// Request to highlight a segment text. // Request to highlight a segment text.
// Use p_id to identify the result. // Use p_id to identify the result.
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp); 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. // When the Web view has been scrolled, it will signal current header anchor.
// Empty @anchor to indicate an invalid header. // Empty @anchor to indicate an invalid header.
// The header does not begins with '#'.
void setHeader(const QString &anchor); void setHeader(const QString &anchor);
void setLog(const QString &p_log); void setLog(const QString &p_log);
@ -49,8 +56,12 @@ public slots:
signals: signals:
void textChanged(const QString &text); void textChanged(const QString &text);
void tocChanged(const QString &toc); void tocChanged(const QString &toc);
void requestScrollToAnchor(const QString &anchor); void requestScrollToAnchor(const QString &anchor);
// @anchor is the id of that anchor, without '#'.
void headerChanged(const QString &anchor); void headerChanged(const QString &anchor);
void htmlChanged(const QString &html); void htmlChanged(const QString &html);
void logChanged(const QString &p_log); void logChanged(const QString &p_log);

View File

@ -4,19 +4,26 @@
#include "vedit.h" #include "vedit.h"
#include "vnote.h" #include "vnote.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vtoc.h" #include "vtableofcontent.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "utils/veditutils.h" #include "utils/veditutils.h"
#include "utils/vmetawordmanager.h"
#include "veditoperations.h" #include "veditoperations.h"
#include "vedittab.h" #include "vedittab.h"
#include "dialog/vinsertlinkdialog.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VNote *g_vnote; 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); update(p_metric);
// Init configs that do not support update later.
m_enableVimMode = g_config->getEnableVimMode(); m_enableVimMode = g_config->getEnableVimMode();
if (g_config->getLineDistanceHeight() <= 0) { if (g_config->getLineDistanceHeight() <= 0) {
@ -26,6 +33,8 @@ void VEditConfig::init(const QFontMetrics &p_metric)
} }
m_highlightWholeBlock = m_enableVimMode; m_highlightWholeBlock = m_enableVimMode;
m_enableHeadingSequence = p_enableHeadingSequence;
} }
void VEditConfig::update(const QFontMetrics &p_metric) void VEditConfig::update(const QFontMetrics &p_metric)
@ -84,7 +93,7 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
updateFontAndPalette(); updateFontAndPalette();
m_config.init(QFontMetrics(font())); m_config.init(QFontMetrics(font()), false);
updateConfig(); updateConfig();
connect(this, &VEdit::cursorPositionChanged, connect(this, &VEdit::cursorPositionChanged,
@ -157,15 +166,16 @@ void VEdit::reloadFile()
setModified(false); setModified(false);
} }
void VEdit::scrollToLine(int p_lineNumber) bool VEdit::scrollToBlock(int p_blockNumber)
{ {
Q_ASSERT(p_lineNumber >= 0); QTextBlock block = document()->findBlockByNumber(p_blockNumber);
QTextBlock block = document()->findBlockByLineNumber(p_lineNumber);
if (block.isValid()) { if (block.isValid()) {
VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0); VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
moveCursor(QTextCursor::EndOfBlock); moveCursor(QTextCursor::EndOfBlock);
return true;
} }
return false;
} }
bool VEdit::isModified() const 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) bool VEdit::peekText(const QString &p_text, uint p_options, bool p_forward)
{ {
if (p_text.isEmpty()) { if (p_text.isEmpty()) {
@ -1383,3 +1436,36 @@ void VEdit::updateBlockLineDistanceHeight(int p_pos,
cursor.endEditBlock(); 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);
}
}

View File

@ -10,7 +10,6 @@
#include <QRect> #include <QRect>
#include <QFontMetrics> #include <QFontMetrics>
#include "vconstants.h" #include "vconstants.h"
#include "vtoc.h"
#include "vnotefile.h" #include "vnotefile.h"
class VEditOperations; class VEditOperations;
@ -38,10 +37,12 @@ public:
m_tabSpaces("\t"), m_tabSpaces("\t"),
m_enableVimMode(false), m_enableVimMode(false),
m_highlightWholeBlock(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. // Only update those configs which could be updated online.
void update(const QFontMetrics &p_metric); void update(const QFontMetrics &p_metric);
@ -64,6 +65,9 @@ public:
// Line distance height in pixels. // Line distance height in pixels.
int m_lineDistanceHeight; int m_lineDistanceHeight;
// Whether enable auto heading sequence.
bool m_enableHeadingSequence;
}; };
class LineNumberArea; class LineNumberArea;
@ -81,10 +85,15 @@ public:
virtual void setModified(bool p_modified); virtual void setModified(bool p_modified);
bool isModified() const; bool isModified() const;
virtual void reloadFile(); virtual void reloadFile();
virtual void scrollToLine(int p_lineNumber);
virtual bool scrollToBlock(int p_blockNumber);
// User requests to insert an image. // User requests to insert an image.
virtual void insertImage(); virtual void insertImage();
// User requests to insert a link.
virtual void insertLink();
// Used for incremental search. // Used for incremental search.
// User has enter the content to search, but does not enter the "find" button yet. // 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); bool peekText(const QString &p_text, uint p_options, bool p_forward = true);
@ -135,6 +144,9 @@ public:
bool isBlockVisible(const QTextBlock &p_block); bool isBlockVisible(const QTextBlock &p_block);
// Evaluate selected text or cursor word as magic words.
void evaluateMagicWords();
signals: signals:
// Request VEditTab to save and exit edit mode. // Request VEditTab to save and exit edit mode.
void saveAndRead(); void saveAndRead();
@ -163,6 +175,9 @@ signals:
// Request the edit tab to close find and replace dialog. // Request the edit tab to close find and replace dialog.
void requestCloseFindReplaceDialog(); void requestCloseFindReplaceDialog();
// Emit when all initialization is ready.
void ready();
public slots: public slots:
virtual void highlightCurrentLine(); virtual void highlightCurrentLine();

View File

@ -7,18 +7,27 @@
#include "vfile.h" #include "vfile.h"
#include "dialog/vfindreplacedialog.h" #include "dialog/vfindreplacedialog.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vfilesessioninfo.h"
#include "vmainwindow.h"
#include "vcaptain.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VNote *g_vnote; extern VNote *g_vnote;
VEditArea::VEditArea(VNote *vnote, QWidget *parent) extern VMainWindow *g_mainWin;
: QWidget(parent), VNavigationMode(),
vnote(vnote), curWindowIndex(-1) VEditArea::VEditArea(QWidget *parent)
: QWidget(parent),
VNavigationMode(),
curWindowIndex(-1)
{ {
setupUI(); setupUI();
insertSplitWindow(0); insertSplitWindow(0);
setCurrentWindow(0, false); setCurrentWindow(0, false);
registerCaptainTargets();
} }
void VEditArea::setupUI() void VEditArea::setupUI()
@ -65,7 +74,7 @@ void VEditArea::setupUI()
void VEditArea::insertSplitWindow(int idx) void VEditArea::insertSplitWindow(int idx)
{ {
VEditWindow *win = new VEditWindow(vnote, this); VEditWindow *win = new VEditWindow(this);
splitter->insertWidget(idx, win); splitter->insertWidget(idx, win);
connect(win, &VEditWindow::tabStatusUpdated, connect(win, &VEditWindow::tabStatusUpdated,
this, &VEditArea::handleWindowTabStatusUpdated); this, &VEditArea::handleWindowTabStatusUpdated);
@ -76,9 +85,9 @@ void VEditArea::insertSplitWindow(int idx)
connect(win, &VEditWindow::getFocused, connect(win, &VEditWindow::getFocused,
this, &VEditArea::handleWindowFocused); this, &VEditArea::handleWindowFocused);
connect(win, &VEditWindow::outlineChanged, connect(win, &VEditWindow::outlineChanged,
this, &VEditArea::handleOutlineChanged); this, &VEditArea::handleWindowOutlineChanged);
connect(win, &VEditWindow::curHeaderChanged, connect(win, &VEditWindow::currentHeaderChanged,
this, &VEditArea::handleCurHeaderChanged); this, &VEditArea::handleWindowCurrentHeaderChanged);
connect(win, &VEditWindow::statusMessage, connect(win, &VEditWindow::statusMessage,
this, &VEditArea::handleWindowStatusMessage); this, &VEditArea::handleWindowStatusMessage);
connect(win, &VEditWindow::vimStatusUpdated, connect(win, &VEditWindow::vimStatusUpdated,
@ -119,17 +128,17 @@ void VEditArea::removeSplitWindow(VEditWindow *win)
win->deleteLater(); 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) { if (!p_file) {
return; return NULL;
} }
// If it is DocType::Unknown, open it using system default method. // If it is DocType::Unknown, open it using system default method.
if (p_file->getDocType() == DocType::Unknown) { if (p_file->getDocType() == DocType::Unknown) {
QUrl url = QUrl::fromLocalFile(p_file->fetchPath()); QUrl url = QUrl::fromLocalFile(p_file->fetchPath());
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
return; return NULL;
} }
// Find if it has been opened already // 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); tabIdx = openFileInWindow(winIdx, p_file, p_mode);
out: out:
VEditTab *tab = getTab(winIdx, tabIdx);
setCurrentTab(winIdx, tabIdx, setFocus); setCurrentTab(winIdx, tabIdx, setFocus);
if (existFile && p_forceMode) { if (existFile && p_forceMode) {
@ -174,6 +185,8 @@ out:
editFile(); editFile();
} }
} }
return tab;
} }
QVector<QPair<int, int> > VEditArea::findTabsByFile(const VFile *p_file) QVector<QPair<int, int> > VEditArea::findTabsByFile(const VFile *p_file)
@ -232,15 +245,13 @@ void VEditArea::updateWindowStatus()
Q_ASSERT(splitter->count() == 0); Q_ASSERT(splitter->count() == 0);
emit tabStatusUpdated(VEditTabInfo()); emit tabStatusUpdated(VEditTabInfo());
emit outlineChanged(VToc()); emit outlineChanged(VTableOfContent());
emit curHeaderChanged(VAnchor()); emit currentHeaderChanged(VHeaderPointer());
return; return;
} }
VEditWindow *win = getWindow(curWindowIndex); VEditWindow *win = getWindow(curWindowIndex);
win->updateTabStatus(); win->updateTabStatus();
win->requestUpdateOutline();
win->requestUpdateCurHeader();
} }
bool VEditArea::closeFile(const VFile *p_file, bool p_forced) 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(); QObject *winObject = sender();
if (splitter->widget(curWindowIndex) == winObject) { 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(); QObject *winObject = sender();
if (splitter->widget(curWindowIndex) == winObject) { 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 VEditWindow *win = getCurrentWindow();
getWindow(curWindowIndex)->scrollCurTab(anchor); if (win) {
win->scrollToHeader(p_header);
}
} }
bool VEditArea::isFileOpened(const VFile *p_file) 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) { if (curWindowIndex == -1) {
return NULL; return NULL;
} }
VEditWindow *win = getWindow(curWindowIndex); 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<VEditTabInfo> VEditArea::getAllTabsInfo() const
{
QVector<VEditTabInfo> 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 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. // Only propogate the search in the IncrementalSearch case.
void VEditArea::handleFindTextChanged(const QString &p_text, uint p_options) void VEditArea::handleFindTextChanged(const QString &p_text, uint p_options)
{ {
VEditTab *tab = currentEditTab(); VEditTab *tab = getCurrentTab();
if (tab) { if (tab) {
if (p_options & FindOption::IncrementalSearch) { if (p_options & FindOption::IncrementalSearch) {
tab->findText(p_text, p_options, true); tab->findText(p_text, p_options, true);
@ -539,7 +574,7 @@ void VEditArea::handleFindNext(const QString &p_text, uint p_options,
bool p_forward) bool p_forward)
{ {
qDebug() << "find next" << p_text << p_options << p_forward; qDebug() << "find next" << p_text << p_options << p_forward;
VEditTab *tab = currentEditTab(); VEditTab *tab = getCurrentTab();
if (tab) { if (tab) {
tab->findText(p_text, p_options, false, p_forward); 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 qDebug() << "replace" << p_text << p_options << "with" << p_replaceText
<< p_findNext; << p_findNext;
VEditTab *tab = currentEditTab(); VEditTab *tab = getCurrentTab();
if (tab) { if (tab) {
tab->replaceText(p_text, p_options, p_replaceText, p_findNext); 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) const QString &p_replaceText)
{ {
qDebug() << "replace all" << p_text << p_options << "with" << p_replaceText; qDebug() << "replace all" << p_text << p_options << "with" << p_replaceText;
VEditTab *tab = currentEditTab(); VEditTab *tab = getCurrentTab();
if (tab) { if (tab) {
tab->replaceTextAll(p_text, p_options, p_replaceText); tab->replaceTextAll(p_text, p_options, p_replaceText);
} }
@ -584,7 +619,7 @@ void VEditArea::handleFindDialogClosed()
QString VEditArea::getSelectedText() QString VEditArea::getSelectedText()
{ {
VEditTab *tab = currentEditTab(); VEditTab *tab = getCurrentTab();
if (tab) { if (tab) {
return tab->getSelectedText(); return tab->getSelectedText();
} else { } else {
@ -621,6 +656,7 @@ VEditWindow *VEditArea::getCurrentWindow() const
if (curWindowIndex < 0) { if (curWindowIndex < 0) {
return NULL; return NULL;
} }
return getWindow(curWindowIndex); return getWindow(curWindowIndex);
} }
@ -694,3 +730,220 @@ bool VEditArea::handleKeyNavigation(int p_key, bool &p_succeed)
return ret; return ret;
} }
int VEditArea::openFiles(const QVector<VFileSessionInfo> &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<VEditArea *>(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<VEditArea *>(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<VEditArea *>(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<VEditArea *>(p_target);
obj->focusNextWindow(-1);
}
void VEditArea::activateSplitRightByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VEditArea *obj = static_cast<VEditArea *>(p_target);
obj->focusNextWindow(1);
}
void VEditArea::moveTabSplitLeftByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VEditArea *obj = static_cast<VEditArea *>(p_target);
obj->moveCurrentTabOneSplit(false);
}
void VEditArea::moveTabSplitRightByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VEditArea *obj = static_cast<VEditArea *>(p_target);
obj->moveCurrentTabOneSplit(true);
}
void VEditArea::activateNextTabByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VEditArea *obj = static_cast<VEditArea *>(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<VEditArea *>(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<VEditArea *>(p_target);
obj->splitCurrentWindow();
}
void VEditArea::removeSplitByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VEditArea *obj = static_cast<VEditArea *>(p_target);
obj->removeCurrentWindow();
}
void VEditArea::evaluateMagicWordsByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VEditArea *obj = static_cast<VEditArea *>(p_target);
VEditTab *tab = obj->getCurrentTab();
if (tab && tab->tabHasFocus()) {
tab->evaluateMagicWords();
}
}

View File

@ -12,10 +12,8 @@
#include <QSplitter> #include <QSplitter>
#include "vnotebook.h" #include "vnotebook.h"
#include "veditwindow.h" #include "veditwindow.h"
#include "vtoc.h"
#include "vnavigationmode.h" #include "vnavigationmode.h"
class VNote;
class VFile; class VFile;
class VDirectory; class VDirectory;
class VFindReplaceDialog; class VFindReplaceDialog;
@ -26,7 +24,7 @@ class VEditArea : public QWidget, public VNavigationMode
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit VEditArea(VNote *vnote, QWidget *parent = 0); explicit VEditArea(QWidget *parent = 0);
// Whether @p_file has been opened in edit area. // Whether @p_file has been opened in edit area.
bool isFileOpened(const VFile *p_file); bool isFileOpened(const VFile *p_file);
@ -35,17 +33,31 @@ public:
bool closeFile(const VFile *p_file, bool p_forced); bool closeFile(const VFile *p_file, bool p_forced);
bool closeFile(const VDirectory *p_dir, bool p_forced); bool closeFile(const VDirectory *p_dir, bool p_forced);
bool closeFile(const VNotebook *p_notebook, bool p_forced); bool closeFile(const VNotebook *p_notebook, bool p_forced);
// Returns current edit tab.
VEditTab *currentEditTab(); // Return current edit window.
// Returns the count of VEditWindow. VEditWindow *getCurrentWindow() const;
inline int windowCount() 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<VEditTabInfo> getAllTabsInfo() const;
// Return the count of VEditWindow.
int windowCount() const;
// Returns the index of @p_window. // Returns the index of @p_window.
int windowIndex(const VEditWindow *p_window) const; int windowIndex(const VEditWindow *p_window) const;
// Move tab widget @p_widget from window @p_fromIdx to @p_toIdx. // Move tab widget @p_widget from window @p_fromIdx to @p_toIdx.
// @p_widget has been removed from the original window. // @p_widget has been removed from the original window.
// If fail, just delete the p_widget. // If fail, just delete the p_widget.
void moveTab(QWidget *p_widget, int p_fromIdx, int p_toIdx); 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. // Return selected text of current edit tab.
QString getSelectedText(); QString getSelectedText();
void splitCurrentWindow(); void splitCurrentWindow();
@ -54,7 +66,6 @@ public:
// Return the new current window index, otherwise, return -1. // Return the new current window index, otherwise, return -1.
int focusNextWindow(int p_biaIdx); int focusNextWindow(int p_biaIdx);
void moveCurrentTabOneSplit(bool p_right); void moveCurrentTabOneSplit(bool p_right);
VEditWindow *getCurrentWindow() const;
// Implementations for VNavigationMode. // Implementations for VNavigationMode.
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE; void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
@ -62,12 +73,18 @@ public:
void hideNavigation() Q_DECL_OVERRIDE; void hideNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
// Open files @p_files.
int openFiles(const QVector<VFileSessionInfo> &p_files);
signals: signals:
// Emit when current window's tab status updated. // Emit when current window's tab status updated.
void tabStatusUpdated(const VEditTabInfo &p_info); void tabStatusUpdated(const VEditTabInfo &p_info);
void outlineChanged(const VToc &toc); // Emit when current window's tab's outline changed.
void curHeaderChanged(const VAnchor &anchor); 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. // Emit when want to show message in status bar.
void statusMessage(const QString &p_msg); void statusMessage(const QString &p_msg);
@ -84,13 +101,16 @@ public slots:
// @p_forceMode is true. // @p_forceMode is true.
// A given file can be opened in multiple split windows. A given file could be // A given file can be opened in multiple split windows. A given file could be
// opened at most in one tab inside a window. // 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 editFile();
void saveFile(); void saveFile();
void readFile(); void readFile();
void saveAndReadFile(); 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 handleFileUpdated(const VFile *p_file);
void handleDirectoryUpdated(const VDirectory *p_dir); void handleDirectoryUpdated(const VDirectory *p_dir);
void handleNotebookUpdated(const VNotebook *p_notebook); void handleNotebookUpdated(const VNotebook *p_notebook);
@ -102,8 +122,11 @@ private slots:
void handleRemoveSplitRequest(VEditWindow *curWindow); void handleRemoveSplitRequest(VEditWindow *curWindow);
void handleWindowFocused(); 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 handleFindTextChanged(const QString &p_text, uint p_options);
void handleFindOptionChanged(uint p_options); void handleFindOptionChanged(uint p_options);
void handleFindNext(const QString &p_text, uint p_options, bool p_forward); 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); int openFileInWindow(int windowIndex, VFile *p_file, OpenFileMode p_mode);
void setCurrentTab(int windowIndex, int tabIndex, bool setFocus); void setCurrentTab(int windowIndex, int tabIndex, bool setFocus);
void setCurrentWindow(int windowIndex, bool setFocus); void setCurrentWindow(int windowIndex, bool setFocus);
inline VEditWindow *getWindow(int windowIndex) const;
VEditWindow *getWindow(int windowIndex) const;
void insertSplitWindow(int idx); void insertSplitWindow(int idx);
void removeSplitWindow(VEditWindow *win); void removeSplitWindow(VEditWindow *win);
// Update status of current window. // Update status of current window.
void updateWindowStatus(); 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; int curWindowIndex;
// Splitter holding multiple split windows // Splitter holding multiple split windows

View File

@ -90,3 +90,10 @@ void VEditOperations::requestUpdateVimStatus()
{ {
emit vimStatusUpdated(m_vim); emit vimStatusUpdated(m_vim);
} }
void VEditOperations::setVimMode(VimMode p_mode)
{
if (m_vim && m_editConfig->m_enableVimMode) {
m_vim->setMode(p_mode);
}
}

View File

@ -18,11 +18,18 @@ class VEditOperations: public QObject
Q_OBJECT Q_OBJECT
public: public:
VEditOperations(VEdit *p_editor, VFile *p_file); VEditOperations(VEdit *p_editor, VFile *p_file);
virtual ~VEditOperations(); virtual ~VEditOperations();
virtual bool insertImageFromMimeData(const QMimeData *source) = 0; virtual bool insertImageFromMimeData(const QMimeData *source) = 0;
virtual bool insertImage() = 0; virtual bool insertImage() = 0;
virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 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 // Return true if @p_event has been handled and no need to be further
// processed. // processed.
virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0; virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0;
@ -33,6 +40,9 @@ public:
// Insert decoration markers or decorate selected text. // 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);};
// Set Vim mode if not NULL.
void setVimMode(VimMode p_mode);
signals: signals:
// Want to display a template message in status bar. // Want to display a template message in status bar.
void statusMessage(const QString &p_msg); void statusMessage(const QString &p_msg);

View File

@ -3,12 +3,13 @@
#include <QWheelEvent> #include <QWheelEvent>
VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent) VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent)
: QWidget(p_parent), m_file(p_file), m_isEditMode(false), : QWidget(p_parent),
m_modified(false), m_editArea(p_editArea) 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, connect(qApp, &QApplication::focusChanged,
this, &VEditTab::handleFocusChanged); this, &VEditTab::handleFocusChanged);
} }
@ -24,6 +25,7 @@ void VEditTab::focusTab()
{ {
focusChild(); focusChild();
emit getFocused(); emit getFocused();
updateStatus();
} }
bool VEditTab::isEditMode() const bool VEditTab::isEditMode() const
@ -33,7 +35,7 @@ bool VEditTab::isEditMode() const
bool VEditTab::isModified() const bool VEditTab::isModified() const
{ {
return m_modified; return m_file->isModified();
} }
VFile *VEditTab::getFile() const VFile *VEditTab::getFile() const
@ -48,21 +50,13 @@ void VEditTab::handleFocusChanged(QWidget * /* p_old */, QWidget *p_now)
focusChild(); focusChild();
emit getFocused(); emit getFocused();
updateStatus();
} else if (isAncestorOf(p_now)) { } else if (isAncestorOf(p_now)) {
emit getFocused(); emit getFocused();
updateStatus();
} }
} }
void VEditTab::requestUpdateCurHeader()
{
emit curHeaderChanged(m_curHeader);
}
void VEditTab::requestUpdateOutline()
{
emit outlineChanged(m_toc);
}
void VEditTab::wheelEvent(QWheelEvent *p_event) void VEditTab::wheelEvent(QWheelEvent *p_event)
{ {
QPoint angle = p_event->angleDelta(); QPoint angle = p_event->angleDelta();
@ -78,17 +72,55 @@ void VEditTab::wheelEvent(QWheelEvent *p_event)
p_event->ignore(); p_event->ignore();
} }
void VEditTab::updateStatus() VEditTabInfo VEditTab::fetchTabInfo() const
{
m_modified = m_file->isModified();
emit statusUpdated(createEditTabInfo());
}
VEditTabInfo VEditTab::createEditTabInfo()
{ {
VEditTabInfo info; VEditTabInfo info;
info.m_editTab = this; info.m_editTab = const_cast<VEditTab *>(this);
return info; 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()
{
}

View File

@ -4,7 +4,7 @@
#include <QWidget> #include <QWidget>
#include <QString> #include <QString>
#include <QPointer> #include <QPointer>
#include "vtoc.h" #include "vtableofcontent.h"
#include "vfile.h" #include "vfile.h"
#include "utils/vvim.h" #include "utils/vvim.h"
#include "vedittabinfo.h" #include "vedittabinfo.h"
@ -37,18 +37,21 @@ public:
void focusTab(); void focusTab();
virtual void requestUpdateOutline(); // Whether this tab has focus.
bool tabHasFocus() const;
virtual void requestUpdateCurHeader(); // Scroll to @p_header.
// Will emit currentHeaderChanged() if @p_header is valid.
// Scroll to anchor @p_anchor. virtual void scrollToHeader(const VHeaderPointer &p_header) { Q_UNUSED(p_header) }
virtual void scrollToAnchor(const VAnchor& p_anchor) = 0;
VFile *getFile() const; VFile *getFile() const;
// User requests to insert image. // User requests to insert image.
virtual void insertImage() = 0; virtual void insertImage() = 0;
// User requests to insert link.
virtual void insertLink();
// Search @p_text in current note. // Search @p_text in current note.
virtual void findText(const QString &p_text, uint p_options, bool p_peek, virtual void findText(const QString &p_text, uint p_options, bool p_peek,
bool p_forward = true) = 0; bool p_forward = true) = 0;
@ -69,15 +72,29 @@ public:
virtual void requestUpdateVimStatus() = 0; virtual void requestUpdateVimStatus() = 0;
// Insert decoration markers or decorate selected text. // 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: public slots:
// Enter edit mode // Enter edit mode
virtual void editFile() = 0; virtual void editFile() = 0;
// Update status of current tab. Emit statusUpdated().
virtual void updateStatus();
protected: protected:
void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE;
@ -87,23 +104,32 @@ protected:
// Called to zoom in/out content. // Called to zoom in/out content.
virtual void zoom(bool p_zoomIn, qreal p_step = 0.25) = 0; virtual void zoom(bool p_zoomIn, qreal p_step = 0.25) = 0;
// Create a filled VEditTabInfo. // Restore from @p_fino.
virtual VEditTabInfo createEditTabInfo(); // Return true if succeed.
virtual bool restoreFromTabInfo(const VEditTabInfo &p_info) = 0;
// File related to this tab. // File related to this tab.
QPointer<VFile> m_file; QPointer<VFile> m_file;
bool m_isEditMode; bool m_isEditMode;
bool m_modified;
VToc m_toc; // Table of content of this tab.
VAnchor m_curHeader; VTableOfContent m_outline;
// Current header in m_outline of this tab.
VHeaderPointer m_currentHeader;
VEditArea *m_editArea; VEditArea *m_editArea;
// Tab info to restore from once ready.
VEditTabInfo m_infoToRestore;
signals: signals:
void getFocused(); 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. // The status of current tab has updates.
void statusUpdated(const VEditTabInfo &p_info); void statusUpdated(const VEditTabInfo &p_info);

View File

@ -6,8 +6,22 @@ class VEditTab;
struct VEditTabInfo struct VEditTabInfo
{ {
VEditTabInfo() VEditTabInfo()
: m_editTab(NULL), m_cursorBlockNumber(-1), m_cursorPositionInBlock(-1), : m_editTab(NULL),
m_blockCount(-1) {} 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; VEditTab *m_editTab;
@ -15,6 +29,9 @@ struct VEditTabInfo
int m_cursorBlockNumber; int m_cursorBlockNumber;
int m_cursorPositionInBlock; int m_cursorPositionInBlock;
int m_blockCount; int m_blockCount;
// Header index in outline.
int m_headerIndex;
}; };
#endif // VEDITTABINFO_H #endif // VEDITTABINFO_H

View File

@ -2,7 +2,6 @@
#include <QtDebug> #include <QtDebug>
#include "veditwindow.h" #include "veditwindow.h"
#include "vedittab.h" #include "vedittab.h"
#include "vnote.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vorphanfile.h" #include "vorphanfile.h"
#include "vmainwindow.h" #include "vmainwindow.h"
@ -13,12 +12,14 @@
#include "vfilelist.h" #include "vfilelist.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
extern VNote *g_vnote;
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VMainWindow *g_mainWin;
VEditWindow::VEditWindow(VNote *vnote, VEditArea *editArea, QWidget *parent) VEditWindow::VEditWindow(VEditArea *editArea, QWidget *parent)
: QTabWidget(parent), vnote(vnote), m_editArea(editArea), : QTabWidget(parent),
m_curTabWidget(NULL), m_lastTabWidget(NULL) m_editArea(editArea),
m_curTabWidget(NULL),
m_lastTabWidget(NULL)
{ {
setAcceptDrops(true); setAcceptDrops(true);
initTabActions(); initTabActions();
@ -137,9 +138,9 @@ void VEditWindow::initTabActions()
Q_ASSERT(file); Q_ASSERT(file);
if (file->getType() == FileType::Note) { if (file->getType() == FileType::Note) {
VNoteFile *tmpFile = dynamic_cast<VNoteFile *>((VFile *)file); VNoteFile *tmpFile = dynamic_cast<VNoteFile *>((VFile *)file);
g_vnote->getMainWindow()->getFileList()->fileInfo(tmpFile); g_mainWin->getFileList()->fileInfo(tmpFile);
} else if (file->getType() == FileType::Orphan) { } 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 VEditWindow::insertEditTab(int p_index, VFile *p_file, QWidget *p_page)
{ {
int idx = insertTab(p_index, p_page, p_file->getName()); int idx = insertTab(p_index,
setTabToolTip(idx, generateTooltip(p_file)); p_page,
p_file->getName());
updateTabInfo(idx);
return idx; return idx;
} }
@ -466,15 +469,17 @@ void VEditWindow::updateTabStatus(int p_index)
if (p_index == -1) { if (p_index == -1) {
emit tabStatusUpdated(VEditTabInfo()); emit tabStatusUpdated(VEditTabInfo());
emit outlineChanged(VToc()); emit outlineChanged(VTableOfContent());
emit curHeaderChanged(VAnchor()); emit currentHeaderChanged(VHeaderPointer());
return; return;
} }
VEditTab *tab = getTab(p_index); VEditTab *tab = getTab(p_index);
tab->updateStatus(); emit tabStatusUpdated(tab->fetchTabInfo());
tab->requestUpdateOutline(); emit outlineChanged(tab->getOutline());
tab->requestUpdateCurHeader(); emit currentHeaderChanged(tab->getCurrentHeader());
updateTabInfo(p_index);
} }
void VEditWindow::updateTabInfo(int p_index) void VEditWindow::updateTabInfo(int p_index)
@ -483,8 +488,7 @@ void VEditWindow::updateTabInfo(int p_index)
const VFile *file = editor->getFile(); const VFile *file = editor->getFile();
bool editMode = editor->isEditMode(); bool editMode = editor->isEditMode();
setTabText(p_index, generateTabText(p_index, file->getName(), setTabText(p_index, generateTabText(p_index, file));
file->isModified(), file->isModifiable()));
setTabToolTip(p_index, generateTooltip(file)); setTabToolTip(p_index, generateTooltip(file));
QString iconUrl(":/resources/icons/reading.svg"); QString iconUrl(":/resources/icons/reading.svg");
@ -501,31 +505,28 @@ void VEditWindow::updateAllTabsSequence()
for (int i = 0; i < count(); ++i) { for (int i = 0; i < count(); ++i) {
VEditTab *editor = getTab(i); VEditTab *editor = getTab(i);
const VFile *file = editor->getFile(); const VFile *file = editor->getFile();
setTabText(i, generateTabText(i, file->getName(), setTabText(i, generateTabText(i, file));
file->isModified(), file->isModifiable()));
} }
} }
// Be requested to report current outline VTableOfContent VEditWindow::getOutline() const
void VEditWindow::requestUpdateOutline()
{ {
int idx = currentIndex(); int idx = currentIndex();
if (idx == -1) { if (idx == -1) {
emit outlineChanged(VToc()); return VTableOfContent();
return;
}
getTab(idx)->requestUpdateOutline();
} }
// Be requested to report current header return getTab(idx)->getOutline();
void VEditWindow::requestUpdateCurHeader() }
VHeaderPointer VEditWindow::getCurrentHeader() const
{ {
int idx = currentIndex(); int idx = currentIndex();
if (idx == -1) { if (idx == -1) {
emit curHeaderChanged(VAnchor()); return VHeaderPointer();
return;
} }
getTab(idx)->requestUpdateCurHeader();
return getTab(idx)->getCurrentHeader();
} }
// Focus this windows. Try to focus current tab. // Focus this windows. Try to focus current tab.
@ -681,44 +682,39 @@ bool VEditWindow::canRemoveSplit()
return splitter->count() > 1; 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 // Only propagate it if it is current tab.
int idx = currentIndex(); VEditTab *tab = getCurrentTab();
if (idx == -1) { if (tab) {
emit outlineChanged(VToc()); if (tab->getFile() == p_outline.getFile()) {
return; emit outlineChanged(p_outline);
} }
const VFile *file = getTab(idx)->getFile(); } else {
if (p_toc.m_file == file) { emit outlineChanged(VTableOfContent());
emit outlineChanged(p_toc); return;
} }
} }
void VEditWindow::handleCurHeaderChanged(const VAnchor &p_anchor) void VEditWindow::handleTabCurrentHeaderChanged(const VHeaderPointer &p_header)
{ {
// Only propagate it if it is current tab // Only propagate it if it is current tab.
int idx = currentIndex(); VEditTab *tab = getCurrentTab();
if (idx == -1) { if (tab) {
emit curHeaderChanged(VAnchor()); if (tab->getFile() == p_header.m_file) {
return; emit currentHeaderChanged(p_header);
} }
const VFile *file = getTab(idx)->getFile(); } else {
if (p_anchor.m_file == file) { emit currentHeaderChanged(VHeaderPointer());
emit curHeaderChanged(p_anchor); return;
} }
} }
void VEditWindow::scrollCurTab(const VAnchor &p_anchor) void VEditWindow::scrollToHeader(const VHeaderPointer &p_header)
{ {
int idx = currentIndex(); VEditTab *tab = getCurrentTab();
if (idx == -1) { if (tab) {
emit curHeaderChanged(VAnchor()); tab->scrollToHeader(p_header);
return;
}
const VFile *file = getTab(idx)->getFile();
if (file == p_anchor.m_file) {
getTab(idx)->scrollToAnchor(p_anchor);
} }
} }
@ -794,22 +790,37 @@ void VEditWindow::updateNotebookInfo(const VNotebook *p_notebook)
} }
} }
VEditTab *VEditWindow::currentEditTab() VEditTab *VEditWindow::getCurrentTab() const
{ {
int idx = currentIndex(); int idx = currentIndex();
if (idx == -1) { if (idx == -1) {
return NULL; return NULL;
} }
return getTab(idx); return getTab(idx);
} }
QVector<VEditTabInfo> VEditWindow::getAllTabsInfo() const
{
int nrTab = count();
QVector<VEditTabInfo> 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() void VEditWindow::handleLocateAct()
{ {
int tab = m_locateAct->data().toInt(); int tab = m_locateAct->data().toInt();
VEditTab *editor = getTab(tab); VEditTab *editor = getTab(tab);
QPointer<VFile> file = editor->getFile(); QPointer<VFile> file = editor->getFile();
if (file->getType() == FileType::Note) { 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, connect(p_tab, &VEditTab::getFocused,
this, &VEditWindow::getFocused); this, &VEditWindow::getFocused);
connect(p_tab, &VEditTab::outlineChanged, connect(p_tab, &VEditTab::outlineChanged,
this, &VEditWindow::handleOutlineChanged); this, &VEditWindow::handleTabOutlineChanged);
connect(p_tab, &VEditTab::curHeaderChanged, connect(p_tab, &VEditTab::currentHeaderChanged,
this, &VEditWindow::handleCurHeaderChanged); this, &VEditWindow::handleTabCurrentHeaderChanged);
connect(p_tab, &VEditTab::statusUpdated, connect(p_tab, &VEditTab::statusUpdated,
this, &VEditWindow::handleTabStatusUpdated); this, &VEditWindow::handleTabStatusUpdated);
connect(p_tab, &VEditTab::statusMessage, connect(p_tab, &VEditTab::statusMessage,
@ -1014,7 +1025,7 @@ void VEditWindow::dropEvent(QDropEvent *p_event)
if (!files.isEmpty()) { if (!files.isEmpty()) {
focusWindow(); focusWindow();
g_vnote->getMainWindow()->openExternalFiles(files); g_mainWin->openFiles(files);
} }
p_event->acceptProposedAction(); p_event->acceptProposedAction();

View File

@ -8,11 +8,9 @@
#include <QDir> #include <QDir>
#include "vnotebook.h" #include "vnotebook.h"
#include "vedittab.h" #include "vedittab.h"
#include "vtoc.h"
#include "vconstants.h" #include "vconstants.h"
#include "vnotefile.h" #include "vnotefile.h"
class VNote;
class QPushButton; class QPushButton;
class QActionGroup; class QActionGroup;
class VEditArea; class VEditArea;
@ -21,7 +19,7 @@ class VEditWindow : public QTabWidget
{ {
Q_OBJECT Q_OBJECT
public: 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 findTabByFile(const VFile *p_file) const;
int openFile(VFile *p_file, OpenFileMode p_mode); int openFile(VFile *p_file, OpenFileMode p_mode);
bool closeFile(const VFile *p_file, bool p_forced); bool closeFile(const VFile *p_file, bool p_forced);
@ -32,15 +30,29 @@ public:
void readFile(); void readFile();
void saveAndReadFile(); void saveAndReadFile();
bool closeAllFiles(bool p_forced); 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 // Focus to current tab's editor
void focusWindow(); 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 updateFileInfo(const VFile *p_file);
void updateDirectoryInfo(const VDirectory *p_dir); void updateDirectoryInfo(const VDirectory *p_dir);
void updateNotebookInfo(const VNotebook *p_notebook); void updateNotebookInfo(const VNotebook *p_notebook);
VEditTab *currentEditTab();
VEditTab *getCurrentTab() const;
VEditTab *getTab(int tabIndex) const;
QVector<VEditTabInfo> getAllTabsInfo() const;
// Insert a tab with @p_widget. @p_widget is a fully initialized VEditTab. // Insert a tab with @p_widget. @p_widget is a fully initialized VEditTab.
bool addEditTab(QWidget *p_widget); bool addEditTab(QWidget *p_widget);
// Set whether it is the current window. // Set whether it is the current window.
@ -53,7 +65,6 @@ public:
bool activateTab(int p_sequence); bool activateTab(int p_sequence);
// Switch to previous activated tab. // Switch to previous activated tab.
bool alternateTab(); bool alternateTab();
VEditTab *getTab(int tabIndex) const;
// Ask tab @p_index to update its status and propogate. // Ask tab @p_index to update its status and propogate.
// The status here means tab status, outline, current header. // The status here means tab status, outline, current header.
@ -79,8 +90,10 @@ signals:
void requestRemoveSplit(VEditWindow *curWindow); void requestRemoveSplit(VEditWindow *curWindow);
// This widget or its children get the focus // This widget or its children get the focus
void getFocused(); 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. // Emit when want to show message in status bar.
void statusMessage(const QString &p_msg); void statusMessage(const QString &p_msg);
@ -100,8 +113,11 @@ private slots:
void handleCurrentIndexChanged(int p_index); void handleCurrentIndexChanged(int p_index);
void contextMenuRequested(QPoint pos); void contextMenuRequested(QPoint pos);
void tabListJump(VFile *p_file); 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 updateSplitMenu();
void tabbarContextMenuRequested(QPoint p_pos); void tabbarContextMenuRequested(QPoint p_pos);
void handleLocateAct(); void handleLocateAct();
@ -124,9 +140,11 @@ private:
int insertEditTab(int p_index, VFile *p_file, QWidget *p_page); int insertEditTab(int p_index, VFile *p_file, QWidget *p_page);
int appendEditTab(VFile *p_file, QWidget *p_page); int appendEditTab(VFile *p_file, QWidget *p_page);
int openFileInTab(VFile *p_file, OpenFileMode p_mode); 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, QString generateTooltip(const VFile *p_file) const;
bool p_modified, bool p_modifiable) const;
QString generateTabText(int p_index, const VFile *p_file) const;
bool canRemoveSplit(); bool canRemoveSplit();
// Move tab at @p_tabIdx one split window. // Move tab at @p_tabIdx one split window.
@ -135,6 +153,7 @@ private:
// and move the tab to the new split. // and move the tab to the new split.
void moveTabOneSplit(int p_tabIdx, bool p_right); 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); void updateTabInfo(int p_idx);
// Update the sequence number of all the tabs. // Update the sequence number of all the tabs.
@ -143,7 +162,6 @@ private:
// Connect the signals of VEditTab to this VEditWindow. // Connect the signals of VEditTab to this VEditWindow.
void connectEditTab(const VEditTab *p_tab); void connectEditTab(const VEditTab *p_tab);
VNote *vnote;
VEditArea *m_editArea; VEditArea *m_editArea;
// These two members are only used for alternateTab(). // 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, inline QString VEditWindow::generateTabText(int p_index, const VFile *p_file) const
bool p_modified, bool p_modifiable) const
{ {
QString seq = QString::number(p_index + c_tabSequenceBase, 10); if (!p_file) {
return seq + ". " + p_name + (p_modifiable ? (p_modified ? "*" : "") : "#"); 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 #endif // VEDITWINDOW_H

View File

@ -263,7 +263,7 @@ void VFileList::fileInfo(VNoteFile *p_file)
void VFileList::fillItem(QListWidgetItem *p_item, const 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->setData(Qt::UserRole, ptr);
p_item->setToolTip(p_file->getName()); p_item->setToolTip(p_file->getName());
p_item->setText(p_file->getName()); p_item->setText(p_file->getName());
@ -330,7 +330,9 @@ void VFileList::newFile()
info = info + "<br>" + tr("Note with name ending with \"%1\" will be treated as Markdown type.") info = info + "<br>" + tr("Note with name ending with \"%1\" will be treated as Markdown type.")
.arg(suffixStr); .arg(suffixStr);
QString defaultName = QString("new_note.%1").arg(defaultSuf); 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); VNewFileDialog dialog(tr("Create Note"), info, defaultName, m_directory, this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
VNoteFile *file = m_directory->createFile(dialog.getNameInput()); VNoteFile *file = m_directory->createFile(dialog.getNameInput());
@ -349,7 +351,7 @@ void VFileList::newFile()
qWarning() << "fail to open newly-created note" << file->getName(); qWarning() << "fail to open newly-created note" << file->getName();
} else { } else {
Q_ASSERT(file->getContent().isEmpty()); 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); file->setContent(content);
if (!file->save()) { if (!file->save()) {
qWarning() << "fail to write to newly-created note" << file->getName(); 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. // Move cursor down if content has been inserted.
if (contentInserted) { if (contentInserted) {
const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->currentEditTab()); const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->getCurrentTab());
if (tab) { if (tab) {
VMdEdit *edit = dynamic_cast<VMdEdit *>(tab->getEditor()); VMdEdit *edit = dynamic_cast<VMdEdit *>(tab->getEditor());
if (edit && edit->getFile() == file) { if (edit && edit->getFile() == file) {
@ -621,7 +623,7 @@ bool VFileList::importFiles(const QStringList &p_files, QString *p_errMsg)
QString name = VUtils::fileNameFromPath(file); QString name = VUtils::fileNameFromPath(file);
Q_ASSERT(!name.isEmpty()); Q_ASSERT(!name.isEmpty());
name = VUtils::getFileNameWithSequence(dirPath, name); name = VUtils::getFileNameWithSequence(dirPath, name, true);
QString targetFilePath = dir.filePath(name); QString targetFilePath = dir.filePath(name);
bool ret = VUtils::copyFile(file, targetFilePath, false); bool ret = VUtils::copyFile(file, targetFilePath, false);
if (!ret) { if (!ret) {
@ -763,10 +765,14 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
} }
// Rename it to xxx_copy.md. // Rename it to xxx_copy.md.
fileName = VUtils::generateCopiedFileName(file->fetchBasePath(), fileName); fileName = VUtils::generateCopiedFileName(file->fetchBasePath(),
fileName,
true);
} else { } else {
// Rename it to xxx_copy.md if needed. // 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; QString msg;

View File

@ -44,7 +44,9 @@ public:
// Locate @p_file in the list widget. // Locate @p_file in the list widget.
bool locateFile(const VNoteFile *p_file); bool locateFile(const VNoteFile *p_file);
inline const VDirectory *currentDirectory() const; const VDirectory *currentDirectory() const;
QWidget *getContentWidget() const;
// Implementations for VNavigationMode. // Implementations for VNavigationMode.
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE; void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
@ -199,4 +201,9 @@ inline const VDirectory *VFileList::currentDirectory() const
return m_directory; return m_directory;
} }
inline QWidget *VFileList::getContentWidget() const
{
return fileList;
}
#endif // VFILELIST_H #endif // VFILELIST_H

74
src/vfilesessioninfo.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "vfilesessioninfo.h"
#include <QSettings>
#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);
}

57
src/vfilesessioninfo.h Normal file
View File

@ -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

View File

@ -32,7 +32,7 @@ void VHtmlTab::setupUI()
{ {
m_editor = new VEdit(m_file, this); m_editor = new VEdit(m_file, this);
connect(m_editor, &VEdit::textChanged, connect(m_editor, &VEdit::textChanged,
this, &VHtmlTab::handleTextChanged); this, &VHtmlTab::updateStatus);
connect(m_editor, &VEdit::saveAndRead, connect(m_editor, &VEdit::saveAndRead,
this, &VHtmlTab::saveAndRead); this, &VHtmlTab::saveAndRead);
connect(m_editor, &VEdit::discardAndRead, connect(m_editor, &VEdit::discardAndRead,
@ -52,17 +52,6 @@ void VHtmlTab::setupUI()
setLayout(mainLayout); setLayout(mainLayout);
} }
void VHtmlTab::handleTextChanged()
{
V_ASSERT(m_file->isModifiable());
if (m_modified) {
return;
}
updateStatus();
}
void VHtmlTab::showFileReadMode() void VHtmlTab::showFileReadMode()
{ {
m_isEditMode = false; m_isEditMode = false;
@ -194,10 +183,6 @@ void VHtmlTab::discardAndRead()
readFile(); readFile();
} }
void VHtmlTab::scrollToAnchor(const VAnchor & /* p_anchor */)
{
}
void VHtmlTab::insertImage() void VHtmlTab::insertImage()
{ {
} }
@ -252,3 +237,12 @@ void VHtmlTab::requestUpdateVimStatus()
{ {
m_editor->requestUpdateVimStatus(); m_editor->requestUpdateVimStatus();
} }
bool VHtmlTab::restoreFromTabInfo(const VEditTabInfo &p_info)
{
if (p_info.m_editTab != this) {
return false;
}
return true;
}

View File

@ -26,9 +26,6 @@ public:
// Save file. // Save file.
bool saveFile() Q_DECL_OVERRIDE; bool saveFile() Q_DECL_OVERRIDE;
// Scroll to anchor @p_anchor.
void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE;
void insertImage() Q_DECL_OVERRIDE; void insertImage() Q_DECL_OVERRIDE;
// Search @p_text in current note. // Search @p_text in current note.
@ -53,9 +50,6 @@ public slots:
void editFile() Q_DECL_OVERRIDE; void editFile() Q_DECL_OVERRIDE;
private slots: private slots:
// Handle text changed in m_editor.
void handleTextChanged();
// m_editor requests to save changes and enter read mode. // m_editor requests to save changes and enter read mode.
void saveAndRead(); void saveAndRead();
@ -78,6 +72,10 @@ private:
// Focus the proper child widget. // Focus the proper child widget.
void focusChild() Q_DECL_OVERRIDE; 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; VEdit *m_editor;
}; };
#endif // VHTMLTAB_H #endif // VHTMLTAB_H

48
src/vlineedit.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "vlineedit.h"
#include <QDebug>
#include <QToolTip>
#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;
}

29
src/vlineedit.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef VLINEEDIT_H
#define VLINEEDIT_H
#include <QLineEdit>
// 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

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,15 @@ class QShortcut;
class VButtonWithWidget; class VButtonWithWidget;
class VAttachmentList; class VAttachmentList;
enum class PanelViewState
{
ExpandMode,
SinglePanel,
TwoPanels,
CompactMode,
Invalid
};
class VMainWindow : public QMainWindow class VMainWindow : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
@ -51,18 +60,17 @@ public:
// Returns true if the location succeeds. // Returns true if the location succeeds.
bool locateFile(VFile *p_file); bool locateFile(VFile *p_file);
// Returns true if the location succeeds.
bool locateCurrentFile();
VFileList *getFileList() const; VFileList *getFileList() const;
VEditArea *getEditArea() const;
// View and edit the information of @p_file, which is an orphan file. // View and edit the information of @p_file, which is an orphan file.
void editOrphanFileInfo(VFile *p_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 // 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. // 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. // Try to open @p_filePath as internal note.
bool tryOpenInternalFile(const QString &p_filePath); bool tryOpenInternalFile(const QString &p_filePath);
@ -70,15 +78,23 @@ public:
// Show a temporary message in status bar. // Show a temporary message in status bar.
void showStatusMessage(const QString &p_msg); void showStatusMessage(const QString &p_msg);
// Popup the attachment list if it is enabled. // Open startup pages according to configuration.
void showAttachmentList(); void openStartupPages();
VCaptain *getCaptain() const;
// Prompt user for new notebook if there is no notebook.
void promptNewNotebookIfEmpty();
private slots: private slots:
void importNoteFromFile(); void importNoteFromFile();
void viewSettings(); void viewSettings();
void changeMarkdownConverter(QAction *action); void changeMarkdownConverter(QAction *action);
void aboutMessage(); void aboutMessage();
void shortcutHelp();
// Display shortcuts help.
void shortcutsHelp();
void changeExpandTab(bool checked); void changeExpandTab(bool checked);
void setTabStopWidth(QAction *action); void setTabStopWidth(QAction *action);
void setEditorBackgroundColor(QAction *action); void setEditorBackgroundColor(QAction *action);
@ -105,17 +121,15 @@ private slots:
void changeHighlightTrailingSapce(bool p_checked); void changeHighlightTrailingSapce(bool p_checked);
void onePanelView(); void onePanelView();
void twoPanelView(); void twoPanelView();
void expandPanelView(bool p_checked); void compactModeView();
void curEditFileInfo(); void curEditFileInfo();
void deleteCurNote(); void deleteCurNote();
void handleCurrentDirectoryChanged(const VDirectory *p_dir); void handleCurrentDirectoryChanged(const VDirectory *p_dir);
void handleCurrentNotebookChanged(const VNotebook *p_notebook); void handleCurrentNotebookChanged(const VNotebook *p_notebook);
void insertImage();
void handleFindDialogTextChanged(const QString &p_text, uint p_options); void handleFindDialogTextChanged(const QString &p_text, uint p_options);
void openFindDialog(); void openFindDialog();
void enableMermaid(bool p_checked); void enableMermaid(bool p_checked);
void enableMathjax(bool p_checked); void enableMathjax(bool p_checked);
void handleCaptainModeChanged(bool p_enabled);
void changeAutoIndent(bool p_checked); void changeAutoIndent(bool p_checked);
void changeAutoList(bool p_checked); void changeAutoList(bool p_checked);
void changeVimMode(bool p_checked); void changeVimMode(bool p_checked);
@ -127,10 +141,14 @@ private slots:
void printNote(); void printNote();
void exportAsPDF(); void exportAsPDF();
// Set the panel view properly.
void enableCompactMode(bool p_enabled);
// Handle Vim status updated. // Handle Vim status updated.
void handleVimStatusUpdated(const VVim *p_vim); void handleVimStatusUpdated(const VVim *p_vim);
// Handle the status update of the current tab of VEditArea. // Handle the status update of the current tab of VEditArea.
// Will be called frequently.
void handleAreaTabStatusUpdated(const VEditTabInfo &p_info); void handleAreaTabStatusUpdated(const VEditTabInfo &p_info);
// Check the shared memory between different instances to see if we have // Check the shared memory between different instances to see if we have
@ -142,6 +160,9 @@ private slots:
// Restore main window. // Restore main window.
void showMainWindow(); void showMainWindow();
// Close current note.
void closeCurrentFile();
protected: protected:
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE; void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
@ -186,17 +207,19 @@ private:
void initEditorLineNumberMenu(QMenu *p_menu); void initEditorLineNumberMenu(QMenu *p_menu);
void initEditorStyleMenu(QMenu *p_emnu); void initEditorStyleMenu(QMenu *p_emnu);
void changeSplitterView(int nrPanel);
void updateWindowTitle(const QString &str); 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 saveStateAndGeometry();
void restoreStateAndGeometry(); void restoreStateAndGeometry();
void repositionAvatar(); void repositionAvatar();
// Should init VCaptain before setupUI().
void initCaptain(); void initCaptain();
void toggleOnePanelView();
void closeCurrentFile(); void registerCaptainAndNavigationTargets();
// Update status bar information. // Update status bar information.
void updateStatusInfo(const VEditTabInfo &p_info); void updateStatusInfo(const VEditTabInfo &p_info);
@ -213,6 +236,37 @@ private:
// Init system tray icon and correspondign context menu. // Init system tray icon and correspondign context menu.
void initTrayIcon(); 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; VNote *vnote;
QPointer<VFile> m_curFile; QPointer<VFile> m_curFile;
QPointer<VEditTab> m_curTab; QPointer<VEditTab> m_curTab;
@ -222,10 +276,18 @@ private:
QLabel *notebookLabel; QLabel *notebookLabel;
QLabel *directoryLabel; QLabel *directoryLabel;
VNotebookSelector *notebookSelector; VNotebookSelector *notebookSelector;
VFileList *fileList; VFileList *m_fileList;
VDirectoryTree *directoryTree; 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; VEditArea *editArea;
QDockWidget *toolDock; QDockWidget *toolDock;
QToolBox *toolBox; QToolBox *toolBox;
VOutline *outline; VOutline *outline;
@ -234,8 +296,8 @@ private:
VVimIndicator *m_vimIndicator; VVimIndicator *m_vimIndicator;
VTabIndicator *m_tabIndicator; VTabIndicator *m_tabIndicator;
// Whether it is one panel or two panles. // SinglePanel, TwoPanels, CompactMode.
bool m_onePanel; PanelViewState m_panelViewState;
// Actions // Actions
QAction *newRootDirAct; QAction *newRootDirAct;
@ -251,7 +313,6 @@ private:
QAction *m_printAct; QAction *m_printAct;
QAction *m_exportAsPDFAct; QAction *m_exportAsPDFAct;
QAction *m_insertImageAct;
QAction *m_findReplaceAct; QAction *m_findReplaceAct;
QAction *m_findNextAct; QAction *m_findNextAct;
QAction *m_findPreviousAct; QAction *m_findPreviousAct;
@ -261,6 +322,9 @@ private:
QAction *m_autoIndentAct; QAction *m_autoIndentAct;
// Enable heading sequence for current note.
QAction *m_headingSequenceAct;
// Act group for render styles. // Act group for render styles.
QActionGroup *m_renderStyleActs; QActionGroup *m_renderStyleActs;
@ -269,6 +333,9 @@ private:
// Act group for code block render styles. // Act group for code block render styles.
QActionGroup *m_codeBlockStyleActs; QActionGroup *m_codeBlockStyleActs;
// Act group for panel view actions.
QActionGroup *m_viewActGroup;
QShortcut *m_closeNoteShortcut; QShortcut *m_closeNoteShortcut;
// Menus // Menus
@ -306,7 +373,17 @@ private:
inline VFileList *VMainWindow::getFileList() const 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 #endif // VMAINWINDOW_H

View File

@ -5,7 +5,7 @@
#include "vmdeditoperations.h" #include "vmdeditoperations.h"
#include "vnote.h" #include "vnote.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vtoc.h" #include "vtableofcontent.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "utils/veditutils.h" #include "utils/veditutils.h"
#include "utils/vpreviewutils.h" #include "utils/vpreviewutils.h"
@ -35,7 +35,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
document()); document());
connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated, 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 // After highlight, the cursor may trun into non-visible. We should make it visible
// in this case. // in this case.
@ -74,7 +74,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
this, &VEdit::vimStatusUpdated); this, &VEdit::vimStatusUpdated);
connect(this, &VMdEdit::cursorPositionChanged, connect(this, &VMdEdit::cursorPositionChanged,
this, &VMdEdit::updateCurHeader); this, &VMdEdit::updateCurrentHeader);
connect(QApplication::clipboard(), &QClipboard::changed, connect(QApplication::clipboard(), &QClipboard::changed,
this, &VMdEdit::handleClipboardChanged); this, &VMdEdit::handleClipboardChanged);
@ -111,7 +111,7 @@ void VMdEdit::beginEdit()
setReadOnly(false); setReadOnly(false);
} }
updateOutline(m_mdHighlighter->getHeaderRegions()); updateHeaders(m_mdHighlighter->getHeaderRegions());
} }
void VMdEdit::endEdit() void VMdEdit::endEdit()
@ -345,43 +345,9 @@ void VMdEdit::clearUnusedImages()
m_initImages.clear(); m_initImages.clear();
} }
int VMdEdit::currentCursorHeader() const void VMdEdit::updateCurrentHeader()
{ {
if (m_headers.isEmpty()) { emit currentHeaderChanged(textCursor().block().blockNumber());
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));
} }
static void addHeaderSequence(QVector<int> &p_sequence, int p_level, int p_baseLevel) static void addHeaderSequence(QVector<int> &p_sequence, int p_level, int p_baseLevel)
@ -448,11 +414,11 @@ static void insertSequenceToHeader(QTextBlock p_block,
} }
} }
void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions) void VMdEdit::updateHeaders(const QVector<VElementRegion> &p_headerRegions)
{ {
QTextDocument *doc = document(); QTextDocument *doc = document();
QVector<VHeader> headers; QVector<VTableOfContentItem> headers;
QVector<int> headerBlockNumbers; QVector<int> headerBlockNumbers;
QVector<QString> headerSequences; QVector<QString> headerSequences;
if (!p_headerRegions.isEmpty()) { if (!p_headerRegions.isEmpty()) {
@ -480,8 +446,10 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
if ((block.userState() == HighlightBlockState::Normal) && if ((block.userState() == HighlightBlockState::Normal) &&
headerReg.exactMatch(block.text())) { headerReg.exactMatch(block.text())) {
int level = headerReg.cap(1).length(); int level = headerReg.cap(1).length();
VHeader header(level, headerReg.cap(2).trimmed(), VTableOfContentItem header(headerReg.cap(2).trimmed(),
"", block.firstLineNumber(), headers.size()); level,
block.blockNumber(),
headers.size());
headers.append(header); headers.append(header);
headerBlockNumbers.append(block.blockNumber()); headerBlockNumbers.append(block.blockNumber());
headerSequences.append(headerReg.cap(3)); headerSequences.append(headerReg.cap(3));
@ -496,7 +464,7 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
m_headers.clear(); m_headers.clear();
bool autoSequence = g_config->getEnableHeadingSequence() && !isReadOnly(); bool autoSequence = m_config.m_enableHeadingSequence && !isReadOnly();
int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel(); int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel();
if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) { if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) {
headingSequenceBaseLevel = 1; headingSequenceBaseLevel = 1;
@ -506,22 +474,25 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
QRegExp preReg(VUtils::c_headerPrefixRegExp); QRegExp preReg(VUtils::c_headerPrefixRegExp);
int curLevel = baseLevel - 1; int curLevel = baseLevel - 1;
for (int i = 0; i < headers.size(); ++i) { for (int i = 0; i < headers.size(); ++i) {
VHeader &item = headers[i]; VTableOfContentItem &item = headers[i];
while (item.level > curLevel + 1) { while (item.m_level > curLevel + 1) {
curLevel += 1; curLevel += 1;
// Insert empty level which is an invalid header. // 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) { if (autoSequence) {
addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel); addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
} }
} }
item.index = m_headers.size(); item.m_index = m_headers.size();
m_headers.append(item); m_headers.append(item);
curLevel = item.level; curLevel = item.m_level;
if (autoSequence) { if (autoSequence) {
addHeaderSequence(seqs, item.level, headingSequenceBaseLevel); addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
QString seqStr = headerSequenceStr(seqs); QString seqStr = headerSequenceStr(seqs);
if (headerSequences[i] != seqStr) { if (headerSequences[i] != seqStr) {
@ -536,25 +507,16 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
emit headersChanged(m_headers); emit headersChanged(m_headers);
updateCurHeader(); updateCurrentHeader();
} }
void VMdEdit::scrollToHeader(const VAnchor &p_anchor) bool VMdEdit::scrollToHeader(int p_blockNumber)
{ {
if (p_anchor.lineNumber == -1 if (p_blockNumber < 0) {
|| p_anchor.m_outlineIndex < 0) { return false;
// 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; return scrollToBlock(p_blockNumber);
} else if (p_anchor.m_outlineIndex >= m_headers.size()) {
return;
}
scrollToLine(p_anchor.lineNumber);
} }
QString VMdEdit::toPlainTextWithoutImg() QString VMdEdit::toPlainTextWithoutImg()
@ -728,9 +690,21 @@ void VMdEdit::resizeEvent(QResizeEvent *p_event)
VEdit::resizeEvent(p_event); VEdit::resizeEvent(p_event);
} }
const QVector<VHeader> &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) 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(); QTextCursor cursor = textCursor();
int cursorLine = cursor.block().firstLineNumber(); int cursorLine = cursor.block().blockNumber();
int targetIdx = -1; int targetIdx = -1;
// -1: skip level check. // -1: skip level check.
int targetLevel = 0; int targetLevel = 0;
int idx = currentCursorHeader(); int idx = indexOfCurrentHeader();
if (idx == -1) { if (idx == -1) {
// Cursor locates at the beginning, before any headers. // Cursor locates at the beginning, before any headers.
if (p_relativeLevel < 0 || !p_forward) { 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; for (targetIdx = idx == -1 ? 0 : idx;
targetIdx >= 0 && targetIdx < m_headers.size(); targetIdx >= 0 && targetIdx < m_headers.size();
targetIdx += delta) { targetIdx += delta) {
const VHeader &header = m_headers[targetIdx]; const VTableOfContentItem &header = m_headers[targetIdx];
if (header.isEmpty()) { if (header.isEmpty()) {
continue; continue;
} }
@ -769,7 +743,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
if (targetLevel == 0) { if (targetLevel == 0) {
// The target level has not been init yet. // The target level has not been init yet.
Q_ASSERT(firstHeader); Q_ASSERT(firstHeader);
targetLevel = header.level; targetLevel = header.m_level;
if (p_relativeLevel < 0) { if (p_relativeLevel < 0) {
targetLevel += p_relativeLevel; targetLevel += p_relativeLevel;
if (targetLevel < 1) { 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 if (firstHeader
&& (cursorLine == header.lineNumber && (cursorLine == header.m_blockNumber
|| p_forward) || p_forward)
&& idx != -1) { && idx != -1) {
// This header is not counted for the repeat. // 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. // Found.
break; break;
} }
} else if (header.level < targetLevel) { } else if (header.m_level < targetLevel) {
// Stop by higher level. // Stop by higher level.
return false; return false;
} }
@ -808,9 +782,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
} }
// Jump to target header. // Jump to target header.
int line = m_headers[targetIdx].lineNumber; int line = m_headers[targetIdx].m_blockNumber;
if (line > -1) { if (line > -1) {
QTextBlock block = document()->findBlockByLineNumber(line); QTextBlock block = document()->findBlockByNumber(line);
if (block.isValid()) { if (block.isValid()) {
cursor.setPosition(block.position()); cursor.setPosition(block.position());
setTextCursor(cursor); setTextCursor(cursor);
@ -837,6 +811,8 @@ void VMdEdit::finishOneAsyncJob(int p_idx)
m_freshEdit = false; m_freshEdit = false;
emit statusChanged(); emit statusChanged();
updateOutline(m_mdHighlighter->getHeaderRegions()); updateHeaders(m_mdHighlighter->getHeaderRegions());
emit ready();
} }
} }

View File

@ -7,7 +7,7 @@
#include <QColor> #include <QColor>
#include <QClipboard> #include <QClipboard>
#include <QImage> #include <QImage>
#include "vtoc.h" #include "vtableofcontent.h"
#include "veditoperations.h" #include "veditoperations.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"
@ -32,31 +32,34 @@ public:
// @p_path is the absolute path of the inserted image. // @p_path is the absolute path of the inserted image.
void imageInserted(const QString &p_path); 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. // Like toPlainText(), but remove image preview characters.
QString toPlainTextWithoutImg(); QString toPlainTextWithoutImg();
const QVector<VHeader> &getHeaders() const;
public slots: public slots:
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE; bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
signals: signals:
void headersChanged(const QVector<VHeader> &headers); // Signal when headers change.
void headersChanged(const QVector<VTableOfContentItem> &p_headers);
// Signal when current header change. // Signal when current header change.
void curHeaderChanged(VAnchor p_anchor); void currentHeaderChanged(int p_blockNumber);
// Signal when the status of VMdEdit changed. // Signal when the status of VMdEdit changed.
// Will be emitted by VImagePreviewer for now. // Will be emitted by VImagePreviewer for now.
void statusChanged(); void statusChanged();
private slots: private slots:
void updateOutline(const QVector<VElementRegion> &p_headerRegions); // Update m_headers according to elements.
void updateHeaders(const QVector<VElementRegion> &p_headerRegions);
// Update current header according to cursor position.
// When there is no header in current cursor, will signal an invalid header. // When there is no header in current cursor, will signal an invalid header.
void updateCurHeader(); void updateCurrentHeader();
void handleClipboardChanged(QClipboard::Mode p_mode); void handleClipboardChanged(QClipboard::Mode p_mode);
@ -95,9 +98,6 @@ private:
// in the selection. Get the QImage. // in the selection. Get the QImage.
QImage tryGetSelectedImage(); QImage tryGetSelectedImage();
// Return the header index in m_headers where current cursor locates.
int currentCursorHeader() const;
QString getPlainTextWithoutPreviewImage() const; QString getPlainTextWithoutPreviewImage() const;
// Try to get all the regions of preview image within @p_block. // Try to get all the regions of preview image within @p_block.
@ -107,6 +107,9 @@ private:
void finishOneAsyncJob(int p_idx); void finishOneAsyncJob(int p_idx);
// Index in m_headers of current header which contains the cursor.
int indexOfCurrentHeader() const;
HGMarkdownHighlighter *m_mdHighlighter; HGMarkdownHighlighter *m_mdHighlighter;
VCodeBlockHighlightHelper *m_cbHighlighter; VCodeBlockHighlightHelper *m_cbHighlighter;
VImagePreviewer *m_imagePreviewer; VImagePreviewer *m_imagePreviewer;
@ -117,7 +120,8 @@ private:
// Image links right at the beginning of the edit. // Image links right at the beginning of the edit.
QVector<ImageLink> m_initImages; QVector<ImageLink> m_initImages;
QVector<VHeader> m_headers; // Mainly used for title jump.
QVector<VTableOfContentItem> m_headers;
bool m_freshEdit; bool m_freshEdit;

View File

@ -26,7 +26,7 @@
extern VConfigManager *g_config; extern VConfigManager *g_config;
const QString VMdEditOperations::c_defaultImageTitle = "image"; const QString VMdEditOperations::c_defaultImageTitle = "";
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file) VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
: VEditOperations(p_editor, p_file), m_autoIndentPos(-1) : VEditOperations(p_editor, p_file), m_autoIndentPos(-1)
@ -278,6 +278,28 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
break; 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: case Qt::Key_O:
{ {
if (modifiers == Qt::ControlModifier) { if (modifiers == Qt::ControlModifier) {
@ -626,33 +648,25 @@ void VMdEditOperations::changeListBlockSeqNumber(QTextBlock &p_block, int p_seq)
bool VMdEditOperations::insertTitle(int p_level) bool VMdEditOperations::insertTitle(int p_level)
{ {
Q_ASSERT(p_level > 0 && p_level < 7);
QTextDocument *doc = m_editor->document(); QTextDocument *doc = m_editor->document();
QString titleMark(p_level, '#');
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
int firstBlock = cursor.block().blockNumber();
int lastBlock = firstBlock;
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
// Insert title # in front of the selected lines. // Insert title # in front of the selected blocks.
int start = cursor.selectionStart(); int start = cursor.selectionStart();
int end = cursor.selectionEnd(); int end = cursor.selectionEnd();
int startBlock = doc->findBlock(start).blockNumber(); firstBlock = doc->findBlock(start).blockNumber();
int endBlock = doc->findBlock(end).blockNumber(); lastBlock = 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.beginEditBlock();
cursor.movePosition(QTextCursor::StartOfBlock); for (int i = firstBlock; i <= lastBlock; ++i) {
cursor.insertText(titleMark + " "); VEditUtils::insertTitleMark(cursor, doc->findBlockByNumber(i), p_level);
cursor.movePosition(QTextCursor::EndOfBlock);
cursor.endEditBlock();
} }
cursor.endEditBlock();
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
return true; return true;
} }
@ -681,6 +695,10 @@ void VMdEditOperations::decorateText(TextDecoration p_decoration)
decorateInlineCode(); decorateInlineCode();
break; break;
case TextDecoration::CodeBlock:
decorateCodeBlock();
break;
default: default:
validDecoration = false; validDecoration = false;
qDebug() << "decoration" << (int)p_decoration << "is not implemented yet"; qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
@ -804,6 +822,86 @@ void VMdEditOperations::decorateInlineCode()
m_editor->setTextCursor(cursor); 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() void VMdEditOperations::decorateStrikethrough()
{ {
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
@ -840,3 +938,16 @@ void VMdEditOperations::decorateStrikethrough()
cursor.endEditBlock(); cursor.endEditBlock();
m_editor->setTextCursor(cursor); 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;
}

View File

@ -16,11 +16,18 @@ class VMdEditOperations : public VEditOperations
Q_OBJECT Q_OBJECT
public: public:
VMdEditOperations(VEdit *p_editor, VFile *p_file); VMdEditOperations(VEdit *p_editor, VFile *p_file);
bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE; bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
bool insertImage() Q_DECL_OVERRIDE; bool insertImage() Q_DECL_OVERRIDE;
bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
bool insertImageFromURL(const QUrl &p_imageUrl) 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. // Insert decoration markers or decorate selected text.
// If it is Vim Normal mode, change to Insert mode first. // If it is Vim Normal mode, change to Insert mode first.
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE; void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
@ -47,6 +54,10 @@ private:
bool handleKeyEsc(QKeyEvent *p_event); bool handleKeyEsc(QKeyEvent *p_event);
bool handleKeyReturn(QKeyEvent *p_event); bool handleKeyReturn(QKeyEvent *p_event);
bool handleKeyBracketLeft(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); bool insertTitle(int p_level);
// Change the sequence number of a list block. // Change the sequence number of a list block.
@ -61,6 +72,9 @@ private:
// Insert inline-code marker or set selected text inline-coded. // Insert inline-code marker or set selected text inline-coded.
void decorateInlineCode(); void decorateInlineCode();
// Insert inline-code marker or set selected text inline-coded.
void decorateCodeBlock();
// Insert strikethrough marker or set selected text strikethrough. // Insert strikethrough marker or set selected text strikethrough.
void decorateStrikethrough(); void decorateStrikethrough();

View File

@ -11,7 +11,7 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vmarkdownconverter.h" #include "vmarkdownconverter.h"
#include "vnotebook.h" #include "vnotebook.h"
#include "vtoc.h" #include "vtableofcontent.h"
#include "vmdedit.h" #include "vmdedit.h"
#include "dialog/vfindreplacedialog.h" #include "dialog/vfindreplacedialog.h"
#include "veditarea.h" #include "veditarea.h"
@ -22,13 +22,25 @@ extern VConfigManager *g_config;
VMdTab::VMdTab(VFile *p_file, VEditArea *p_editArea, VMdTab::VMdTab(VFile *p_file, VEditArea *p_editArea,
OpenFileMode p_mode, QWidget *p_parent) OpenFileMode p_mode, QWidget *p_parent)
: VEditTab(p_file, p_editArea, p_parent), m_editor(NULL), m_webViewer(NULL), : VEditTab(p_file, p_editArea, p_parent),
m_document(NULL), m_mdConType(g_config->getMdConverterType()) m_editor(NULL),
m_webViewer(NULL),
m_document(NULL),
m_mdConType(g_config->getMdConverterType()),
m_enableHeadingSequence(false)
{ {
V_ASSERT(m_file->getDocType() == DocType::Markdown); V_ASSERT(m_file->getDocType() == DocType::Markdown);
m_file->open(); 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(); setupUI();
if (p_mode == OpenFileMode::Edit) { if (p_mode == OpenFileMode::Edit) {
@ -50,54 +62,110 @@ void VMdTab::setupUI()
setLayout(m_stacks); setLayout(m_stacks);
} }
void VMdTab::handleTextChanged()
{
V_ASSERT(m_file->isModifiable());
if (m_modified) {
return;
}
updateStatus();
}
void VMdTab::showFileReadMode() void VMdTab::showFileReadMode()
{ {
m_isEditMode = false; m_isEditMode = false;
int outlineIndex = m_curHeader.m_outlineIndex; VHeaderPointer header(m_currentHeader);
if (m_mdConType == MarkdownConverterType::Hoedown) { if (m_mdConType == MarkdownConverterType::Hoedown) {
viewWebByConverter(); viewWebByConverter();
} else { } else {
m_document->updateText(); m_document->updateText();
updateTocFromHtml(m_document->getToc()); updateOutlineFromHtml(m_document->getToc());
} }
m_stacks->setCurrentWidget(m_webViewer); m_stacks->setCurrentWidget(m_webViewer);
clearSearchedWordHighlight(); clearSearchedWordHighlight();
scrollWebViewToHeader(outlineIndex); scrollWebViewToHeader(header);
updateStatus(); updateStatus();
} }
void VMdTab::scrollWebViewToHeader(int p_outlineIndex) bool VMdTab::scrollWebViewToHeader(const VHeaderPointer &p_header)
{ {
QString anchor; if (!m_outline.isMatched(p_header)
|| m_outline.getType() != VTableOfContentType::Anchor) {
m_curHeader = VAnchor(m_file, anchor, -1, p_outlineIndex); return false;
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);
} }
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<VMdEdit *>(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() void VMdTab::viewWebByConverter()
@ -108,7 +176,7 @@ void VMdTab::viewWebByConverter()
g_config->getMarkdownExtensions(), g_config->getMarkdownExtensions(),
toc); toc);
m_document->setHtml(html); m_document->setHtml(html);
updateTocFromHtml(toc); updateOutlineFromHtml(toc);
} }
void VMdTab::showFileEditMode() void VMdTab::showFileEditMode()
@ -117,42 +185,28 @@ void VMdTab::showFileEditMode()
return; return;
} }
VHeaderPointer header(m_currentHeader);
m_isEditMode = true; m_isEditMode = true;
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor()); VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor());
V_ASSERT(mdEdit); V_ASSERT(mdEdit);
// beginEdit() may change m_curHeader.
int outlineIndex = m_curHeader.m_outlineIndex;
mdEdit->beginEdit(); mdEdit->beginEdit();
m_stacks->setCurrentWidget(mdEdit); m_stacks->setCurrentWidget(mdEdit);
int lineNumber = -1;
const QVector<VHeader> &headers = mdEdit->getHeaders();
// If editor is not init, we need to wait for it to init headers. // If editor is not init, we need to wait for it to init headers.
// Generally, beginEdit() will generate the headers. Wait is needed when // Generally, beginEdit() will generate the headers. Wait is needed when
// highlight completion is going to re-generate the headers. // highlight completion is going to re-generate the headers.
int nrRetry = 5; 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"; qDebug() << "wait another 100 ms for editor's headers ready";
VUtils::sleepWait(100); VUtils::sleepWait(100);
} }
if (outlineIndex < 0 || outlineIndex >= headers.size()) { scrollEditorToHeader(header);
lineNumber = -1;
outlineIndex = -1;
} else {
lineNumber = headers[outlineIndex].lineNumber;
}
VAnchor anchor(m_file, "", lineNumber, outlineIndex);
mdEdit->scrollToHeader(anchor);
mdEdit->setFocus(); mdEdit->setFocus();
updateStatus();
} }
bool VMdTab::closeFile(bool p_forced) bool VMdTab::closeFile(bool p_forced)
@ -232,7 +286,7 @@ bool VMdTab::saveFile()
return true; return true;
} }
bool ret; bool ret = true;
// Make sure the file already exists. Temporary deal with cases when user delete or move // Make sure the file already exists. Temporary deal with cases when user delete or move
// a file. // a file.
QString filePath = m_file->fetchPath(); QString filePath = m_file->fetchPath();
@ -242,9 +296,8 @@ bool VMdTab::saveFile()
tr("File <span style=\"%1\">%2</span> being written has been removed.") tr("File <span style=\"%1\">%2</span> being written has been removed.")
.arg(g_config->c_dataTextStyle).arg(filePath), .arg(g_config->c_dataTextStyle).arg(filePath),
QMessageBox::Ok, QMessageBox::Ok, this); QMessageBox::Ok, QMessageBox::Ok, this);
return false; ret = false;
} } else {
m_editor->saveFile(); m_editor->saveFile();
ret = m_file->save(); ret = m_file->save();
if (!ret) { if (!ret) {
@ -253,6 +306,7 @@ bool VMdTab::saveFile()
QMessageBox::Ok, QMessageBox::Ok, this); QMessageBox::Ok, QMessageBox::Ok, this);
m_editor->setModified(true); m_editor->setModified(true);
} }
}
updateStatus(); updateStatus();
@ -285,11 +339,13 @@ void VMdTab::setupMarkdownViewer()
QWebChannel *channel = new QWebChannel(m_webViewer); QWebChannel *channel = new QWebChannel(m_webViewer);
channel->registerObject(QStringLiteral("content"), m_document); channel->registerObject(QStringLiteral("content"), m_document);
connect(m_document, &VDocument::tocChanged, connect(m_document, &VDocument::tocChanged,
this, &VMdTab::updateTocFromHtml); this, &VMdTab::updateOutlineFromHtml);
connect(m_document, SIGNAL(headerChanged(const QString &)), connect(m_document, SIGNAL(headerChanged(const QString &)),
this, SLOT(updateCurHeader(const QString &))); this, SLOT(updateCurrentHeader(const QString &)));
connect(m_document, &VDocument::keyPressed, connect(m_document, &VDocument::keyPressed,
this, &VMdTab::handleWebKeyPressed); this, &VMdTab::handleWebKeyPressed);
connect(m_document, SIGNAL(logicsFinished(void)),
this, SLOT(restoreFromTabInfo(void)));
page->setWebChannel(channel); page->setWebChannel(channel);
m_webViewer->setHtml(VUtils::generateHtmlTemplate(m_mdConType, false), m_webViewer->setHtml(VUtils::generateHtmlTemplate(m_mdConType, false),
@ -302,15 +358,16 @@ void VMdTab::setupMarkdownEditor()
{ {
Q_ASSERT(m_file->isModifiable() && !m_editor); Q_ASSERT(m_file->isModifiable() && !m_editor);
qDebug() << "create Markdown editor"; qDebug() << "create Markdown editor";
m_editor = new VMdEdit(m_file, m_document, m_mdConType, this); m_editor = new VMdEdit(m_file, m_document, m_mdConType, this);
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::headersChanged, connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::headersChanged,
this, &VMdTab::updateTocFromHeaders); this, &VMdTab::updateOutlineFromHeaders);
connect(dynamic_cast<VMdEdit *>(m_editor), SIGNAL(currentHeaderChanged(int)),
this, SLOT(updateCurrentHeader(int)));
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::statusChanged, connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::statusChanged,
this, &VMdTab::updateStatus); this, &VMdTab::updateStatus);
connect(m_editor, SIGNAL(curHeaderChanged(VAnchor)),
this, SLOT(updateCurHeader(VAnchor)));
connect(m_editor, &VEdit::textChanged, connect(m_editor, &VEdit::textChanged,
this, &VMdTab::handleTextChanged); this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::cursorPositionChanged, connect(m_editor, &VEdit::cursorPositionChanged,
this, &VMdTab::updateStatus); this, &VMdTab::updateStatus);
connect(m_editor, &VEdit::saveAndRead, connect(m_editor, &VEdit::saveAndRead,
@ -327,190 +384,79 @@ void VMdTab::setupMarkdownEditor()
this, [this]() { this, [this]() {
this->m_editArea->getFindReplaceDialog()->closeDialog(); this->m_editArea->getFindReplaceDialog()->closeDialog();
}); });
connect(m_editor, SIGNAL(ready(void)),
this, SLOT(restoreFromTabInfo(void)));
enableHeadingSequence(m_enableHeadingSequence);
m_editor->reloadFile(); m_editor->reloadFile();
m_stacks->addWidget(m_editor); m_stacks->addWidget(m_editor);
} }
static void parseTocUl(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers, void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml)
int p_level);
static void parseTocLi(QXmlStreamReader &p_xml, QVector<VHeader> &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 <a> should be ended by </a>" << 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 <li> should contain <a> or <ul>" << 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<VHeader> &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 <ul> should contain <li>" << p_xml.name();
break;
}
}
}
static bool parseTocHtml(const QString &p_tocHtml,
QVector<VHeader> &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 <ul>";
}
}
if (xml.hasError()) {
qWarning() << "fail to parse TOC in HTML";
return false;
}
}
return true;
}
void VMdTab::updateTocFromHtml(const QString &p_tocHtml)
{ {
if (m_isEditMode) { if (m_isEditMode) {
return; return;
} }
m_toc.type = VHeaderType::Anchor; m_outline.clear();
m_toc.headers.clear();
if (!parseTocHtml(p_tocHtml, m_toc.headers)) { if (m_outline.parseTableFromHtml(p_tocHtml)) {
return; m_outline.setFile(m_file);
m_outline.setType(VTableOfContentType::Anchor);
} }
m_toc.m_file = m_file; m_currentHeader.reset();
m_toc.valid = true;
emit outlineChanged(m_toc); emit outlineChanged(m_outline);
} }
void VMdTab::updateTocFromHeaders(const QVector<VHeader> &p_headers) void VMdTab::updateOutlineFromHeaders(const QVector<VTableOfContentItem> &p_headers)
{ {
if (!m_isEditMode) { if (!m_isEditMode) {
return; return;
} }
m_toc.type = VHeaderType::LineNumber; m_outline.update(m_file,
m_toc.headers = p_headers; p_headers,
m_toc.m_file = m_file; VTableOfContentType::BlockNumber);
m_toc.valid = true;
// Clear current header. m_currentHeader.reset();
m_curHeader = VAnchor(m_file, "", -1, -1);
emit curHeaderChanged(m_curHeader);
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)) {
return; // Scroll only when @p_header is valid.
} scrollToHeaderInternal(p_header);
m_curHeader = p_anchor;
if (m_isEditMode) {
dynamic_cast<VMdEdit *>(getEditor())->scrollToHeader(p_anchor);
} else {
if (!p_anchor.anchor.isEmpty()) {
m_document->scrollToAnchor(p_anchor.anchor.mid(1));
}
} }
} }
void VMdTab::updateCurHeader(const QString &p_anchor) void VMdTab::updateCurrentHeader(const QString &p_anchor)
{
if (m_isEditMode || m_curHeader.anchor.mid(1) == p_anchor) {
return;
}
m_curHeader = VAnchor(m_file, "#" + p_anchor, -1);
if (!p_anchor.isEmpty()) {
const QVector<VHeader> &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;
}
}
}
emit curHeaderChanged(m_curHeader);
}
void VMdTab::updateCurHeader(VAnchor p_anchor)
{ {
if (m_isEditMode) { if (m_isEditMode) {
if (!p_anchor.anchor.isEmpty() || p_anchor.lineNumber == m_curHeader.lineNumber) {
return; return;
} }
} else {
if (p_anchor.lineNumber != -1 || p_anchor.anchor == m_curHeader.anchor) { // Find the index of the anchor in outline.
int idx = m_outline.indexOfItemByAnchor(p_anchor);
m_currentHeader.update(m_file, idx);
emit currentHeaderChanged(m_currentHeader);
}
void VMdTab::updateCurrentHeader(int p_blockNumber)
{
if (!m_isEditMode) {
return; return;
} }
}
m_curHeader = p_anchor; // 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); emit currentHeaderChanged(m_currentHeader);
} }
void VMdTab::insertImage() void VMdTab::insertImage()
@ -523,6 +469,16 @@ void VMdTab::insertImage()
m_editor->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, void VMdTab::findText(const QString &p_text, uint p_options, bool p_peek,
bool p_forward) 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) { if (m_editor) {
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();
@ -693,6 +649,8 @@ VEditTabInfo VMdTab::createEditTabInfo()
info.m_blockCount = m_editor->document()->blockCount(); info.m_blockCount = m_editor->document()->blockCount();
} }
info.m_headerIndex = m_currentHeader.m_index;
return info; return info;
} }
@ -702,3 +660,45 @@ void VMdTab::decorateText(TextDecoration p_decoration)
m_editor->decorateText(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();
}
}

View File

@ -31,11 +31,13 @@ public:
// Save file. // Save file.
bool saveFile() Q_DECL_OVERRIDE; bool saveFile() Q_DECL_OVERRIDE;
// Scroll to anchor @p_anchor. // Scroll to @p_header.
void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE; void scrollToHeader(const VHeaderPointer &p_header) Q_DECL_OVERRIDE;
void insertImage() Q_DECL_OVERRIDE; void insertImage() Q_DECL_OVERRIDE;
void insertLink() Q_DECL_OVERRIDE;
// Search @p_text in current note. // Search @p_text in current note.
void findText(const QString &p_text, uint p_options, bool p_peek, void findText(const QString &p_text, uint p_options, bool p_peek,
bool p_forward = true) Q_DECL_OVERRIDE; bool p_forward = true) Q_DECL_OVERRIDE;
@ -62,25 +64,34 @@ public:
// Insert decoration markers or decorate selected text. // Insert decoration markers or decorate selected text.
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE; 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: public slots:
// Enter edit mode. // Enter edit mode.
void editFile() Q_DECL_OVERRIDE; void editFile() Q_DECL_OVERRIDE;
private slots: private slots:
// Handle text changed in m_editor. // Update m_outline according to @p_tocHtml for read mode.
void handleTextChanged(); void updateOutlineFromHtml(const QString &p_tocHtml);
// Update m_toc according to @p_tocHtml for read mode. // Update m_outline accroding to @p_headers for edit mode.
void updateTocFromHtml(const QString &p_tocHtml); void updateOutlineFromHeaders(const QVector<VTableOfContentItem> &p_headers);
// Update m_toc accroding to @p_headers for edit mode.
void updateTocFromHeaders(const QVector<VHeader> &p_headers);
// Web viewer requests to update current header. // 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. // Editor requests to update current header.
void updateCurHeader(VAnchor p_anchor); void updateCurrentHeader(int p_blockNumber);
// Handle key press event in Web view. // Handle key press event in Web view.
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift); 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. // m_editor requests to discard changes and enter read mode.
void discardAndRead(); void discardAndRead();
// Restore from m_infoToRestore.
void restoreFromTabInfo();
private: private:
// Setup UI. // Setup UI.
void setupUI(); void setupUI();
@ -111,8 +125,14 @@ private:
void viewWebByConverter(); void viewWebByConverter();
// Scroll Web view to given header. // Scroll Web view to given header.
// @p_outlineIndex is the index in m_toc.headers. // Return true if scroll was made.
void scrollWebViewToHeader(int p_outlineIndex); 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. // Search text in Web view.
void findTextInWebView(const QString &p_text, uint p_options, bool p_peek, void findTextInWebView(const QString &p_text, uint p_options, bool p_peek,
@ -127,17 +147,21 @@ private:
// Focus the proper child widget. // Focus the proper child widget.
void focusChild() Q_DECL_OVERRIDE; 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. // Get the markdown editor. If not init yet, init and return it.
VEdit *getEditor(); VEdit *getEditor();
// Restore from @p_fino.
// Return true if succeed.
bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
VEdit *m_editor; VEdit *m_editor;
VWebView *m_webViewer; VWebView *m_webViewer;
VDocument *m_document; VDocument *m_document;
MarkdownConverterType m_mdConType; MarkdownConverterType m_mdConType;
// Whether heading sequence is enabled.
bool m_enableHeadingSequence;
QStackedLayout *m_stacks; QStackedLayout *m_stacks;
}; };

View File

@ -16,6 +16,10 @@
extern VConfigManager *g_config; extern VConfigManager *g_config;
// Meta word manager.
VMetaWordManager *g_mwMgr;
QString VNote::s_markdownTemplate; QString VNote::s_markdownTemplate;
QString VNote::s_markdownTemplatePDF; 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"; const QString VNote::c_markdownGuideDocFile_zh = ":/resources/docs/markdown_guide_zh.md";
VNote::VNote(QObject *parent) VNote::VNote(QObject *parent)
: QObject(parent), m_mainWindow(dynamic_cast<VMainWindow *>(parent)) : QObject(parent)
{ {
initTemplate(); initTemplate();
g_config->getNotebooks(m_notebooks, this); g_config->getNotebooks(m_notebooks, this);
m_metaWordMgr.init();
g_mwMgr = &m_metaWordMgr;
} }
void VNote::initPalette(QPalette palette) void VNote::initPalette(QPalette palette)
@ -330,6 +339,16 @@ VNoteFile *VNote::getInternalFile(const QString &p_path)
return file; 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 *VNote::getInternalDirectory(const QString &p_path)
{ {
VDirectory *dir = NULL; VDirectory *dir = NULL;

View File

@ -12,11 +12,12 @@
#include <QPalette> #include <QPalette>
#include "vnotebook.h" #include "vnotebook.h"
#include "vconstants.h" #include "vconstants.h"
#include "utils/vmetawordmanager.h"
class VMainWindow;
class VOrphanFile; class VOrphanFile;
class VNoteFile; class VNoteFile;
class VNote : public QObject class VNote : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -74,10 +75,13 @@ public:
const QVector<QPair<QString, QString> > &getPalette() const; const QVector<QPair<QString, QString> > &getPalette() const;
void initPalette(QPalette palette); void initPalette(QPalette palette);
QString getColorFromPalette(const QString &p_name) const; QString getColorFromPalette(const QString &p_name) const;
VMainWindow *getMainWindow() const;
QString getNavigationLabelStyle(const QString &p_str) 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. // Given the path of an external file, create a VOrphanFile struct.
VOrphanFile *getOrphanFile(const QString &p_path, VOrphanFile *getOrphanFile(const QString &p_path,
bool p_modifiable, bool p_modifiable,
@ -102,7 +106,8 @@ private:
// Maintain all the notebooks. Other holder should use QPointer. // Maintain all the notebooks. Other holder should use QPointer.
QVector<VNotebook *> m_notebooks; QVector<VNotebook *> m_notebooks;
QVector<QPair<QString, QString> > m_palette; QVector<QPair<QString, QString> > m_palette;
VMainWindow *m_mainWindow;
VMetaWordManager m_metaWordMgr;
// Hold all external file: Orphan File. // Hold all external file: Orphan File.
// Need to clean up periodly. // Need to clean up periodly.
@ -114,9 +119,4 @@ inline const QVector<QPair<QString, QString> >& VNote::getPalette() const
return m_palette; return m_palette;
} }
inline VMainWindow *VNote::getMainWindow() const
{
return m_mainWindow;
}
#endif // VNOTE_H #endif // VNOTE_H

View File

@ -130,5 +130,9 @@
<file>resources/icons/delete_attachment.svg</file> <file>resources/icons/delete_attachment.svg</file>
<file>resources/icons/sort.svg</file> <file>resources/icons/sort.svg</file>
<file>resources/icons/create_subdir.svg</file> <file>resources/icons/create_subdir.svg</file>
<file>resources/icons/compact_mode.svg</file>
<file>resources/icons/heading_sequence.svg</file>
<file>resources/icons/link.svg</file>
<file>resources/icons/code_block.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -9,7 +9,7 @@
extern VConfigManager *g_config; extern VConfigManager *g_config;
VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent) 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_path = QDir::cleanPath(path);
m_recycleBinFolder = g_config->getRecycleBinFolder(); m_recycleBinFolder = g_config->getRecycleBinFolder();
@ -29,6 +29,7 @@ bool VNotebook::readConfigNotebook()
QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path); QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path);
if (configJson.isEmpty()) { if (configJson.isEmpty()) {
qWarning() << "fail to read notebook configuration" << m_path; qWarning() << "fail to read notebook configuration" << m_path;
m_valid = false;
return false; return false;
} }
@ -58,6 +59,7 @@ bool VNotebook::readConfigNotebook()
writeConfigNotebook(); writeConfigNotebook();
} }
m_valid = true;
return true; return true;
} }

View File

@ -93,6 +93,8 @@ public:
// Need to check if this notebook has been opened. // Need to check if this notebook has been opened.
QDateTime getCreatedTimeUtc(); QDateTime getCreatedTimeUtc();
bool isValid() const;
private: private:
// Serialize current instance to json. // Serialize current instance to json.
QJsonObject toConfigJson() const; QJsonObject toConfigJson() const;
@ -118,6 +120,10 @@ private:
// Parent is NULL for root directory // Parent is NULL for root directory
VDirectory *m_rootDir; VDirectory *m_rootDir;
// Whether this notebook is valid.
// Will set to true after readConfigNotebook().
bool m_valid;
}; };
inline VDirectory *VNotebook::getRootDir() const inline VDirectory *VNotebook::getRootDir() const
@ -130,4 +136,9 @@ inline const QString &VNotebook::getRecycleBinFolder() const
return m_recycleBinFolder; return m_recycleBinFolder;
} }
inline bool VNotebook::isValid() const
{
return m_valid;
}
#endif // VNOTEBOOK_H #endif // VNOTEBOOK_H

View File

@ -20,22 +20,27 @@
#include "vnote.h" #include "vnote.h"
#include "veditarea.h" #include "veditarea.h"
#include "vnofocusitemdelegate.h" #include "vnofocusitemdelegate.h"
#include "vmainwindow.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VNote *g_vnote; extern VNote *g_vnote;
const int VNotebookSelector::c_notebookStartIdx = 1; extern VMainWindow *g_mainWin;
VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent) VNotebookSelector::VNotebookSelector(QWidget *p_parent)
: QComboBox(p_parent), VNavigationMode(), : QComboBox(p_parent),
m_vnote(vnote), m_notebooks(m_vnote->getNotebooks()), VNavigationMode(),
m_editArea(NULL), m_lastValidIndex(-1), m_naviLabel(NULL) m_notebooks(g_vnote->getNotebooks()),
m_lastValidIndex(-1),
m_muted(false),
m_naviLabel(NULL)
{ {
m_listWidget = new QListWidget(this); m_listWidget = new QListWidget(this);
m_listWidget->setItemDelegate(new VNoFocusItemDelegate(this)); m_listWidget->setItemDelegate(new VNoFocusItemDelegate(this));
m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu); m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_listWidget, &QListWidget::customContextMenuRequested, connect(m_listWidget, &QListWidget::customContextMenuRequested,
this, &VNotebookSelector::requestPopupListContextMenu); this, &VNotebookSelector::popupListContextMenuRequested);
setModel(m_listWidget->model()); setModel(m_listWidget->model());
setView(m_listWidget); setView(m_listWidget);
@ -47,8 +52,6 @@ VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent)
connect(this, SIGNAL(currentIndexChanged(int)), connect(this, SIGNAL(currentIndexChanged(int)),
this, SLOT(handleCurIndexChanged(int))); this, SLOT(handleCurIndexChanged(int)));
connect(this, SIGNAL(activated(int)),
this, SLOT(handleItemActivated(int)));
} }
void VNotebookSelector::initActions() void VNotebookSelector::initActions()
@ -75,9 +78,7 @@ void VNotebookSelector::initActions()
} }
Q_ASSERT(items.size() == 1); Q_ASSERT(items.size() == 1);
QListWidgetItem *item = items[0]; VNotebook *notebook = getNotebook(items[0]);
int index = this->indexOfListItem(item);
VNotebook *notebook = this->getNotebookFromComboIndex(index);
QUrl url = QUrl::fromLocalFile(notebook->getPath()); QUrl url = QUrl::fromLocalFile(notebook->getPath());
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
}); });
@ -93,9 +94,7 @@ void VNotebookSelector::initActions()
} }
Q_ASSERT(items.size() == 1); Q_ASSERT(items.size() == 1);
QListWidgetItem *item = items[0]; VNotebook *notebook = getNotebook(items[0]);
int index = this->indexOfListItem(item);
VNotebook *notebook = this->getNotebookFromComboIndex(index);
QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath()); QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
}); });
@ -111,9 +110,7 @@ void VNotebookSelector::initActions()
} }
Q_ASSERT(items.size() == 1); Q_ASSERT(items.size() == 1);
QListWidgetItem *item = items[0]; VNotebook *notebook = getNotebook(items[0]);
int index = this->indexOfListItem(item);
VNotebook *notebook = this->getNotebookFromComboIndex(index);
QString binPath = notebook->getRecycleBinFolderPath(); QString binPath = notebook->getRecycleBinFolderPath();
int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"), int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
@ -129,7 +126,9 @@ void VNotebookSelector::initActions()
.arg(g_config->c_dataTextStyle) .arg(g_config->c_dataTextStyle)
.arg(binPath), .arg(binPath),
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok, this, MessageBoxType::Danger); QMessageBox::Ok,
this,
MessageBoxType::Danger);
if (ret == QMessageBox::Ok) { if (ret == QMessageBox::Ok) {
QString info; QString info;
if (VUtils::emptyDirectory(notebook, binPath, true)) { if (VUtils::emptyDirectory(notebook, binPath, true)) {
@ -157,37 +156,60 @@ void VNotebookSelector::initActions()
void VNotebookSelector::updateComboBox() void VNotebookSelector::updateComboBox()
{ {
m_muted = true;
int index = g_config->getCurNotebookIndex(); int index = g_config->getCurNotebookIndex();
disconnect(this, SIGNAL(currentIndexChanged(int)),
this, SLOT(handleCurIndexChanged(int)));
clear(); clear();
m_listWidget->clear(); m_listWidget->clear();
insertAddNotebookItem(); insertAddNotebookItem();
for (int i = 0; i < m_notebooks.size(); ++i) { for (int i = 0; i < m_notebooks.size(); ++i) {
addNotebookItem(m_notebooks[i]->getName()); addNotebookItem(m_notebooks[i]);
} }
setCurrentIndex(-1); setCurrentIndex(-1);
connect(this, SIGNAL(currentIndexChanged(int)),
this, SLOT(handleCurIndexChanged(int))); m_muted = false;
if (m_notebooks.isEmpty()) { if (m_notebooks.isEmpty()) {
g_config->setCurNotebookIndex(-1); g_config->setCurNotebookIndex(-1);
setCurrentIndex(0); setCurrentIndex(0);
} else { } 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; 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) { setCurrentIndex(itemIndexOfNotebook(p_notebook));
p_index += c_notebookStartIdx;
} }
setCurrentIndex(p_index);
int VNotebookSelector::itemIndexOfNotebook(const VNotebook *p_notebook) const
{
if (!p_notebook) {
return -1;
}
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() void VNotebookSelector::insertAddNotebookItem()
@ -199,49 +221,51 @@ void VNotebookSelector::insertAddNotebookItem()
font.setItalic(true); font.setItalic(true);
item->setData(Qt::FontRole, font); item->setData(Qt::FontRole, font);
item->setToolTip(tr("Create or import a notebook")); item->setToolTip(tr("Create or import a notebook"));
m_listWidget->insertItem(0, item); m_listWidget->insertItem(0, item);
} }
void VNotebookSelector::handleCurIndexChanged(int p_index) void VNotebookSelector::handleCurIndexChanged(int p_index)
{ {
qDebug() << "current index changed" << p_index << "startIdx" << c_notebookStartIdx; if (m_muted) {
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; 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);
}
void VNotebookSelector::handleItemActivated(int p_index) QString tooltip = tr("View and edit notebooks");
{ VNotebook *nb = NULL;
if (p_index > -1 && p_index < c_notebookStartIdx) { if (p_index > -1) {
// Click a special action item nb = getNotebook(p_index);
if (m_lastValidIndex > -1) { if (!nb) {
// Add notebook.
setToolTip(tooltip);
if (m_lastValidIndex != p_index && m_lastValidIndex > -1) {
setCurrentIndex(m_lastValidIndex); setCurrentIndex(m_lastValidIndex);
} }
if (!m_notebooks.isEmpty()) {
newNotebook(); newNotebook();
} }
return;
}
}
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() void VNotebookSelector::update()
@ -260,13 +284,14 @@ bool VNotebookSelector::newNotebook()
info += tr("* A previously created notebook could be imported into VNote " info += tr("* A previously created notebook could be imported into VNote "
"by choosing its root folder."); "by choosing its root folder.");
QString defaultName;
QString defaultPath;
// Use empty default name and path to let the dialog to auto generate a name // Use empty default name and path to let the dialog to auto generate a name
// under the default VNote notebook folder. // under the default VNote notebook folder.
VNewNotebookDialog dialog(tr("Add Notebook"), info, defaultName, VNewNotebookDialog dialog(tr("Add Notebook"),
defaultPath, m_notebooks, this); info,
"",
"",
m_notebooks,
this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
createNotebook(dialog.getNameInput(), createNotebook(dialog.getNameInput(),
dialog.getPathInput(), dialog.getPathInput(),
@ -289,7 +314,7 @@ void VNotebookSelector::createNotebook(const QString &p_name,
{ {
VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import, VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import,
p_imageFolder, p_attachmentFolder, p_imageFolder, p_attachmentFolder,
m_vnote); g_vnote);
if (!nb) { if (!nb) {
VUtils::showMessage(QMessageBox::Warning, tr("Warning"), VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
tr("Fail to create notebook " tr("Fail to create notebook "
@ -302,8 +327,8 @@ void VNotebookSelector::createNotebook(const QString &p_name,
m_notebooks.append(nb); m_notebooks.append(nb);
g_config->setNotebooks(m_notebooks); g_config->setNotebooks(m_notebooks);
addNotebookItem(nb->getName()); addNotebookItem(nb);
setCurrentIndexNotebook(m_notebooks.size() - 1); setCurrentItemToNotebook(nb);
} }
void VNotebookSelector::deleteNotebook() void VNotebookSelector::deleteNotebook()
@ -312,62 +337,60 @@ void VNotebookSelector::deleteNotebook()
if (items.isEmpty()) { if (items.isEmpty()) {
return; 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); Q_ASSERT(notebook);
VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook, this); VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook, this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
bool deleteFiles = dialog.getDeleteFiles(); bool deleteFiles = dialog.getDeleteFiles();
m_editArea->closeFile(notebook, true); g_mainWin->getEditArea()->closeFile(notebook, true);
deleteNotebook(notebook, deleteFiles); deleteNotebook(notebook, deleteFiles);
} }
} }
void VNotebookSelector::deleteNotebook(VNotebook *p_notebook, bool p_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.removeOne(p_notebook);
m_notebooks.remove(idx);
g_config->setNotebooks(m_notebooks); 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 name(p_notebook->getName());
QString path(p_notebook->getPath()); QString path(p_notebook->getPath());
bool ret = VNotebook::deleteNotebook(p_notebook, p_deleteFiles); bool ret = VNotebook::deleteNotebook(p_notebook, p_deleteFiles);
if (!ret) { if (!ret) {
// Notebook could not be deleted completely. // 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 " tr("Fail to delete the root folder of notebook "
"<span style=\"%1\">%2</span> from disk. You may open " "<span style=\"%1\">%2</span> from disk. You may open "
"the folder and check it manually.") "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::Open | QMessageBox::Ok,
QMessageBox::Ok, this); QMessageBox::Ok,
this);
if (cho == QMessageBox::Open) { if (cho == QMessageBox::Open) {
// Open the notebook location. // Open the notebook location.
QUrl url = QUrl::fromLocalFile(path); QUrl url = QUrl::fromLocalFile(path);
QDesktopServices::openUrl(url); QDesktopServices::openUrl(url);
} }
} }
}
int VNotebookSelector::indexOfNotebook(const VNotebook *p_notebook) if (m_notebooks.isEmpty()) {
{ m_muted = true;
for (int i = 0; i < m_notebooks.size(); ++i) { setCurrentIndex(0);
if (m_notebooks[i] == p_notebook) { m_muted = false;
return i;
} }
} }
return -1;
}
void VNotebookSelector::editNotebookInfo() void VNotebookSelector::editNotebookInfo()
{ {
@ -375,23 +398,22 @@ void VNotebookSelector::editNotebookInfo()
if (items.isEmpty()) { if (items.isEmpty()) {
return; return;
} }
Q_ASSERT(items.size() == 1); Q_ASSERT(items.size() == 1);
QListWidgetItem *item = items[0];
int index = indexOfListItem(item);
VNotebook *notebook = getNotebookFromComboIndex(index); VNotebook *notebook = getNotebook(items[0]);
QString curName = notebook->getName(); VNotebookInfoDialog dialog(tr("Notebook Information"),
"",
VNotebookInfoDialog dialog(tr("Notebook Information"), "", notebook, notebook,
m_notebooks, this); m_notebooks,
this);
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
bool updated = false; bool updated = false;
bool configUpdated = false; bool configUpdated = false;
QString name = dialog.getName(); QString name = dialog.getName();
if (name != curName) { if (name != notebook->getName()) {
updated = true; updated = true;
notebook->rename(name); notebook->rename(name);
updateComboBoxItem(index, name);
g_config->setNotebooks(m_notebooks); g_config->setNotebooks(m_notebooks);
} }
@ -407,54 +429,58 @@ void VNotebookSelector::editNotebookInfo()
} }
if (updated) { if (updated) {
fillItem(items[0], notebook);
emit notebookUpdated(notebook); emit notebookUpdated(notebook);
} }
} }
} }
void VNotebookSelector::addNotebookItem(const QString &p_name) void VNotebookSelector::addNotebookItem(const VNotebook *p_notebook)
{ {
QListWidgetItem *item = new QListWidgetItem(m_listWidget); QListWidgetItem *item = new QListWidgetItem(m_listWidget);
item->setText(p_name); fillItem(item, p_notebook);
item->setToolTip(p_name);
item->setIcon(QIcon(":/resources/icons/notebook_item.svg"));
} }
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); p_item->setText(p_notebook->getName());
m_listWidget->removeItemWidget(item); p_item->setToolTip(p_notebook->getName());
delete item; 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); QListWidgetItem *item = m_listWidget->itemAt(p_pos);
if (!index.isValid() || index.row() < c_notebookStartIdx) { if (!item) {
return;
}
const VNotebook *nb = getNotebook(item);
if (!nb) {
return; return;
} }
QListWidgetItem *item = m_listWidget->itemAt(p_pos);
Q_ASSERT(item);
m_listWidget->clearSelection(); m_listWidget->clearSelection();
item->setSelected(true); item->setSelected(true);
QMenu menu(this); QMenu menu(this);
menu.setToolTipsVisible(true); menu.setToolTipsVisible(true);
menu.addAction(m_deleteNotebookAct); menu.addAction(m_deleteNotebookAct);
if (nb->isValid()) {
menu.addSeparator(); menu.addSeparator();
menu.addAction(m_recycleBinAct); menu.addAction(m_recycleBinAct);
menu.addAction(m_emptyRecycleBinAct); menu.addAction(m_emptyRecycleBinAct);
}
menu.addSeparator(); menu.addSeparator();
menu.addAction(m_openLocationAct); menu.addAction(m_openLocationAct);
if (nb->isValid()) {
menu.addAction(m_notebookInfoAct); menu.addAction(m_notebookInfoAct);
}
menu.exec(m_listWidget->mapToGlobal(p_pos)); menu.exec(m_listWidget->mapToGlobal(p_pos));
} }
@ -471,40 +497,30 @@ bool VNotebookSelector::eventFilter(QObject *watched, QEvent *event)
return true; return true;
} }
} }
return QComboBox::eventFilter(watched, event);
}
int VNotebookSelector::indexOfListItem(const QListWidgetItem *p_item) return QComboBox::eventFilter(watched, event);
{
int nrItems = m_listWidget->count();
for (int i = 0; i < nrItems; ++i) {
if (m_listWidget->item(i) == p_item) {
return i;
}
}
return -1;
} }
bool VNotebookSelector::locateNotebook(const VNotebook *p_notebook) bool VNotebookSelector::locateNotebook(const VNotebook *p_notebook)
{ {
if (p_notebook) { bool ret = false;
for (int i = 0; i < m_notebooks.size(); ++i) { int index = itemIndexOfNotebook(p_notebook);
if (m_notebooks[i] == p_notebook) { if (index > -1) {
setCurrentIndexNotebook(i); setCurrentIndex(index);
return true; ret = true;
} }
}
} return ret;
return false;
} }
void VNotebookSelector::showPopup() void VNotebookSelector::showPopup()
{ {
if (count() <= c_notebookStartIdx) { if (m_notebooks.isEmpty()) {
// No normal notebook items. Just add notebook. // No normal notebook items. Just add notebook.
newNotebook(); newNotebook();
return; return;
} }
resizeListWidgetToContent(); resizeListWidgetToContent();
QComboBox::showPopup(); QComboBox::showPopup();
} }
@ -526,6 +542,7 @@ void VNotebookSelector::resizeListWidgetToContent()
minHeight = m_listWidget->sizeHintForRow(0) * m_listWidget->count() + 10; minHeight = m_listWidget->sizeHintForRow(0) * m_listWidget->count() + 10;
minHeight = qMin(minHeight, maxMinHeight); minHeight = qMin(minHeight, maxMinHeight);
} }
m_listWidget->setMinimumSize(minWidth, minHeight); m_listWidget->setMinimumSize(minWidth, minHeight);
} }
@ -616,3 +633,23 @@ bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event)
return false; 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;
}

View File

@ -7,8 +7,6 @@
#include "vnavigationmode.h" #include "vnavigationmode.h"
class VNotebook; class VNotebook;
class VNote;
class VEditArea;
class QListWidget; class QListWidget;
class QAction; class QAction;
class QListWidgetItem; class QListWidgetItem;
@ -18,11 +16,15 @@ class VNotebookSelector : public QComboBox, public VNavigationMode
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit VNotebookSelector(VNote *vnote, QWidget *p_parent = 0); explicit VNotebookSelector(QWidget *p_parent = 0);
// Update Combox from m_notebooks.
void update(); void update();
inline void setEditArea(VEditArea *p_editArea);
// Select notebook @p_notebook. // Select notebook @p_notebook.
bool locateNotebook(const VNotebook *p_notebook); bool locateNotebook(const VNotebook *p_notebook);
// Add notebook on popup if no notebooks currently.
void showPopup() Q_DECL_OVERRIDE; void showPopup() Q_DECL_OVERRIDE;
// Implementations for VNavigationMode. // Implementations for VNavigationMode.
@ -41,24 +43,32 @@ signals:
void notebookCreated(const QString &p_name, bool p_import); void notebookCreated(const QString &p_name, bool p_import);
public slots: public slots:
// Popup a dialog to prompt user to create a notebook.
bool newNotebook(); bool newNotebook();
protected: protected:
bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE;
private slots: private slots:
// Act to currentIndexChanged() signal if m_muted is false.
void handleCurIndexChanged(int p_index); 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(); void deleteNotebook();
// View and edit notebook information of selected notebook.
void editNotebookInfo(); void editNotebookInfo();
private: private:
void initActions(); void initActions();
// Update Combox from m_notebooks.
void updateComboBox(); void updateComboBox();
// Return the index of @p_notebook in m_noteboks. // Return the item index of @p_notebook.
int indexOfNotebook(const VNotebook *p_notebook); int itemIndexOfNotebook(const VNotebook *p_notebook) const;
// If @p_import is true, we will use the existing config file. // If @p_import is true, we will use the existing config file.
// If @p_imageFolder is empty, we will use the global one. // If @p_imageFolder is empty, we will use the global one.
@ -68,26 +78,37 @@ private:
const QString &p_attachmentFolder); const QString &p_attachmentFolder);
void deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles); 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. // Add an item corresponding to @p_notebook to combo box.
void removeNotebookItem(int p_index); void addNotebookItem(const VNotebook *p_notebook);
// @p_index is the index of QComboBox.
void updateComboBoxItem(int p_index, const QString &p_name); void fillItem(QListWidgetItem *p_item, const VNotebook *p_notebook) const;
// Insert "Add Notebook" item to combo box.
void insertAddNotebookItem(); void insertAddNotebookItem();
// @p_index is the index of m_notebooks.
void setCurrentIndexNotebook(int p_index); // Set current item corresponding to @p_notebook.
int indexOfListItem(const QListWidgetItem *p_item); void setCurrentItemToNotebook(const VNotebook *p_notebook);
// @p_index is the idnex of QComboBox.
inline VNotebook *getNotebookFromComboIndex(int p_index); // 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(); void resizeListWidgetToContent();
bool handlePopupKeyPress(QKeyEvent *p_event); bool handlePopupKeyPress(QKeyEvent *p_event);
VNote *m_vnote;
QVector<VNotebook *> &m_notebooks; QVector<VNotebook *> &m_notebooks;
VEditArea *m_editArea;
QListWidget *m_listWidget; QListWidget *m_listWidget;
// Used to restore after clicking Add Notebook item.
int m_lastValidIndex; int m_lastValidIndex;
// Whether it is muted from currentIndexChanged().
bool m_muted;
// Actions // Actions
QAction *m_deleteNotebookAct; QAction *m_deleteNotebookAct;
QAction *m_notebookInfoAct; QAction *m_notebookInfoAct;
@ -95,28 +116,7 @@ private:
QAction *m_recycleBinAct; QAction *m_recycleBinAct;
QAction *m_emptyRecycleBinAct; 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; 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 #endif // VNOTEBOOKSELECTOR_H

View File

@ -233,7 +233,9 @@ bool VNoteFile::addAttachment(const QString &p_file)
QString folderPath = fetchAttachmentFolderPath(); QString folderPath = fetchAttachmentFolderPath();
QString name = VUtils::fileNameFromPath(p_file); QString name = VUtils::fileNameFromPath(p_file);
Q_ASSERT(!name.isEmpty()); 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); QString destPath = QDir(folderPath).filePath(name);
if (!VUtils::copyFile(p_file, destPath, false)) { if (!VUtils::copyFile(p_file, destPath, false)) {
return false; return false;
@ -543,7 +545,7 @@ bool VNoteFile::copyFile(VDirectory *p_destDir,
if (!attaFolderPath.isEmpty()) { if (!attaFolderPath.isEmpty()) {
QDir dir(destFile->fetchBasePath()); QDir dir(destFile->fetchBasePath());
QString folderPath = dir.filePath(destFile->getNotebook()->getAttachmentFolder()); QString folderPath = dir.filePath(destFile->getNotebook()->getAttachmentFolder());
attaFolder = VUtils::getFileNameWithSequence(folderPath, attaFolder); attaFolder = VUtils::getDirNameWithSequence(folderPath, attaFolder);
folderPath = QDir(folderPath).filePath(attaFolder); folderPath = QDir(folderPath).filePath(attaFolder);
// Copy attaFolderPath to folderPath. // Copy attaFolderPath to folderPath.

View File

@ -1,44 +1,39 @@
#include <QDebug>
#include <QVector> #include <QVector>
#include <QString> #include <QString>
#include <QKeyEvent> #include <QKeyEvent>
#include <QLabel> #include <QLabel>
#include <QCoreApplication> #include <QCoreApplication>
#include "voutline.h" #include "voutline.h"
#include "vtoc.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vnote.h" #include "vnote.h"
#include "vfile.h"
extern VNote *g_vnote; extern VNote *g_vnote;
VOutline::VOutline(QWidget *parent) VOutline::VOutline(QWidget *parent)
: QTreeWidget(parent), VNavigationMode() : QTreeWidget(parent),
VNavigationMode(),
m_muted(false)
{ {
setColumnCount(1); setColumnCount(1);
setHeaderHidden(true); setHeaderHidden(true);
setSelectionMode(QAbstractItemView::SingleSelection); setSelectionMode(QAbstractItemView::SingleSelection);
// TODO: jump to the header when user click the same item twice.
connect(this, &VOutline::currentItemChanged, 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<VHeader> &headers = p_toc.headers; if (p_outline == m_outline) {
return;
for (int i = 0; i < headers.size(); ++i) {
V_ASSERT(headers[i].index == i);
}
} }
void VOutline::updateOutline(const VToc &toc)
{
// Clear current header // Clear current header
curHeader = VAnchor(); m_currentHeader.clear();
checkOutline(toc); m_outline = p_outline;
outline = toc;
updateTreeFromOutline(); updateTreeFromOutline();
@ -49,22 +44,25 @@ void VOutline::updateTreeFromOutline()
{ {
clear(); clear();
if (!outline.valid) { if (m_outline.isEmpty()) {
return; return;
} }
const QVector<VHeader> &headers = outline.headers; const QVector<VTableOfContentItem> &headers = m_outline.getTable();
int idx = 0; int idx = 0;
updateTreeByLevel(headers, idx, NULL, NULL, 1); updateTreeByLevel(headers, idx, NULL, NULL, 1);
} }
void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index, void VOutline::updateTreeByLevel(const QVector<VTableOfContentItem> &headers,
QTreeWidgetItem *parent, QTreeWidgetItem *last, int level) int &index,
QTreeWidgetItem *parent,
QTreeWidgetItem *last,
int level)
{ {
while (index < headers.size()) { while (index < headers.size()) {
const VHeader &header = headers[index]; const VTableOfContentItem &header = headers[index];
QTreeWidgetItem *item; QTreeWidgetItem *item;
if (header.level == level) { if (header.m_level == level) {
if (parent) { if (parent) {
item = new QTreeWidgetItem(parent); item = new QTreeWidgetItem(parent);
} else { } else {
@ -75,7 +73,7 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
last = item; last = item;
++index; ++index;
} else if (header.level < level) { } else if (header.m_level < level) {
return; return;
} else { } else {
updateTreeByLevel(headers, index, last, NULL, level + 1); updateTreeByLevel(headers, index, last, NULL, level + 1);
@ -83,11 +81,11 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &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->setData(0, Qt::UserRole, p_header.m_index);
p_item->setText(0, p_header.name); p_item->setText(0, p_header.m_name);
p_item->setToolTip(0, p_header.name); p_item->setToolTip(0, p_header.m_name);
if (p_header.isEmpty()) { if (p_header.isEmpty()) {
p_item->setForeground(0, QColor("grey")); p_item->setForeground(0, QColor("grey"));
@ -99,127 +97,81 @@ void VOutline::expandTree()
if (topLevelItemCount() == 0) { if (topLevelItemCount() == 0) {
return; return;
} }
expandAll(); 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) { if (!p_curItem) {
return; return;
} }
const VHeader *header = getHeaderFromItem(p_curItem); const VTableOfContentItem *header = getHeaderFromItem(p_curItem);
if (!header) { Q_ASSERT(header);
return; m_currentHeader.update(m_outline.getFile(), header->m_index);
}
VAnchor tmp(outline.m_file, header->anchor, header->lineNumber, header->index); if (!header->isEmpty() && !m_muted) {
if (tmp == curHeader) { emit outlineItemActivated(m_currentHeader);
return;
}
curHeader = tmp;
if (!header->isEmpty()) {
emit outlineItemActivated(curHeader);
} }
} }
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; return;
} }
curHeader = anchor; // Item change should not emit the signal.
if (outline.type == VHeaderType::Anchor) { m_muted = true;
selectAnchor(anchor.anchor); m_currentHeader = p_header;
} else { selectHeader(m_currentHeader);
// Select by lineNumber. m_muted = false;
selectLineNumber(anchor.lineNumber);
}
} }
void VOutline::selectAnchor(const QString &anchor) void VOutline::selectHeader(const VHeaderPointer &p_header)
{ {
setCurrentItem(NULL); setCurrentItem(NULL);
if (anchor.isEmpty()) { if (!m_outline.getItem(p_header)) {
return; return;
} }
int nrTop = topLevelItemCount(); int nrTop = topLevelItemCount();
for (int i = 0; i < nrTop; ++i) { for (int i = 0; i < nrTop; ++i) {
if (selectAnchorOne(topLevelItem(i), anchor)) { if (selectHeaderOne(topLevelItem(i), p_header)) {
return; 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; return false;
} }
const VHeader *header = getHeaderFromItem(item); const VTableOfContentItem *header = getHeaderFromItem(p_item);
if (!header) { if (!header) {
return false; return false;
} }
if (header->anchor == anchor) { if (header->isMatched(p_header)) {
setCurrentItem(item); setCurrentItem(p_item);
return true; return true;
} }
int nrChild = item->childCount(); int nrChild = p_item->childCount();
for (int i = 0; i < nrChild; ++i) { for (int i = 0; i < nrChild; ++i) {
if (selectAnchorOne(item->child(i), anchor)) { if (selectHeaderOne(p_item->child(i), p_header)) {
return true; 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; return false;
} }
@ -373,17 +325,8 @@ QList<QTreeWidgetItem *> VOutline::getVisibleChildItems(const QTreeWidgetItem *p
return items; 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(); int index = p_item->data(0, Qt::UserRole).toInt();
if (index < 0 || index >= outline.headers.size()) { return m_outline.getItem(index);
return header;
}
header = &(outline.headers[index]);
Q_ASSERT(header->index == index);
return header;
} }

View File

@ -5,11 +5,13 @@
#include <QVector> #include <QVector>
#include <QMap> #include <QMap>
#include <QChar> #include <QChar>
#include "vtoc.h" #include "vtableofcontent.h"
#include "vnavigationmode.h" #include "vnavigationmode.h"
class QLabel; 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 class VOutline : public QTreeWidget, public VNavigationMode
{ {
Q_OBJECT Q_OBJECT
@ -23,45 +25,60 @@ public:
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
signals: 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: public slots:
void updateOutline(const VToc &toc); // Called to update outline and the tree.
void updateCurHeader(const VAnchor &anchor); // 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: protected:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
private slots: 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: private:
// Update tree according to outline. // Update tree according to outline.
void updateTreeFromOutline(); void updateTreeFromOutline();
// @index: the index in @headers. // @index: the index in @headers.
void updateTreeByLevel(const QVector<VHeader> &headers, int &index, QTreeWidgetItem *parent, void updateTreeByLevel(const QVector<VTableOfContentItem> &headers,
QTreeWidgetItem *last, int level); int &index,
QTreeWidgetItem *parent,
QTreeWidgetItem *last,
int level);
void expandTree(); void expandTree();
void selectAnchor(const QString &anchor);
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor); // Set the item corresponding to @p_header as current item.
void selectLineNumber(int lineNumber); void selectHeader(const VHeaderPointer &p_header);
bool selectLineNumberOne(QTreeWidgetItem *item, int lineNumber);
bool selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header);
QList<QTreeWidgetItem *> getVisibleItems() const; QList<QTreeWidgetItem *> getVisibleItems() const;
QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const; QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
// Fill the info of @p_item. // Fill the info of @p_item.
void fillItem(QTreeWidgetItem *p_item, const VHeader &p_header); void fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header);
// Check if @p_toc is valid.
void checkOutline(const VToc &p_toc) const;
// Return NULL if no corresponding header in outline. // Return NULL if no corresponding header in outline.
const VHeader *getHeaderFromItem(QTreeWidgetItem *p_item) const; const VTableOfContentItem *getHeaderFromItem(QTreeWidgetItem *p_item) const;
VToc outline; VTableOfContent m_outline;
VAnchor curHeader;
VHeaderPointer m_currentHeader;
// When true, won't emit outlineItemActivated().
bool m_muted;
// Navigation Mode. // Navigation Mode.
// Map second key to QTreeWidgetItem. // Map second key to QTreeWidgetItem.

View File

@ -2,10 +2,9 @@
#include <QDesktopServices> #include <QDesktopServices>
#include "vnote.h"
#include "vmainwindow.h" #include "vmainwindow.h"
extern VNote *g_vnote; extern VMainWindow *g_mainWin;
VPreviewPage::VPreviewPage(QWidget *parent) : QWebEnginePage(parent) VPreviewPage::VPreviewPage(QWidget *parent) : QWebEnginePage(parent)
{ {
@ -21,7 +20,7 @@ bool VPreviewPage::acceptNavigationRequest(const QUrl &p_url,
if (p_url.isLocalFile()) { if (p_url.isLocalFile()) {
QString filePath = p_url.toLocalFile(); QString filePath = p_url.toLocalFile();
if (g_vnote->getMainWindow()->tryOpenInternalFile(filePath)) { if (g_mainWin->tryOpenInternalFile(filePath)) {
qDebug() << "internal notes jump" << filePath; qDebug() << "internal notes jump" << filePath;
return false; return false;
} }

Some files were not shown because too many files have changed in this diff Show More