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

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

|
||||
|
||||
@ -137,7 +138,7 @@ VNote还支持其他很多的功能,比如:
|
||||
- 自动缩进和自动列表;
|
||||
|
||||
# 构建与开发
|
||||
VNote需要5.7版本以上的Qt进行构建。
|
||||
VNote需要5.9.1版本以上的Qt进行构建。
|
||||
|
||||
1. 克隆代码仓库
|
||||
```
|
||||
@ -146,15 +147,15 @@ VNote需要5.7版本以上的Qt进行构建。
|
||||
git submodule update --init
|
||||
```
|
||||
2. 下载Qt
|
||||
下载[Qt 5.7.0](http://info.qt.io/download-qt-for-application-development),导入`VNote.pro`创建一个工程。
|
||||
下载[Qt 5.9.1](http://info.qt.io/download-qt-for-application-development),导入`VNote.pro`创建一个工程。
|
||||
|
||||
## Linux
|
||||
如果您的Linux发行版不提供5.7以上版本的Qt,那么您需要从其他来源获取Qt。在Ubuntu中,您可以执行以下步骤:
|
||||
如果您的Linux发行版不提供5.9.1以上版本的Qt,那么您需要从其他来源获取Qt。在Ubuntu中,您可以执行以下步骤:
|
||||
|
||||
```
|
||||
sudo add-apt-repository ppa:beineri/opt-qt571-trusty -y
|
||||
sudo add-apt-repository ppa:beineri/opt-qt591-trusty -y
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get -y install qt57base qt57webengine qt57webchannel qt57svg qt57location qt57tools qt57translations
|
||||
sudo apt-get -y install qt59base qt59webengine qt59webchannel qt59svg qt59location qt59tools qt59translations
|
||||
source /opt/qt*/bin/qt*-env.sh
|
||||
```
|
||||
|
||||
@ -175,15 +176,15 @@ sudo make install
|
||||
在macOS下,您可以执行以下步骤来编译:
|
||||
|
||||
1. 安装Xcode和Homebrew:
|
||||
2. 通过Homebrew安装Qt5.7:
|
||||
2. 通过Homebrew安装Qt5.9.1:
|
||||
|
||||
```
|
||||
brew install qt@5.7
|
||||
brew install qt@5.9.1
|
||||
```
|
||||
3. 在VNote源码根目录下,新建一个文件`build_macos.sh`:
|
||||
|
||||
```sh
|
||||
QTDIR="/usr/local/opt/qt@5.7"
|
||||
QTDIR="/usr/local/opt/qt@5.9.1"
|
||||
PATH="$QTDIR/bin:$PATH"
|
||||
LDFLAGS=-L$QTDIR/lib
|
||||
CPPFLAGS=-I$QTDIR/include
|
||||
@ -203,7 +204,7 @@ sudo make install
|
||||
5. 此时得到VNote的Bundle `path/to/project/build/src/VNote.app`,打开即可。
|
||||
|
||||
# 依赖
|
||||
- [Qt 5.7](http://qt-project.org) (L-GPL v3)
|
||||
- [Qt 5.9](http://qt-project.org) (L-GPL v3)
|
||||
- [PEG Markdown Highlight](http://hasseg.org/peg-markdown-highlight/) (MIT License)
|
||||
- [Hoedown 3.0.7](https://github.com/hoedown/hoedown/) (ISC License)
|
||||
- [Marked](https://github.com/chjj/marked) (MIT License)
|
||||
@ -216,6 +217,7 @@ sudo make install
|
||||
- [MathJax](https://www.mathjax.org/) (Apache-2.0)
|
||||
- [showdown](https://github.com/showdownjs/showdown) (Unknown)
|
||||
- [flowchart.js](https://github.com/adrai/flowchart.js) (MIT License)
|
||||
- 图标由九梦岛主(a326703305@qq.com)制作
|
||||
|
||||
# 代码许可
|
||||
VNote使用[MIT许可](http://opensource.org/licenses/MIT)。
|
||||
|
@ -11,9 +11,9 @@ environment:
|
||||
VSVER: 14
|
||||
|
||||
matrix:
|
||||
- QT: C:\Qt\5.7\msvc2015_64
|
||||
- QT: C:\Qt\5.9.1\msvc2015_64
|
||||
PLATFORM: amd64
|
||||
- QT: C:\Qt\5.7\msvc2015
|
||||
- QT: C:\Qt\5.9.1\msvc2015
|
||||
PLATFORM: x86
|
||||
|
||||
clone_depth: 1
|
||||
@ -29,8 +29,8 @@ before_build:
|
||||
# After calling vcvarsall.bat, %PLATFORM% will be X64 or x86
|
||||
- mkdir build
|
||||
- cd build
|
||||
- if "%PLATFORM%" EQU "X64" (qmake -r -spec win32-msvc2015 CONFIG+=x86_64 CONFIG-=debug CONFIG+=release ../VNote.pro)
|
||||
- if "%PLATFORM%" EQU "x86" (qmake -r -spec win32-msvc2015 CONFIG+=Win32 CONFIG-=debug CONFIG+=release ../VNote.pro)
|
||||
- if "%PLATFORM%" EQU "X64" (qmake -r -spec win32-msvc CONFIG+=x86_64 CONFIG-=debug CONFIG+=release ../VNote.pro)
|
||||
- if "%PLATFORM%" EQU "x86" (qmake -r -spec win32-msvc CONFIG+=Win32 CONFIG-=debug CONFIG+=release ../VNote.pro)
|
||||
|
||||
# custom build scripts
|
||||
build_script:
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "vdirinfodialog.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vlineedit.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
@ -16,7 +17,7 @@ VDirInfoDialog::VDirInfoDialog(const QString &title,
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
@ -28,15 +29,18 @@ void VDirInfoDialog::setupUI()
|
||||
infoLabel = new QLabel(info);
|
||||
}
|
||||
|
||||
nameEdit = new QLineEdit(m_directory->getName());
|
||||
nameEdit->selectAll();
|
||||
m_nameEdit = new VLineEdit(m_directory->getName());
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
m_nameEdit->selectAll();
|
||||
|
||||
// Created time.
|
||||
QString createdTimeStr = VUtils::displayDateTime(m_directory->getCreatedTimeUtc().toLocalTime());
|
||||
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Folder &name:"), nameEdit);
|
||||
topLayout->addRow(tr("Folder &name:"), m_nameEdit);
|
||||
topLayout->addRow(tr("Created time:"), createdTimeLabel);
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
@ -49,7 +53,7 @@ void VDirInfoDialog::setupUI()
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
@ -67,19 +71,35 @@ void VDirInfoDialog::setupUI()
|
||||
void VDirInfoDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = nameEdit->text();
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk && name != m_directory->getName()) {
|
||||
// Check if the name conflicts with existing directory name.
|
||||
// Case-insensitive when creating note.
|
||||
const VDirectory *directory = m_parentDirectory->findSubDirectory(name, false);
|
||||
QString warnText;
|
||||
if (directory && directory != m_directory) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<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;
|
||||
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(nameConflictText);
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,5 +111,5 @@ void VDirInfoDialog::handleInputChanged()
|
||||
|
||||
QString VDirInfoDialog::getNameInput() const
|
||||
{
|
||||
return nameEdit->text();
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <QDialog>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VDirectory;
|
||||
@ -27,7 +27,7 @@ private slots:
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QLineEdit *nameEdit;
|
||||
VLineEdit *m_nameEdit;
|
||||
QLabel *m_warnLabel;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "vnotefile.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vlineedit.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -16,7 +17,7 @@ VFileInfoDialog::VFileInfoDialog(const QString &title,
|
||||
{
|
||||
setupUI(title, info);
|
||||
|
||||
connect(nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
@ -30,7 +31,10 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
|
||||
// File name.
|
||||
QString name = m_file->getName();
|
||||
nameEdit = new QLineEdit(name);
|
||||
m_nameEdit = new VLineEdit(name);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
int baseStart = 0, baseLength = name.size();
|
||||
int dotIdx = name.lastIndexOf('.');
|
||||
if (dotIdx != -1) {
|
||||
@ -38,7 +42,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
}
|
||||
|
||||
// Select without suffix.
|
||||
nameEdit->setSelection(baseStart, baseLength);
|
||||
m_nameEdit->setSelection(baseStart, baseLength);
|
||||
|
||||
// Attachment folder.
|
||||
QLineEdit *attachmentFolderEdit = new QLineEdit(m_file->getAttachmentFolder());
|
||||
@ -56,7 +60,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
modifiedTimeLabel->setToolTip(tr("Last modified time within VNote"));
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Note &name:"), nameEdit);
|
||||
topLayout->addRow(tr("Note &name:"), m_nameEdit);
|
||||
topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit);
|
||||
topLayout->addRow(tr("Created time:"), createdTimeLabel);
|
||||
topLayout->addRow(tr("Modified time:"), modifiedTimeLabel);
|
||||
@ -71,7 +75,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
@ -90,28 +94,43 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
void VFileInfoDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = nameEdit->text();
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk && name != m_file->getName()) {
|
||||
// Check if the name conflicts with existing note name.
|
||||
// Case-insensitive when creating note.
|
||||
const VNoteFile *file = m_directory->findFile(name, false);
|
||||
QString warnText;
|
||||
if (file && file != m_file) {
|
||||
nameOk = false;
|
||||
showWarnLabel = true;
|
||||
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(nameConflictText);
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (m_file->getDocType() != DocType::Unknown
|
||||
&& VUtils::docTypeFromName(name) != m_file->getDocType()) {
|
||||
// Check if the name change the doc type.
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Changing type of the note is not supported. "
|
||||
"Please use the same suffix as the old one.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<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;
|
||||
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Changing type of the note is not supported. "
|
||||
"Please use the same suffix as the old one.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(nameConflictText);
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,5 +142,5 @@ void VFileInfoDialog::handleInputChanged()
|
||||
|
||||
QString VFileInfoDialog::getNameInput() const
|
||||
{
|
||||
return nameEdit->text();
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <QDialog>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VDirectory;
|
||||
@ -29,7 +29,7 @@ private slots:
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
QLineEdit *nameEdit;
|
||||
VLineEdit *m_nameEdit;
|
||||
QLabel *m_warnLabel;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <QRegExp>
|
||||
#include "vinsertimagedialog.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vlineedit.h"
|
||||
|
||||
VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
|
||||
const QString &defaultPath, QWidget *parent)
|
||||
@ -11,11 +12,14 @@ VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defa
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(imageTitleEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
|
||||
connect(pathEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
|
||||
connect(browseBtn, &QPushButton::clicked, this, &VInsertImageDialog::handleBrowseBtnClicked);
|
||||
connect(m_imageTitleEdit, &QLineEdit::textChanged,
|
||||
this, &VInsertImageDialog::handleInputChanged);
|
||||
connect(pathEdit, &QLineEdit::textChanged,
|
||||
this, &VInsertImageDialog::handleInputChanged);
|
||||
connect(browseBtn, &QPushButton::clicked,
|
||||
this, &VInsertImageDialog::handleBrowseBtnClicked);
|
||||
|
||||
enableOkButton();
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
VInsertImageDialog::~VInsertImageDialog()
|
||||
@ -34,19 +38,19 @@ void VInsertImageDialog::setupUI()
|
||||
browseBtn = new QPushButton(tr("&Browse"));
|
||||
|
||||
imageTitleLabel = new QLabel(tr("&Image title:"));
|
||||
imageTitleEdit = new QLineEdit(defaultImageTitle);
|
||||
imageTitleEdit->selectAll();
|
||||
imageTitleLabel->setBuddy(imageTitleEdit);
|
||||
QRegExp regExp("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]+");
|
||||
QValidator *validator = new QRegExpValidator(regExp, this);
|
||||
imageTitleEdit->setValidator(validator);
|
||||
m_imageTitleEdit = new VLineEdit(defaultImageTitle);
|
||||
m_imageTitleEdit->selectAll();
|
||||
imageTitleLabel->setBuddy(m_imageTitleEdit);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp),
|
||||
m_imageTitleEdit);
|
||||
m_imageTitleEdit->setValidator(validator);
|
||||
|
||||
QGridLayout *topLayout = new QGridLayout();
|
||||
topLayout->addWidget(pathLabel, 0, 0);
|
||||
topLayout->addWidget(pathEdit, 0, 1);
|
||||
topLayout->addWidget(browseBtn, 0, 2);
|
||||
topLayout->addWidget(imageTitleLabel, 1, 0);
|
||||
topLayout->addWidget(imageTitleEdit, 1, 1, 1, 2);
|
||||
topLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 2);
|
||||
topLayout->setColumnStretch(0, 0);
|
||||
topLayout->setColumnStretch(1, 1);
|
||||
topLayout->setColumnStretch(2, 0);
|
||||
@ -67,22 +71,31 @@ void VInsertImageDialog::setupUI()
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(title);
|
||||
|
||||
imageTitleEdit->setFocus();
|
||||
m_imageTitleEdit->setFocus();
|
||||
}
|
||||
|
||||
void VInsertImageDialog::enableOkButton()
|
||||
void VInsertImageDialog::handleInputChanged()
|
||||
{
|
||||
bool enabled = true;
|
||||
if (imageTitleEdit->text().isEmpty() || !image) {
|
||||
enabled = false;
|
||||
bool pathOk = true;
|
||||
if (pathEdit->isVisible() && !pathEdit->isReadOnly()) {
|
||||
QString path = pathEdit->text();
|
||||
if (path.isEmpty()
|
||||
|| !VUtils::checkPathLegal(path)) {
|
||||
pathOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
QString title = m_imageTitleEdit->getEvaluatedText();
|
||||
QRegExp reg(VUtils::c_imageTitleRegExp);
|
||||
bool titleOk = reg.exactMatch(title);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(enabled);
|
||||
okBtn->setEnabled(pathOk && titleOk);
|
||||
}
|
||||
|
||||
QString VInsertImageDialog::getImageTitleInput() const
|
||||
{
|
||||
return imageTitleEdit->text();
|
||||
return m_imageTitleEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
QString VInsertImageDialog::getPathInput() const
|
||||
@ -130,7 +143,8 @@ void VInsertImageDialog::setImage(const QImage &image)
|
||||
|
||||
imagePreviewLabel->setPixmap(pixmap);
|
||||
imagePreviewLabel->setVisible(true);
|
||||
enableOkButton();
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VInsertImageDialog::setBrowseable(bool browseable, bool visible)
|
||||
@ -141,6 +155,8 @@ void VInsertImageDialog::setBrowseable(bool browseable, bool visible)
|
||||
pathLabel->setVisible(visible);
|
||||
pathEdit->setVisible(visible);
|
||||
browseBtn->setVisible(visible);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VInsertImageDialog::imageDownloaded(const QByteArray &data)
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
|
||||
@ -30,14 +31,14 @@ public slots:
|
||||
void imageDownloaded(const QByteArray &data);
|
||||
|
||||
private slots:
|
||||
void enableOkButton();
|
||||
void handleInputChanged();
|
||||
void handleBrowseBtnClicked();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QLabel *imageTitleLabel;
|
||||
QLineEdit *imageTitleEdit;
|
||||
VLineEdit *m_imageTitleEdit;
|
||||
QLabel *pathLabel;
|
||||
QLineEdit *pathEdit;
|
||||
QPushButton *browseBtn;
|
||||
|
132
src/dialog/vinsertlinkdialog.cpp
Normal 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();
|
||||
}
|
46
src/dialog/vinsertlinkdialog.h
Normal 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
|
@ -2,6 +2,8 @@
|
||||
#include "vnewdirdialog.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vlineedit.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -15,7 +17,7 @@ VNewDirDialog::VNewDirDialog(const QString &title,
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
@ -29,9 +31,12 @@ void VNewDirDialog::setupUI()
|
||||
}
|
||||
|
||||
QLabel *nameLabel = new QLabel(tr("Folder &name:"));
|
||||
nameEdit = new QLineEdit(defaultName);
|
||||
nameEdit->selectAll();
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
m_nameEdit = new VLineEdit(defaultName);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
m_nameEdit->selectAll();
|
||||
nameLabel->setBuddy(m_nameEdit);
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
@ -44,10 +49,10 @@ void VNewDirDialog::setupUI()
|
||||
|
||||
QHBoxLayout *topLayout = new QHBoxLayout();
|
||||
topLayout->addWidget(nameLabel);
|
||||
topLayout->addWidget(nameEdit);
|
||||
topLayout->addWidget(m_nameEdit);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
if (infoLabel) {
|
||||
@ -64,18 +69,34 @@ void VNewDirDialog::setupUI()
|
||||
void VNewDirDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = nameEdit->text();
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk) {
|
||||
// Check if the name conflicts with existing directory name.
|
||||
// Case-insensitive when creating folder.
|
||||
QString warnText;
|
||||
if (m_directory->findSubDirectory(name, false)) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<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;
|
||||
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(nameConflictText);
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,5 +108,5 @@ void VNewDirDialog::handleInputChanged()
|
||||
|
||||
QString VNewDirDialog::getNameInput() const
|
||||
{
|
||||
return nameEdit->text();
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <QDialog>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VDirectory;
|
||||
@ -27,7 +27,7 @@ private slots:
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QLineEdit *nameEdit;
|
||||
VLineEdit *m_nameEdit;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
QLabel *m_warnLabel;
|
||||
|
@ -2,6 +2,8 @@
|
||||
#include "vnewfiledialog.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vlineedit.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -13,7 +15,7 @@ VNewFileDialog::VNewFileDialog(const QString &title, const QString &info,
|
||||
{
|
||||
setupUI();
|
||||
|
||||
connect(nameEdit, &QLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
|
||||
connect(m_nameEdit, &VLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
|
||||
|
||||
handleInputChanged();
|
||||
}
|
||||
@ -27,10 +29,13 @@ void VNewFileDialog::setupUI()
|
||||
|
||||
// Name.
|
||||
QLabel *nameLabel = new QLabel(tr("Note &name:"));
|
||||
nameEdit = new QLineEdit(defaultName);
|
||||
m_nameEdit = new VLineEdit(defaultName);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
int dotIndex = defaultName.lastIndexOf('.');
|
||||
nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex);
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
m_nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex);
|
||||
nameLabel->setBuddy(m_nameEdit);
|
||||
|
||||
// InsertTitle.
|
||||
m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)"));
|
||||
@ -42,10 +47,10 @@ void VNewFileDialog::setupUI()
|
||||
});
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(nameLabel, nameEdit);
|
||||
topLayout->addRow(nameLabel, m_nameEdit);
|
||||
topLayout->addWidget(m_insertTitleCB);
|
||||
|
||||
nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
|
||||
m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
|
||||
|
||||
m_warnLabel = new QLabel();
|
||||
m_warnLabel->setWordWrap(true);
|
||||
@ -73,18 +78,34 @@ void VNewFileDialog::setupUI()
|
||||
void VNewFileDialog::handleInputChanged()
|
||||
{
|
||||
bool showWarnLabel = false;
|
||||
QString name = nameEdit->text();
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (nameOk) {
|
||||
// Check if the name conflicts with existing note name.
|
||||
// Case-insensitive when creating note.
|
||||
QString warnText;
|
||||
if (m_directory->findFile(name, false)) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<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;
|
||||
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(nameConflictText);
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +117,7 @@ void VNewFileDialog::handleInputChanged()
|
||||
|
||||
QString VNewFileDialog::getNameInput() const
|
||||
{
|
||||
return nameEdit->text();
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
bool VNewFileDialog::getInsertTitleInput() const
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <QDialog>
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QCheckBox;
|
||||
class VDirectory;
|
||||
@ -27,7 +27,7 @@ private slots:
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QLineEdit *nameEdit;
|
||||
VLineEdit *m_nameEdit;
|
||||
QCheckBox *m_insertTitleCB;
|
||||
|
||||
QPushButton *okBtn;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vnotebook.h"
|
||||
#include "vlineedit.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -18,7 +19,7 @@ VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info
|
||||
{
|
||||
setupUI(title, info);
|
||||
|
||||
connect(nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
|
||||
connect(pathEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
|
||||
connect(browseBtn, &QPushButton::clicked, this, &VNewNotebookDialog::handleBrowseBtnClicked);
|
||||
|
||||
@ -34,8 +35,11 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
}
|
||||
|
||||
QLabel *nameLabel = new QLabel(tr("Notebook &name:"));
|
||||
nameEdit = new QLineEdit(defaultName);
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
m_nameEdit = new VLineEdit(defaultName);
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
nameLabel->setBuddy(m_nameEdit);
|
||||
|
||||
QLabel *pathLabel = new QLabel(tr("Notebook &root folder:"));
|
||||
pathEdit = new QLineEdit(defaultPath);
|
||||
@ -50,7 +54,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
|
||||
"(empty to use global configuration)"));
|
||||
imageFolderLabel->setToolTip(m_imageFolderEdit->toolTip());
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
|
||||
m_imageFolderEdit->setValidator(validator);
|
||||
|
||||
QLabel *attachmentFolderLabel = new QLabel(tr("&Attachment folder:"));
|
||||
@ -66,7 +70,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
|
||||
QGridLayout *topLayout = new QGridLayout();
|
||||
topLayout->addWidget(nameLabel, 0, 0);
|
||||
topLayout->addWidget(nameEdit, 0, 1, 1, 2);
|
||||
topLayout->addWidget(m_nameEdit, 0, 1, 1, 2);
|
||||
topLayout->addWidget(pathLabel, 1, 0);
|
||||
topLayout->addWidget(pathEdit, 1, 1);
|
||||
topLayout->addWidget(browseBtn, 1, 2);
|
||||
@ -104,7 +108,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
|
||||
QString VNewNotebookDialog::getNameInput() const
|
||||
{
|
||||
return nameEdit->text();
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
QString VNewNotebookDialog::getPathInput() const
|
||||
@ -142,7 +146,8 @@ void VNewNotebookDialog::handleBrowseBtnClicked()
|
||||
}
|
||||
}
|
||||
|
||||
QString dirPath = QFileDialog::getExistingDirectory(this, tr("Select Root Folder Of The Notebook"),
|
||||
QString dirPath = QFileDialog::getExistingDirectory(this,
|
||||
tr("Select Root Folder Of The Notebook"),
|
||||
defaultPath,
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
|
||||
@ -160,7 +165,7 @@ bool VNewNotebookDialog::isImportExistingNotebook() const
|
||||
|
||||
void VNewNotebookDialog::showEvent(QShowEvent *event)
|
||||
{
|
||||
nameEdit->setFocus();
|
||||
m_nameEdit->setFocus();
|
||||
QDialog::showEvent(event);
|
||||
}
|
||||
|
||||
@ -182,7 +187,7 @@ void VNewNotebookDialog::handleInputChanged()
|
||||
m_manualPath = true;
|
||||
}
|
||||
|
||||
if (nameEdit->isModified()) {
|
||||
if (m_nameEdit->isModified()) {
|
||||
m_manualName = true;
|
||||
}
|
||||
|
||||
@ -247,7 +252,7 @@ void VNewNotebookDialog::handleInputChanged()
|
||||
}
|
||||
}
|
||||
|
||||
QString name = nameEdit->text();
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
if (pathOk && nameOk) {
|
||||
// Check if the name conflicts with existing notebook name.
|
||||
@ -259,13 +264,29 @@ void VNewNotebookDialog::handleInputChanged()
|
||||
}
|
||||
}
|
||||
|
||||
QString warnText;
|
||||
if (idx < m_notebooks.size()) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<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;
|
||||
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(nameConflictText);
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,6 +301,8 @@ void VNewNotebookDialog::handleInputChanged()
|
||||
|
||||
bool VNewNotebookDialog::autoComplete()
|
||||
{
|
||||
QString nameText = m_nameEdit->getEvaluatedText();
|
||||
|
||||
if (m_manualPath) {
|
||||
if (m_manualName) {
|
||||
return false;
|
||||
@ -289,8 +312,8 @@ bool VNewNotebookDialog::autoComplete()
|
||||
QString pathText = pathEdit->text();
|
||||
if (!pathText.isEmpty()) {
|
||||
QString autoName = VUtils::directoryNameFromPath(pathText);
|
||||
if (autoName != nameEdit->text()) {
|
||||
nameEdit->setText(autoName);
|
||||
if (autoName != nameText) {
|
||||
m_nameEdit->setText(autoName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -306,7 +329,6 @@ bool VNewNotebookDialog::autoComplete()
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
QString nameText = nameEdit->text();
|
||||
if (nameText.isEmpty()) {
|
||||
if (m_manualName) {
|
||||
return false;
|
||||
@ -314,8 +336,8 @@ bool VNewNotebookDialog::autoComplete()
|
||||
|
||||
// Get a folder name under vnoteFolder and set it as the name of the notebook.
|
||||
QString name = "vnotebook";
|
||||
name = VUtils::getFileNameWithSequence(vnoteFolder, name);
|
||||
nameEdit->setText(name);
|
||||
name = VUtils::getDirNameWithSequence(vnoteFolder, name);
|
||||
m_nameEdit->setText(name);
|
||||
ret = true;
|
||||
} else {
|
||||
// Use the name as the folder name under vnoteFolder.
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
class VNotebook;
|
||||
@ -52,7 +53,7 @@ private:
|
||||
// Returns true if name or path is modified.
|
||||
bool autoComplete();
|
||||
|
||||
QLineEdit *nameEdit;
|
||||
VLineEdit *m_nameEdit;
|
||||
QLineEdit *pathEdit;
|
||||
QPushButton *browseBtn;
|
||||
QLabel *m_warnLabel;
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "vnotebook.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vlineedit.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -29,7 +30,10 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
infoLabel = new QLabel(p_info);
|
||||
}
|
||||
|
||||
m_nameEdit = new QLineEdit(m_notebook->getName());
|
||||
m_nameEdit = new VLineEdit(m_notebook->getName());
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
|
||||
m_nameEdit);
|
||||
m_nameEdit->setValidator(validator);
|
||||
m_nameEdit->selectAll();
|
||||
|
||||
m_pathEdit = new QLineEdit(m_notebook->getPath());
|
||||
@ -41,7 +45,7 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
.arg(g_config->getImageFolder()));
|
||||
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
|
||||
"(empty to use global configuration)"));
|
||||
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
|
||||
m_imageFolderEdit->setValidator(validator);
|
||||
|
||||
// Attachment folder.
|
||||
@ -98,7 +102,7 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
|
||||
void VNotebookInfoDialog::handleInputChanged()
|
||||
{
|
||||
QString name = m_nameEdit->text();
|
||||
QString name = m_nameEdit->getEvaluatedText();
|
||||
bool nameOk = !name.isEmpty();
|
||||
bool showWarnLabel = false;
|
||||
|
||||
@ -112,13 +116,29 @@ void VNotebookInfoDialog::handleInputChanged()
|
||||
}
|
||||
}
|
||||
|
||||
QString warnText;
|
||||
if (idx < m_notebooks.size() && m_notebooks[idx] != m_notebook) {
|
||||
nameOk = false;
|
||||
warnText = tr("<span style=\"%1\">WARNING</span>: "
|
||||
"Name (case-insensitive) <span style=\"%2\">%3</span> already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(name);
|
||||
} else if (!VUtils::checkFileNameLegal(name)) {
|
||||
// Check if evaluated name contains illegal characters.
|
||||
nameOk = false;
|
||||
warnText = tr("<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;
|
||||
QString nameConflictText = tr("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. "
|
||||
"Please choose another name.")
|
||||
.arg(g_config->c_warningTextStyle);
|
||||
m_warnLabel->setText(nameConflictText);
|
||||
m_warnLabel->setText(warnText);
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +150,7 @@ void VNotebookInfoDialog::handleInputChanged()
|
||||
|
||||
QString VNotebookInfoDialog::getName() const
|
||||
{
|
||||
return m_nameEdit->text();
|
||||
return m_nameEdit->getEvaluatedText();
|
||||
}
|
||||
|
||||
QString VNotebookInfoDialog::getImageFolder() const
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class VLineEdit;
|
||||
class QDialogButtonBox;
|
||||
class QString;
|
||||
class VNotebook;
|
||||
@ -38,7 +39,7 @@ private:
|
||||
|
||||
const VNotebook *m_notebook;
|
||||
|
||||
QLineEdit *m_nameEdit;
|
||||
VLineEdit *m_nameEdit;
|
||||
QLineEdit *m_pathEdit;
|
||||
QLineEdit *m_imageFolderEdit;
|
||||
// Read-only.
|
||||
|
@ -25,7 +25,6 @@ void VOrphanFileInfoDialog::setupUI()
|
||||
QLabel *fileLabel = new QLabel(m_file->fetchPath());
|
||||
topLayout->addRow(tr("File:"), fileLabel);
|
||||
|
||||
QLabel *imageFolderLabel = new QLabel(tr("Image folder:"));
|
||||
m_imageFolderEdit = new QLineEdit(m_file->getImageFolder());
|
||||
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getImageFolderExt()));
|
||||
@ -33,9 +32,8 @@ void VOrphanFileInfoDialog::setupUI()
|
||||
"of this file.\nIf absolute path is used, "
|
||||
"VNote will not manage those images."
|
||||
"(empty to use global configuration)");
|
||||
imageFolderLabel->setToolTip(imgFolderTip);
|
||||
m_imageFolderEdit->setToolTip(imgFolderTip);
|
||||
topLayout->addRow(imageFolderLabel, m_imageFolderEdit);
|
||||
topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
@ -169,9 +169,6 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
|
||||
m_langCombo->addItem(lang.second, lang.first);
|
||||
}
|
||||
|
||||
QLabel *langLabel = new QLabel(tr("Language:"), this);
|
||||
langLabel->setToolTip(m_langCombo->toolTip());
|
||||
|
||||
// System tray checkbox.
|
||||
m_systemTray = new QCheckBox(tr("System tray"), this);
|
||||
m_systemTray->setToolTip(tr("Minimized to the system tray after closing VNote"
|
||||
@ -181,9 +178,13 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
|
||||
m_systemTray->setEnabled(false);
|
||||
#endif
|
||||
|
||||
// Startup pages.
|
||||
QLayout *startupLayout = setupStartupPagesLayout();
|
||||
|
||||
QFormLayout *optionLayout = new QFormLayout();
|
||||
optionLayout->addRow(langLabel, m_langCombo);
|
||||
optionLayout->addRow(tr("Language:"), m_langCombo);
|
||||
optionLayout->addRow(m_systemTray);
|
||||
optionLayout->addRow(tr("Startup pages:"), startupLayout);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(optionLayout);
|
||||
@ -191,6 +192,60 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
QLayout *VGeneralTab::setupStartupPagesLayout()
|
||||
{
|
||||
m_startupPageTypeCombo = new QComboBox(this);
|
||||
m_startupPageTypeCombo->setToolTip(tr("Restore tabs or open specific notes on startup"));
|
||||
m_startupPageTypeCombo->addItem(tr("None"), (int)StartupPageType::None);
|
||||
m_startupPageTypeCombo->addItem(tr("Continue where you left off"), (int)StartupPageType::ContinueLeftOff);
|
||||
m_startupPageTypeCombo->addItem(tr("Open specific pages"), (int)StartupPageType::SpecificPages);
|
||||
connect(m_startupPageTypeCombo, static_cast<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()
|
||||
{
|
||||
if (!loadLanguage()) {
|
||||
@ -201,6 +256,10 @@ bool VGeneralTab::loadConfiguration()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadStartupPageType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -214,6 +273,10 @@ bool VGeneralTab::saveConfiguration()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveStartupPageType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -264,6 +327,42 @@ bool VGeneralTab::saveSystemTray()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::loadStartupPageType()
|
||||
{
|
||||
StartupPageType type = g_config->getStartupPageType();
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_startupPageTypeCombo->count(); ++i) {
|
||||
if (m_startupPageTypeCombo->itemData(i).toInt() == (int)type) {
|
||||
found = true;
|
||||
m_startupPageTypeCombo->setCurrentIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(found);
|
||||
|
||||
const QStringList &pages = g_config->getStartupPages();
|
||||
m_startupPagesEdit->setPlainText(pages.join("\n"));
|
||||
|
||||
bool pagesEditVisible = type == StartupPageType::SpecificPages;
|
||||
m_startupPagesEdit->setVisible(pagesEditVisible);
|
||||
m_startupPagesAddBtn->setVisible(pagesEditVisible);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VGeneralTab::saveStartupPageType()
|
||||
{
|
||||
StartupPageType type = (StartupPageType)m_startupPageTypeCombo->currentData().toInt();
|
||||
g_config->setStartupPageType(type);
|
||||
|
||||
if (type == StartupPageType::SpecificPages) {
|
||||
QStringList pages = m_startupPagesEdit->toPlainText().split("\n");
|
||||
g_config->setStartupPages(pages);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VReadEditTab::VReadEditTab(QWidget *p_parent)
|
||||
: QWidget(p_parent)
|
||||
{
|
||||
@ -504,28 +603,34 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
|
||||
m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read);
|
||||
m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
|
||||
|
||||
QLabel *openModeLabel = new QLabel(tr("Note open mode:"));
|
||||
openModeLabel->setToolTip(m_openModeCombo->toolTip());
|
||||
|
||||
// Heading sequence.
|
||||
m_headingSequence = new QCheckBox(tr("Heading sequence"));
|
||||
m_headingSequence->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)"));
|
||||
m_headingSequenceCombo = new QComboBox();
|
||||
m_headingSequenceCombo->setToolTip(tr("Base level to start heading sequence"));
|
||||
m_headingSequenceCombo->addItem(tr("1"), 1);
|
||||
m_headingSequenceCombo->addItem(tr("2"), 2);
|
||||
m_headingSequenceCombo->addItem(tr("3"), 3);
|
||||
m_headingSequenceCombo->addItem(tr("4"), 4);
|
||||
m_headingSequenceCombo->addItem(tr("5"), 5);
|
||||
m_headingSequenceCombo->addItem(tr("6"), 6);
|
||||
m_headingSequenceCombo->setEnabled(false);
|
||||
connect(m_headingSequence, &QCheckBox::stateChanged,
|
||||
this, [this](int p_state){
|
||||
this->m_headingSequenceCombo->setEnabled(p_state == Qt::Checked);
|
||||
m_headingSequenceTypeCombo = new QComboBox();
|
||||
m_headingSequenceTypeCombo->setToolTip(tr("Enable auto sequence for all headings (in the form like 1.2.3.4.)"));
|
||||
m_headingSequenceTypeCombo->addItem(tr("Disabled"), (int)HeadingSequenceType::Disabled);
|
||||
m_headingSequenceTypeCombo->addItem(tr("Enabled"), (int)HeadingSequenceType::Enabled);
|
||||
m_headingSequenceTypeCombo->addItem(tr("Enabled for notes only"), (int)HeadingSequenceType::EnabledNoteOnly);
|
||||
|
||||
m_headingSequenceLevelCombo = new QComboBox();
|
||||
m_headingSequenceLevelCombo->setToolTip(tr("Base level to start heading sequence"));
|
||||
m_headingSequenceLevelCombo->addItem(tr("1"), 1);
|
||||
m_headingSequenceLevelCombo->addItem(tr("2"), 2);
|
||||
m_headingSequenceLevelCombo->addItem(tr("3"), 3);
|
||||
m_headingSequenceLevelCombo->addItem(tr("4"), 4);
|
||||
m_headingSequenceLevelCombo->addItem(tr("5"), 5);
|
||||
m_headingSequenceLevelCombo->addItem(tr("6"), 6);
|
||||
m_headingSequenceLevelCombo->setEnabled(false);
|
||||
|
||||
connect(m_headingSequenceTypeCombo, static_cast<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();
|
||||
headingSequenceLayout->addWidget(m_headingSequence);
|
||||
headingSequenceLayout->addWidget(m_headingSequenceCombo);
|
||||
headingSequenceLayout->addWidget(m_headingSequenceTypeCombo);
|
||||
headingSequenceLayout->addWidget(m_headingSequenceLevelCombo);
|
||||
|
||||
// Web Zoom Factor.
|
||||
m_customWebZoom = new QCheckBox(tr("Custom Web zoom factor"), this);
|
||||
@ -553,8 +658,8 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
|
||||
colorColumnLabel->setToolTip(m_colorColumnEdit->toolTip());
|
||||
|
||||
QFormLayout *mainLayout = new QFormLayout();
|
||||
mainLayout->addRow(openModeLabel, m_openModeCombo);
|
||||
mainLayout->addRow(headingSequenceLayout);
|
||||
mainLayout->addRow(tr("Note open mode:"), m_openModeCombo);
|
||||
mainLayout->addRow(tr("Heading sequence:"), headingSequenceLayout);
|
||||
mainLayout->addRow(zoomFactorLayout);
|
||||
mainLayout->addRow(colorColumnLabel, m_colorColumnEdit);
|
||||
|
||||
@ -628,21 +733,28 @@ bool VMarkdownTab::saveOpenMode()
|
||||
|
||||
bool VMarkdownTab::loadHeadingSequence()
|
||||
{
|
||||
bool enabled = g_config->getEnableHeadingSequence();
|
||||
HeadingSequenceType type = g_config->getHeadingSequenceType();
|
||||
int level = g_config->getHeadingSequenceBaseLevel();
|
||||
if (level < 1 || level > 6) {
|
||||
level = 1;
|
||||
}
|
||||
|
||||
m_headingSequence->setChecked(enabled);
|
||||
m_headingSequenceCombo->setCurrentIndex(level - 1);
|
||||
int idx = m_headingSequenceTypeCombo->findData((int)type);
|
||||
Q_ASSERT(idx > -1);
|
||||
m_headingSequenceTypeCombo->setCurrentIndex(idx);
|
||||
m_headingSequenceLevelCombo->setCurrentIndex(level - 1);
|
||||
m_headingSequenceLevelCombo->setEnabled(type != HeadingSequenceType::Disabled);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMarkdownTab::saveHeadingSequence()
|
||||
{
|
||||
g_config->setEnableHeadingSequence(m_headingSequence->isChecked());
|
||||
g_config->setHeadingSequenceBaseLevel(m_headingSequenceCombo->currentData().toInt());
|
||||
QVariant typeData = m_headingSequenceTypeCombo->currentData();
|
||||
Q_ASSERT(typeData.isValid());
|
||||
g_config->setHeadingSequenceType((HeadingSequenceType)typeData.toInt());
|
||||
g_config->setHeadingSequenceBaseLevel(m_headingSequenceLevelCombo->currentData().toInt());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,8 @@ class QCheckBox;
|
||||
class QLineEdit;
|
||||
class QStackedLayout;
|
||||
class QListWidget;
|
||||
class QPlainTextEdit;
|
||||
class QVBoxLayout;
|
||||
|
||||
class VGeneralTab : public QWidget
|
||||
{
|
||||
@ -23,18 +25,32 @@ public:
|
||||
bool saveConfiguration();
|
||||
|
||||
private:
|
||||
QLayout *setupStartupPagesLayout();
|
||||
|
||||
bool loadLanguage();
|
||||
bool saveLanguage();
|
||||
|
||||
bool loadSystemTray();
|
||||
bool saveSystemTray();
|
||||
|
||||
bool loadStartupPageType();
|
||||
bool saveStartupPageType();
|
||||
|
||||
// Language
|
||||
QComboBox *m_langCombo;
|
||||
|
||||
// System tray
|
||||
QCheckBox *m_systemTray;
|
||||
|
||||
// Startup page type.
|
||||
QComboBox *m_startupPageTypeCombo;
|
||||
|
||||
// Startup pages.
|
||||
QPlainTextEdit *m_startupPagesEdit;
|
||||
|
||||
// Startup pages add files button.
|
||||
QPushButton *m_startupPagesAddBtn;
|
||||
|
||||
static const QVector<QString> c_availableLangs;
|
||||
};
|
||||
|
||||
@ -101,8 +117,8 @@ public:
|
||||
QComboBox *m_openModeCombo;
|
||||
|
||||
// Whether enable heading sequence.
|
||||
QCheckBox *m_headingSequence;
|
||||
QComboBox *m_headingSequenceCombo;
|
||||
QComboBox *m_headingSequenceTypeCombo;
|
||||
QComboBox *m_headingSequenceLevelCombo;
|
||||
|
||||
// Web zoom factor.
|
||||
QCheckBox *m_customWebZoom;
|
||||
|
26
src/main.cpp
@ -13,7 +13,6 @@
|
||||
#include "vconfigmanager.h"
|
||||
|
||||
VConfigManager *g_config;
|
||||
VMainWindow *g_mainWin;
|
||||
|
||||
#if defined(QT_NO_DEBUG)
|
||||
QFile g_logFile;
|
||||
@ -116,22 +115,10 @@ int main(int argc, char *argv[])
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// The file path passed via command line arguments.
|
||||
QStringList filePaths;
|
||||
QStringList args = app.arguments();
|
||||
for (int i = 1; i < args.size(); ++i) {
|
||||
if (QFileInfo::exists(args[i])) {
|
||||
QString filePath = args[i];
|
||||
QFileInfo fi(filePath);
|
||||
if (fi.isFile()) {
|
||||
// Need to use absolute path here since VNote may be launched
|
||||
// in different working directory.
|
||||
filePath = QDir::cleanPath(fi.absoluteFilePath());
|
||||
filePaths.append(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
QStringList filePaths = VUtils::filterFilePathsToOpen(app.arguments().mid(1));
|
||||
|
||||
qDebug() << "command line arguments" << args;
|
||||
qDebug() << "command line arguments" << app.arguments();
|
||||
qDebug() << "files to open from arguments" << filePaths;
|
||||
|
||||
if (!canRun) {
|
||||
// Ask another instance to open files passed in.
|
||||
@ -169,7 +156,6 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
VMainWindow w(&guard);
|
||||
g_mainWin = &w;
|
||||
QString style = VUtils::readFileFromDisk(":/resources/vnote.qss");
|
||||
if (!style.isEmpty()) {
|
||||
VUtils::processStyle(style, w.getPalette());
|
||||
@ -178,7 +164,11 @@ int main(int argc, char *argv[])
|
||||
|
||||
w.show();
|
||||
|
||||
w.openExternalFiles(filePaths);
|
||||
w.openStartupPages();
|
||||
|
||||
w.openFiles(filePaths);
|
||||
|
||||
w.promptNewNotebookIfEmpty();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
@ -49,13 +49,17 @@ Save current changes and exit edit mode.
|
||||
|
||||
#### Text Editing
|
||||
- `Ctrl+B`
|
||||
Insert bold. Press `Ctrl+B` again to exit. Current selected text will be changed to bold if exist.
|
||||
Insert bold. Press `Ctrl+B` again to exit. Current selected text will be changed to bold if exists.
|
||||
- `Ctrl+I`
|
||||
Insert italic. Press `Ctrl+I` again to exit. Current selected text will be changed to italic if exist.
|
||||
Insert italic. Press `Ctrl+I` again to exit. Current selected text will be changed to italic if exists.
|
||||
- `Ctrl+D`
|
||||
Insert strikethrought. Press `Ctrl+D` again to exit. Current selected text will be changed to strikethrough if exist.
|
||||
Insert strikethrought. Press `Ctrl+D` again to exit. Current selected text will be changed to strikethrough if exists.
|
||||
- `Ctrl+O`
|
||||
Insert inline code. Press `Ctrl+O` again to exit. Current selected text will be changed to inline code if exist.
|
||||
Insert inline code. Press `Ctrl+O` again to exit. Current selected text will be changed to inline code if exists.
|
||||
- `Ctrl+M`
|
||||
Insert fenced code block. Press `Ctrl+M` again to exit. Current selected text will be wrapped into a code block if exists.
|
||||
- `Ctrl+L`
|
||||
Insert link.
|
||||
- `Ctrl+H`
|
||||
Backspace. Delete a character backward.
|
||||
- `Ctrl+W`
|
||||
@ -63,7 +67,7 @@ Delete all the characters from current cursor to the first space backward.
|
||||
- `Ctrl+U`
|
||||
Delete all the characters from current cursor to the beginning of current line.
|
||||
- `Ctrl+<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`
|
||||
Increase or decrease the indentation. If any text is selected, the indentation will operate on all these selected lines.
|
||||
- `Shift+Enter`
|
||||
@ -80,42 +84,102 @@ Expand the selection to the beginning or end of current line.
|
||||
Expand the selection to the beginning or end of current note.
|
||||
|
||||
## Custom Shortcuts
|
||||
VNote supports customing some standard shortcuts, though it is not recommended. VNote stores shortcuts' configuration information in the `[shortcuts]` section of user configuration file `vnote.ini`.
|
||||
VNote supports customing some standard shortcuts, though it is not recommended. VNote stores shortcuts' configuration information in the `[shortcuts]` and `[captain_mode_shortcuts]` sections of user configuration file `vnote.ini`.
|
||||
|
||||
For example, the default configruation may look like this:
|
||||
|
||||
```ini
|
||||
[shortcuts]
|
||||
1\operation=NewNote
|
||||
1\keysequence=Ctrl+N
|
||||
2\operation=SaveNote
|
||||
2\keysequence=Ctrl+S
|
||||
3\operation=SaveAndRead
|
||||
3\keysequence=Ctrl+T
|
||||
4\operation=EditNote
|
||||
4\keysequence=Ctrl+W
|
||||
5\operation=CloseNote
|
||||
5\keysequence=
|
||||
6\operation=Find
|
||||
6\keysequence=Ctrl+F
|
||||
7\operation=FindNext
|
||||
7\keysequence=F3
|
||||
8\operation=FindPrevious
|
||||
8\keysequence=Shift+F3
|
||||
size=8
|
||||
; Define shortcuts here, with each item in the form "operation=keysequence".
|
||||
; Leave keysequence empty to disable the shortcut of an operation.
|
||||
; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
|
||||
; Ctrl+Q is reserved for quitting VNote.
|
||||
|
||||
; Leader key of Captain mode
|
||||
CaptainMode=Ctrl+E
|
||||
; Create a note in current folder
|
||||
NewNote=Ctrl+Alt+N
|
||||
; Save current note
|
||||
SaveNote=Ctrl+S
|
||||
; Save changes and enter read mode
|
||||
SaveAndRead=Ctrl+T
|
||||
; Edit current note
|
||||
EditNote=Ctrl+W
|
||||
; Close current note
|
||||
CloseNote=
|
||||
; Open file/replace dialog
|
||||
Find=Ctrl+F
|
||||
; Find next occurence
|
||||
FindNext=F3
|
||||
; Find previous occurence
|
||||
FindPrevious=Shift+F3
|
||||
|
||||
[captain_mode_shortcuts]
|
||||
; Define shortcuts in Captain mode here.
|
||||
; There shortcuts are the sub-sequence after the CaptainMode key sequence
|
||||
; in [shortcuts].
|
||||
|
||||
; Enter Navigation mode
|
||||
NavigationMode=W
|
||||
; Show attachment list of current note
|
||||
AttachmentList=A
|
||||
; Locate to the folder of current note
|
||||
LocateCurrentFile=D
|
||||
; Toggle Expand mode
|
||||
ExpandMode=E
|
||||
; Alternate one/two panels view
|
||||
OnePanelView=P
|
||||
; Discard changes and enter read mode
|
||||
DiscardAndRead=Q
|
||||
; Toggle Tools dock widget
|
||||
ToolsDock=T
|
||||
; Close current note
|
||||
CloseNote=X
|
||||
; Show shortcuts help document
|
||||
ShortcutsHelp=?
|
||||
; Flush the log file
|
||||
FlushLogFile=";"
|
||||
; Show opened files list
|
||||
OpenedFileList=F
|
||||
; Activate the ith tab
|
||||
ActivateTab1=1
|
||||
ActivateTab2=2
|
||||
ActivateTab3=3
|
||||
ActivateTab4=4
|
||||
ActivateTab5=5
|
||||
ActivateTab6=6
|
||||
ActivateTab7=7
|
||||
ActivateTab8=8
|
||||
ActivateTab9=9
|
||||
; Alternate between current and last tab
|
||||
AlternateTab=0
|
||||
; Activate next tab
|
||||
ActivateNextTab=J
|
||||
; Activate previous tab
|
||||
ActivatePreviousTab=K
|
||||
; Activate the window split on the left
|
||||
ActivateSplitLeft=H
|
||||
; Activate the window split on the right
|
||||
ActivateSplitRight=L
|
||||
; Move current tab one split left
|
||||
MoveTabSplitLeft=Shift+H
|
||||
; Move current tab one split right
|
||||
MoveTabSplitRight=Shift+L
|
||||
; Create a vertical split
|
||||
VerticalSplit=V
|
||||
; Remove current split
|
||||
RemoveSplit=R
|
||||
```
|
||||
|
||||
`size=8` tells VNote that there are 8 shotcuts defined here, with each beginning with the number sequence. You could change the `keysequence` value to change the default key sequence of a specified operation. Leave the `keysequence` empty (`keysequence=`) to disable shortcut for that operation.
|
||||
Each item is in the form `operation=keysequence`, with `keysequence` empty to disable shortcuts for that operation.
|
||||
|
||||
Pay attention that `Ctrl+E` is reserved for *Captain Mode* and `Ctrl+Q` is reserved for quitting VNote.
|
||||
Pay attention that `Ctrl+Q` is reserved for quitting VNote.
|
||||
|
||||
# Captain Mode
|
||||
To efficiently utilize the shortcuts, VNote supports the **Captain Mode**.
|
||||
|
||||
Press the leader key `Ctrl+E`, then VNote will enter the Captain Mode, within which VNote supports more efficient shortcuts.
|
||||
|
||||
By the way, in this mode, `Ctrl+W` and `W` is equivalent, thus pressing `Ctrl+E+W` equals to `Ctrl+E W`.
|
||||
|
||||
- `E`
|
||||
Toggle expanding the edit area.
|
||||
- `P`
|
||||
|
@ -56,6 +56,10 @@
|
||||
插入删除线;再次按`Ctrl+D`退出。如果已经选择文本,则将当前选择文本改为删除线。
|
||||
- `Ctrl+O`
|
||||
插入行内代码;再次按`Ctrl+O`退出。如果已经选择文本,则将当前选择文本改为行内代码。
|
||||
- `Ctrl+M`
|
||||
插入代码块;再次按`Ctrl+M`退出。如果已经选择文本,则将当前选择文本嵌入到代码块中。
|
||||
- `Ctrl+L`
|
||||
插入链接。
|
||||
- `Ctrl+H`
|
||||
退格键,向前删除一个字符。
|
||||
- `Ctrl+W`
|
||||
@ -80,43 +84,103 @@
|
||||
扩展选定到笔记开始或结尾处。
|
||||
|
||||
## 自定义快捷键
|
||||
VNote支持自定义部分标准快捷键(但并不建议这么做)。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]`小节。
|
||||
VNote支持自定义部分标准快捷键(但并不建议这么做)。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]`和`[captain_mode_shortcuts]`两个小节。
|
||||
|
||||
例如,默认的配置可能是这样子的:
|
||||
|
||||
|
||||
```ini
|
||||
[shortcuts]
|
||||
1\operation=NewNote
|
||||
1\keysequence=Ctrl+N
|
||||
2\operation=SaveNote
|
||||
2\keysequence=Ctrl+S
|
||||
3\operation=SaveAndRead
|
||||
3\keysequence=Ctrl+T
|
||||
4\operation=EditNote
|
||||
4\keysequence=Ctrl+W
|
||||
5\operation=CloseNote
|
||||
5\keysequence=
|
||||
6\operation=Find
|
||||
6\keysequence=Ctrl+F
|
||||
7\operation=FindNext
|
||||
7\keysequence=F3
|
||||
8\operation=FindPrevious
|
||||
8\keysequence=Shift+F3
|
||||
size=8
|
||||
; Define shortcuts here, with each item in the form "operation=keysequence".
|
||||
; Leave keysequence empty to disable the shortcut of an operation.
|
||||
; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
|
||||
; Ctrl+Q is reserved for quitting VNote.
|
||||
|
||||
; Leader key of Captain mode
|
||||
CaptainMode=Ctrl+E
|
||||
; Create a note in current folder
|
||||
NewNote=Ctrl+Alt+N
|
||||
; Save current note
|
||||
SaveNote=Ctrl+S
|
||||
; Save changes and enter read mode
|
||||
SaveAndRead=Ctrl+T
|
||||
; Edit current note
|
||||
EditNote=Ctrl+W
|
||||
; Close current note
|
||||
CloseNote=
|
||||
; Open file/replace dialog
|
||||
Find=Ctrl+F
|
||||
; Find next occurence
|
||||
FindNext=F3
|
||||
; Find previous occurence
|
||||
FindPrevious=Shift+F3
|
||||
|
||||
[captain_mode_shortcuts]
|
||||
; Define shortcuts in Captain mode here.
|
||||
; There shortcuts are the sub-sequence after the CaptainMode key sequence
|
||||
; in [shortcuts].
|
||||
|
||||
; Enter Navigation mode
|
||||
NavigationMode=W
|
||||
; Show attachment list of current note
|
||||
AttachmentList=A
|
||||
; Locate to the folder of current note
|
||||
LocateCurrentFile=D
|
||||
; Toggle Expand mode
|
||||
ExpandMode=E
|
||||
; Alternate one/two panels view
|
||||
OnePanelView=P
|
||||
; Discard changes and enter read mode
|
||||
DiscardAndRead=Q
|
||||
; Toggle Tools dock widget
|
||||
ToolsDock=T
|
||||
; Close current note
|
||||
CloseNote=X
|
||||
; Show shortcuts help document
|
||||
ShortcutsHelp=?
|
||||
; Flush the log file
|
||||
FlushLogFile=";"
|
||||
; Show opened files list
|
||||
OpenedFileList=F
|
||||
; Activate the ith tab
|
||||
ActivateTab1=1
|
||||
ActivateTab2=2
|
||||
ActivateTab3=3
|
||||
ActivateTab4=4
|
||||
ActivateTab5=5
|
||||
ActivateTab6=6
|
||||
ActivateTab7=7
|
||||
ActivateTab8=8
|
||||
ActivateTab9=9
|
||||
; Alternate between current and last tab
|
||||
AlternateTab=0
|
||||
; Activate next tab
|
||||
ActivateNextTab=J
|
||||
; Activate previous tab
|
||||
ActivatePreviousTab=K
|
||||
; Activate the window split on the left
|
||||
ActivateSplitLeft=H
|
||||
; Activate the window split on the right
|
||||
ActivateSplitRight=L
|
||||
; Move current tab one split left
|
||||
MoveTabSplitLeft=Shift+H
|
||||
; Move current tab one split right
|
||||
MoveTabSplitRight=Shift+L
|
||||
; Create a vertical split
|
||||
VerticalSplit=V
|
||||
; Remove current split
|
||||
RemoveSplit=R
|
||||
```
|
||||
|
||||
`size=8` 告诉VNote这里定义了8组快捷键,每组快捷键都以一个数字序号开始。通过改变每组快捷键中`keysequence`的值来改变某个操作的默认快捷键。将`keysequence`设置为空(`keysequence=`)则会禁用该操作的任何快捷键。
|
||||
每一项配置的形式为`操作=按键序列`。如果`按键序列`为空,则表示禁用该操作的快捷键。
|
||||
|
||||
注意,`Ctrl+E`保留作为*舰长模式*的前导键,`Ctrl+Q`保留为退出VNote。
|
||||
注意,`Ctrl+Q`保留为退出VNote。
|
||||
|
||||
# 舰长模式
|
||||
为了更有效地利用快捷键,VNote支持 **舰长模式**。
|
||||
|
||||
按前导键`Ctrl+E`后,VNote会进入舰长模式。在舰长模式中,VNote会支持更多高效的快捷操作。
|
||||
|
||||
另外,在该模式中,`Ctrl+W`和`W`是等效的,因此,可以`Ctrl+E+W`来实现`Ctrl+E W`的操作。
|
||||
|
||||
- `E`
|
||||
是否扩展编辑区域。
|
||||
- `P`
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 662 B |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 930 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.9 KiB |
6
src/resources/icons/code_block.svg
Normal 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 |
10
src/resources/icons/compact_mode.svg
Normal 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 |
7
src/resources/icons/heading_sequence.svg
Normal 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 |
15
src/resources/icons/link.svg
Normal 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 |
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 361 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 5.8 KiB |
@ -1,17 +1 @@
|
||||
<?xml version="1.0"?>
|
||||
<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>
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
src/resources/icons/vnote_update.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
@ -1,17 +1 @@
|
||||
<?xml version="1.0"?>
|
||||
<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>
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
@ -97,7 +97,8 @@ insert_title_from_note_name=true
|
||||
note_open_mode=0
|
||||
|
||||
; Whether auto generate heading sequence
|
||||
enable_heading_sequence=false
|
||||
; 0 - Disabled, 1 - Enabled, 2 - Enabled only for notes
|
||||
heading_sequence_type=0
|
||||
|
||||
; Heading sequence base level
|
||||
heading_sequence_base_level=1
|
||||
@ -135,13 +136,26 @@ confirm_reload_folder=true
|
||||
; Whether double click on a tab to close it
|
||||
double_click_close_tab=true
|
||||
|
||||
; Whether put folder and note panel in one vertical column
|
||||
enable_compact_mode=false
|
||||
|
||||
; Whether enable tools dock widget
|
||||
tools_dock_checked=true
|
||||
|
||||
; Pages to open on startup
|
||||
; 0 - none; 1 - Continue where you left off; 2 - specific pages
|
||||
startup_page_type=0
|
||||
|
||||
; Specific pages to open on startup when startup_page_type is 2
|
||||
; A list of file path separated by ,
|
||||
; Notice: should escape \ by \\
|
||||
; C:\users\vnote\vnote.md -> C:\\users\\vnote\\vnote.md
|
||||
startup_pages=
|
||||
|
||||
[web]
|
||||
; Location and configuration for Mathjax
|
||||
mathjax_javascript=https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML
|
||||
|
||||
[session]
|
||||
tools_dock_checked=true
|
||||
|
||||
[predefined_colors]
|
||||
1\name=White
|
||||
1\rgb=EEEEEE
|
||||
@ -154,25 +168,84 @@ tools_dock_checked=true
|
||||
size=4
|
||||
|
||||
[shortcuts]
|
||||
; Define shortcuts here, with each item in the form "operation->keysequence".
|
||||
; Define shortcuts here, with each item in the form "operation=keysequence".
|
||||
; Leave keysequence empty to disable the shortcut of an operation.
|
||||
; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
|
||||
; Ctrl+E is reserved for Captain Mode.
|
||||
; Ctrl+Q is reserved for quitting VNote.
|
||||
1\operation=NewNote
|
||||
1\keysequence=Ctrl+Alt+N
|
||||
2\operation=SaveNote
|
||||
2\keysequence=Ctrl+S
|
||||
3\operation=SaveAndRead
|
||||
3\keysequence=Ctrl+T
|
||||
4\operation=EditNote
|
||||
4\keysequence=Ctrl+W
|
||||
5\operation=CloseNote
|
||||
5\keysequence=
|
||||
6\operation=Find
|
||||
6\keysequence=Ctrl+F
|
||||
7\operation=FindNext
|
||||
7\keysequence=F3
|
||||
8\operation=FindPrevious
|
||||
8\keysequence=Shift+F3
|
||||
size=8
|
||||
|
||||
; Leader key of Captain mode
|
||||
CaptainMode=Ctrl+E
|
||||
; Create a note in current folder
|
||||
NewNote=Ctrl+Alt+N
|
||||
; Save current note
|
||||
SaveNote=Ctrl+S
|
||||
; Save changes and enter read mode
|
||||
SaveAndRead=Ctrl+T
|
||||
; Edit current note
|
||||
EditNote=Ctrl+W
|
||||
; Close current note
|
||||
CloseNote=
|
||||
; Open file/replace dialog
|
||||
Find=Ctrl+F
|
||||
; Find next occurence
|
||||
FindNext=F3
|
||||
; Find previous occurence
|
||||
FindPrevious=Shift+F3
|
||||
|
||||
[captain_mode_shortcuts]
|
||||
; Define shortcuts in Captain mode here.
|
||||
; There shortcuts are the sub-sequence after the CaptainMode key sequence
|
||||
; in [shortcuts].
|
||||
|
||||
; Enter Navigation mode
|
||||
NavigationMode=W
|
||||
; Show attachment list of current note
|
||||
AttachmentList=A
|
||||
; Locate to the folder of current note
|
||||
LocateCurrentFile=D
|
||||
; Toggle Expand mode
|
||||
ExpandMode=E
|
||||
; Alternate one/two panels view
|
||||
OnePanelView=P
|
||||
; Discard changes and enter read mode
|
||||
DiscardAndRead=Q
|
||||
; Toggle Tools dock widget
|
||||
ToolsDock=T
|
||||
; Close current note
|
||||
CloseNote=X
|
||||
; Show shortcuts help document
|
||||
ShortcutsHelp=?
|
||||
; Flush the log file
|
||||
FlushLogFile=";"
|
||||
; Show opened files list
|
||||
OpenedFileList=F
|
||||
; Activate the ith tab
|
||||
ActivateTab1=1
|
||||
ActivateTab2=2
|
||||
ActivateTab3=3
|
||||
ActivateTab4=4
|
||||
ActivateTab5=5
|
||||
ActivateTab6=6
|
||||
ActivateTab7=7
|
||||
ActivateTab8=8
|
||||
ActivateTab9=9
|
||||
; Alternate between current and last tab
|
||||
AlternateTab=0
|
||||
; Activate next tab
|
||||
ActivateNextTab=J
|
||||
; Activate previous tab
|
||||
ActivatePreviousTab=K
|
||||
; Activate the window split on the left
|
||||
ActivateSplitLeft=H
|
||||
; Activate the window split on the right
|
||||
ActivateSplitRight=L
|
||||
; Move current tab one split left
|
||||
MoveTabSplitLeft=Shift+H
|
||||
; Move current tab one split right
|
||||
MoveTabSplitRight=Shift+L
|
||||
; Create a vertical split
|
||||
VerticalSplit=V
|
||||
; Remove current split
|
||||
RemoveSplit=R
|
||||
; Evaluate selected text or cursor word as magic words
|
||||
MagicWord=M
|
||||
|
@ -9,11 +9,11 @@ QPushButton[CornerBtn="true"]::menu-indicator {
|
||||
image: none;
|
||||
}
|
||||
|
||||
QPushButton[CornerBtn="true"]::hover {
|
||||
QPushButton[CornerBtn="true"]:hover {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
|
||||
QPushButton[CornerBtn="true"]::focus {
|
||||
QPushButton[CornerBtn="true"]:focus {
|
||||
background-color: @focus-color;
|
||||
}
|
||||
|
||||
@ -25,11 +25,11 @@ QPushButton[StatusBtn="true"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
QPushButton[StatusBtn="true"]::hover {
|
||||
QPushButton[StatusBtn="true"]:hover {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
|
||||
QPushButton[StatusBtn="true"]::focus {
|
||||
QPushButton[StatusBtn="true"]:focus {
|
||||
background-color: @focus-color;
|
||||
}
|
||||
|
||||
@ -40,11 +40,11 @@ QPushButton[FlatBtn="true"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
QPushButton[FlatBtn="true"]::hover {
|
||||
QPushButton[FlatBtn="true"]:hover {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
|
||||
QPushButton[FlatBtn="true"]::focus {
|
||||
QPushButton[FlatBtn="true"]:focus {
|
||||
background-color: @focus-color;
|
||||
}
|
||||
|
||||
@ -56,11 +56,11 @@ QPushButton[SelectionBtn="true"] {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
QPushButton[SelectionBtn="true"]::hover {
|
||||
QPushButton[SelectionBtn="true"]:hover {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
|
||||
QPushButton[SelectionBtn="true"]::focus {
|
||||
QPushButton[SelectionBtn="true"]:focus {
|
||||
background-color: @focus-color;
|
||||
}
|
||||
|
||||
@ -71,11 +71,11 @@ QPushButton[TitleBtn="true"] {
|
||||
background-color: @base-color;
|
||||
}
|
||||
|
||||
QPushButton[TitleBtn="true"]::hover {
|
||||
QPushButton[TitleBtn="true"]:hover {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
|
||||
QPushButton[TitleBtn="true"]::focus {
|
||||
QPushButton[TitleBtn="true"]:focus {
|
||||
background-color: @focus-color;
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ QPushButton[DangerBtn="true"] {
|
||||
background-color: #d9534f;
|
||||
}
|
||||
|
||||
QPushButton[DangerBtn="true"]::hover {
|
||||
QPushButton[DangerBtn="true"]:hover {
|
||||
color: #fff;
|
||||
border-color: #ac2925;
|
||||
background-color: #c9302c;
|
||||
@ -95,10 +95,14 @@ QToolBar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
QToolButton::hover {
|
||||
QToolButton:hover {
|
||||
background-color: @hover-color;
|
||||
}
|
||||
|
||||
/* Override default shift behavior */
|
||||
QToolButton::menu-arrow:open {
|
||||
}
|
||||
|
||||
QMenuBar {
|
||||
border: none;
|
||||
}
|
||||
@ -141,7 +145,7 @@ QComboBox#NotebookSelector {
|
||||
icon-size: 30px;
|
||||
}
|
||||
|
||||
QComboBox#NotebookSelector::focus {
|
||||
QComboBox#NotebookSelector:focus {
|
||||
background-color: @focus-color;
|
||||
}
|
||||
|
||||
|
16
src/src.pro
@ -44,7 +44,6 @@ SOURCES += main.cpp\
|
||||
veditwindow.cpp \
|
||||
vedittab.cpp \
|
||||
voutline.cpp \
|
||||
vtoc.cpp \
|
||||
vsingleinstanceguard.cpp \
|
||||
vdirectory.cpp \
|
||||
vfile.cpp \
|
||||
@ -77,7 +76,12 @@ SOURCES += main.cpp\
|
||||
dialog/vconfirmdeletiondialog.cpp \
|
||||
vnotefile.cpp \
|
||||
vattachmentlist.cpp \
|
||||
dialog/vsortdialog.cpp
|
||||
dialog/vsortdialog.cpp \
|
||||
vfilesessioninfo.cpp \
|
||||
vtableofcontent.cpp \
|
||||
utils/vmetawordmanager.cpp \
|
||||
vlineedit.cpp \
|
||||
dialog/vinsertlinkdialog.cpp
|
||||
|
||||
HEADERS += vmainwindow.h \
|
||||
vdirectorytree.h \
|
||||
@ -107,7 +111,6 @@ HEADERS += vmainwindow.h \
|
||||
veditwindow.h \
|
||||
vedittab.h \
|
||||
voutline.h \
|
||||
vtoc.h \
|
||||
vsingleinstanceguard.h \
|
||||
vdirectory.h \
|
||||
vfile.h \
|
||||
@ -142,7 +145,12 @@ HEADERS += vmainwindow.h \
|
||||
dialog/vconfirmdeletiondialog.h \
|
||||
vnotefile.h \
|
||||
vattachmentlist.h \
|
||||
dialog/vsortdialog.h
|
||||
dialog/vsortdialog.h \
|
||||
vfilesessioninfo.h \
|
||||
vtableofcontent.h \
|
||||
utils/vmetawordmanager.h \
|
||||
vlineedit.h \
|
||||
dialog/vinsertlinkdialog.h
|
||||
|
||||
RESOURCES += \
|
||||
vnote.qrc \
|
||||
|
@ -48,7 +48,7 @@ bool VEditUtils::insertBlockWithIndent(QTextCursor &p_cursor)
|
||||
{
|
||||
V_ASSERT(!p_cursor.hasSelection());
|
||||
p_cursor.insertBlock();
|
||||
return indentBlockAsPreviousBlock(p_cursor);
|
||||
return indentBlockAsBlock(p_cursor, false);
|
||||
}
|
||||
|
||||
bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor)
|
||||
@ -85,21 +85,16 @@ bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor)
|
||||
|
||||
}
|
||||
|
||||
bool VEditUtils::indentBlockAsPreviousBlock(QTextCursor &p_cursor)
|
||||
bool VEditUtils::indentBlockAsBlock(QTextCursor &p_cursor, bool p_next)
|
||||
{
|
||||
bool changed = false;
|
||||
QTextBlock block = p_cursor.block();
|
||||
if (block.blockNumber() == 0) {
|
||||
// The first block.
|
||||
QTextBlock refBlock = p_next ? block.next() : block.previous();
|
||||
if (!refBlock.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextBlock preBlock = block.previous();
|
||||
QString text = preBlock.text();
|
||||
QRegExp regExp("(^\\s*)");
|
||||
regExp.indexIn(text);
|
||||
V_ASSERT(regExp.captureCount() == 1);
|
||||
QString leadingSpaces = regExp.capturedTexts()[1];
|
||||
QString leadingSpaces = fetchIndentSpaces(refBlock);
|
||||
|
||||
moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::MoveAnchor);
|
||||
if (!p_cursor.atBlockStart()) {
|
||||
@ -689,3 +684,130 @@ bool VEditUtils::needToCancelAutoIndent(int p_autoIndentPos, const QTextCursor &
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void VEditUtils::insertTitleMark(QTextCursor &p_cursor,
|
||||
const QTextBlock &p_block,
|
||||
int p_level)
|
||||
{
|
||||
if (!p_block.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(p_level >= 1 && p_level <= 6);
|
||||
|
||||
bool needInsert = true;
|
||||
|
||||
p_cursor.setPosition(p_block.position());
|
||||
|
||||
// Test if this block contains title marks.
|
||||
QRegExp headerReg(VUtils::c_headerRegExp);
|
||||
QString text = p_block.text();
|
||||
bool matched = headerReg.exactMatch(text);
|
||||
if (matched) {
|
||||
int level = headerReg.cap(1).length();
|
||||
if (level == p_level) {
|
||||
needInsert = false;
|
||||
} else {
|
||||
// Remove the title mark.
|
||||
p_cursor.movePosition(QTextCursor::NextCharacter,
|
||||
QTextCursor::KeepAnchor,
|
||||
level);
|
||||
p_cursor.removeSelectedText();
|
||||
}
|
||||
}
|
||||
|
||||
// Insert titleMark + " " at the front of the block.
|
||||
if (needInsert) {
|
||||
// Remove the spaces at front.
|
||||
// insertText() will remove the selection.
|
||||
moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::KeepAnchor);
|
||||
|
||||
// Insert.
|
||||
const QString titleMark(p_level, '#');
|
||||
p_cursor.insertText(titleMark + " ");
|
||||
}
|
||||
|
||||
// Go to the end of this block.
|
||||
p_cursor.movePosition(QTextCursor::EndOfBlock);
|
||||
}
|
||||
|
||||
void VEditUtils::findCurrentWord(QTextCursor p_cursor,
|
||||
int &p_start,
|
||||
int &p_end)
|
||||
{
|
||||
QString text = p_cursor.block().text();
|
||||
int pib = p_cursor.positionInBlock();
|
||||
|
||||
if (pib < text.size() && text[pib].isSpace()) {
|
||||
p_start = p_end = p_cursor.position();
|
||||
return;
|
||||
}
|
||||
|
||||
p_cursor.movePosition(QTextCursor::StartOfWord);
|
||||
p_start = p_cursor.position();
|
||||
p_cursor.movePosition(QTextCursor::EndOfWord);
|
||||
p_end = p_cursor.position();
|
||||
}
|
||||
|
||||
void VEditUtils::findCurrentWORD(const QTextCursor &p_cursor,
|
||||
int &p_start,
|
||||
int &p_end)
|
||||
{
|
||||
QTextBlock block = p_cursor.block();
|
||||
QString text = block.text();
|
||||
int pib = p_cursor.positionInBlock();
|
||||
|
||||
if (pib < text.size() && text[pib].isSpace()) {
|
||||
p_start = p_end = p_cursor.position();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the start.
|
||||
p_start = 0;
|
||||
for (int i = pib - 1; i >= 0; --i) {
|
||||
if (text[i].isSpace()) {
|
||||
p_start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the end.
|
||||
p_end = block.length() - 1;
|
||||
for (int i = pib; i < text.size(); ++i) {
|
||||
if (text[i].isSpace()) {
|
||||
p_end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p_start += block.position();
|
||||
p_end += block.position();
|
||||
}
|
||||
|
||||
QString VEditUtils::fetchIndentSpaces(const QTextBlock &p_block)
|
||||
{
|
||||
QString text = p_block.text();
|
||||
QRegExp regExp("(^\\s*)");
|
||||
regExp.indexIn(text);
|
||||
Q_ASSERT(regExp.captureCount() == 1);
|
||||
return regExp.capturedTexts()[1];
|
||||
}
|
||||
|
||||
void VEditUtils::insertBlock(QTextCursor &p_cursor,
|
||||
bool p_above)
|
||||
{
|
||||
p_cursor.movePosition(p_above ? QTextCursor::StartOfBlock
|
||||
: QTextCursor::EndOfBlock,
|
||||
QTextCursor::MoveAnchor,
|
||||
1);
|
||||
|
||||
p_cursor.insertBlock();
|
||||
|
||||
if (p_above) {
|
||||
p_cursor.movePosition(QTextCursor::PreviousBlock,
|
||||
QTextCursor::MoveAnchor,
|
||||
1);
|
||||
}
|
||||
|
||||
p_cursor.movePosition(QTextCursor::EndOfBlock);
|
||||
}
|
||||
|
@ -24,10 +24,11 @@ public:
|
||||
// Need to call setTextCursor() to make it take effect.
|
||||
static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
|
||||
QTextCursor::MoveMode p_mode);
|
||||
// Indent current block as previous block.
|
||||
// Indent current block as next/previous block.
|
||||
// Return true if some changes have been made.
|
||||
// @p_cursor will be placed at the position after inserting leading spaces.
|
||||
static bool indentBlockAsPreviousBlock(QTextCursor &p_cursor);
|
||||
// @p_next: indent as next block or previous block.
|
||||
static bool indentBlockAsBlock(QTextCursor &p_cursor, bool p_next);
|
||||
|
||||
// Returns true if two blocks has the same indent.
|
||||
static bool hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb);
|
||||
@ -132,7 +133,38 @@ public:
|
||||
|
||||
// Check if we need to cancel auto indent.
|
||||
// @p_autoIndentPos: the position of the cursor after auto indent.
|
||||
static bool needToCancelAutoIndent(int p_autoIndentPos, const QTextCursor &p_cursor);
|
||||
static bool needToCancelAutoIndent(int p_autoIndentPos,
|
||||
const QTextCursor &p_cursor);
|
||||
|
||||
// Insert title Mark at level @p_level in front of block @p_block
|
||||
// If there already exists title marks, remove it first.
|
||||
// Move cursor at the end of the block after insertion.
|
||||
static void insertTitleMark(QTextCursor &p_cursor,
|
||||
const QTextBlock &p_block,
|
||||
int p_level);
|
||||
|
||||
// Find the start and end of the word @p_cursor locates in (within a single block).
|
||||
// @p_start and @p_end will be the global position of the start and end of the word.
|
||||
// @p_start will equals to @p_end if @p_cursor is a space.
|
||||
static void findCurrentWord(QTextCursor p_cursor,
|
||||
int &p_start,
|
||||
int &p_end);
|
||||
|
||||
// Find the start and end of the WORD @p_cursor locates in (within a single block).
|
||||
// @p_start and @p_end will be the global position of the start and end of the WORD.
|
||||
// @p_start will equals to @p_end if @p_cursor is a space.
|
||||
// Attention: www|sss will select www, which is different from findCurrentWord().
|
||||
static void findCurrentWORD(const QTextCursor &p_cursor,
|
||||
int &p_start,
|
||||
int &p_end);
|
||||
|
||||
// Return the leading spaces of @p_block.
|
||||
static QString fetchIndentSpaces(const QTextBlock &p_block);
|
||||
|
||||
// Insert a block above/below current block. Move the cursor to the start of
|
||||
// the new block after insertion.
|
||||
static void insertBlock(QTextCursor &p_cursor,
|
||||
bool p_above);
|
||||
|
||||
private:
|
||||
VEditUtils() {}
|
||||
|
596
src/utils/vmetawordmanager.cpp
Normal 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;
|
||||
}
|
205
src/utils/vmetawordmanager.h
Normal 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
|
@ -33,6 +33,8 @@ QVector<QPair<QString, QString>> VUtils::s_availableLanguages;
|
||||
|
||||
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)");
|
||||
|
||||
const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]*");
|
||||
|
||||
const QString VUtils::c_fileNameRegExp = QString("[^\\\\/:\\*\\?\"<>\\|]*");
|
||||
|
||||
const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
|
||||
@ -43,7 +45,7 @@ const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfff
|
||||
|
||||
const QString VUtils::c_headerRegExp = QString("^(#{1,6})\\s+(((\\d+\\.)+(?=\\s))?\\s?\\S.*)\\s*$");
|
||||
|
||||
const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s?)\\S.*\\s*$");
|
||||
const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s?)($|\\S.*\\s*$)");
|
||||
|
||||
void VUtils::initAvailableLanguage()
|
||||
{
|
||||
@ -104,10 +106,10 @@ QRgb VUtils::QRgbFromString(const QString &str)
|
||||
return QRgb();
|
||||
}
|
||||
|
||||
QString VUtils::generateImageFileName(const QString &path, const QString &title,
|
||||
QString VUtils::generateImageFileName(const QString &path,
|
||||
const QString &title,
|
||||
const QString &format)
|
||||
{
|
||||
Q_ASSERT(!title.isEmpty());
|
||||
QRegExp regExp("\\W");
|
||||
QString baseName(title.toLower());
|
||||
|
||||
@ -117,7 +119,9 @@ QString VUtils::generateImageFileName(const QString &path, const QString &title,
|
||||
// Constrain the length of the name.
|
||||
baseName.truncate(10);
|
||||
|
||||
baseName.prepend('_');
|
||||
if (!baseName.isEmpty()) {
|
||||
baseName.prepend('_');
|
||||
}
|
||||
|
||||
// Add current time and random number to make the name be most likely unique
|
||||
baseName = baseName + '_' + QString::number(QDateTime::currentDateTime().toTime_t());
|
||||
@ -377,31 +381,35 @@ int VUtils::showMessage(QMessageBox::Icon p_icon, const QString &p_title, const
|
||||
return msgBox.exec();
|
||||
}
|
||||
|
||||
QString VUtils::generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName)
|
||||
QString VUtils::generateCopiedFileName(const QString &p_dirPath,
|
||||
const QString &p_fileName,
|
||||
bool p_completeBaseName)
|
||||
{
|
||||
QString suffix;
|
||||
QString base = p_fileName;
|
||||
int dotIdx = p_fileName.lastIndexOf('.');
|
||||
if (dotIdx != -1) {
|
||||
// .md
|
||||
suffix = p_fileName.right(p_fileName.size() - dotIdx);
|
||||
base = p_fileName.left(dotIdx);
|
||||
QDir dir(p_dirPath);
|
||||
if (!dir.exists() || !dir.exists(p_fileName)) {
|
||||
return p_fileName;
|
||||
}
|
||||
|
||||
QDir dir(p_dirPath);
|
||||
QString name = p_fileName;
|
||||
QFileInfo fi(p_fileName);
|
||||
QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
|
||||
QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
|
||||
|
||||
int index = 0;
|
||||
while (dir.exists(name)) {
|
||||
QString fileName;
|
||||
do {
|
||||
QString seq;
|
||||
if (index > 0) {
|
||||
seq = QString::number(index);
|
||||
seq = QString("%1").arg(QString::number(index), 3, '0');
|
||||
}
|
||||
|
||||
index++;
|
||||
name = QString("%1_copy%2%3").arg(base).arg(seq).arg(suffix);
|
||||
}
|
||||
fileName = QString("%1_copy%2").arg(baseName).arg(seq);
|
||||
if (!suffix.isEmpty()) {
|
||||
fileName = fileName + "." + suffix;
|
||||
}
|
||||
} while (fileExists(dir, fileName, true));
|
||||
|
||||
return name;
|
||||
return fileName;
|
||||
}
|
||||
|
||||
QString VUtils::generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName)
|
||||
@ -614,7 +622,8 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exp
|
||||
}
|
||||
|
||||
QString VUtils::getFileNameWithSequence(const QString &p_directory,
|
||||
const QString &p_baseFileName)
|
||||
const QString &p_baseFileName,
|
||||
bool p_completeBaseName)
|
||||
{
|
||||
QDir dir(p_directory);
|
||||
if (!dir.exists() || !dir.exists(p_baseFileName)) {
|
||||
@ -623,8 +632,8 @@ QString VUtils::getFileNameWithSequence(const QString &p_directory,
|
||||
|
||||
// Append a sequence.
|
||||
QFileInfo fi(p_baseFileName);
|
||||
QString baseName = fi.baseName();
|
||||
QString suffix = fi.completeSuffix();
|
||||
QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
|
||||
QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
|
||||
int seq = 1;
|
||||
QString fileName;
|
||||
do {
|
||||
@ -637,6 +646,24 @@ QString VUtils::getFileNameWithSequence(const QString &p_directory,
|
||||
return fileName;
|
||||
}
|
||||
|
||||
QString VUtils::getDirNameWithSequence(const QString &p_directory,
|
||||
const QString &p_baseDirName)
|
||||
{
|
||||
QDir dir(p_directory);
|
||||
if (!dir.exists() || !dir.exists(p_baseDirName)) {
|
||||
return p_baseDirName;
|
||||
}
|
||||
|
||||
// Append a sequence.
|
||||
int seq = 1;
|
||||
QString fileName;
|
||||
do {
|
||||
fileName = QString("%1_%2").arg(p_baseDirName).arg(QString::number(seq++), 3, '0');
|
||||
} while (fileExists(dir, fileName, true));
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
QString VUtils::getRandomFileName(const QString &p_directory)
|
||||
{
|
||||
Q_ASSERT(!p_directory.isEmpty());
|
||||
@ -702,6 +729,16 @@ bool VUtils::checkPathLegal(const QString &p_path)
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VUtils::checkFileNameLegal(const QString &p_name)
|
||||
{
|
||||
if (p_name.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QRegExp exp(c_fileNameRegExp);
|
||||
return exp.exactMatch(p_name);
|
||||
}
|
||||
|
||||
bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
|
||||
{
|
||||
QString a = QDir::cleanPath(p_patha);
|
||||
@ -850,7 +887,8 @@ bool VUtils::deleteFile(const QString &p_recycleBinFolderPath,
|
||||
}
|
||||
|
||||
QString destName = getFileNameWithSequence(binPath,
|
||||
fileNameFromPath(p_path));
|
||||
fileNameFromPath(p_path),
|
||||
true);
|
||||
|
||||
qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
|
||||
if (!binDir.rename(p_path, binDir.filePath(destName))) {
|
||||
@ -935,3 +973,30 @@ void VUtils::addErrMsg(QString *p_msg, const QString &p_str)
|
||||
*p_msg = *p_msg + '\n' + p_str;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList VUtils::filterFilePathsToOpen(const QStringList &p_files)
|
||||
{
|
||||
QStringList paths;
|
||||
for (int i = 0; i < p_files.size(); ++i) {
|
||||
QString path = validFilePathToOpen(p_files[i]);
|
||||
if (!path.isEmpty()) {
|
||||
paths.append(path);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
QString VUtils::validFilePathToOpen(const QString &p_file)
|
||||
{
|
||||
if (QFileInfo::exists(p_file)) {
|
||||
QFileInfo fi(p_file);
|
||||
if (fi.isFile()) {
|
||||
// Need to use absolute path here since VNote may be launched
|
||||
// in different working directory.
|
||||
return QDir::cleanPath(fi.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
@ -20,6 +20,32 @@ class VNotebook;
|
||||
#define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
|
||||
#endif
|
||||
|
||||
// Thanks to CGAL/cgal.
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0 // Compatibility with non-clang compilers.
|
||||
#endif
|
||||
|
||||
#ifndef __has_cpp_attribute
|
||||
#define __has_cpp_attribute(x) 0 // Compatibility with non-supporting compilers.
|
||||
#endif
|
||||
|
||||
// The fallthrough attribute.
|
||||
// See for clang:
|
||||
// http://clang.llvm.org/docs/AttributeReference.html#statement-attributes
|
||||
// See for gcc:
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
|
||||
#if __has_cpp_attribute(fallthrough)
|
||||
# define V_FALLTHROUGH [[fallthrough]]
|
||||
#elif __has_cpp_attribute(gnu::fallthrough)
|
||||
# define V_FALLTHROUGH [[gnu::fallthrough]]
|
||||
#elif __has_cpp_attribute(clang::fallthrough)
|
||||
# define V_FALLTHROUGH [[clang::fallthrough]]
|
||||
#elif __has_attribute(fallthrough) && ! __clang__
|
||||
# define V_FALLTHROUGH __attribute__ ((fallthrough))
|
||||
#else
|
||||
# define V_FALLTHROUGH while(false){}
|
||||
#endif
|
||||
|
||||
enum class MessageBoxType
|
||||
{
|
||||
Normal = 0,
|
||||
@ -54,9 +80,19 @@ public:
|
||||
|
||||
// Given the file name @p_fileName and directory path @p_dirPath, generate
|
||||
// a file name based on @p_fileName which does not exist in @p_dirPath.
|
||||
static QString generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName);
|
||||
// @p_completeBaseName: use complete base name or complete suffix. For example,
|
||||
// "abc.tar.gz", if @p_completeBaseName is true, the base name is "abc.tar",
|
||||
// otherwise, it is "abc".
|
||||
static QString generateCopiedFileName(const QString &p_dirPath,
|
||||
const QString &p_fileName,
|
||||
bool p_completeBaseName = true);
|
||||
|
||||
// Given the directory name @p_dirName and directory path @p_parentDirPath,
|
||||
// generate a directory name based on @p_dirName which does not exist in
|
||||
// @p_parentDirPath.
|
||||
static QString generateCopiedDirName(const QString &p_parentDirPath,
|
||||
const QString &p_dirName);
|
||||
|
||||
static QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
|
||||
static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
|
||||
|
||||
// Return the last directory name of @p_path.
|
||||
@ -122,8 +158,18 @@ public:
|
||||
// Get an available file name in @p_directory with base @p_baseFileName.
|
||||
// If there already exists a file named @p_baseFileName, try to add sequence
|
||||
// suffix to the name, such as _001.
|
||||
// @p_completeBaseName: use complete base name or complete suffix. For example,
|
||||
// "abc.tar.gz", if @p_completeBaseName is true, the base name is "abc.tar",
|
||||
// otherwise, it is "abc".
|
||||
static QString getFileNameWithSequence(const QString &p_directory,
|
||||
const QString &p_baseFileName);
|
||||
const QString &p_baseFileName,
|
||||
bool p_completeBaseName = true);
|
||||
|
||||
// Get an available directory name in @p_directory with base @p_baseDirName.
|
||||
// If there already exists a file named @p_baseFileName, try to add sequence
|
||||
// suffix to the name, such as _001.
|
||||
static QString getDirNameWithSequence(const QString &p_directory,
|
||||
const QString &p_baseDirName);
|
||||
|
||||
// Get an available random file name in @p_directory.
|
||||
static QString getRandomFileName(const QString &p_directory);
|
||||
@ -131,6 +177,9 @@ public:
|
||||
// Try to check if @p_path is legal.
|
||||
static bool checkPathLegal(const QString &p_path);
|
||||
|
||||
// Check if file/folder name is legal.
|
||||
static bool checkFileNameLegal(const QString &p_name);
|
||||
|
||||
// Returns true if @p_patha and @p_pathb points to the same file/directory.
|
||||
static bool equalPath(const QString &p_patha, const QString &p_pathb);
|
||||
|
||||
@ -186,6 +235,13 @@ public:
|
||||
// Assign @p_str to @p_msg if it is not NULL.
|
||||
static void addErrMsg(QString *p_msg, const QString &p_str);
|
||||
|
||||
// Check each file of @p_files and return valid ones for VNote to open.
|
||||
static QStringList filterFilePathsToOpen(const QStringList &p_files);
|
||||
|
||||
// Return the normalized file path of @p_file if it is valid to open.
|
||||
// Return empty if it is not valid.
|
||||
static QString validFilePathToOpen(const QString &p_file);
|
||||
|
||||
// Regular expression for image link.
|
||||
// 
|
||||
// Captured texts (need to be trimmed):
|
||||
@ -195,6 +251,9 @@ public:
|
||||
// 4. Unused;
|
||||
static const QString c_imageLinkRegExp;
|
||||
|
||||
// Regular expression for image title.
|
||||
static const QString c_imageTitleRegExp;
|
||||
|
||||
// Regular expression for file/directory name.
|
||||
// Forbidden char: \/:*?"<>|
|
||||
static const QString c_fileNameRegExp;
|
||||
|
@ -18,6 +18,7 @@ extern VConfigManager *g_config;
|
||||
const QChar VVim::c_unnamedRegister = QChar('"');
|
||||
const QChar VVim::c_blackHoleRegister = QChar('_');
|
||||
const QChar VVim::c_selectionRegister = QChar('+');
|
||||
QMap<QChar, VVim::Register> VVim::s_registers;
|
||||
|
||||
const int VVim::SearchHistory::c_capacity = 50;
|
||||
|
||||
@ -170,62 +171,6 @@ static void findCurrentSpace(const QTextCursor &p_cursor, int &p_start, int &p_e
|
||||
p_end += block.position();
|
||||
}
|
||||
|
||||
// Find the start and end of the word @p_cursor locates in (within a single block).
|
||||
// @p_start and @p_end will be the global position of the start and end of the word.
|
||||
// @p_start will equals to @p_end if @p_cursor is a space.
|
||||
static void findCurrentWord(QTextCursor p_cursor, int &p_start, int &p_end)
|
||||
{
|
||||
QString text = p_cursor.block().text();
|
||||
int pib = p_cursor.positionInBlock();
|
||||
|
||||
if (pib < text.size() && text[pib].isSpace()) {
|
||||
p_start = p_end = p_cursor.position();
|
||||
return;
|
||||
}
|
||||
|
||||
p_cursor.movePosition(QTextCursor::StartOfWord);
|
||||
p_start = p_cursor.position();
|
||||
p_cursor.movePosition(QTextCursor::EndOfWord);
|
||||
p_end = p_cursor.position();
|
||||
}
|
||||
|
||||
// Find the start and end of the WORD @p_cursor locates in (within a single block).
|
||||
// @p_start and @p_end will be the global position of the start and end of the WORD.
|
||||
// @p_start will equals to @p_end if @p_cursor is a space.
|
||||
// Attention: www|sss will select www, which is different from findCurrentWord().
|
||||
static void findCurrentWORD(const QTextCursor &p_cursor, int &p_start, int &p_end)
|
||||
{
|
||||
QTextBlock block = p_cursor.block();
|
||||
QString text = block.text();
|
||||
int pib = p_cursor.positionInBlock();
|
||||
|
||||
if (pib < text.size() && text[pib].isSpace()) {
|
||||
p_start = p_end = p_cursor.position();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the start.
|
||||
p_start = 0;
|
||||
for (int i = pib - 1; i >= 0; --i) {
|
||||
if (text[i].isSpace()) {
|
||||
p_start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the end.
|
||||
p_end = block.length() - 1;
|
||||
for (int i = pib; i < text.size(); ++i) {
|
||||
if (text[i].isSpace()) {
|
||||
p_end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p_start += block.position();
|
||||
p_end += block.position();
|
||||
}
|
||||
|
||||
// Move @p_cursor to skip spaces if current cursor is placed at a space
|
||||
// (may move across blocks). It will stop by the empty block on the way.
|
||||
// Forward: wwwwsssss|wwww
|
||||
@ -351,7 +296,7 @@ static void insertChangeBlockAfterDeletion(QTextCursor &p_cursor, int p_deletion
|
||||
}
|
||||
|
||||
if (g_config->getAutoIndent()) {
|
||||
VEditUtils::indentBlockAsPreviousBlock(p_cursor);
|
||||
VEditUtils::indentBlockAsBlock(p_cursor, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,7 +501,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
QChar reg = keyToRegisterName(keyInfo);
|
||||
if (!reg.isNull()) {
|
||||
// Insert register content.
|
||||
m_editor->insertPlainText(m_registers[reg].read());
|
||||
m_editor->insertPlainText(getRegister(reg).read());
|
||||
}
|
||||
|
||||
goto clear_accept;
|
||||
@ -602,11 +547,12 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
QChar reg = keyToRegisterName(keyInfo);
|
||||
if (!reg.isNull()) {
|
||||
m_keys.clear();
|
||||
setRegister(reg);
|
||||
if (m_registers[reg].isNamedRegister()) {
|
||||
m_registers[reg].m_append = (modifiers == Qt::ShiftModifier);
|
||||
setCurrentRegisterName(reg);
|
||||
Register &r = getRegister(reg);
|
||||
if (r.isNamedRegister()) {
|
||||
r.m_append = (modifiers == Qt::ShiftModifier);
|
||||
} else {
|
||||
Q_ASSERT(!m_registers[reg].m_append);
|
||||
Q_ASSERT(!r.m_append);
|
||||
}
|
||||
|
||||
goto accept;
|
||||
@ -965,7 +911,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
|
||||
bool textInserted = false;
|
||||
if (g_config->getAutoIndent()) {
|
||||
textInserted = VEditUtils::indentBlockAsPreviousBlock(cursor);
|
||||
textInserted = VEditUtils::indentBlockAsBlock(cursor, false);
|
||||
if (g_config->getAutoList()) {
|
||||
textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor)
|
||||
|| textInserted;
|
||||
@ -1115,6 +1061,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
V_FALLTHROUGH;
|
||||
}
|
||||
|
||||
case Qt::Key_PageUp:
|
||||
@ -1381,6 +1329,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
V_FALLTHROUGH;
|
||||
}
|
||||
|
||||
case Qt::Key_Escape:
|
||||
@ -2219,7 +2169,7 @@ void VVim::resetState()
|
||||
m_keys.clear();
|
||||
m_tokens.clear();
|
||||
m_pendingKeys.clear();
|
||||
setRegister(c_unnamedRegister);
|
||||
setCurrentRegisterName(c_unnamedRegister);
|
||||
m_resetPositionInBlock = true;
|
||||
m_registerPending = false;
|
||||
}
|
||||
@ -2723,7 +2673,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
|
||||
for (int i = 0; i < p_repeat; ++i) {
|
||||
int start, end;
|
||||
// [start, end] is current WORD.
|
||||
findCurrentWORD(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWORD(p_cursor, start, end);
|
||||
|
||||
// Move cursor to end of current WORD.
|
||||
p_cursor.setPosition(end, p_moveMode);
|
||||
@ -2773,7 +2723,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
|
||||
|
||||
int start, end;
|
||||
// [start, end] is current WORD.
|
||||
findCurrentWORD(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWORD(p_cursor, start, end);
|
||||
|
||||
// Move cursor to the end of current WORD.
|
||||
p_cursor.setPosition(end, p_moveMode);
|
||||
@ -2819,7 +2769,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
|
||||
|
||||
int start, end;
|
||||
// [start, end] is current WORD.
|
||||
findCurrentWORD(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWORD(p_cursor, start, end);
|
||||
|
||||
// Move cursor to the start of current WORD.
|
||||
p_cursor.setPosition(start, p_moveMode);
|
||||
@ -2856,7 +2806,7 @@ bool VVim::processMovement(QTextCursor &p_cursor,
|
||||
|
||||
for (int i = 0; i < p_repeat; ++i) {
|
||||
int start, end;
|
||||
findCurrentWORD(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWORD(p_cursor, start, end);
|
||||
|
||||
p_cursor.setPosition(start, p_moveMode);
|
||||
|
||||
@ -3045,7 +2995,7 @@ handle_target:
|
||||
// Different from Vim:
|
||||
// We do not recognize a word as strict as Vim.
|
||||
int start, end;
|
||||
findCurrentWord(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWord(p_cursor, start, end);
|
||||
if (start == end) {
|
||||
// Spaces, find next word.
|
||||
QTextCursor cursor = p_cursor;
|
||||
@ -3056,7 +3006,7 @@ handle_target:
|
||||
}
|
||||
|
||||
if (!doc->characterAt(cursor.position()).isSpace()) {
|
||||
findCurrentWord(cursor, start, end);
|
||||
VEditUtils::findCurrentWord(cursor, start, end);
|
||||
Q_ASSERT(start != end);
|
||||
break;
|
||||
}
|
||||
@ -3181,7 +3131,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
|
||||
Q_ASSERT(p_repeat == -1);
|
||||
bool spaces = false;
|
||||
int start, end;
|
||||
findCurrentWord(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWord(p_cursor, start, end);
|
||||
|
||||
if (start == end) {
|
||||
// Select the space between previous word and next word.
|
||||
@ -3221,7 +3171,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
|
||||
findCurrentSpace(p_cursor, start, end);
|
||||
|
||||
if (start == end) {
|
||||
findCurrentWORD(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWORD(p_cursor, start, end);
|
||||
} else {
|
||||
// Select the space between previous WORD and next WORD.
|
||||
spaces = true;
|
||||
@ -3240,7 +3190,7 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
|
||||
moveCursorAcrossSpaces(p_cursor, moveMode, true);
|
||||
|
||||
// [start, end] is current WORD.
|
||||
findCurrentWORD(p_cursor, start, end);
|
||||
VEditUtils::findCurrentWORD(p_cursor, start, end);
|
||||
|
||||
// Move cursor to the end of current WORD.
|
||||
p_cursor.setPosition(end, moveMode);
|
||||
@ -3481,6 +3431,8 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
|
||||
// Fall through.
|
||||
mayCrossBlock = true;
|
||||
|
||||
V_FALLTHROUGH;
|
||||
|
||||
case Range::WordAround:
|
||||
// Fall through.
|
||||
case Range::WordInner:
|
||||
@ -3712,6 +3664,8 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
|
||||
// Fall through.
|
||||
mayCrossBlock = true;
|
||||
|
||||
V_FALLTHROUGH;
|
||||
|
||||
case Range::WordAround:
|
||||
// Fall through.
|
||||
case Range::WordInner:
|
||||
@ -3873,7 +3827,7 @@ void VVim::processPasteAction(QList<Token> &p_tokens, bool p_pasteBefore)
|
||||
repeat = to.m_repeat;
|
||||
}
|
||||
|
||||
Register ® = m_registers[m_regName];
|
||||
Register ® = getRegister(m_regName);
|
||||
QString value = reg.read();
|
||||
bool isBlock = reg.isBlock();
|
||||
if (value.isEmpty()) {
|
||||
@ -4847,14 +4801,17 @@ void VVim::expandSelectionToWholeLines(QTextCursor &p_cursor)
|
||||
|
||||
void VVim::initRegisters()
|
||||
{
|
||||
m_registers.clear();
|
||||
for (char ch = 'a'; ch <= 'z'; ++ch) {
|
||||
m_registers[QChar(ch)] = Register(QChar(ch));
|
||||
if (!s_registers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_registers[c_unnamedRegister] = Register(c_unnamedRegister);
|
||||
m_registers[c_blackHoleRegister] = Register(c_blackHoleRegister);
|
||||
m_registers[c_selectionRegister] = Register(c_selectionRegister);
|
||||
for (char ch = 'a'; ch <= 'z'; ++ch) {
|
||||
s_registers[QChar(ch)] = Register(QChar(ch));
|
||||
}
|
||||
|
||||
s_registers[c_unnamedRegister] = Register(c_unnamedRegister);
|
||||
s_registers[c_blackHoleRegister] = Register(c_blackHoleRegister);
|
||||
s_registers[c_selectionRegister] = Register(c_selectionRegister);
|
||||
}
|
||||
|
||||
bool VVim::expectingRegisterName() const
|
||||
@ -5137,12 +5094,12 @@ void VVim::saveToRegister(const QString &p_text)
|
||||
|
||||
qDebug() << QString("save text(%1) to register(%2)").arg(text).arg(m_regName);
|
||||
|
||||
Register ® = m_registers[m_regName];
|
||||
Register ® = getRegister(m_regName);
|
||||
reg.update(text);
|
||||
|
||||
if (!reg.isBlackHoleRegister() && !reg.isUnnamedRegister()) {
|
||||
// Save it to unnamed register.
|
||||
m_registers[c_unnamedRegister].update(reg.m_value);
|
||||
setRegister(c_unnamedRegister, reg.m_value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5249,7 +5206,7 @@ void VVim::message(const QString &p_msg)
|
||||
|
||||
const QMap<QChar, VVim::Register> &VVim::getRegisters() const
|
||||
{
|
||||
return m_registers;
|
||||
return s_registers;
|
||||
}
|
||||
|
||||
const VVim::Marks &VVim::getMarks() const
|
||||
@ -5272,7 +5229,7 @@ QString VVim::getPendingKeys() const
|
||||
return str;
|
||||
}
|
||||
|
||||
void VVim::setRegister(QChar p_reg)
|
||||
void VVim::setCurrentRegisterName(QChar p_reg)
|
||||
{
|
||||
m_regName = p_reg;
|
||||
}
|
||||
@ -5785,7 +5742,7 @@ QString VVim::readRegister(int p_key, int p_modifiers)
|
||||
Key keyInfo(p_key, p_modifiers);
|
||||
QChar reg = keyToRegisterName(keyInfo);
|
||||
if (!reg.isNull()) {
|
||||
return m_registers[reg].read();
|
||||
return getRegister(reg).read();
|
||||
}
|
||||
|
||||
return "";
|
||||
|
@ -166,7 +166,7 @@ public:
|
||||
void setMode(VimMode p_mode, bool p_clearSelection = true);
|
||||
|
||||
// Set current register.
|
||||
void setRegister(QChar p_reg);
|
||||
void setCurrentRegisterName(QChar p_reg);
|
||||
|
||||
// Get m_registers.
|
||||
const QMap<QChar, Register> &getRegisters() const;
|
||||
@ -797,6 +797,10 @@ private:
|
||||
// Clear search highlight.
|
||||
void clearSearchHighlight();
|
||||
|
||||
// Function utils for register.
|
||||
Register &getRegister(QChar p_regName) const;
|
||||
void setRegister(QChar p_regName, const QString &p_val);
|
||||
|
||||
VEdit *m_editor;
|
||||
const VEditConfig *m_editConfig;
|
||||
VimMode m_mode;
|
||||
@ -814,8 +818,6 @@ private:
|
||||
// Whether reset the position in block when moving cursor.
|
||||
bool m_resetPositionInBlock;
|
||||
|
||||
QMap<QChar, Register> m_registers;
|
||||
|
||||
// Currently used register.
|
||||
QChar m_regName;
|
||||
|
||||
@ -843,6 +845,18 @@ private:
|
||||
static const QChar c_unnamedRegister;
|
||||
static const QChar c_blackHoleRegister;
|
||||
static const QChar c_selectionRegister;
|
||||
|
||||
static QMap<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
|
||||
|
@ -5,13 +5,12 @@
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vbuttonwithwidget.h"
|
||||
#include "vnote.h"
|
||||
#include "vmainwindow.h"
|
||||
#include "dialog/vconfirmdeletiondialog.h"
|
||||
#include "dialog/vsortdialog.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
extern VNote *g_vnote;
|
||||
extern VMainWindow *g_mainWin;
|
||||
|
||||
VAttachmentList::VAttachmentList(QWidget *p_parent)
|
||||
: QWidget(p_parent), m_file(NULL)
|
||||
@ -53,7 +52,7 @@ void VAttachmentList::setupUI()
|
||||
.arg(m_file->fetchAttachmentFolderPath()),
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow(),
|
||||
g_mainWin,
|
||||
MessageBoxType::Danger);
|
||||
if (ret == QMessageBox::Ok) {
|
||||
if (!m_file->deleteAttachments()) {
|
||||
@ -66,7 +65,7 @@ void VAttachmentList::setupUI()
|
||||
"maintain the configuration file manually."),
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow());
|
||||
g_mainWin);
|
||||
}
|
||||
|
||||
m_attachmentList->clear();
|
||||
@ -204,7 +203,7 @@ void VAttachmentList::addAttachment()
|
||||
}
|
||||
|
||||
static QString lastPath = QDir::homePath();
|
||||
QStringList files = QFileDialog::getOpenFileNames(g_vnote->getMainWindow(),
|
||||
QStringList files = QFileDialog::getOpenFileNames(g_mainWin,
|
||||
tr("Select Files As Attachments"),
|
||||
lastPath);
|
||||
if (files.isEmpty()) {
|
||||
@ -236,16 +235,16 @@ void VAttachmentList::addAttachments(const QStringList &p_files)
|
||||
"",
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow());
|
||||
g_mainWin);
|
||||
} else {
|
||||
++addedFiles;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedFiles > 0) {
|
||||
g_vnote->getMainWindow()->showStatusMessage(tr("%1 %2 added as attachments")
|
||||
.arg(addedFiles)
|
||||
.arg(addedFiles > 1 ? tr("files") : tr("file")));
|
||||
g_mainWin->showStatusMessage(tr("%1 %2 added as attachments")
|
||||
.arg(addedFiles)
|
||||
.arg(addedFiles > 1 ? tr("files") : tr("file")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,7 +329,7 @@ void VAttachmentList::deleteSelectedItems()
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
g_vnote->getMainWindow());
|
||||
g_mainWin);
|
||||
if (dialog.exec()) {
|
||||
items = dialog.getConfirmedItems();
|
||||
|
||||
@ -349,7 +348,7 @@ void VAttachmentList::deleteSelectedItems()
|
||||
"maintain the configuration file manually."),
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow());
|
||||
g_mainWin);
|
||||
}
|
||||
|
||||
updateButtonState();
|
||||
@ -370,7 +369,7 @@ void VAttachmentList::sortItems()
|
||||
"in the configuration file.")
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_file->getName()),
|
||||
g_vnote->getMainWindow());
|
||||
g_mainWin);
|
||||
QTreeWidget *tree = dialog.getTreeWidget();
|
||||
tree->clear();
|
||||
tree->setColumnCount(1);
|
||||
@ -624,7 +623,7 @@ void VAttachmentList::checkAttachments()
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
g_vnote->getMainWindow());
|
||||
g_mainWin);
|
||||
if (dialog.exec()) {
|
||||
items = dialog.getConfirmedItems();
|
||||
|
||||
@ -643,7 +642,7 @@ void VAttachmentList::checkAttachments()
|
||||
"maintain the configuration file manually."),
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow());
|
||||
g_mainWin);
|
||||
}
|
||||
|
||||
updateButtonState();
|
||||
|
406
src/vcaptain.cpp
@ -1,6 +1,5 @@
|
||||
#include <QtWidgets>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
#include <QShortcut>
|
||||
#include "vcaptain.h"
|
||||
@ -9,38 +8,39 @@
|
||||
#include "vedittab.h"
|
||||
#include "vfilelist.h"
|
||||
#include "vnavigationmode.h"
|
||||
#include "vconfigmanager.h"
|
||||
|
||||
extern VMainWindow *g_mainWin;
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
// 3s pending time after the leader keys.
|
||||
const int c_pendingTime = 3 * 1000;
|
||||
|
||||
#if defined(QT_NO_DEBUG)
|
||||
extern QFile g_logFile;
|
||||
#endif
|
||||
|
||||
VCaptain::VCaptain(VMainWindow *p_parent)
|
||||
: QWidget(p_parent), m_mainWindow(p_parent), m_mode(VCaptain::Normal),
|
||||
m_widgetBeforeCaptain(NULL), m_nextMajorKey('a'), m_ignoreFocusChange(false)
|
||||
VCaptain::VCaptain(QWidget *p_parent)
|
||||
: QWidget(p_parent),
|
||||
m_mode(CaptainMode::Normal),
|
||||
m_widgetBeforeNavigation(NULL),
|
||||
m_nextMajorKey('a'),
|
||||
m_ignoreFocusChange(false),
|
||||
m_leaderKey(g_config->getShortcutKeySequence("CaptainMode"))
|
||||
{
|
||||
m_pendingTimer = new QTimer(this);
|
||||
m_pendingTimer->setSingleShot(true);
|
||||
m_pendingTimer->setInterval(c_pendingTime);
|
||||
connect(m_pendingTimer, &QTimer::timeout,
|
||||
this, &VCaptain::pendingTimerTimeout);
|
||||
Q_ASSERT(!m_leaderKey.isEmpty());
|
||||
|
||||
connect(qApp, &QApplication::focusChanged,
|
||||
this, &VCaptain::handleFocusChanged);
|
||||
|
||||
QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+E"), this,
|
||||
Q_NULLPTR, Q_NULLPTR);
|
||||
connect(shortcut, &QShortcut::activated,
|
||||
this, &VCaptain::trigger);
|
||||
|
||||
qApp->installEventFilter(this);
|
||||
|
||||
setWindowFlags(Qt::FramelessWindowHint);
|
||||
|
||||
// Make it as small as possible. This widget will stay at the top-left corner
|
||||
// of VMainWindow.
|
||||
resize(1, 1);
|
||||
|
||||
// Register Navigation mode as Captain mode target.
|
||||
registerCaptainTarget(tr("NavigationMode"),
|
||||
g_config->getCaptainShortcutKeySequence("NavigationMode"),
|
||||
this,
|
||||
navigationModeByCaptain);
|
||||
}
|
||||
|
||||
QChar VCaptain::getNextMajorKey()
|
||||
@ -59,53 +59,26 @@ void VCaptain::registerNavigationTarget(VNavigationMode *p_target)
|
||||
QChar key = getNextMajorKey();
|
||||
if (!key.isNull()) {
|
||||
p_target->registerNavigation(key);
|
||||
m_targets.append(NaviModeTarget(p_target, true));
|
||||
m_naviTargets.push_back(NaviModeTarget(p_target, true));
|
||||
}
|
||||
}
|
||||
|
||||
// In pending mode, if user click other widgets, we need to exit Captain mode.
|
||||
void VCaptain::handleFocusChanged(QWidget *p_old, QWidget * /* p_now */)
|
||||
void VCaptain::handleFocusChanged(QWidget *p_old, QWidget * p_now)
|
||||
{
|
||||
if (!m_ignoreFocusChange && p_old == this) {
|
||||
exitCaptainMode();
|
||||
Q_UNUSED(p_now);
|
||||
|
||||
if (!m_ignoreFocusChange
|
||||
&& !checkMode(CaptainMode::Normal)
|
||||
&& p_old == this) {
|
||||
exitNavigationMode();
|
||||
}
|
||||
}
|
||||
|
||||
void VCaptain::pendingTimerTimeout()
|
||||
{
|
||||
qDebug() << "Captain mode timeout";
|
||||
exitCaptainMode();
|
||||
restoreFocus();
|
||||
}
|
||||
|
||||
void VCaptain::trigger()
|
||||
{
|
||||
if (m_mode != VCaptain::Normal) {
|
||||
return;
|
||||
}
|
||||
qDebug() << "trigger Captain mode";
|
||||
// Focus to listen pending key press.
|
||||
m_widgetBeforeCaptain = QApplication::focusWidget();
|
||||
setFocus();
|
||||
m_mode = VCaptain::Pending;
|
||||
m_pendingTimer->stop();
|
||||
m_pendingTimer->start();
|
||||
|
||||
emit captainModeChanged(true);
|
||||
}
|
||||
|
||||
void VCaptain::keyPressEvent(QKeyEvent *p_event)
|
||||
{
|
||||
int key = p_event->key();
|
||||
Qt::KeyboardModifiers modifiers = p_event->modifiers();
|
||||
|
||||
if (m_mode == VCaptain::Normal) {
|
||||
// Should not in focus while in Normal mode.
|
||||
QWidget::keyPressEvent(p_event);
|
||||
m_mainWindow->focusNextChild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == Qt::Key_Control || key == Qt::Key_Shift) {
|
||||
QWidget::keyPressEvent(p_event);
|
||||
return;
|
||||
@ -120,230 +93,28 @@ void VCaptain::keyPressEvent(QKeyEvent *p_event)
|
||||
|
||||
bool VCaptain::handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers)
|
||||
{
|
||||
bool ret = true;
|
||||
if (!checkMode(CaptainMode::Navigation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_key == Qt::Key_Escape
|
||||
|| (p_key == Qt::Key_BracketLeft
|
||||
&& p_modifiers == Qt::ControlModifier)) {
|
||||
goto exit;
|
||||
exitNavigationMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_ignoreFocusChange = true;
|
||||
|
||||
if (m_mode == VCaptainMode::Navigation) {
|
||||
ret = handleKeyPressNavigationMode(p_key, p_modifiers);
|
||||
m_ignoreFocusChange = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// In Captain mode, Ctrl key won't make a difference.
|
||||
switch (p_key) {
|
||||
case Qt::Key_1:
|
||||
case Qt::Key_2:
|
||||
case Qt::Key_3:
|
||||
case Qt::Key_4:
|
||||
case Qt::Key_5:
|
||||
case Qt::Key_6:
|
||||
case Qt::Key_7:
|
||||
case Qt::Key_8:
|
||||
case Qt::Key_9:
|
||||
{
|
||||
// Switch to tab <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;
|
||||
return handleKeyPressNavigationMode(p_key, p_modifiers);
|
||||
}
|
||||
|
||||
bool VCaptain::handleKeyPressNavigationMode(int p_key,
|
||||
Qt::KeyboardModifiers /* p_modifiers */)
|
||||
{
|
||||
Q_ASSERT(m_mode == VCaptainMode::Navigation);
|
||||
Q_ASSERT(m_mode == CaptainMode::Navigation);
|
||||
bool hasConsumed = false;
|
||||
bool pending = false;
|
||||
for (auto &target : m_targets) {
|
||||
m_ignoreFocusChange = true;
|
||||
for (auto &target : m_naviTargets) {
|
||||
if (hasConsumed) {
|
||||
target.m_available = false;
|
||||
target.m_target->hideNavigation();
|
||||
@ -351,12 +122,13 @@ bool VCaptain::handleKeyPressNavigationMode(int p_key,
|
||||
}
|
||||
if (target.m_available) {
|
||||
bool succeed = false;
|
||||
// May change focus, so we need to ignore focus change here.
|
||||
bool consumed = target.m_target->handleKeyNavigation(p_key, succeed);
|
||||
if (consumed) {
|
||||
hasConsumed = true;
|
||||
if (succeed) {
|
||||
// Exit.
|
||||
m_widgetBeforeCaptain = NULL;
|
||||
m_widgetBeforeNavigation = NULL;
|
||||
} else {
|
||||
// Consumed but not succeed. Need more keys.
|
||||
pending = true;
|
||||
@ -368,20 +140,23 @@ bool VCaptain::handleKeyPressNavigationMode(int p_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_ignoreFocusChange = false;
|
||||
if (pending) {
|
||||
return true;
|
||||
}
|
||||
exitCaptainMode();
|
||||
restoreFocus();
|
||||
|
||||
exitNavigationMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
void VCaptain::triggerNavigationMode()
|
||||
{
|
||||
m_pendingTimer->stop();
|
||||
m_mode = VCaptainMode::Navigation;
|
||||
|
||||
for (auto &target : m_targets) {
|
||||
setMode(CaptainMode::Navigation);
|
||||
m_widgetBeforeNavigation = QApplication::focusWidget();
|
||||
// Focus to listen pending key press.
|
||||
setFocus();
|
||||
for (auto &target : m_naviTargets) {
|
||||
target.m_available = true;
|
||||
target.m_target->showNavigation();
|
||||
}
|
||||
@ -389,51 +164,74 @@ void VCaptain::triggerNavigationMode()
|
||||
|
||||
void VCaptain::exitNavigationMode()
|
||||
{
|
||||
m_mode = VCaptainMode::Normal;
|
||||
setMode(CaptainMode::Normal);
|
||||
|
||||
for (auto &target : m_targets) {
|
||||
for (auto &target : m_naviTargets) {
|
||||
target.m_available = true;
|
||||
target.m_target->hideNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
bool VCaptain::eventFilter(QObject *p_obj, QEvent *p_event)
|
||||
{
|
||||
if (m_mode != VCaptain::Normal && p_event->type() == QEvent::Shortcut) {
|
||||
qDebug() << "filter" << p_event;
|
||||
QShortcutEvent *keyEve = dynamic_cast<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();
|
||||
}
|
||||
return QWidget::eventFilter(p_obj, p_event);
|
||||
restoreFocus();
|
||||
}
|
||||
|
||||
void VCaptain::restoreFocus()
|
||||
{
|
||||
if (m_widgetBeforeCaptain) {
|
||||
m_widgetBeforeCaptain->setFocus();
|
||||
if (m_widgetBeforeNavigation) {
|
||||
m_widgetBeforeNavigation->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void VCaptain::exitCaptainMode()
|
||||
bool VCaptain::registerCaptainTarget(const QString &p_name,
|
||||
const QString &p_key,
|
||||
void *p_target,
|
||||
CaptainFunc p_func)
|
||||
{
|
||||
if (m_mode == VCaptainMode::Navigation) {
|
||||
exitNavigationMode();
|
||||
if (p_key.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
m_mode = VCaptain::Normal;
|
||||
m_pendingTimer->stop();
|
||||
m_ignoreFocusChange = false;
|
||||
|
||||
emit captainModeChanged(false);
|
||||
QString lowerKey = p_key.toLower();
|
||||
|
||||
if (m_captainTargets.contains(lowerKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register shortcuts.
|
||||
QString sequence = QString("%1,%2").arg(m_leaderKey).arg(p_key);
|
||||
QShortcut *shortcut = new QShortcut(QKeySequence(sequence),
|
||||
this);
|
||||
shortcut->setContext(Qt::ApplicationShortcut);
|
||||
|
||||
connect(shortcut, &QShortcut::activated,
|
||||
this, std::bind(&VCaptain::triggerCaptainTarget, this, p_key));
|
||||
|
||||
|
||||
CaptainModeTarget target(p_name,
|
||||
p_key,
|
||||
p_target,
|
||||
p_func,
|
||||
shortcut);
|
||||
m_captainTargets.insert(lowerKey, target);
|
||||
|
||||
qDebug() << "registered:" << target.toString() << sequence;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VCaptain::triggerCaptainTarget(const QString &p_key)
|
||||
{
|
||||
auto it = m_captainTargets.find(p_key.toLower());
|
||||
Q_ASSERT(it != m_captainTargets.end());
|
||||
const CaptainModeTarget &target = it.value();
|
||||
|
||||
qDebug() << "triggered:" << target.toString();
|
||||
|
||||
target.m_function(target.m_target, nullptr);
|
||||
}
|
||||
|
||||
void VCaptain::navigationModeByCaptain(void *p_target, void *p_data)
|
||||
{
|
||||
Q_UNUSED(p_data);
|
||||
VCaptain *obj = static_cast<VCaptain *>(p_target);
|
||||
obj->triggerNavigationMode();
|
||||
}
|
||||
|
171
src/vcaptain.h
@ -1,77 +1,166 @@
|
||||
#ifndef VCAPTAIN_H
|
||||
#define VCAPTAIN_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QList>
|
||||
#include <functional>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QVector>
|
||||
#include <QHash>
|
||||
|
||||
class QTimer;
|
||||
class QKeyEvent;
|
||||
class VMainWindow;
|
||||
class QEvent;
|
||||
class VNavigationMode;
|
||||
class QShortcut;
|
||||
|
||||
// void func(void *p_target, void *p_data);
|
||||
typedef std::function<void(void *, void *)> CaptainFunc;
|
||||
|
||||
class VCaptain : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VCaptain(VMainWindow *p_parent);
|
||||
explicit VCaptain(QWidget *p_parent);
|
||||
|
||||
// Trigger Captain mode.
|
||||
void trigger();
|
||||
|
||||
// Register a target for Navigation Mode.
|
||||
// Register a target for Navigation mode.
|
||||
void registerNavigationTarget(VNavigationMode *p_target);
|
||||
|
||||
signals:
|
||||
void captainModeChanged(bool p_enabled);
|
||||
// Register a target for Captain mode.
|
||||
bool registerCaptainTarget(const QString &p_name,
|
||||
const QString &p_key,
|
||||
void *p_target,
|
||||
CaptainFunc p_func);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
||||
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
public slots:
|
||||
|
||||
private slots:
|
||||
void pendingTimerTimeout();
|
||||
// Exit Navigation mode if focus lost.
|
||||
void handleFocusChanged(QWidget *p_old, QWidget *p_new);
|
||||
|
||||
private:
|
||||
// Restore the focus to m_widgetBeforeCaptain.
|
||||
void restoreFocus();
|
||||
void exitCaptainMode();
|
||||
// Return true if finish handling the event; otherwise, let the base widget
|
||||
// to handle it.
|
||||
bool handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers);
|
||||
bool handleKeyPressNavigationMode(int p_key,
|
||||
Qt::KeyboardModifiers p_modifiers);
|
||||
QChar getNextMajorKey();
|
||||
void triggerNavigationMode();
|
||||
void exitNavigationMode();
|
||||
// A widget target for Navigation mode.
|
||||
struct NaviModeTarget {
|
||||
NaviModeTarget()
|
||||
: m_target(nullptr), m_available(false)
|
||||
{
|
||||
}
|
||||
|
||||
enum VCaptainMode {
|
||||
NaviModeTarget(VNavigationMode *p_target, bool p_available)
|
||||
: m_target(p_target), m_available(p_available)
|
||||
{
|
||||
}
|
||||
|
||||
VNavigationMode *m_target;
|
||||
bool m_available;
|
||||
};
|
||||
|
||||
// Modes.
|
||||
enum class CaptainMode {
|
||||
Normal = 0,
|
||||
Pending,
|
||||
Navigation
|
||||
};
|
||||
|
||||
VMainWindow *m_mainWindow;
|
||||
QTimer *m_pendingTimer;
|
||||
int m_mode;
|
||||
// The widget which has the focus before entering Captain mode.
|
||||
QWidget* m_widgetBeforeCaptain;
|
||||
struct CaptainModeTarget {
|
||||
CaptainModeTarget()
|
||||
: m_target(nullptr), m_function(nullptr), m_shortcut(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
struct NaviModeTarget {
|
||||
VNavigationMode *m_target;
|
||||
bool m_available;
|
||||
CaptainModeTarget(const QString &p_name,
|
||||
const QString &p_key,
|
||||
void *p_target,
|
||||
CaptainFunc p_func,
|
||||
QShortcut *p_shortcut)
|
||||
: m_name(p_name),
|
||||
m_key(p_key),
|
||||
m_target(p_target),
|
||||
m_function(p_func),
|
||||
m_shortcut(p_shortcut)
|
||||
{
|
||||
}
|
||||
|
||||
NaviModeTarget(VNavigationMode *p_target, bool p_available)
|
||||
: m_target(p_target), m_available(p_available) {}
|
||||
QString toString() const
|
||||
{
|
||||
return QString("Captain mode target %1 key[%2]").arg(m_name).arg(m_key);
|
||||
}
|
||||
|
||||
// Name to display.
|
||||
QString m_name;
|
||||
|
||||
// Key sequence to trigger this target.
|
||||
// This is the sub-sequence after leader key.
|
||||
QString m_key;
|
||||
|
||||
// Target.
|
||||
void *m_target;
|
||||
|
||||
// Function to call when this target is trigger.
|
||||
CaptainFunc m_function;
|
||||
|
||||
// Shortcut for this target.
|
||||
QShortcut *m_shortcut;
|
||||
};
|
||||
QList<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;
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
|
@ -8,52 +8,60 @@
|
||||
#include <QtDebug>
|
||||
#include <QTextEdit>
|
||||
#include <QStandardPaths>
|
||||
#include <QCoreApplication>
|
||||
#include "utils/vutils.h"
|
||||
#include "vstyleparser.h"
|
||||
|
||||
const QString VConfigManager::orgName = QString("vnote");
|
||||
|
||||
const QString VConfigManager::appName = QString("vnote");
|
||||
|
||||
const QString VConfigManager::c_version = QString("1.9");
|
||||
|
||||
const QString VConfigManager::c_obsoleteDirConfigFile = QString(".vnote.json");
|
||||
|
||||
const QString VConfigManager::c_dirConfigFile = QString("_vnote.json");
|
||||
const QString VConfigManager::defaultConfigFilePath = QString(":/resources/vnote.ini");
|
||||
|
||||
const QString VConfigManager::c_defaultConfigFilePath = QString(":/resources/vnote.ini");
|
||||
|
||||
const QString VConfigManager::c_defaultConfigFile = QString("vnote.ini");
|
||||
|
||||
const QString VConfigManager::c_sessionConfigFile = QString("session.ini");
|
||||
|
||||
const QString VConfigManager::c_styleConfigFolder = QString("styles");
|
||||
|
||||
const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles");
|
||||
|
||||
const QString VConfigManager::c_defaultCssFile = QString(":/resources/styles/default.css");
|
||||
|
||||
const QString VConfigManager::c_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.css");
|
||||
|
||||
const QString VConfigManager::c_defaultMdhlFile = QString(":/resources/styles/default.mdhl");
|
||||
|
||||
const QString VConfigManager::c_solarizedDarkMdhlFile = QString(":/resources/styles/solarized-dark.mdhl");
|
||||
|
||||
const QString VConfigManager::c_solarizedLightMdhlFile = QString(":/resources/styles/solarized-light.mdhl");
|
||||
|
||||
const QString VConfigManager::c_warningTextStyle = QString("color: red; font: bold");
|
||||
|
||||
const QString VConfigManager::c_dataTextStyle = QString("font: bold");
|
||||
|
||||
const QString VConfigManager::c_dangerBtnStyle = QString("QPushButton {color: #fff; border-color: #d43f3a; background-color: #d9534f;}"
|
||||
"QPushButton::hover {color: #fff; border-color: #ac2925; background-color: #c9302c;}");
|
||||
|
||||
const QString VConfigManager::c_vnoteNotebookFolderName = QString("vnote_notebooks");
|
||||
|
||||
VConfigManager::VConfigManager(QObject *p_parent)
|
||||
: QObject(p_parent), userSettings(NULL), defaultSettings(NULL)
|
||||
: QObject(p_parent),
|
||||
userSettings(NULL),
|
||||
defaultSettings(NULL),
|
||||
m_sessionSettings(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
void VConfigManager::migrateIniFile()
|
||||
{
|
||||
const QString originalFolder = "tamlok";
|
||||
const QString newFolder = orgName;
|
||||
QString configFolder = getConfigFolder();
|
||||
QDir dir(configFolder);
|
||||
dir.cdUp();
|
||||
dir.rename(originalFolder, newFolder);
|
||||
userSettings->sync();
|
||||
}
|
||||
|
||||
void VConfigManager::initialize()
|
||||
{
|
||||
Q_ASSERT(!userSettings && !defaultSettings);
|
||||
userSettings = new QSettings(QSettings::IniFormat, QSettings::UserScope,
|
||||
orgName, appName, this);
|
||||
defaultSettings = new QSettings(defaultConfigFilePath, QSettings::IniFormat, this);
|
||||
|
||||
migrateIniFile();
|
||||
initSettings();
|
||||
|
||||
// Override the default css styles on start up.
|
||||
outputDefaultCssStyle();
|
||||
@ -68,7 +76,6 @@ void VConfigManager::initialize()
|
||||
m_templateCss = getConfigFromSettings("global", "template_css").toString();
|
||||
m_templateCodeBlockCss = getConfigFromSettings("global", "template_code_block_css").toString();
|
||||
m_templateCodeBlockCssUrl = getConfigFromSettings("global", "template_code_block_css_url").toString();
|
||||
curNotebookIndex = getConfigFromSettings("global", "current_notebook").toInt();
|
||||
|
||||
markdownExtensions = hoedown_extensions(HOEDOWN_EXT_TABLES | HOEDOWN_EXT_FENCED_CODE |
|
||||
HOEDOWN_EXT_HIGHLIGHT | HOEDOWN_EXT_AUTOLINK |
|
||||
@ -91,10 +98,7 @@ void VConfigManager::initialize()
|
||||
curRenderBackgroundColor = getConfigFromSettings("global",
|
||||
"current_render_background_color").toString();
|
||||
|
||||
m_toolsDockChecked = getConfigFromSettings("session", "tools_dock_checked").toBool();
|
||||
m_mainWindowGeometry = getConfigFromSettings("session", "main_window_geometry").toByteArray();
|
||||
m_mainWindowState = getConfigFromSettings("session", "main_window_state").toByteArray();
|
||||
m_mainSplitterState = getConfigFromSettings("session", "main_splitter_state").toByteArray();
|
||||
m_toolsDockChecked = getConfigFromSettings("global", "tools_dock_checked").toBool();
|
||||
|
||||
m_findCaseSensitive = getConfigFromSettings("global",
|
||||
"find_case_sensitive").toBool();
|
||||
@ -168,6 +172,8 @@ void VConfigManager::initialize()
|
||||
|
||||
readShortcutsFromSettings();
|
||||
|
||||
readCaptainShortcutsFromSettings();
|
||||
|
||||
initDocSuffixes();
|
||||
|
||||
m_markdownHighlightInterval = getConfigFromSettings("global",
|
||||
@ -187,8 +193,14 @@ void VConfigManager::initialize()
|
||||
m_noteOpenMode = OpenFileMode::Read;
|
||||
}
|
||||
|
||||
m_enableHeadingSequence = getConfigFromSettings("global",
|
||||
"enable_heading_sequence").toBool();
|
||||
int tmpHeadingSequenceType = getConfigFromSettings("global",
|
||||
"heading_sequence_type").toInt();
|
||||
if (tmpHeadingSequenceType < (int)HeadingSequenceType::Invalid
|
||||
&& tmpHeadingSequenceType >= (int)HeadingSequenceType::Disabled) {
|
||||
m_headingSequenceType = (HeadingSequenceType)tmpHeadingSequenceType;
|
||||
} else {
|
||||
m_headingSequenceType = HeadingSequenceType::Disabled;
|
||||
}
|
||||
|
||||
m_headingSequenceBaseLevel = getConfigFromSettings("global",
|
||||
"heading_sequence_base_level").toInt();
|
||||
@ -227,6 +239,76 @@ void VConfigManager::initialize()
|
||||
|
||||
m_doubleClickCloseTab = getConfigFromSettings("global",
|
||||
"double_click_close_tab").toBool();
|
||||
|
||||
m_enableCompactMode = getConfigFromSettings("global",
|
||||
"enable_compact_mode").toBool();
|
||||
|
||||
int tmpStartupPageMode = getConfigFromSettings("global",
|
||||
"startup_page_type").toInt();
|
||||
if (tmpStartupPageMode < (int)StartupPageType::Invalid
|
||||
&& tmpStartupPageMode >= (int)StartupPageType::None) {
|
||||
m_startupPageType = (StartupPageType)tmpStartupPageMode;
|
||||
} else {
|
||||
m_startupPageType = StartupPageType::None;
|
||||
}
|
||||
|
||||
m_startupPages = getConfigFromSettings("global",
|
||||
"startup_pages").toStringList();
|
||||
|
||||
initFromSessionSettings();
|
||||
}
|
||||
|
||||
void VConfigManager::initSettings()
|
||||
{
|
||||
Q_ASSERT(!userSettings && !defaultSettings && !m_sessionSettings);
|
||||
|
||||
const char *codecForIni = "UTF-8";
|
||||
|
||||
// vnote.ini.
|
||||
// First try to read vnote.ini from the directory of the executable.
|
||||
QString userIniPath = QDir(QCoreApplication::applicationDirPath()).filePath(c_defaultConfigFile);
|
||||
if (QFileInfo::exists(userIniPath)) {
|
||||
userSettings = new QSettings(userIniPath,
|
||||
QSettings::IniFormat,
|
||||
this);
|
||||
} else {
|
||||
userSettings = new QSettings(QSettings::IniFormat,
|
||||
QSettings::UserScope,
|
||||
orgName,
|
||||
appName,
|
||||
this);
|
||||
}
|
||||
|
||||
userSettings->setIniCodec(codecForIni);
|
||||
|
||||
qDebug() << "use user config" << userSettings->fileName();
|
||||
|
||||
// Default vnote.ini from resource file.
|
||||
defaultSettings = new QSettings(c_defaultConfigFilePath, QSettings::IniFormat, this);
|
||||
defaultSettings->setIniCodec(codecForIni);
|
||||
|
||||
// session.ini.
|
||||
m_sessionSettings = new QSettings(QDir(getConfigFolder()).filePath(c_sessionConfigFile),
|
||||
QSettings::IniFormat,
|
||||
this);
|
||||
m_sessionSettings->setIniCodec(codecForIni);
|
||||
}
|
||||
|
||||
void VConfigManager::initFromSessionSettings()
|
||||
{
|
||||
curNotebookIndex = getConfigFromSessionSettings("global", "current_notebook").toInt();
|
||||
|
||||
m_mainWindowGeometry = getConfigFromSessionSettings("geometry",
|
||||
"main_window_geometry").toByteArray();
|
||||
|
||||
m_mainWindowState = getConfigFromSessionSettings("geometry",
|
||||
"main_window_state").toByteArray();
|
||||
|
||||
m_mainSplitterState = getConfigFromSessionSettings("geometry",
|
||||
"main_splitter_state").toByteArray();
|
||||
|
||||
m_naviSplitterState = getConfigFromSessionSettings("geometry",
|
||||
"navi_splitter_state").toByteArray();
|
||||
}
|
||||
|
||||
void VConfigManager::readPredefinedColorsFromSettings()
|
||||
@ -245,49 +327,70 @@ void VConfigManager::readPredefinedColorsFromSettings()
|
||||
<< "pre-defined colors from [predefined_colors] section";
|
||||
}
|
||||
|
||||
void VConfigManager::readNotebookFromSettings(QVector<VNotebook *> &p_notebooks, QObject *parent)
|
||||
void VConfigManager::readNotebookFromSettings(QSettings *p_settings,
|
||||
QVector<VNotebook *> &p_notebooks,
|
||||
QObject *parent)
|
||||
{
|
||||
Q_ASSERT(p_notebooks.isEmpty());
|
||||
int size = userSettings->beginReadArray("notebooks");
|
||||
int size = p_settings->beginReadArray("notebooks");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
userSettings->setArrayIndex(i);
|
||||
QString name = userSettings->value("name").toString();
|
||||
QString path = userSettings->value("path").toString();
|
||||
p_settings->setArrayIndex(i);
|
||||
QString name = p_settings->value("name").toString();
|
||||
QString path = p_settings->value("path").toString();
|
||||
VNotebook *notebook = new VNotebook(name, path, parent);
|
||||
notebook->readConfigNotebook();
|
||||
p_notebooks.append(notebook);
|
||||
}
|
||||
userSettings->endArray();
|
||||
|
||||
p_settings->endArray();
|
||||
qDebug() << "read" << p_notebooks.size()
|
||||
<< "notebook items from [notebooks] section";
|
||||
}
|
||||
|
||||
void VConfigManager::writeNotebookToSettings(const QVector<VNotebook *> &p_notebooks)
|
||||
void VConfigManager::writeNotebookToSettings(QSettings *p_settings,
|
||||
const QVector<VNotebook *> &p_notebooks)
|
||||
{
|
||||
// Clear it first
|
||||
userSettings->beginGroup("notebooks");
|
||||
userSettings->remove("");
|
||||
userSettings->endGroup();
|
||||
p_settings->beginGroup("notebooks");
|
||||
p_settings->remove("");
|
||||
p_settings->endGroup();
|
||||
|
||||
userSettings->beginWriteArray("notebooks");
|
||||
p_settings->beginWriteArray("notebooks");
|
||||
for (int i = 0; i < p_notebooks.size(); ++i) {
|
||||
userSettings->setArrayIndex(i);
|
||||
p_settings->setArrayIndex(i);
|
||||
const VNotebook ¬ebook = *p_notebooks[i];
|
||||
userSettings->setValue("name", notebook.getName());
|
||||
userSettings->setValue("path", notebook.getPath());
|
||||
p_settings->setValue("name", notebook.getName());
|
||||
p_settings->setValue("path", notebook.getPath());
|
||||
}
|
||||
userSettings->endArray();
|
||||
|
||||
p_settings->endArray();
|
||||
qDebug() << "write" << p_notebooks.size()
|
||||
<< "notebook items in [notebooks] section";
|
||||
}
|
||||
|
||||
static QVariant getConfigFromSettingsBySectionKey(const QSettings *p_settings,
|
||||
const QString &p_section,
|
||||
const QString &p_key)
|
||||
{
|
||||
QString fullKey = p_section + "/" + p_key;
|
||||
return p_settings->value(fullKey);
|
||||
}
|
||||
|
||||
static void setConfigToSettingsBySectionKey(QSettings *p_settings,
|
||||
const QString &p_section,
|
||||
const QString &p_key,
|
||||
const QVariant &p_value)
|
||||
{
|
||||
QString fullKey = p_section + "/" + p_key;
|
||||
return p_settings->setValue(fullKey, p_value);
|
||||
}
|
||||
|
||||
QVariant VConfigManager::getConfigFromSettings(const QString §ion, const QString &key) const
|
||||
{
|
||||
QString fullKey = section + "/" + key;
|
||||
// First, look up the user-scoped config file
|
||||
QVariant value = userSettings->value(fullKey);
|
||||
QVariant value = getConfigFromSettingsBySectionKey(userSettings, section, key);
|
||||
if (!value.isNull()) {
|
||||
qDebug() << "user config:" << fullKey << value.toString();
|
||||
qDebug() << "user config:" << (section + "/" + key) << value;
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -298,18 +401,14 @@ QVariant VConfigManager::getConfigFromSettings(const QString §ion, const QSt
|
||||
void VConfigManager::setConfigToSettings(const QString §ion, const QString &key, const QVariant &value)
|
||||
{
|
||||
// Set the user-scoped config file
|
||||
QString fullKey = section + "/" + key;
|
||||
userSettings->setValue(fullKey, value);
|
||||
qDebug() << "set user config:" << fullKey << value.toString();
|
||||
setConfigToSettingsBySectionKey(userSettings, section, key, value);
|
||||
qDebug() << "set user config:" << (section + "/" + key) << value;
|
||||
}
|
||||
|
||||
QVariant VConfigManager::getDefaultConfig(const QString &p_section, const QString &p_key) const
|
||||
{
|
||||
QString fullKey = p_section + "/" + p_key;
|
||||
|
||||
QVariant value = defaultSettings->value(fullKey);
|
||||
qDebug() << "default config:" << fullKey << value.toString();
|
||||
|
||||
QVariant value = getConfigFromSettingsBySectionKey(defaultSettings, p_section, p_key);
|
||||
qDebug() << "default config:" << (p_section + "/" + p_key) << value;
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -321,6 +420,24 @@ QVariant VConfigManager::resetDefaultConfig(const QString &p_section, const QStr
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
QVariant VConfigManager::getConfigFromSessionSettings(const QString &p_section,
|
||||
const QString &p_key) const
|
||||
{
|
||||
return getConfigFromSettingsBySectionKey(m_sessionSettings,
|
||||
p_section,
|
||||
p_key);
|
||||
}
|
||||
|
||||
void VConfigManager::setConfigToSessionSettings(const QString &p_section,
|
||||
const QString &p_key,
|
||||
const QVariant &p_value)
|
||||
{
|
||||
setConfigToSettingsBySectionKey(m_sessionSettings,
|
||||
p_section,
|
||||
p_key,
|
||||
p_value);
|
||||
}
|
||||
|
||||
QString VConfigManager::fetchDirConfigFilePath(const QString &p_path)
|
||||
{
|
||||
QDir dir(p_path);
|
||||
@ -887,76 +1004,120 @@ QString VConfigManager::getVnoteNotebookFolderPath()
|
||||
return QDir::home().filePath(c_vnoteNotebookFolderName);
|
||||
}
|
||||
|
||||
QHash<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)
|
||||
{
|
||||
QString lower = p_seq.toLower();
|
||||
return lower != "ctrl+q" && lower != "ctrl+e";
|
||||
return p_seq.toLower() != "ctrl+q"
|
||||
&& !QKeySequence(p_seq).isEmpty();
|
||||
}
|
||||
|
||||
void VConfigManager::readShortcutsFromSettings()
|
||||
{
|
||||
const QString group("shortcuts");
|
||||
|
||||
m_shortcuts.clear();
|
||||
int size = defaultSettings->beginReadArray("shortcuts");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
defaultSettings->setArrayIndex(i);
|
||||
QString op = defaultSettings->value("operation").toString();
|
||||
QString seq = defaultSettings->value("keysequence").toString().trimmed();
|
||||
m_shortcuts = readShortcutsFromSettings(defaultSettings, group);
|
||||
|
||||
if (isValidKeySequence(seq)) {
|
||||
qDebug() << "read shortcut config" << op << seq;
|
||||
m_shortcuts[op] = seq;
|
||||
}
|
||||
}
|
||||
|
||||
defaultSettings->endArray();
|
||||
|
||||
// Whether we need to update user settings.
|
||||
bool needUpdate = false;
|
||||
size = userSettings->beginReadArray("shortcuts");
|
||||
// Update default settings according to user settings.
|
||||
QHash<QString, QString> userShortcuts = readShortcutsFromSettings(userSettings,
|
||||
group);
|
||||
QSet<QString> matched;
|
||||
matched.reserve(m_shortcuts.size());
|
||||
for (int i = 0; i < size; ++i) {
|
||||
userSettings->setArrayIndex(i);
|
||||
QString op = userSettings->value("operation").toString();
|
||||
QString seq = userSettings->value("keysequence").toString().trimmed();
|
||||
|
||||
if (isValidKeySequence(seq)) {
|
||||
qDebug() << "read user shortcut config" << op << seq;
|
||||
auto it = m_shortcuts.find(op);
|
||||
if (it == m_shortcuts.end()) {
|
||||
// Could not find this in default settings.
|
||||
needUpdate = true;
|
||||
for (auto it = userShortcuts.begin(); it != userShortcuts.end(); ++it) {
|
||||
auto defaultIt = m_shortcuts.find(it.key());
|
||||
if (defaultIt != m_shortcuts.end()) {
|
||||
QString sequence = it.value().trimmed();
|
||||
if (sequence != defaultIt.value()) {
|
||||
if (isValidKeySequence(sequence)) {
|
||||
matched.insert(it.key());
|
||||
*defaultIt = sequence;
|
||||
}
|
||||
} else {
|
||||
matched.insert(op);
|
||||
*it = seq;
|
||||
matched.insert(it.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userSettings->endArray();
|
||||
|
||||
if (needUpdate || matched.size() < m_shortcuts.size()) {
|
||||
// Write the combined config to user settings.
|
||||
writeShortcutsToSettings();
|
||||
if (matched.size() < m_shortcuts.size()) {
|
||||
writeShortcutsToSettings(userSettings, group, m_shortcuts);
|
||||
}
|
||||
|
||||
qDebug() << "shortcuts:" << m_shortcuts;
|
||||
}
|
||||
|
||||
void VConfigManager::writeShortcutsToSettings()
|
||||
void VConfigManager::readCaptainShortcutsFromSettings()
|
||||
{
|
||||
// Clear it first
|
||||
userSettings->beginGroup("shortcuts");
|
||||
userSettings->remove("");
|
||||
userSettings->endGroup();
|
||||
const QString group("captain_mode_shortcuts");
|
||||
|
||||
userSettings->beginWriteArray("shortcuts");
|
||||
int idx = 0;
|
||||
for (auto it = m_shortcuts.begin(); it != m_shortcuts.end(); ++it, ++idx) {
|
||||
userSettings->setArrayIndex(idx);
|
||||
userSettings->setValue("operation", it.key());
|
||||
userSettings->setValue("keysequence", it.value());
|
||||
m_captainShortcuts.clear();
|
||||
m_captainShortcuts = readShortcutsFromSettings(defaultSettings, group);
|
||||
|
||||
// Update default settings according to user settings.
|
||||
QHash<QString, QString> userShortcuts = readShortcutsFromSettings(userSettings,
|
||||
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
|
||||
@ -969,6 +1130,16 @@ QString VConfigManager::getShortcutKeySequence(const QString &p_operation) const
|
||||
return *it;
|
||||
}
|
||||
|
||||
QString VConfigManager::getCaptainShortcutKeySequence(const QString &p_operation) const
|
||||
{
|
||||
auto it = m_captainShortcuts.find(p_operation);
|
||||
if (it == m_captainShortcuts.end()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
void VConfigManager::initDocSuffixes()
|
||||
{
|
||||
m_docSuffixes.clear();
|
||||
@ -999,3 +1170,59 @@ void VConfigManager::initDocSuffixes()
|
||||
|
||||
qDebug() << "doc suffixes" << m_docSuffixes;
|
||||
}
|
||||
|
||||
QVector<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;
|
||||
}
|
||||
|
@ -11,6 +11,9 @@
|
||||
#include "hgmarkdownhighlighter.h"
|
||||
#include "vmarkdownconverter.h"
|
||||
#include "vconstants.h"
|
||||
#include "vfilesessioninfo.h"
|
||||
#include "utils/vmetawordmanager.h"
|
||||
|
||||
|
||||
class QJsonObject;
|
||||
class QString;
|
||||
@ -41,6 +44,18 @@ struct MarkdownitOption
|
||||
bool m_linkify;
|
||||
};
|
||||
|
||||
// Type of heading sequence.
|
||||
enum class HeadingSequenceType
|
||||
{
|
||||
Disabled = 0,
|
||||
Enabled,
|
||||
|
||||
// Enabled only for internal notes.
|
||||
EnabledNoteOnly,
|
||||
|
||||
Invalid
|
||||
};
|
||||
|
||||
class VConfigManager : public QObject
|
||||
{
|
||||
public:
|
||||
@ -106,7 +121,10 @@ public:
|
||||
int getCurNotebookIndex() const;
|
||||
void setCurNotebookIndex(int index);
|
||||
|
||||
void getNotebooks(QVector<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);
|
||||
|
||||
hoedown_extensions getMarkdownExtensions() const;
|
||||
@ -153,6 +171,9 @@ public:
|
||||
const QByteArray &getMainSplitterState() const;
|
||||
void setMainSplitterState(const QByteArray &p_state);
|
||||
|
||||
const QByteArray &getNaviSplitterState() const;
|
||||
void setNaviSplitterState(const QByteArray &p_state);
|
||||
|
||||
bool getFindCaseSensitive() const;
|
||||
void setFindCaseSensitive(bool p_enabled);
|
||||
|
||||
@ -254,8 +275,8 @@ public:
|
||||
OpenFileMode getNoteOpenMode() const;
|
||||
void setNoteOpenMode(OpenFileMode p_mode);
|
||||
|
||||
bool getEnableHeadingSequence() const;
|
||||
void setEnableHeadingSequence(bool p_enabled);
|
||||
HeadingSequenceType getHeadingSequenceType() const;
|
||||
void setHeadingSequenceType(HeadingSequenceType p_type);
|
||||
|
||||
int getHeadingSequenceBaseLevel() const;
|
||||
void setHeadingSequenceBaseLevel(int p_level);
|
||||
@ -291,10 +312,32 @@ public:
|
||||
// Whether user specify template_code_block_css_url directly.
|
||||
bool getUserSpecifyTemplateCodeBlockCssUrl() const;
|
||||
|
||||
bool getEnableCompactMode() const;
|
||||
void setEnableCompactMode(bool p_enabled);
|
||||
|
||||
StartupPageType getStartupPageType() const;
|
||||
void setStartupPageType(StartupPageType p_type);
|
||||
|
||||
const QStringList &getStartupPages() const;
|
||||
void setStartupPages(const QStringList &p_pages);
|
||||
|
||||
// Read last opened files from [last_opened_files] of session.ini.
|
||||
QVector<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 empty if there is no corresponding config.
|
||||
QString getShortcutKeySequence(const QString &p_operation) const;
|
||||
|
||||
// Return the configured key sequence in Captain mode.
|
||||
// Return empty if there is no corresponding config.
|
||||
QString getCaptainShortcutKeySequence(const QString &p_operation) const;
|
||||
|
||||
// Get the folder the ini file exists.
|
||||
QString getConfigFolder() const;
|
||||
|
||||
@ -317,7 +360,10 @@ public:
|
||||
QVector<QString> getEditorStyles() const;
|
||||
|
||||
private:
|
||||
// Look up a config from user and default settings.
|
||||
QVariant getConfigFromSettings(const QString §ion, const QString &key) const;
|
||||
|
||||
// Set a config to user settings.
|
||||
void setConfigToSettings(const QString §ion, const QString &key, const QVariant &value);
|
||||
|
||||
// Get default config from vnote.ini.
|
||||
@ -326,8 +372,29 @@ private:
|
||||
// Reset user config to default config and return the default config value.
|
||||
QVariant resetDefaultConfig(const QString &p_section, const QString &p_key);
|
||||
|
||||
void readNotebookFromSettings(QVector<VNotebook *> &p_notebooks, QObject *parent);
|
||||
void writeNotebookToSettings(const QVector<VNotebook *> &p_notebooks);
|
||||
// Look up a config from session settings.
|
||||
QVariant getConfigFromSessionSettings(const QString &p_section, const QString &p_key) const;
|
||||
|
||||
// Set a config to session settings.
|
||||
void setConfigToSessionSettings(const QString &p_section,
|
||||
const QString &p_key,
|
||||
const QVariant &p_value);
|
||||
|
||||
// Init defaultSettings, userSettings, and m_sessionSettings.
|
||||
void initSettings();
|
||||
|
||||
// Init from m_sessionSettings.
|
||||
void initFromSessionSettings();
|
||||
|
||||
// Read [notebooks] section from @p_settings.
|
||||
void readNotebookFromSettings(QSettings *p_settings,
|
||||
QVector<VNotebook *> &p_notebooks,
|
||||
QObject *parent);
|
||||
|
||||
// Write to [notebooks] section to @p_settings.
|
||||
void writeNotebookToSettings(QSettings *p_settings,
|
||||
const QVector<VNotebook *> &p_notebooks);
|
||||
|
||||
void readPredefinedColorsFromSettings();
|
||||
|
||||
// 1. Update styles common in HTML and Markdown;
|
||||
@ -336,10 +403,6 @@ private:
|
||||
|
||||
void updateMarkdownEditStyle();
|
||||
|
||||
// Migrate ini file from tamlok/vnote.ini to vnote/vnote.ini.
|
||||
// This is for the change of org name.
|
||||
void migrateIniFile();
|
||||
|
||||
// Output pre-defined CSS styles to style folder.
|
||||
bool outputDefaultCssStyle() const;
|
||||
|
||||
@ -359,8 +422,16 @@ private:
|
||||
// write the combined configs to user settings.
|
||||
void readShortcutsFromSettings();
|
||||
|
||||
// Write m_shortcuts to the [shortcuts] section in the user settings.
|
||||
void writeShortcutsToSettings();
|
||||
// Read the [captain_mode_shortcuts] section in the settings to init
|
||||
// m_captainShortcuts.
|
||||
void readCaptainShortcutsFromSettings();
|
||||
|
||||
QHash<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.
|
||||
bool isValidKeySequence(const QString &p_seq);
|
||||
@ -393,6 +464,8 @@ private:
|
||||
QString m_templateCodeBlockCssUrl;
|
||||
|
||||
QString m_editorStyle;
|
||||
|
||||
// Index of current notebook.
|
||||
int curNotebookIndex;
|
||||
|
||||
// Markdown Converter
|
||||
@ -429,6 +502,7 @@ private:
|
||||
QByteArray m_mainWindowGeometry;
|
||||
QByteArray m_mainWindowState;
|
||||
QByteArray m_mainSplitterState;
|
||||
QByteArray m_naviSplitterState;
|
||||
|
||||
// Find/Replace dialog options
|
||||
bool m_findCaseSensitive;
|
||||
@ -530,6 +604,10 @@ private:
|
||||
// Operation -> KeySequence.
|
||||
QHash<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.
|
||||
// -1: uninitialized;
|
||||
// 0: do not minimize to the tay;
|
||||
@ -553,7 +631,7 @@ private:
|
||||
OpenFileMode m_noteOpenMode;
|
||||
|
||||
// Whether auto genearte heading sequence.
|
||||
bool m_enableHeadingSequence;
|
||||
HeadingSequenceType m_headingSequenceType;
|
||||
|
||||
// Heading sequence base level.
|
||||
int m_headingSequenceBaseLevel;
|
||||
@ -600,6 +678,15 @@ private:
|
||||
// Whether double click on a tab to close it.
|
||||
bool m_doubleClickCloseTab;
|
||||
|
||||
// Whether put folder and note panel in one single column.
|
||||
bool m_enableCompactMode;
|
||||
|
||||
// Type of the pages to open on startup.
|
||||
StartupPageType m_startupPageType;
|
||||
|
||||
// File paths to open on startup.
|
||||
QStringList m_startupPages;
|
||||
|
||||
// The name of the config file in each directory, obsolete.
|
||||
// Use c_dirConfigFile instead.
|
||||
static const QString c_obsoleteDirConfigFile;
|
||||
@ -607,13 +694,25 @@ private:
|
||||
// The name of the config file in each directory.
|
||||
static const QString c_dirConfigFile;
|
||||
|
||||
// The name of the default configuration file
|
||||
static const QString defaultConfigFilePath;
|
||||
// The path of the default configuration file
|
||||
static const QString c_defaultConfigFilePath;
|
||||
|
||||
// The name of the config file.
|
||||
static const QString c_defaultConfigFile;
|
||||
|
||||
// The name of the config file for session information.
|
||||
static const QString c_sessionConfigFile;
|
||||
|
||||
// QSettings for the user configuration
|
||||
QSettings *userSettings;
|
||||
// Qsettings for @defaultConfigFileName
|
||||
|
||||
// Qsettings for @c_defaultConfigFilePath.
|
||||
QSettings *defaultSettings;
|
||||
|
||||
// QSettings for the session configuration, such as notebooks,
|
||||
// geometry, last opened files.
|
||||
QSettings *m_sessionSettings;
|
||||
|
||||
// The folder name of style files.
|
||||
static const QString c_styleConfigFolder;
|
||||
|
||||
@ -681,18 +780,35 @@ inline void VConfigManager::setCurNotebookIndex(int index)
|
||||
if (index == curNotebookIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
curNotebookIndex = index;
|
||||
setConfigToSettings("global", "current_notebook", index);
|
||||
setConfigToSessionSettings("global", "current_notebook", index);
|
||||
}
|
||||
|
||||
inline void VConfigManager::getNotebooks(QVector<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)
|
||||
{
|
||||
writeNotebookToSettings(p_notebooks);
|
||||
writeNotebookToSettings(m_sessionSettings, p_notebooks);
|
||||
}
|
||||
|
||||
inline hoedown_extensions VConfigManager::getMarkdownExtensions() const
|
||||
@ -860,7 +976,7 @@ inline bool VConfigManager::getToolsDockChecked() const
|
||||
inline void VConfigManager::setToolsDockChecked(bool p_checked)
|
||||
{
|
||||
m_toolsDockChecked = p_checked;
|
||||
setConfigToSettings("session", "tools_dock_checked",
|
||||
setConfigToSettings("global", "tools_dock_checked",
|
||||
m_toolsDockChecked);
|
||||
}
|
||||
|
||||
@ -872,8 +988,9 @@ inline const QByteArray& VConfigManager::getMainWindowGeometry() const
|
||||
inline void VConfigManager::setMainWindowGeometry(const QByteArray &p_geometry)
|
||||
{
|
||||
m_mainWindowGeometry = p_geometry;
|
||||
setConfigToSettings("session", "main_window_geometry",
|
||||
m_mainWindowGeometry);
|
||||
setConfigToSessionSettings("geometry",
|
||||
"main_window_geometry",
|
||||
m_mainWindowGeometry);
|
||||
}
|
||||
|
||||
inline const QByteArray& VConfigManager::getMainWindowState() const
|
||||
@ -884,8 +1001,9 @@ inline const QByteArray& VConfigManager::getMainWindowState() const
|
||||
inline void VConfigManager::setMainWindowState(const QByteArray &p_state)
|
||||
{
|
||||
m_mainWindowState = p_state;
|
||||
setConfigToSettings("session", "main_window_state",
|
||||
m_mainWindowState);
|
||||
setConfigToSessionSettings("geometry",
|
||||
"main_window_state",
|
||||
m_mainWindowState);
|
||||
}
|
||||
|
||||
inline const QByteArray& VConfigManager::getMainSplitterState() const
|
||||
@ -896,7 +1014,22 @@ inline const QByteArray& VConfigManager::getMainSplitterState() const
|
||||
inline void VConfigManager::setMainSplitterState(const QByteArray &p_state)
|
||||
{
|
||||
m_mainSplitterState = p_state;
|
||||
setConfigToSettings("session", "main_splitter_state", m_mainSplitterState);
|
||||
setConfigToSessionSettings("geometry",
|
||||
"main_splitter_state",
|
||||
m_mainSplitterState);
|
||||
}
|
||||
|
||||
inline const QByteArray& VConfigManager::getNaviSplitterState() const
|
||||
{
|
||||
return m_naviSplitterState;
|
||||
}
|
||||
|
||||
inline void VConfigManager::setNaviSplitterState(const QByteArray &p_state)
|
||||
{
|
||||
m_naviSplitterState = p_state;
|
||||
setConfigToSessionSettings("geometry",
|
||||
"navi_splitter_state",
|
||||
m_naviSplitterState);
|
||||
}
|
||||
|
||||
inline bool VConfigManager::getFindCaseSensitive() const
|
||||
@ -1370,20 +1503,21 @@ inline void VConfigManager::setNoteOpenMode(OpenFileMode p_mode)
|
||||
m_noteOpenMode == OpenFileMode::Read ? 0 : 1);
|
||||
}
|
||||
|
||||
inline bool VConfigManager::getEnableHeadingSequence() const
|
||||
inline HeadingSequenceType VConfigManager::getHeadingSequenceType() const
|
||||
{
|
||||
return m_enableHeadingSequence;
|
||||
return m_headingSequenceType;
|
||||
}
|
||||
|
||||
inline void VConfigManager::setEnableHeadingSequence(bool p_enabled)
|
||||
inline void VConfigManager::setHeadingSequenceType(HeadingSequenceType p_type)
|
||||
{
|
||||
if (m_enableHeadingSequence == p_enabled) {
|
||||
if (m_headingSequenceType == p_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_enableHeadingSequence = p_enabled;
|
||||
setConfigToSettings("global", "enable_heading_sequence",
|
||||
m_enableHeadingSequence);
|
||||
m_headingSequenceType = p_type;
|
||||
setConfigToSettings("global",
|
||||
"heading_sequence_type",
|
||||
(int)m_headingSequenceType);
|
||||
}
|
||||
|
||||
inline int VConfigManager::getHeadingSequenceBaseLevel() const
|
||||
@ -1570,4 +1704,49 @@ inline bool VConfigManager::getUserSpecifyTemplateCodeBlockCssUrl() const
|
||||
return !m_templateCodeBlockCssUrl.isEmpty();
|
||||
}
|
||||
|
||||
inline bool VConfigManager::getEnableCompactMode() const
|
||||
{
|
||||
return m_enableCompactMode;
|
||||
}
|
||||
|
||||
inline void VConfigManager::setEnableCompactMode(bool p_enabled)
|
||||
{
|
||||
if (m_enableCompactMode == p_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_enableCompactMode = p_enabled;
|
||||
setConfigToSettings("global", "enable_compact_mode", m_enableCompactMode);
|
||||
}
|
||||
|
||||
inline StartupPageType VConfigManager::getStartupPageType() const
|
||||
{
|
||||
return m_startupPageType;
|
||||
}
|
||||
|
||||
inline void VConfigManager::setStartupPageType(StartupPageType p_type)
|
||||
{
|
||||
if (m_startupPageType == p_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_startupPageType = p_type;
|
||||
setConfigToSettings("global", "startup_page_type", (int)m_startupPageType);
|
||||
}
|
||||
|
||||
inline const QStringList &VConfigManager::getStartupPages() const
|
||||
{
|
||||
return m_startupPages;
|
||||
}
|
||||
|
||||
inline void VConfigManager::setStartupPages(const QStringList &p_pages)
|
||||
{
|
||||
if (m_startupPages == p_pages) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_startupPages = p_pages;
|
||||
setConfigToSettings("global", "startup_pages", m_startupPages);
|
||||
}
|
||||
|
||||
#endif // VCONFIGMANAGER_H
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef VCONSTANTS_H
|
||||
#define VCONSTANTS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
// Html: rich text file;
|
||||
// Markdown: Markdown text file;
|
||||
// List: Infinite list file like WorkFlowy;
|
||||
@ -22,7 +24,7 @@ namespace ClipboardConfig
|
||||
static const QString c_dirs = "dirs";
|
||||
}
|
||||
|
||||
enum class OpenFileMode {Read = 0, Edit};
|
||||
enum class OpenFileMode {Read = 0, Edit, Invalid };
|
||||
|
||||
static const qreal c_webZoomFactorMax = 5;
|
||||
static const qreal c_webZoomFactorMin = 0.25;
|
||||
@ -50,12 +52,16 @@ namespace DirConfig
|
||||
|
||||
static const QString c_emptyHeaderName = "[EMPTY]";
|
||||
|
||||
enum class TextDecoration { None,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
Strikethrough,
|
||||
InlineCode };
|
||||
enum class TextDecoration
|
||||
{
|
||||
None,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
Strikethrough,
|
||||
InlineCode,
|
||||
CodeBlock
|
||||
};
|
||||
|
||||
enum FindOption
|
||||
{
|
||||
@ -97,4 +103,13 @@ enum class LineNumberType
|
||||
CodeBlock
|
||||
};
|
||||
|
||||
// Pages to open on start up.
|
||||
enum class StartupPageType
|
||||
{
|
||||
None = 0,
|
||||
ContinueLeftOff = 1,
|
||||
SpecificPages = 2,
|
||||
Invalid
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -430,7 +430,7 @@ void VDirectoryTree::newSubDirectory()
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(curDir->getName());
|
||||
QString defaultName("new_folder");
|
||||
defaultName = VUtils::getFileNameWithSequence(curDir->fetchPath(), defaultName);
|
||||
defaultName = VUtils::getDirNameWithSequence(curDir->fetchPath(), defaultName);
|
||||
VNewDirDialog dialog(tr("Create Folder"), info, defaultName, curDir, this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QString name = dialog.getNameInput();
|
||||
@ -467,7 +467,7 @@ void VDirectoryTree::newRootDirectory()
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_notebook->getName());
|
||||
QString defaultName("new_folder");
|
||||
defaultName = VUtils::getFileNameWithSequence(rootDir->fetchPath(), defaultName);
|
||||
defaultName = VUtils::getDirNameWithSequence(rootDir->fetchPath(), defaultName);
|
||||
VNewDirDialog dialog(tr("Create Root Folder"), info, defaultName, rootDir, this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QString name = dialog.getNameInput();
|
||||
|
@ -16,9 +16,15 @@ class VDocument : public QObject
|
||||
public:
|
||||
// @p_file could be NULL.
|
||||
VDocument(const VFile *p_file, QObject *p_parent = 0);
|
||||
|
||||
QString getToc();
|
||||
|
||||
// Scroll to @anchor in the web.
|
||||
// @anchor is the id without '#', like "toc_1". If empty, will scroll to top.
|
||||
void scrollToAnchor(const QString &anchor);
|
||||
|
||||
void setHtml(const QString &html);
|
||||
|
||||
// Request to highlight a segment text.
|
||||
// Use p_id to identify the result.
|
||||
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
|
||||
@ -35,6 +41,7 @@ public slots:
|
||||
|
||||
// When the Web view has been scrolled, it will signal current header anchor.
|
||||
// Empty @anchor to indicate an invalid header.
|
||||
// The header does not begins with '#'.
|
||||
void setHeader(const QString &anchor);
|
||||
|
||||
void setLog(const QString &p_log);
|
||||
@ -49,8 +56,12 @@ public slots:
|
||||
|
||||
signals:
|
||||
void textChanged(const QString &text);
|
||||
|
||||
void tocChanged(const QString &toc);
|
||||
|
||||
void requestScrollToAnchor(const QString &anchor);
|
||||
|
||||
// @anchor is the id of that anchor, without '#'.
|
||||
void headerChanged(const QString &anchor);
|
||||
void htmlChanged(const QString &html);
|
||||
void logChanged(const QString &p_log);
|
||||
|
100
src/vedit.cpp
@ -4,19 +4,26 @@
|
||||
#include "vedit.h"
|
||||
#include "vnote.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vtoc.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "utils/veditutils.h"
|
||||
#include "utils/vmetawordmanager.h"
|
||||
#include "veditoperations.h"
|
||||
#include "vedittab.h"
|
||||
#include "dialog/vinsertlinkdialog.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
extern VNote *g_vnote;
|
||||
|
||||
void VEditConfig::init(const QFontMetrics &p_metric)
|
||||
extern VMetaWordManager *g_mwMgr;
|
||||
|
||||
void VEditConfig::init(const QFontMetrics &p_metric,
|
||||
bool p_enableHeadingSequence)
|
||||
{
|
||||
update(p_metric);
|
||||
|
||||
// Init configs that do not support update later.
|
||||
m_enableVimMode = g_config->getEnableVimMode();
|
||||
|
||||
if (g_config->getLineDistanceHeight() <= 0) {
|
||||
@ -26,6 +33,8 @@ void VEditConfig::init(const QFontMetrics &p_metric)
|
||||
}
|
||||
|
||||
m_highlightWholeBlock = m_enableVimMode;
|
||||
|
||||
m_enableHeadingSequence = p_enableHeadingSequence;
|
||||
}
|
||||
|
||||
void VEditConfig::update(const QFontMetrics &p_metric)
|
||||
@ -84,7 +93,7 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
|
||||
|
||||
updateFontAndPalette();
|
||||
|
||||
m_config.init(QFontMetrics(font()));
|
||||
m_config.init(QFontMetrics(font()), false);
|
||||
updateConfig();
|
||||
|
||||
connect(this, &VEdit::cursorPositionChanged,
|
||||
@ -157,15 +166,16 @@ void VEdit::reloadFile()
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
void VEdit::scrollToLine(int p_lineNumber)
|
||||
bool VEdit::scrollToBlock(int p_blockNumber)
|
||||
{
|
||||
Q_ASSERT(p_lineNumber >= 0);
|
||||
|
||||
QTextBlock block = document()->findBlockByLineNumber(p_lineNumber);
|
||||
QTextBlock block = document()->findBlockByNumber(p_blockNumber);
|
||||
if (block.isValid()) {
|
||||
VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
|
||||
moveCursor(QTextCursor::EndOfBlock);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VEdit::isModified() const
|
||||
@ -189,6 +199,49 @@ void VEdit::insertImage()
|
||||
}
|
||||
}
|
||||
|
||||
void VEdit::insertLink()
|
||||
{
|
||||
if (!m_editOps) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text;
|
||||
QString linkText, linkUrl;
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.hasSelection()) {
|
||||
text = VEditUtils::selectedText(cursor).trimmed();
|
||||
// Only pure space is accepted.
|
||||
QRegExp reg("[\\S ]*");
|
||||
if (reg.exactMatch(text)) {
|
||||
QUrl url = QUrl::fromUserInput(text,
|
||||
m_file->fetchBasePath());
|
||||
QRegExp urlReg("[\\.\\\\/]");
|
||||
if (url.isValid()
|
||||
&& text.contains(urlReg)) {
|
||||
// Url.
|
||||
linkUrl = text;
|
||||
} else {
|
||||
// Text.
|
||||
linkText = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VInsertLinkDialog dialog(tr("Insert Link"),
|
||||
"",
|
||||
"",
|
||||
linkText,
|
||||
linkUrl,
|
||||
this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
linkText = dialog.getLinkText();
|
||||
linkUrl = dialog.getLinkUrl();
|
||||
Q_ASSERT(!linkText.isEmpty() && !linkUrl.isEmpty());
|
||||
|
||||
m_editOps->insertLink(linkText, linkUrl);
|
||||
}
|
||||
}
|
||||
|
||||
bool VEdit::peekText(const QString &p_text, uint p_options, bool p_forward)
|
||||
{
|
||||
if (p_text.isEmpty()) {
|
||||
@ -1383,3 +1436,36 @@ void VEdit::updateBlockLineDistanceHeight(int p_pos,
|
||||
cursor.endEditBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void VEdit::evaluateMagicWords()
|
||||
{
|
||||
QString text;
|
||||
QTextCursor cursor = textCursor();
|
||||
if (!cursor.hasSelection()) {
|
||||
// Get the WORD in current cursor.
|
||||
int start, end;
|
||||
VEditUtils::findCurrentWORD(cursor, start, end);
|
||||
|
||||
if (start == end) {
|
||||
return;
|
||||
} else {
|
||||
cursor.setPosition(start);
|
||||
cursor.setPosition(end, QTextCursor::KeepAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
text = VEditUtils::selectedText(cursor);
|
||||
Q_ASSERT(!text.isEmpty());
|
||||
QString evaText = g_mwMgr->evaluate(text);
|
||||
if (text != evaText) {
|
||||
qDebug() << "evaluateMagicWords" << text << evaText;
|
||||
|
||||
cursor.insertText(evaText);
|
||||
|
||||
if (m_editOps) {
|
||||
m_editOps->setVimMode(VimMode::Insert);
|
||||
}
|
||||
|
||||
setTextCursor(cursor);
|
||||
}
|
||||
}
|
||||
|
23
src/vedit.h
@ -10,7 +10,6 @@
|
||||
#include <QRect>
|
||||
#include <QFontMetrics>
|
||||
#include "vconstants.h"
|
||||
#include "vtoc.h"
|
||||
#include "vnotefile.h"
|
||||
|
||||
class VEditOperations;
|
||||
@ -38,10 +37,12 @@ public:
|
||||
m_tabSpaces("\t"),
|
||||
m_enableVimMode(false),
|
||||
m_highlightWholeBlock(false),
|
||||
m_lineDistanceHeight(0)
|
||||
m_lineDistanceHeight(0),
|
||||
m_enableHeadingSequence(false)
|
||||
{}
|
||||
|
||||
void init(const QFontMetrics &p_metric);
|
||||
void init(const QFontMetrics &p_metric,
|
||||
bool p_enableHeadingSequence);
|
||||
|
||||
// Only update those configs which could be updated online.
|
||||
void update(const QFontMetrics &p_metric);
|
||||
@ -64,6 +65,9 @@ public:
|
||||
|
||||
// Line distance height in pixels.
|
||||
int m_lineDistanceHeight;
|
||||
|
||||
// Whether enable auto heading sequence.
|
||||
bool m_enableHeadingSequence;
|
||||
};
|
||||
|
||||
class LineNumberArea;
|
||||
@ -81,10 +85,15 @@ public:
|
||||
virtual void setModified(bool p_modified);
|
||||
bool isModified() const;
|
||||
virtual void reloadFile();
|
||||
virtual void scrollToLine(int p_lineNumber);
|
||||
|
||||
virtual bool scrollToBlock(int p_blockNumber);
|
||||
|
||||
// User requests to insert an image.
|
||||
virtual void insertImage();
|
||||
|
||||
// User requests to insert a link.
|
||||
virtual void insertLink();
|
||||
|
||||
// Used for incremental search.
|
||||
// User has enter the content to search, but does not enter the "find" button yet.
|
||||
bool peekText(const QString &p_text, uint p_options, bool p_forward = true);
|
||||
@ -135,6 +144,9 @@ public:
|
||||
|
||||
bool isBlockVisible(const QTextBlock &p_block);
|
||||
|
||||
// Evaluate selected text or cursor word as magic words.
|
||||
void evaluateMagicWords();
|
||||
|
||||
signals:
|
||||
// Request VEditTab to save and exit edit mode.
|
||||
void saveAndRead();
|
||||
@ -163,6 +175,9 @@ signals:
|
||||
// Request the edit tab to close find and replace dialog.
|
||||
void requestCloseFindReplaceDialog();
|
||||
|
||||
// Emit when all initialization is ready.
|
||||
void ready();
|
||||
|
||||
public slots:
|
||||
virtual void highlightCurrentLine();
|
||||
|
||||
|
@ -7,18 +7,27 @@
|
||||
#include "vfile.h"
|
||||
#include "dialog/vfindreplacedialog.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vfilesessioninfo.h"
|
||||
#include "vmainwindow.h"
|
||||
#include "vcaptain.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
extern VNote *g_vnote;
|
||||
|
||||
VEditArea::VEditArea(VNote *vnote, QWidget *parent)
|
||||
: QWidget(parent), VNavigationMode(),
|
||||
vnote(vnote), curWindowIndex(-1)
|
||||
extern VMainWindow *g_mainWin;
|
||||
|
||||
VEditArea::VEditArea(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
VNavigationMode(),
|
||||
curWindowIndex(-1)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
insertSplitWindow(0);
|
||||
setCurrentWindow(0, false);
|
||||
|
||||
registerCaptainTargets();
|
||||
}
|
||||
|
||||
void VEditArea::setupUI()
|
||||
@ -65,7 +74,7 @@ void VEditArea::setupUI()
|
||||
|
||||
void VEditArea::insertSplitWindow(int idx)
|
||||
{
|
||||
VEditWindow *win = new VEditWindow(vnote, this);
|
||||
VEditWindow *win = new VEditWindow(this);
|
||||
splitter->insertWidget(idx, win);
|
||||
connect(win, &VEditWindow::tabStatusUpdated,
|
||||
this, &VEditArea::handleWindowTabStatusUpdated);
|
||||
@ -76,9 +85,9 @@ void VEditArea::insertSplitWindow(int idx)
|
||||
connect(win, &VEditWindow::getFocused,
|
||||
this, &VEditArea::handleWindowFocused);
|
||||
connect(win, &VEditWindow::outlineChanged,
|
||||
this, &VEditArea::handleOutlineChanged);
|
||||
connect(win, &VEditWindow::curHeaderChanged,
|
||||
this, &VEditArea::handleCurHeaderChanged);
|
||||
this, &VEditArea::handleWindowOutlineChanged);
|
||||
connect(win, &VEditWindow::currentHeaderChanged,
|
||||
this, &VEditArea::handleWindowCurrentHeaderChanged);
|
||||
connect(win, &VEditWindow::statusMessage,
|
||||
this, &VEditArea::handleWindowStatusMessage);
|
||||
connect(win, &VEditWindow::vimStatusUpdated,
|
||||
@ -119,17 +128,17 @@ void VEditArea::removeSplitWindow(VEditWindow *win)
|
||||
win->deleteLater();
|
||||
}
|
||||
|
||||
void VEditArea::openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode)
|
||||
VEditTab *VEditArea::openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode)
|
||||
{
|
||||
if (!p_file) {
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If it is DocType::Unknown, open it using system default method.
|
||||
if (p_file->getDocType() == DocType::Unknown) {
|
||||
QUrl url = QUrl::fromLocalFile(p_file->fetchPath());
|
||||
QDesktopServices::openUrl(url);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find if it has been opened already
|
||||
@ -165,6 +174,8 @@ void VEditArea::openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode)
|
||||
tabIdx = openFileInWindow(winIdx, p_file, p_mode);
|
||||
|
||||
out:
|
||||
VEditTab *tab = getTab(winIdx, tabIdx);
|
||||
|
||||
setCurrentTab(winIdx, tabIdx, setFocus);
|
||||
|
||||
if (existFile && p_forceMode) {
|
||||
@ -174,6 +185,8 @@ out:
|
||||
editFile();
|
||||
}
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
QVector<QPair<int, int> > VEditArea::findTabsByFile(const VFile *p_file)
|
||||
@ -232,15 +245,13 @@ void VEditArea::updateWindowStatus()
|
||||
Q_ASSERT(splitter->count() == 0);
|
||||
|
||||
emit tabStatusUpdated(VEditTabInfo());
|
||||
emit outlineChanged(VToc());
|
||||
emit curHeaderChanged(VAnchor());
|
||||
emit outlineChanged(VTableOfContent());
|
||||
emit currentHeaderChanged(VHeaderPointer());
|
||||
return;
|
||||
}
|
||||
|
||||
VEditWindow *win = getWindow(curWindowIndex);
|
||||
win->updateTabStatus();
|
||||
win->requestUpdateOutline();
|
||||
win->requestUpdateCurHeader();
|
||||
}
|
||||
|
||||
bool VEditArea::closeFile(const VFile *p_file, bool p_forced)
|
||||
@ -431,26 +442,28 @@ void VEditArea::handleWindowFocused()
|
||||
}
|
||||
}
|
||||
|
||||
void VEditArea::handleOutlineChanged(const VToc &toc)
|
||||
void VEditArea::handleWindowOutlineChanged(const VTableOfContent &p_outline)
|
||||
{
|
||||
QObject *winObject = sender();
|
||||
if (splitter->widget(curWindowIndex) == winObject) {
|
||||
emit outlineChanged(toc);
|
||||
emit outlineChanged(p_outline);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditArea::handleCurHeaderChanged(const VAnchor &anchor)
|
||||
void VEditArea::handleWindowCurrentHeaderChanged(const VHeaderPointer &p_header)
|
||||
{
|
||||
QObject *winObject = sender();
|
||||
if (splitter->widget(curWindowIndex) == winObject) {
|
||||
emit curHeaderChanged(anchor);
|
||||
emit currentHeaderChanged(p_header);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditArea::handleOutlineItemActivated(const VAnchor &anchor)
|
||||
void VEditArea::scrollToHeader(const VHeaderPointer &p_header)
|
||||
{
|
||||
// Notice current window
|
||||
getWindow(curWindowIndex)->scrollCurTab(anchor);
|
||||
VEditWindow *win = getCurrentWindow();
|
||||
if (win) {
|
||||
win->scrollToHeader(p_header);
|
||||
}
|
||||
}
|
||||
|
||||
bool VEditArea::isFileOpened(const VFile *p_file)
|
||||
@ -482,13 +495,35 @@ void VEditArea::handleNotebookUpdated(const VNotebook *p_notebook)
|
||||
}
|
||||
}
|
||||
|
||||
VEditTab *VEditArea::currentEditTab()
|
||||
VEditTab *VEditArea::getCurrentTab() const
|
||||
{
|
||||
if (curWindowIndex == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VEditWindow *win = getWindow(curWindowIndex);
|
||||
return win->currentEditTab();
|
||||
return win->getCurrentTab();
|
||||
}
|
||||
|
||||
VEditTab *VEditArea::getTab(int p_winIdx, int p_tabIdx) const
|
||||
{
|
||||
VEditWindow *win = getWindow(p_winIdx);
|
||||
if (!win) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return win->getTab(p_tabIdx);
|
||||
}
|
||||
|
||||
QVector<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
|
||||
@ -518,7 +553,7 @@ void VEditArea::moveTab(QWidget *p_widget, int p_fromIdx, int p_toIdx)
|
||||
// Only propogate the search in the IncrementalSearch case.
|
||||
void VEditArea::handleFindTextChanged(const QString &p_text, uint p_options)
|
||||
{
|
||||
VEditTab *tab = currentEditTab();
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
if (p_options & FindOption::IncrementalSearch) {
|
||||
tab->findText(p_text, p_options, true);
|
||||
@ -539,7 +574,7 @@ void VEditArea::handleFindNext(const QString &p_text, uint p_options,
|
||||
bool p_forward)
|
||||
{
|
||||
qDebug() << "find next" << p_text << p_options << p_forward;
|
||||
VEditTab *tab = currentEditTab();
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
tab->findText(p_text, p_options, false, p_forward);
|
||||
}
|
||||
@ -550,7 +585,7 @@ void VEditArea::handleReplace(const QString &p_text, uint p_options,
|
||||
{
|
||||
qDebug() << "replace" << p_text << p_options << "with" << p_replaceText
|
||||
<< p_findNext;
|
||||
VEditTab *tab = currentEditTab();
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
tab->replaceText(p_text, p_options, p_replaceText, p_findNext);
|
||||
}
|
||||
@ -560,7 +595,7 @@ void VEditArea::handleReplaceAll(const QString &p_text, uint p_options,
|
||||
const QString &p_replaceText)
|
||||
{
|
||||
qDebug() << "replace all" << p_text << p_options << "with" << p_replaceText;
|
||||
VEditTab *tab = currentEditTab();
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
tab->replaceTextAll(p_text, p_options, p_replaceText);
|
||||
}
|
||||
@ -584,7 +619,7 @@ void VEditArea::handleFindDialogClosed()
|
||||
|
||||
QString VEditArea::getSelectedText()
|
||||
{
|
||||
VEditTab *tab = currentEditTab();
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
return tab->getSelectedText();
|
||||
} else {
|
||||
@ -621,6 +656,7 @@ VEditWindow *VEditArea::getCurrentWindow() const
|
||||
if (curWindowIndex < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return getWindow(curWindowIndex);
|
||||
}
|
||||
|
||||
@ -694,3 +730,220 @@ bool VEditArea::handleKeyNavigation(int p_key, bool &p_succeed)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int VEditArea::openFiles(const QVector<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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,10 +12,8 @@
|
||||
#include <QSplitter>
|
||||
#include "vnotebook.h"
|
||||
#include "veditwindow.h"
|
||||
#include "vtoc.h"
|
||||
#include "vnavigationmode.h"
|
||||
|
||||
class VNote;
|
||||
class VFile;
|
||||
class VDirectory;
|
||||
class VFindReplaceDialog;
|
||||
@ -26,7 +24,7 @@ class VEditArea : public QWidget, public VNavigationMode
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VEditArea(VNote *vnote, QWidget *parent = 0);
|
||||
explicit VEditArea(QWidget *parent = 0);
|
||||
|
||||
// Whether @p_file has been opened in edit area.
|
||||
bool isFileOpened(const VFile *p_file);
|
||||
@ -35,17 +33,31 @@ public:
|
||||
bool closeFile(const VFile *p_file, bool p_forced);
|
||||
bool closeFile(const VDirectory *p_dir, bool p_forced);
|
||||
bool closeFile(const VNotebook *p_notebook, bool p_forced);
|
||||
// Returns current edit tab.
|
||||
VEditTab *currentEditTab();
|
||||
// Returns the count of VEditWindow.
|
||||
inline int windowCount() const;
|
||||
|
||||
// Return current edit window.
|
||||
VEditWindow *getCurrentWindow() const;
|
||||
|
||||
// Return current edit tab.
|
||||
VEditTab *getCurrentTab() const;
|
||||
|
||||
// Return the @p_tabIdx tab in the @p_winIdx window.
|
||||
VEditTab *getTab(int p_winIdx, int p_tabIdx) const;
|
||||
|
||||
// Return VEditTabInfo of all edit tabs.
|
||||
QVector<VEditTabInfo> getAllTabsInfo() const;
|
||||
|
||||
// Return the count of VEditWindow.
|
||||
int windowCount() const;
|
||||
|
||||
// Returns the index of @p_window.
|
||||
int windowIndex(const VEditWindow *p_window) const;
|
||||
// Move tab widget @p_widget from window @p_fromIdx to @p_toIdx.
|
||||
// @p_widget has been removed from the original window.
|
||||
// If fail, just delete the p_widget.
|
||||
void moveTab(QWidget *p_widget, int p_fromIdx, int p_toIdx);
|
||||
inline VFindReplaceDialog *getFindReplaceDialog() const;
|
||||
|
||||
VFindReplaceDialog *getFindReplaceDialog() const;
|
||||
|
||||
// Return selected text of current edit tab.
|
||||
QString getSelectedText();
|
||||
void splitCurrentWindow();
|
||||
@ -54,7 +66,6 @@ public:
|
||||
// Return the new current window index, otherwise, return -1.
|
||||
int focusNextWindow(int p_biaIdx);
|
||||
void moveCurrentTabOneSplit(bool p_right);
|
||||
VEditWindow *getCurrentWindow() const;
|
||||
|
||||
// Implementations for VNavigationMode.
|
||||
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
|
||||
@ -62,12 +73,18 @@ public:
|
||||
void hideNavigation() Q_DECL_OVERRIDE;
|
||||
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
|
||||
|
||||
// Open files @p_files.
|
||||
int openFiles(const QVector<VFileSessionInfo> &p_files);
|
||||
|
||||
signals:
|
||||
// Emit when current window's tab status updated.
|
||||
void tabStatusUpdated(const VEditTabInfo &p_info);
|
||||
|
||||
void outlineChanged(const VToc &toc);
|
||||
void curHeaderChanged(const VAnchor &anchor);
|
||||
// Emit when current window's tab's outline changed.
|
||||
void outlineChanged(const VTableOfContent &p_outline);
|
||||
|
||||
// Emit when current window's tab's current header changed.
|
||||
void currentHeaderChanged(const VHeaderPointer &p_header);
|
||||
|
||||
// Emit when want to show message in status bar.
|
||||
void statusMessage(const QString &p_msg);
|
||||
@ -84,13 +101,16 @@ public slots:
|
||||
// @p_forceMode is true.
|
||||
// A given file can be opened in multiple split windows. A given file could be
|
||||
// opened at most in one tab inside a window.
|
||||
void openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode = false);
|
||||
VEditTab *openFile(VFile *p_file, OpenFileMode p_mode, bool p_forceMode = false);
|
||||
|
||||
void editFile();
|
||||
void saveFile();
|
||||
void readFile();
|
||||
void saveAndReadFile();
|
||||
void handleOutlineItemActivated(const VAnchor &anchor);
|
||||
|
||||
// Scroll current tab to @p_header.
|
||||
void scrollToHeader(const VHeaderPointer &p_header);
|
||||
|
||||
void handleFileUpdated(const VFile *p_file);
|
||||
void handleDirectoryUpdated(const VDirectory *p_dir);
|
||||
void handleNotebookUpdated(const VNotebook *p_notebook);
|
||||
@ -102,8 +122,11 @@ private slots:
|
||||
|
||||
void handleRemoveSplitRequest(VEditWindow *curWindow);
|
||||
void handleWindowFocused();
|
||||
void handleOutlineChanged(const VToc &toc);
|
||||
void handleCurHeaderChanged(const VAnchor &anchor);
|
||||
|
||||
void handleWindowOutlineChanged(const VTableOfContent &p_outline);
|
||||
|
||||
void handleWindowCurrentHeaderChanged(const VHeaderPointer &p_header);
|
||||
|
||||
void handleFindTextChanged(const QString &p_text, uint p_options);
|
||||
void handleFindOptionChanged(uint p_options);
|
||||
void handleFindNext(const QString &p_text, uint p_options, bool p_forward);
|
||||
@ -128,14 +151,48 @@ private:
|
||||
int openFileInWindow(int windowIndex, VFile *p_file, OpenFileMode p_mode);
|
||||
void setCurrentTab(int windowIndex, int tabIndex, bool setFocus);
|
||||
void setCurrentWindow(int windowIndex, bool setFocus);
|
||||
inline VEditWindow *getWindow(int windowIndex) const;
|
||||
|
||||
VEditWindow *getWindow(int windowIndex) const;
|
||||
|
||||
void insertSplitWindow(int idx);
|
||||
void removeSplitWindow(VEditWindow *win);
|
||||
|
||||
// Update status of current window.
|
||||
void updateWindowStatus();
|
||||
|
||||
VNote *vnote;
|
||||
// Init targets for Captain mode.
|
||||
void registerCaptainTargets();
|
||||
|
||||
// Captain mode functions.
|
||||
|
||||
// Activate tab @p_idx.
|
||||
static void activateTabByCaptain(void *p_target, void *p_data, int p_idx);
|
||||
|
||||
static void alternateTabByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void showOpenedFileListByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void activateSplitLeftByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void activateSplitRightByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void moveTabSplitLeftByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void moveTabSplitRightByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void activateNextTabByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void activatePreviousTabByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void verticalSplitByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void removeSplitByCaptain(void *p_target, void *p_data);
|
||||
|
||||
// Evaluate selected text or the word on cursor as magic words.
|
||||
static void evaluateMagicWordsByCaptain(void *p_target, void *p_data);
|
||||
|
||||
// End Captain mode functions.
|
||||
|
||||
int curWindowIndex;
|
||||
|
||||
// Splitter holding multiple split windows
|
||||
|
@ -90,3 +90,10 @@ void VEditOperations::requestUpdateVimStatus()
|
||||
{
|
||||
emit vimStatusUpdated(m_vim);
|
||||
}
|
||||
|
||||
void VEditOperations::setVimMode(VimMode p_mode)
|
||||
{
|
||||
if (m_vim && m_editConfig->m_enableVimMode) {
|
||||
m_vim->setMode(p_mode);
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,18 @@ class VEditOperations: public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
VEditOperations(VEdit *p_editor, VFile *p_file);
|
||||
|
||||
virtual ~VEditOperations();
|
||||
|
||||
virtual bool insertImageFromMimeData(const QMimeData *source) = 0;
|
||||
|
||||
virtual bool insertImage() = 0;
|
||||
|
||||
virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 0;
|
||||
|
||||
virtual bool insertLink(const QString &p_linkText,
|
||||
const QString &p_linkUrl) = 0;
|
||||
|
||||
// Return true if @p_event has been handled and no need to be further
|
||||
// processed.
|
||||
virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0;
|
||||
@ -33,6 +40,9 @@ public:
|
||||
// Insert decoration markers or decorate selected text.
|
||||
virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);};
|
||||
|
||||
// Set Vim mode if not NULL.
|
||||
void setVimMode(VimMode p_mode);
|
||||
|
||||
signals:
|
||||
// Want to display a template message in status bar.
|
||||
void statusMessage(const QString &p_msg);
|
||||
|
@ -3,12 +3,13 @@
|
||||
#include <QWheelEvent>
|
||||
|
||||
VEditTab::VEditTab(VFile *p_file, VEditArea *p_editArea, QWidget *p_parent)
|
||||
: QWidget(p_parent), m_file(p_file), m_isEditMode(false),
|
||||
m_modified(false), m_editArea(p_editArea)
|
||||
: QWidget(p_parent),
|
||||
m_file(p_file),
|
||||
m_isEditMode(false),
|
||||
m_outline(p_file),
|
||||
m_currentHeader(p_file, -1),
|
||||
m_editArea(p_editArea)
|
||||
{
|
||||
m_toc.m_file = m_file;
|
||||
m_curHeader.m_file = m_file;
|
||||
|
||||
connect(qApp, &QApplication::focusChanged,
|
||||
this, &VEditTab::handleFocusChanged);
|
||||
}
|
||||
@ -24,6 +25,7 @@ void VEditTab::focusTab()
|
||||
{
|
||||
focusChild();
|
||||
emit getFocused();
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
bool VEditTab::isEditMode() const
|
||||
@ -33,7 +35,7 @@ bool VEditTab::isEditMode() const
|
||||
|
||||
bool VEditTab::isModified() const
|
||||
{
|
||||
return m_modified;
|
||||
return m_file->isModified();
|
||||
}
|
||||
|
||||
VFile *VEditTab::getFile() const
|
||||
@ -48,21 +50,13 @@ void VEditTab::handleFocusChanged(QWidget * /* p_old */, QWidget *p_now)
|
||||
focusChild();
|
||||
|
||||
emit getFocused();
|
||||
updateStatus();
|
||||
} else if (isAncestorOf(p_now)) {
|
||||
emit getFocused();
|
||||
updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void VEditTab::requestUpdateCurHeader()
|
||||
{
|
||||
emit curHeaderChanged(m_curHeader);
|
||||
}
|
||||
|
||||
void VEditTab::requestUpdateOutline()
|
||||
{
|
||||
emit outlineChanged(m_toc);
|
||||
}
|
||||
|
||||
void VEditTab::wheelEvent(QWheelEvent *p_event)
|
||||
{
|
||||
QPoint angle = p_event->angleDelta();
|
||||
@ -78,17 +72,55 @@ void VEditTab::wheelEvent(QWheelEvent *p_event)
|
||||
p_event->ignore();
|
||||
}
|
||||
|
||||
void VEditTab::updateStatus()
|
||||
{
|
||||
m_modified = m_file->isModified();
|
||||
|
||||
emit statusUpdated(createEditTabInfo());
|
||||
}
|
||||
|
||||
VEditTabInfo VEditTab::createEditTabInfo()
|
||||
VEditTabInfo VEditTab::fetchTabInfo() const
|
||||
{
|
||||
VEditTabInfo info;
|
||||
info.m_editTab = this;
|
||||
info.m_editTab = const_cast<VEditTab *>(this);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
const VHeaderPointer &VEditTab::getCurrentHeader() const
|
||||
{
|
||||
return m_currentHeader;
|
||||
}
|
||||
|
||||
const VTableOfContent &VEditTab::getOutline() const
|
||||
{
|
||||
return m_outline;
|
||||
}
|
||||
|
||||
void VEditTab::tryRestoreFromTabInfo(const VEditTabInfo &p_info)
|
||||
{
|
||||
if (p_info.m_editTab != this) {
|
||||
m_infoToRestore.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (restoreFromTabInfo(p_info)) {
|
||||
m_infoToRestore.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Save it and restore later.
|
||||
m_infoToRestore = p_info;
|
||||
}
|
||||
|
||||
void VEditTab::updateStatus()
|
||||
{
|
||||
emit statusUpdated(fetchTabInfo());
|
||||
}
|
||||
|
||||
void VEditTab::evaluateMagicWords()
|
||||
{
|
||||
}
|
||||
|
||||
bool VEditTab::tabHasFocus() const
|
||||
{
|
||||
QWidget *wid = QApplication::focusWidget();
|
||||
return wid == this || isAncestorOf(wid);
|
||||
}
|
||||
|
||||
void VEditTab::insertLink()
|
||||
{
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QPointer>
|
||||
#include "vtoc.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "vfile.h"
|
||||
#include "utils/vvim.h"
|
||||
#include "vedittabinfo.h"
|
||||
@ -37,18 +37,21 @@ public:
|
||||
|
||||
void focusTab();
|
||||
|
||||
virtual void requestUpdateOutline();
|
||||
// Whether this tab has focus.
|
||||
bool tabHasFocus() const;
|
||||
|
||||
virtual void requestUpdateCurHeader();
|
||||
|
||||
// Scroll to anchor @p_anchor.
|
||||
virtual void scrollToAnchor(const VAnchor& p_anchor) = 0;
|
||||
// Scroll to @p_header.
|
||||
// Will emit currentHeaderChanged() if @p_header is valid.
|
||||
virtual void scrollToHeader(const VHeaderPointer &p_header) { Q_UNUSED(p_header) }
|
||||
|
||||
VFile *getFile() const;
|
||||
|
||||
// User requests to insert image.
|
||||
virtual void insertImage() = 0;
|
||||
|
||||
// User requests to insert link.
|
||||
virtual void insertLink();
|
||||
|
||||
// Search @p_text in current note.
|
||||
virtual void findText(const QString &p_text, uint p_options, bool p_peek,
|
||||
bool p_forward = true) = 0;
|
||||
@ -69,15 +72,29 @@ public:
|
||||
virtual void requestUpdateVimStatus() = 0;
|
||||
|
||||
// Insert decoration markers or decorate selected text.
|
||||
virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);};
|
||||
virtual void decorateText(TextDecoration p_decoration) {Q_UNUSED(p_decoration);}
|
||||
|
||||
// Create a filled VEditTabInfo.
|
||||
virtual VEditTabInfo fetchTabInfo() const;
|
||||
|
||||
const VTableOfContent &getOutline() const;
|
||||
|
||||
const VHeaderPointer &getCurrentHeader() const;
|
||||
|
||||
// Restore status from @p_info.
|
||||
// If this tab is not ready yet, it will restore once it is ready.
|
||||
void tryRestoreFromTabInfo(const VEditTabInfo &p_info);
|
||||
|
||||
// Emit signal to update current status.
|
||||
virtual void updateStatus();
|
||||
|
||||
// Called by evaluateMagicWordsByCaptain() to evaluate the magic words.
|
||||
virtual void evaluateMagicWords();
|
||||
|
||||
public slots:
|
||||
// Enter edit mode
|
||||
virtual void editFile() = 0;
|
||||
|
||||
// Update status of current tab. Emit statusUpdated().
|
||||
virtual void updateStatus();
|
||||
|
||||
protected:
|
||||
void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
@ -87,23 +104,32 @@ protected:
|
||||
// Called to zoom in/out content.
|
||||
virtual void zoom(bool p_zoomIn, qreal p_step = 0.25) = 0;
|
||||
|
||||
// Create a filled VEditTabInfo.
|
||||
virtual VEditTabInfo createEditTabInfo();
|
||||
// Restore from @p_fino.
|
||||
// Return true if succeed.
|
||||
virtual bool restoreFromTabInfo(const VEditTabInfo &p_info) = 0;
|
||||
|
||||
// File related to this tab.
|
||||
QPointer<VFile> m_file;
|
||||
|
||||
bool m_isEditMode;
|
||||
bool m_modified;
|
||||
VToc m_toc;
|
||||
VAnchor m_curHeader;
|
||||
|
||||
// Table of content of this tab.
|
||||
VTableOfContent m_outline;
|
||||
|
||||
// Current header in m_outline of this tab.
|
||||
VHeaderPointer m_currentHeader;
|
||||
|
||||
VEditArea *m_editArea;
|
||||
|
||||
// Tab info to restore from once ready.
|
||||
VEditTabInfo m_infoToRestore;
|
||||
|
||||
signals:
|
||||
void getFocused();
|
||||
|
||||
void outlineChanged(const VToc &p_toc);
|
||||
void outlineChanged(const VTableOfContent &p_outline);
|
||||
|
||||
void curHeaderChanged(const VAnchor &p_anchor);
|
||||
void currentHeaderChanged(const VHeaderPointer &p_header);
|
||||
|
||||
// The status of current tab has updates.
|
||||
void statusUpdated(const VEditTabInfo &p_info);
|
||||
|
@ -6,8 +6,22 @@ class VEditTab;
|
||||
struct VEditTabInfo
|
||||
{
|
||||
VEditTabInfo()
|
||||
: m_editTab(NULL), m_cursorBlockNumber(-1), m_cursorPositionInBlock(-1),
|
||||
m_blockCount(-1) {}
|
||||
: m_editTab(NULL),
|
||||
m_cursorBlockNumber(-1),
|
||||
m_cursorPositionInBlock(-1),
|
||||
m_blockCount(-1),
|
||||
m_headerIndex(-1)
|
||||
{
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_editTab = NULL;
|
||||
m_cursorBlockNumber = -1;
|
||||
m_cursorPositionInBlock = -1;
|
||||
m_blockCount = -1;
|
||||
m_headerIndex = -1;
|
||||
}
|
||||
|
||||
VEditTab *m_editTab;
|
||||
|
||||
@ -15,6 +29,9 @@ struct VEditTabInfo
|
||||
int m_cursorBlockNumber;
|
||||
int m_cursorPositionInBlock;
|
||||
int m_blockCount;
|
||||
|
||||
// Header index in outline.
|
||||
int m_headerIndex;
|
||||
};
|
||||
|
||||
#endif // VEDITTABINFO_H
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include <QtDebug>
|
||||
#include "veditwindow.h"
|
||||
#include "vedittab.h"
|
||||
#include "vnote.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vorphanfile.h"
|
||||
#include "vmainwindow.h"
|
||||
@ -13,12 +12,14 @@
|
||||
#include "vfilelist.h"
|
||||
#include "vconfigmanager.h"
|
||||
|
||||
extern VNote *g_vnote;
|
||||
extern VConfigManager *g_config;
|
||||
extern VMainWindow *g_mainWin;
|
||||
|
||||
VEditWindow::VEditWindow(VNote *vnote, VEditArea *editArea, QWidget *parent)
|
||||
: QTabWidget(parent), vnote(vnote), m_editArea(editArea),
|
||||
m_curTabWidget(NULL), m_lastTabWidget(NULL)
|
||||
VEditWindow::VEditWindow(VEditArea *editArea, QWidget *parent)
|
||||
: QTabWidget(parent),
|
||||
m_editArea(editArea),
|
||||
m_curTabWidget(NULL),
|
||||
m_lastTabWidget(NULL)
|
||||
{
|
||||
setAcceptDrops(true);
|
||||
initTabActions();
|
||||
@ -137,9 +138,9 @@ void VEditWindow::initTabActions()
|
||||
Q_ASSERT(file);
|
||||
if (file->getType() == FileType::Note) {
|
||||
VNoteFile *tmpFile = dynamic_cast<VNoteFile *>((VFile *)file);
|
||||
g_vnote->getMainWindow()->getFileList()->fileInfo(tmpFile);
|
||||
g_mainWin->getFileList()->fileInfo(tmpFile);
|
||||
} else if (file->getType() == FileType::Orphan) {
|
||||
g_vnote->getMainWindow()->editOrphanFileInfo(file);
|
||||
g_mainWin->editOrphanFileInfo(file);
|
||||
}
|
||||
});
|
||||
|
||||
@ -262,8 +263,10 @@ void VEditWindow::removeEditTab(int p_index)
|
||||
|
||||
int VEditWindow::insertEditTab(int p_index, VFile *p_file, QWidget *p_page)
|
||||
{
|
||||
int idx = insertTab(p_index, p_page, p_file->getName());
|
||||
setTabToolTip(idx, generateTooltip(p_file));
|
||||
int idx = insertTab(p_index,
|
||||
p_page,
|
||||
p_file->getName());
|
||||
updateTabInfo(idx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
@ -466,15 +469,17 @@ void VEditWindow::updateTabStatus(int p_index)
|
||||
|
||||
if (p_index == -1) {
|
||||
emit tabStatusUpdated(VEditTabInfo());
|
||||
emit outlineChanged(VToc());
|
||||
emit curHeaderChanged(VAnchor());
|
||||
emit outlineChanged(VTableOfContent());
|
||||
emit currentHeaderChanged(VHeaderPointer());
|
||||
return;
|
||||
}
|
||||
|
||||
VEditTab *tab = getTab(p_index);
|
||||
tab->updateStatus();
|
||||
tab->requestUpdateOutline();
|
||||
tab->requestUpdateCurHeader();
|
||||
emit tabStatusUpdated(tab->fetchTabInfo());
|
||||
emit outlineChanged(tab->getOutline());
|
||||
emit currentHeaderChanged(tab->getCurrentHeader());
|
||||
|
||||
updateTabInfo(p_index);
|
||||
}
|
||||
|
||||
void VEditWindow::updateTabInfo(int p_index)
|
||||
@ -483,8 +488,7 @@ void VEditWindow::updateTabInfo(int p_index)
|
||||
const VFile *file = editor->getFile();
|
||||
bool editMode = editor->isEditMode();
|
||||
|
||||
setTabText(p_index, generateTabText(p_index, file->getName(),
|
||||
file->isModified(), file->isModifiable()));
|
||||
setTabText(p_index, generateTabText(p_index, file));
|
||||
setTabToolTip(p_index, generateTooltip(file));
|
||||
|
||||
QString iconUrl(":/resources/icons/reading.svg");
|
||||
@ -501,31 +505,28 @@ void VEditWindow::updateAllTabsSequence()
|
||||
for (int i = 0; i < count(); ++i) {
|
||||
VEditTab *editor = getTab(i);
|
||||
const VFile *file = editor->getFile();
|
||||
setTabText(i, generateTabText(i, file->getName(),
|
||||
file->isModified(), file->isModifiable()));
|
||||
setTabText(i, generateTabText(i, file));
|
||||
}
|
||||
}
|
||||
|
||||
// Be requested to report current outline
|
||||
void VEditWindow::requestUpdateOutline()
|
||||
VTableOfContent VEditWindow::getOutline() const
|
||||
{
|
||||
int idx = currentIndex();
|
||||
if (idx == -1) {
|
||||
emit outlineChanged(VToc());
|
||||
return;
|
||||
return VTableOfContent();
|
||||
}
|
||||
getTab(idx)->requestUpdateOutline();
|
||||
|
||||
return getTab(idx)->getOutline();
|
||||
}
|
||||
|
||||
// Be requested to report current header
|
||||
void VEditWindow::requestUpdateCurHeader()
|
||||
VHeaderPointer VEditWindow::getCurrentHeader() const
|
||||
{
|
||||
int idx = currentIndex();
|
||||
if (idx == -1) {
|
||||
emit curHeaderChanged(VAnchor());
|
||||
return;
|
||||
return VHeaderPointer();
|
||||
}
|
||||
getTab(idx)->requestUpdateCurHeader();
|
||||
|
||||
return getTab(idx)->getCurrentHeader();
|
||||
}
|
||||
|
||||
// Focus this windows. Try to focus current tab.
|
||||
@ -681,44 +682,39 @@ bool VEditWindow::canRemoveSplit()
|
||||
return splitter->count() > 1;
|
||||
}
|
||||
|
||||
void VEditWindow::handleOutlineChanged(const VToc &p_toc)
|
||||
void VEditWindow::handleTabOutlineChanged(const VTableOfContent &p_outline)
|
||||
{
|
||||
// Only propagate it if it is current tab
|
||||
int idx = currentIndex();
|
||||
if (idx == -1) {
|
||||
emit outlineChanged(VToc());
|
||||
// Only propagate it if it is current tab.
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
if (tab->getFile() == p_outline.getFile()) {
|
||||
emit outlineChanged(p_outline);
|
||||
}
|
||||
} else {
|
||||
emit outlineChanged(VTableOfContent());
|
||||
return;
|
||||
}
|
||||
const VFile *file = getTab(idx)->getFile();
|
||||
if (p_toc.m_file == file) {
|
||||
emit outlineChanged(p_toc);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditWindow::handleCurHeaderChanged(const VAnchor &p_anchor)
|
||||
void VEditWindow::handleTabCurrentHeaderChanged(const VHeaderPointer &p_header)
|
||||
{
|
||||
// Only propagate it if it is current tab
|
||||
int idx = currentIndex();
|
||||
if (idx == -1) {
|
||||
emit curHeaderChanged(VAnchor());
|
||||
// Only propagate it if it is current tab.
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
if (tab->getFile() == p_header.m_file) {
|
||||
emit currentHeaderChanged(p_header);
|
||||
}
|
||||
} else {
|
||||
emit currentHeaderChanged(VHeaderPointer());
|
||||
return;
|
||||
}
|
||||
const VFile *file = getTab(idx)->getFile();
|
||||
if (p_anchor.m_file == file) {
|
||||
emit curHeaderChanged(p_anchor);
|
||||
}
|
||||
}
|
||||
|
||||
void VEditWindow::scrollCurTab(const VAnchor &p_anchor)
|
||||
void VEditWindow::scrollToHeader(const VHeaderPointer &p_header)
|
||||
{
|
||||
int idx = currentIndex();
|
||||
if (idx == -1) {
|
||||
emit curHeaderChanged(VAnchor());
|
||||
return;
|
||||
}
|
||||
const VFile *file = getTab(idx)->getFile();
|
||||
if (file == p_anchor.m_file) {
|
||||
getTab(idx)->scrollToAnchor(p_anchor);
|
||||
VEditTab *tab = getCurrentTab();
|
||||
if (tab) {
|
||||
tab->scrollToHeader(p_header);
|
||||
}
|
||||
}
|
||||
|
||||
@ -794,22 +790,37 @@ void VEditWindow::updateNotebookInfo(const VNotebook *p_notebook)
|
||||
}
|
||||
}
|
||||
|
||||
VEditTab *VEditWindow::currentEditTab()
|
||||
VEditTab *VEditWindow::getCurrentTab() const
|
||||
{
|
||||
int idx = currentIndex();
|
||||
if (idx == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return getTab(idx);
|
||||
}
|
||||
|
||||
QVector<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()
|
||||
{
|
||||
int tab = m_locateAct->data().toInt();
|
||||
VEditTab *editor = getTab(tab);
|
||||
QPointer<VFile> file = editor->getFile();
|
||||
if (file->getType() == FileType::Note) {
|
||||
vnote->getMainWindow()->locateFile(file);
|
||||
g_mainWin->locateFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
@ -901,9 +912,9 @@ void VEditWindow::connectEditTab(const VEditTab *p_tab)
|
||||
connect(p_tab, &VEditTab::getFocused,
|
||||
this, &VEditWindow::getFocused);
|
||||
connect(p_tab, &VEditTab::outlineChanged,
|
||||
this, &VEditWindow::handleOutlineChanged);
|
||||
connect(p_tab, &VEditTab::curHeaderChanged,
|
||||
this, &VEditWindow::handleCurHeaderChanged);
|
||||
this, &VEditWindow::handleTabOutlineChanged);
|
||||
connect(p_tab, &VEditTab::currentHeaderChanged,
|
||||
this, &VEditWindow::handleTabCurrentHeaderChanged);
|
||||
connect(p_tab, &VEditTab::statusUpdated,
|
||||
this, &VEditWindow::handleTabStatusUpdated);
|
||||
connect(p_tab, &VEditTab::statusMessage,
|
||||
@ -1014,7 +1025,7 @@ void VEditWindow::dropEvent(QDropEvent *p_event)
|
||||
|
||||
if (!files.isEmpty()) {
|
||||
focusWindow();
|
||||
g_vnote->getMainWindow()->openExternalFiles(files);
|
||||
g_mainWin->openFiles(files);
|
||||
}
|
||||
|
||||
p_event->acceptProposedAction();
|
||||
|
@ -8,11 +8,9 @@
|
||||
#include <QDir>
|
||||
#include "vnotebook.h"
|
||||
#include "vedittab.h"
|
||||
#include "vtoc.h"
|
||||
#include "vconstants.h"
|
||||
#include "vnotefile.h"
|
||||
|
||||
class VNote;
|
||||
class QPushButton;
|
||||
class QActionGroup;
|
||||
class VEditArea;
|
||||
@ -21,7 +19,7 @@ class VEditWindow : public QTabWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VEditWindow(VNote *vnote, VEditArea *editArea, QWidget *parent = 0);
|
||||
explicit VEditWindow(VEditArea *editArea, QWidget *parent = 0);
|
||||
int findTabByFile(const VFile *p_file) const;
|
||||
int openFile(VFile *p_file, OpenFileMode p_mode);
|
||||
bool closeFile(const VFile *p_file, bool p_forced);
|
||||
@ -32,15 +30,29 @@ public:
|
||||
void readFile();
|
||||
void saveAndReadFile();
|
||||
bool closeAllFiles(bool p_forced);
|
||||
void requestUpdateOutline();
|
||||
void requestUpdateCurHeader();
|
||||
|
||||
// Return outline of current tab.
|
||||
VTableOfContent getOutline() const;
|
||||
|
||||
// Return current header of current tab.
|
||||
VHeaderPointer getCurrentHeader() const;
|
||||
|
||||
// Focus to current tab's editor
|
||||
void focusWindow();
|
||||
void scrollCurTab(const VAnchor &p_anchor);
|
||||
|
||||
// Scroll current tab to header @p_header.
|
||||
void scrollToHeader(const VHeaderPointer &p_header);
|
||||
|
||||
void updateFileInfo(const VFile *p_file);
|
||||
void updateDirectoryInfo(const VDirectory *p_dir);
|
||||
void updateNotebookInfo(const VNotebook *p_notebook);
|
||||
VEditTab *currentEditTab();
|
||||
|
||||
VEditTab *getCurrentTab() const;
|
||||
|
||||
VEditTab *getTab(int tabIndex) const;
|
||||
|
||||
QVector<VEditTabInfo> getAllTabsInfo() const;
|
||||
|
||||
// Insert a tab with @p_widget. @p_widget is a fully initialized VEditTab.
|
||||
bool addEditTab(QWidget *p_widget);
|
||||
// Set whether it is the current window.
|
||||
@ -53,7 +65,6 @@ public:
|
||||
bool activateTab(int p_sequence);
|
||||
// Switch to previous activated tab.
|
||||
bool alternateTab();
|
||||
VEditTab *getTab(int tabIndex) const;
|
||||
|
||||
// Ask tab @p_index to update its status and propogate.
|
||||
// The status here means tab status, outline, current header.
|
||||
@ -79,8 +90,10 @@ signals:
|
||||
void requestRemoveSplit(VEditWindow *curWindow);
|
||||
// This widget or its children get the focus
|
||||
void getFocused();
|
||||
void outlineChanged(const VToc &toc);
|
||||
void curHeaderChanged(const VAnchor &anchor);
|
||||
|
||||
void outlineChanged(const VTableOfContent &p_outline);
|
||||
|
||||
void currentHeaderChanged(const VHeaderPointer &p_header);
|
||||
|
||||
// Emit when want to show message in status bar.
|
||||
void statusMessage(const QString &p_msg);
|
||||
@ -100,8 +113,11 @@ private slots:
|
||||
void handleCurrentIndexChanged(int p_index);
|
||||
void contextMenuRequested(QPoint pos);
|
||||
void tabListJump(VFile *p_file);
|
||||
void handleOutlineChanged(const VToc &p_toc);
|
||||
void handleCurHeaderChanged(const VAnchor &p_anchor);
|
||||
|
||||
void handleTabOutlineChanged(const VTableOfContent &p_outline);
|
||||
|
||||
void handleTabCurrentHeaderChanged(const VHeaderPointer &p_header);
|
||||
|
||||
void updateSplitMenu();
|
||||
void tabbarContextMenuRequested(QPoint p_pos);
|
||||
void handleLocateAct();
|
||||
@ -124,9 +140,11 @@ private:
|
||||
int insertEditTab(int p_index, VFile *p_file, QWidget *p_page);
|
||||
int appendEditTab(VFile *p_file, QWidget *p_page);
|
||||
int openFileInTab(VFile *p_file, OpenFileMode p_mode);
|
||||
inline QString generateTooltip(const VFile *p_file) const;
|
||||
inline QString generateTabText(int p_index, const QString &p_name,
|
||||
bool p_modified, bool p_modifiable) const;
|
||||
|
||||
QString generateTooltip(const VFile *p_file) const;
|
||||
|
||||
QString generateTabText(int p_index, const VFile *p_file) const;
|
||||
|
||||
bool canRemoveSplit();
|
||||
|
||||
// Move tab at @p_tabIdx one split window.
|
||||
@ -135,6 +153,7 @@ private:
|
||||
// and move the tab to the new split.
|
||||
void moveTabOneSplit(int p_tabIdx, bool p_right);
|
||||
|
||||
// Update info of tab @p_idx according to the state of the editor and file.
|
||||
void updateTabInfo(int p_idx);
|
||||
|
||||
// Update the sequence number of all the tabs.
|
||||
@ -143,7 +162,6 @@ private:
|
||||
// Connect the signals of VEditTab to this VEditWindow.
|
||||
void connectEditTab(const VEditTab *p_tab);
|
||||
|
||||
VNote *vnote;
|
||||
VEditArea *m_editArea;
|
||||
|
||||
// These two members are only used for alternateTab().
|
||||
@ -197,11 +215,16 @@ inline QString VEditWindow::generateTooltip(const VFile *p_file) const
|
||||
}
|
||||
}
|
||||
|
||||
inline QString VEditWindow::generateTabText(int p_index, const QString &p_name,
|
||||
bool p_modified, bool p_modifiable) const
|
||||
inline QString VEditWindow::generateTabText(int p_index, const VFile *p_file) const
|
||||
{
|
||||
QString seq = QString::number(p_index + c_tabSequenceBase, 10);
|
||||
return seq + ". " + p_name + (p_modifiable ? (p_modified ? "*" : "") : "#");
|
||||
if (!p_file) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return QString("%1.%2%3").arg(QString::number(p_index + c_tabSequenceBase, 10))
|
||||
.arg(p_file->getName())
|
||||
.arg(p_file->isModifiable()
|
||||
? (p_file->isModified() ? "*" : "") : "#");
|
||||
}
|
||||
|
||||
#endif // VEDITWINDOW_H
|
||||
|
@ -263,7 +263,7 @@ void VFileList::fileInfo(VNoteFile *p_file)
|
||||
|
||||
void VFileList::fillItem(QListWidgetItem *p_item, const VNoteFile *p_file)
|
||||
{
|
||||
unsigned long long ptr = (long long)p_file;
|
||||
qulonglong ptr = (qulonglong)p_file;
|
||||
p_item->setData(Qt::UserRole, ptr);
|
||||
p_item->setToolTip(p_file->getName());
|
||||
p_item->setText(p_file->getName());
|
||||
@ -330,7 +330,9 @@ void VFileList::newFile()
|
||||
info = info + "<br>" + tr("Note with name ending with \"%1\" will be treated as Markdown type.")
|
||||
.arg(suffixStr);
|
||||
QString defaultName = QString("new_note.%1").arg(defaultSuf);
|
||||
defaultName = VUtils::getFileNameWithSequence(m_directory->fetchPath(), defaultName);
|
||||
defaultName = VUtils::getFileNameWithSequence(m_directory->fetchPath(),
|
||||
defaultName,
|
||||
true);
|
||||
VNewFileDialog dialog(tr("Create Note"), info, defaultName, m_directory, this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
VNoteFile *file = m_directory->createFile(dialog.getNameInput());
|
||||
@ -349,7 +351,7 @@ void VFileList::newFile()
|
||||
qWarning() << "fail to open newly-created note" << file->getName();
|
||||
} else {
|
||||
Q_ASSERT(file->getContent().isEmpty());
|
||||
QString content = QString("# %1\n").arg(QFileInfo(file->getName()).baseName());
|
||||
QString content = QString("# %1\n").arg(QFileInfo(file->getName()).completeBaseName());
|
||||
file->setContent(content);
|
||||
if (!file->save()) {
|
||||
qWarning() << "fail to write to newly-created note" << file->getName();
|
||||
@ -372,7 +374,7 @@ void VFileList::newFile()
|
||||
|
||||
// Move cursor down if content has been inserted.
|
||||
if (contentInserted) {
|
||||
const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->currentEditTab());
|
||||
const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->getCurrentTab());
|
||||
if (tab) {
|
||||
VMdEdit *edit = dynamic_cast<VMdEdit *>(tab->getEditor());
|
||||
if (edit && edit->getFile() == file) {
|
||||
@ -621,7 +623,7 @@ bool VFileList::importFiles(const QStringList &p_files, QString *p_errMsg)
|
||||
|
||||
QString name = VUtils::fileNameFromPath(file);
|
||||
Q_ASSERT(!name.isEmpty());
|
||||
name = VUtils::getFileNameWithSequence(dirPath, name);
|
||||
name = VUtils::getFileNameWithSequence(dirPath, name, true);
|
||||
QString targetFilePath = dir.filePath(name);
|
||||
bool ret = VUtils::copyFile(file, targetFilePath, false);
|
||||
if (!ret) {
|
||||
@ -763,10 +765,14 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
|
||||
}
|
||||
|
||||
// Rename it to xxx_copy.md.
|
||||
fileName = VUtils::generateCopiedFileName(file->fetchBasePath(), fileName);
|
||||
fileName = VUtils::generateCopiedFileName(file->fetchBasePath(),
|
||||
fileName,
|
||||
true);
|
||||
} else {
|
||||
// Rename it to xxx_copy.md if needed.
|
||||
fileName = VUtils::generateCopiedFileName(p_destDir->fetchPath(), fileName);
|
||||
fileName = VUtils::generateCopiedFileName(p_destDir->fetchPath(),
|
||||
fileName,
|
||||
true);
|
||||
}
|
||||
|
||||
QString msg;
|
||||
|
@ -44,7 +44,9 @@ public:
|
||||
// Locate @p_file in the list widget.
|
||||
bool locateFile(const VNoteFile *p_file);
|
||||
|
||||
inline const VDirectory *currentDirectory() const;
|
||||
const VDirectory *currentDirectory() const;
|
||||
|
||||
QWidget *getContentWidget() const;
|
||||
|
||||
// Implementations for VNavigationMode.
|
||||
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
|
||||
@ -199,4 +201,9 @@ inline const VDirectory *VFileList::currentDirectory() const
|
||||
return m_directory;
|
||||
}
|
||||
|
||||
inline QWidget *VFileList::getContentWidget() const
|
||||
{
|
||||
return fileList;
|
||||
}
|
||||
|
||||
#endif // VFILELIST_H
|
||||
|
74
src/vfilesessioninfo.cpp
Normal 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
@ -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
|
@ -32,7 +32,7 @@ void VHtmlTab::setupUI()
|
||||
{
|
||||
m_editor = new VEdit(m_file, this);
|
||||
connect(m_editor, &VEdit::textChanged,
|
||||
this, &VHtmlTab::handleTextChanged);
|
||||
this, &VHtmlTab::updateStatus);
|
||||
connect(m_editor, &VEdit::saveAndRead,
|
||||
this, &VHtmlTab::saveAndRead);
|
||||
connect(m_editor, &VEdit::discardAndRead,
|
||||
@ -52,17 +52,6 @@ void VHtmlTab::setupUI()
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
void VHtmlTab::handleTextChanged()
|
||||
{
|
||||
V_ASSERT(m_file->isModifiable());
|
||||
|
||||
if (m_modified) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void VHtmlTab::showFileReadMode()
|
||||
{
|
||||
m_isEditMode = false;
|
||||
@ -194,10 +183,6 @@ void VHtmlTab::discardAndRead()
|
||||
readFile();
|
||||
}
|
||||
|
||||
void VHtmlTab::scrollToAnchor(const VAnchor & /* p_anchor */)
|
||||
{
|
||||
}
|
||||
|
||||
void VHtmlTab::insertImage()
|
||||
{
|
||||
}
|
||||
@ -252,3 +237,12 @@ void VHtmlTab::requestUpdateVimStatus()
|
||||
{
|
||||
m_editor->requestUpdateVimStatus();
|
||||
}
|
||||
|
||||
bool VHtmlTab::restoreFromTabInfo(const VEditTabInfo &p_info)
|
||||
{
|
||||
if (p_info.m_editTab != this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -26,9 +26,6 @@ public:
|
||||
// Save file.
|
||||
bool saveFile() Q_DECL_OVERRIDE;
|
||||
|
||||
// Scroll to anchor @p_anchor.
|
||||
void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE;
|
||||
|
||||
void insertImage() Q_DECL_OVERRIDE;
|
||||
|
||||
// Search @p_text in current note.
|
||||
@ -53,9 +50,6 @@ public slots:
|
||||
void editFile() Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
// Handle text changed in m_editor.
|
||||
void handleTextChanged();
|
||||
|
||||
// m_editor requests to save changes and enter read mode.
|
||||
void saveAndRead();
|
||||
|
||||
@ -78,6 +72,10 @@ private:
|
||||
// Focus the proper child widget.
|
||||
void focusChild() Q_DECL_OVERRIDE;
|
||||
|
||||
// Restore from @p_fino.
|
||||
// Return true if succeed.
|
||||
bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
|
||||
|
||||
VEdit *m_editor;
|
||||
};
|
||||
#endif // VHTMLTAB_H
|
||||
|
48
src/vlineedit.cpp
Normal 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
@ -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
|
@ -38,6 +38,15 @@ class QShortcut;
|
||||
class VButtonWithWidget;
|
||||
class VAttachmentList;
|
||||
|
||||
enum class PanelViewState
|
||||
{
|
||||
ExpandMode,
|
||||
SinglePanel,
|
||||
TwoPanels,
|
||||
CompactMode,
|
||||
Invalid
|
||||
};
|
||||
|
||||
class VMainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -51,18 +60,17 @@ public:
|
||||
// Returns true if the location succeeds.
|
||||
bool locateFile(VFile *p_file);
|
||||
|
||||
// Returns true if the location succeeds.
|
||||
bool locateCurrentFile();
|
||||
|
||||
VFileList *getFileList() const;
|
||||
|
||||
VEditArea *getEditArea() const;
|
||||
|
||||
// View and edit the information of @p_file, which is an orphan file.
|
||||
void editOrphanFileInfo(VFile *p_file);
|
||||
|
||||
// Open external files @p_files as orphan files.
|
||||
// Open files @p_files as orphan files or internal note files.
|
||||
// If @p_forceOrphan is false, for each file, VNote will try to find out if
|
||||
// it is a note inside VNote. If yes, VNote will open it as internal file.
|
||||
void openExternalFiles(const QStringList &p_files, bool p_forceOrphan = false);
|
||||
void openFiles(const QStringList &p_files, bool p_forceOrphan = false);
|
||||
|
||||
// Try to open @p_filePath as internal note.
|
||||
bool tryOpenInternalFile(const QString &p_filePath);
|
||||
@ -70,15 +78,23 @@ public:
|
||||
// Show a temporary message in status bar.
|
||||
void showStatusMessage(const QString &p_msg);
|
||||
|
||||
// Popup the attachment list if it is enabled.
|
||||
void showAttachmentList();
|
||||
// Open startup pages according to configuration.
|
||||
void openStartupPages();
|
||||
|
||||
VCaptain *getCaptain() const;
|
||||
|
||||
// Prompt user for new notebook if there is no notebook.
|
||||
void promptNewNotebookIfEmpty();
|
||||
|
||||
private slots:
|
||||
void importNoteFromFile();
|
||||
void viewSettings();
|
||||
void changeMarkdownConverter(QAction *action);
|
||||
void aboutMessage();
|
||||
void shortcutHelp();
|
||||
|
||||
// Display shortcuts help.
|
||||
void shortcutsHelp();
|
||||
|
||||
void changeExpandTab(bool checked);
|
||||
void setTabStopWidth(QAction *action);
|
||||
void setEditorBackgroundColor(QAction *action);
|
||||
@ -105,17 +121,15 @@ private slots:
|
||||
void changeHighlightTrailingSapce(bool p_checked);
|
||||
void onePanelView();
|
||||
void twoPanelView();
|
||||
void expandPanelView(bool p_checked);
|
||||
void compactModeView();
|
||||
void curEditFileInfo();
|
||||
void deleteCurNote();
|
||||
void handleCurrentDirectoryChanged(const VDirectory *p_dir);
|
||||
void handleCurrentNotebookChanged(const VNotebook *p_notebook);
|
||||
void insertImage();
|
||||
void handleFindDialogTextChanged(const QString &p_text, uint p_options);
|
||||
void openFindDialog();
|
||||
void enableMermaid(bool p_checked);
|
||||
void enableMathjax(bool p_checked);
|
||||
void handleCaptainModeChanged(bool p_enabled);
|
||||
void changeAutoIndent(bool p_checked);
|
||||
void changeAutoList(bool p_checked);
|
||||
void changeVimMode(bool p_checked);
|
||||
@ -127,10 +141,14 @@ private slots:
|
||||
void printNote();
|
||||
void exportAsPDF();
|
||||
|
||||
// Set the panel view properly.
|
||||
void enableCompactMode(bool p_enabled);
|
||||
|
||||
// Handle Vim status updated.
|
||||
void handleVimStatusUpdated(const VVim *p_vim);
|
||||
|
||||
// Handle the status update of the current tab of VEditArea.
|
||||
// Will be called frequently.
|
||||
void handleAreaTabStatusUpdated(const VEditTabInfo &p_info);
|
||||
|
||||
// Check the shared memory between different instances to see if we have
|
||||
@ -142,6 +160,9 @@ private slots:
|
||||
// Restore main window.
|
||||
void showMainWindow();
|
||||
|
||||
// Close current note.
|
||||
void closeCurrentFile();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
|
||||
void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
|
||||
@ -186,17 +207,19 @@ private:
|
||||
void initEditorLineNumberMenu(QMenu *p_menu);
|
||||
|
||||
void initEditorStyleMenu(QMenu *p_emnu);
|
||||
void changeSplitterView(int nrPanel);
|
||||
void updateWindowTitle(const QString &str);
|
||||
void updateActionStateFromTabStatusChange(const VFile *p_file,
|
||||
bool p_editMode);
|
||||
|
||||
// Update state of actions according to @p_tab.
|
||||
void updateActionsStateFromTab(const VEditTab *p_tab);
|
||||
|
||||
void saveStateAndGeometry();
|
||||
void restoreStateAndGeometry();
|
||||
void repositionAvatar();
|
||||
|
||||
// Should init VCaptain before setupUI().
|
||||
void initCaptain();
|
||||
void toggleOnePanelView();
|
||||
void closeCurrentFile();
|
||||
|
||||
void registerCaptainAndNavigationTargets();
|
||||
|
||||
// Update status bar information.
|
||||
void updateStatusInfo(const VEditTabInfo &p_info);
|
||||
@ -213,6 +236,37 @@ private:
|
||||
// Init system tray icon and correspondign context menu.
|
||||
void initTrayIcon();
|
||||
|
||||
// Change the panel view according to @p_state.
|
||||
// Will not change m_panelViewState.
|
||||
void changePanelView(PanelViewState p_state);
|
||||
|
||||
// Whether heading sequence is applicable to current tab.
|
||||
// Only available for writable Markdown file.
|
||||
bool isHeadingSequenceApplicable() const;
|
||||
|
||||
// Captain mode functions.
|
||||
|
||||
// Popup the attachment list if it is enabled.
|
||||
static void showAttachmentListByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void locateCurrentFileByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void toggleExpandModeByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void toggleOnePanelViewByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void discardAndReadByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void toggleToolsDockByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void closeFileByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void shortcutsHelpByCaptain(void *p_target, void *p_data);
|
||||
|
||||
static void flushLogFileByCaptain(void *p_target, void *p_data);
|
||||
|
||||
// End Captain mode functions.
|
||||
|
||||
VNote *vnote;
|
||||
QPointer<VFile> m_curFile;
|
||||
QPointer<VEditTab> m_curTab;
|
||||
@ -222,10 +276,18 @@ private:
|
||||
QLabel *notebookLabel;
|
||||
QLabel *directoryLabel;
|
||||
VNotebookSelector *notebookSelector;
|
||||
VFileList *fileList;
|
||||
VFileList *m_fileList;
|
||||
VDirectoryTree *directoryTree;
|
||||
QSplitter *mainSplitter;
|
||||
|
||||
// Splitter for directory | files | edit.
|
||||
QSplitter *m_mainSplitter;
|
||||
|
||||
// Splitter for directory | files.
|
||||
// Move directory and file panel in one compact vertical split.
|
||||
QSplitter *m_naviSplitter;
|
||||
|
||||
VEditArea *editArea;
|
||||
|
||||
QDockWidget *toolDock;
|
||||
QToolBox *toolBox;
|
||||
VOutline *outline;
|
||||
@ -234,8 +296,8 @@ private:
|
||||
VVimIndicator *m_vimIndicator;
|
||||
VTabIndicator *m_tabIndicator;
|
||||
|
||||
// Whether it is one panel or two panles.
|
||||
bool m_onePanel;
|
||||
// SinglePanel, TwoPanels, CompactMode.
|
||||
PanelViewState m_panelViewState;
|
||||
|
||||
// Actions
|
||||
QAction *newRootDirAct;
|
||||
@ -251,7 +313,6 @@ private:
|
||||
QAction *m_printAct;
|
||||
QAction *m_exportAsPDFAct;
|
||||
|
||||
QAction *m_insertImageAct;
|
||||
QAction *m_findReplaceAct;
|
||||
QAction *m_findNextAct;
|
||||
QAction *m_findPreviousAct;
|
||||
@ -261,6 +322,9 @@ private:
|
||||
|
||||
QAction *m_autoIndentAct;
|
||||
|
||||
// Enable heading sequence for current note.
|
||||
QAction *m_headingSequenceAct;
|
||||
|
||||
// Act group for render styles.
|
||||
QActionGroup *m_renderStyleActs;
|
||||
|
||||
@ -269,6 +333,9 @@ private:
|
||||
// Act group for code block render styles.
|
||||
QActionGroup *m_codeBlockStyleActs;
|
||||
|
||||
// Act group for panel view actions.
|
||||
QActionGroup *m_viewActGroup;
|
||||
|
||||
QShortcut *m_closeNoteShortcut;
|
||||
|
||||
// Menus
|
||||
@ -306,7 +373,17 @@ private:
|
||||
|
||||
inline VFileList *VMainWindow::getFileList() const
|
||||
{
|
||||
return fileList;
|
||||
return m_fileList;
|
||||
}
|
||||
|
||||
inline VEditArea *VMainWindow::getEditArea() const
|
||||
{
|
||||
return editArea;
|
||||
}
|
||||
|
||||
inline VCaptain *VMainWindow::getCaptain() const
|
||||
{
|
||||
return m_captain;
|
||||
}
|
||||
|
||||
#endif // VMAINWINDOW_H
|
||||
|
130
src/vmdedit.cpp
@ -5,7 +5,7 @@
|
||||
#include "vmdeditoperations.h"
|
||||
#include "vnote.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "vtoc.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "utils/veditutils.h"
|
||||
#include "utils/vpreviewutils.h"
|
||||
@ -35,7 +35,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||
document());
|
||||
|
||||
connect(m_mdHighlighter, &HGMarkdownHighlighter::headersUpdated,
|
||||
this, &VMdEdit::updateOutline);
|
||||
this, &VMdEdit::updateHeaders);
|
||||
|
||||
// After highlight, the cursor may trun into non-visible. We should make it visible
|
||||
// in this case.
|
||||
@ -74,7 +74,7 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type,
|
||||
this, &VEdit::vimStatusUpdated);
|
||||
|
||||
connect(this, &VMdEdit::cursorPositionChanged,
|
||||
this, &VMdEdit::updateCurHeader);
|
||||
this, &VMdEdit::updateCurrentHeader);
|
||||
|
||||
connect(QApplication::clipboard(), &QClipboard::changed,
|
||||
this, &VMdEdit::handleClipboardChanged);
|
||||
@ -111,7 +111,7 @@ void VMdEdit::beginEdit()
|
||||
setReadOnly(false);
|
||||
}
|
||||
|
||||
updateOutline(m_mdHighlighter->getHeaderRegions());
|
||||
updateHeaders(m_mdHighlighter->getHeaderRegions());
|
||||
}
|
||||
|
||||
void VMdEdit::endEdit()
|
||||
@ -345,43 +345,9 @@ void VMdEdit::clearUnusedImages()
|
||||
m_initImages.clear();
|
||||
}
|
||||
|
||||
int VMdEdit::currentCursorHeader() const
|
||||
void VMdEdit::updateCurrentHeader()
|
||||
{
|
||||
if (m_headers.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int curLine = textCursor().block().firstLineNumber();
|
||||
int i = 0;
|
||||
for (i = m_headers.size() - 1; i >= 0; --i) {
|
||||
if (!m_headers[i].isEmpty()) {
|
||||
if (m_headers[i].lineNumber <= curLine) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
Q_ASSERT(m_headers[i].index == i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
void VMdEdit::updateCurHeader()
|
||||
{
|
||||
if (m_headers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = currentCursorHeader();
|
||||
if (idx == -1) {
|
||||
emit curHeaderChanged(VAnchor(m_file, "", -1, -1));
|
||||
return;
|
||||
}
|
||||
|
||||
emit curHeaderChanged(VAnchor(m_file, "", m_headers[idx].lineNumber, m_headers[idx].index));
|
||||
emit currentHeaderChanged(textCursor().block().blockNumber());
|
||||
}
|
||||
|
||||
static void addHeaderSequence(QVector<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();
|
||||
|
||||
QVector<VHeader> headers;
|
||||
QVector<VTableOfContentItem> headers;
|
||||
QVector<int> headerBlockNumbers;
|
||||
QVector<QString> headerSequences;
|
||||
if (!p_headerRegions.isEmpty()) {
|
||||
@ -480,8 +446,10 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
|
||||
if ((block.userState() == HighlightBlockState::Normal) &&
|
||||
headerReg.exactMatch(block.text())) {
|
||||
int level = headerReg.cap(1).length();
|
||||
VHeader header(level, headerReg.cap(2).trimmed(),
|
||||
"", block.firstLineNumber(), headers.size());
|
||||
VTableOfContentItem header(headerReg.cap(2).trimmed(),
|
||||
level,
|
||||
block.blockNumber(),
|
||||
headers.size());
|
||||
headers.append(header);
|
||||
headerBlockNumbers.append(block.blockNumber());
|
||||
headerSequences.append(headerReg.cap(3));
|
||||
@ -496,7 +464,7 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
|
||||
|
||||
m_headers.clear();
|
||||
|
||||
bool autoSequence = g_config->getEnableHeadingSequence() && !isReadOnly();
|
||||
bool autoSequence = m_config.m_enableHeadingSequence && !isReadOnly();
|
||||
int headingSequenceBaseLevel = g_config->getHeadingSequenceBaseLevel();
|
||||
if (headingSequenceBaseLevel < 1 || headingSequenceBaseLevel > 6) {
|
||||
headingSequenceBaseLevel = 1;
|
||||
@ -506,22 +474,25 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
|
||||
QRegExp preReg(VUtils::c_headerPrefixRegExp);
|
||||
int curLevel = baseLevel - 1;
|
||||
for (int i = 0; i < headers.size(); ++i) {
|
||||
VHeader &item = headers[i];
|
||||
while (item.level > curLevel + 1) {
|
||||
VTableOfContentItem &item = headers[i];
|
||||
while (item.m_level > curLevel + 1) {
|
||||
curLevel += 1;
|
||||
|
||||
// Insert empty level which is an invalid header.
|
||||
m_headers.append(VHeader(curLevel, c_emptyHeaderName, "", -1, m_headers.size()));
|
||||
m_headers.append(VTableOfContentItem(c_emptyHeaderName,
|
||||
curLevel,
|
||||
-1,
|
||||
m_headers.size()));
|
||||
if (autoSequence) {
|
||||
addHeaderSequence(seqs, curLevel, headingSequenceBaseLevel);
|
||||
}
|
||||
}
|
||||
|
||||
item.index = m_headers.size();
|
||||
item.m_index = m_headers.size();
|
||||
m_headers.append(item);
|
||||
curLevel = item.level;
|
||||
curLevel = item.m_level;
|
||||
if (autoSequence) {
|
||||
addHeaderSequence(seqs, item.level, headingSequenceBaseLevel);
|
||||
addHeaderSequence(seqs, item.m_level, headingSequenceBaseLevel);
|
||||
|
||||
QString seqStr = headerSequenceStr(seqs);
|
||||
if (headerSequences[i] != seqStr) {
|
||||
@ -536,25 +507,16 @@ void VMdEdit::updateOutline(const QVector<VElementRegion> &p_headerRegions)
|
||||
|
||||
emit headersChanged(m_headers);
|
||||
|
||||
updateCurHeader();
|
||||
updateCurrentHeader();
|
||||
}
|
||||
|
||||
void VMdEdit::scrollToHeader(const VAnchor &p_anchor)
|
||||
bool VMdEdit::scrollToHeader(int p_blockNumber)
|
||||
{
|
||||
if (p_anchor.lineNumber == -1
|
||||
|| p_anchor.m_outlineIndex < 0) {
|
||||
// Move to the start of document if m_headers is not empty.
|
||||
// Otherwise, there is no outline, so just let it be.
|
||||
if (!m_headers.isEmpty()) {
|
||||
moveCursor(QTextCursor::Start);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (p_anchor.m_outlineIndex >= m_headers.size()) {
|
||||
return;
|
||||
if (p_blockNumber < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
scrollToLine(p_anchor.lineNumber);
|
||||
return scrollToBlock(p_blockNumber);
|
||||
}
|
||||
|
||||
QString VMdEdit::toPlainTextWithoutImg()
|
||||
@ -728,9 +690,21 @@ void VMdEdit::resizeEvent(QResizeEvent *p_event)
|
||||
VEdit::resizeEvent(p_event);
|
||||
}
|
||||
|
||||
const QVector<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)
|
||||
@ -740,11 +714,11 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
|
||||
}
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
int cursorLine = cursor.block().firstLineNumber();
|
||||
int cursorLine = cursor.block().blockNumber();
|
||||
int targetIdx = -1;
|
||||
// -1: skip level check.
|
||||
int targetLevel = 0;
|
||||
int idx = currentCursorHeader();
|
||||
int idx = indexOfCurrentHeader();
|
||||
if (idx == -1) {
|
||||
// Cursor locates at the beginning, before any headers.
|
||||
if (p_relativeLevel < 0 || !p_forward) {
|
||||
@ -761,7 +735,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
|
||||
for (targetIdx = idx == -1 ? 0 : idx;
|
||||
targetIdx >= 0 && targetIdx < m_headers.size();
|
||||
targetIdx += delta) {
|
||||
const VHeader &header = m_headers[targetIdx];
|
||||
const VTableOfContentItem &header = m_headers[targetIdx];
|
||||
if (header.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@ -769,7 +743,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
|
||||
if (targetLevel == 0) {
|
||||
// The target level has not been init yet.
|
||||
Q_ASSERT(firstHeader);
|
||||
targetLevel = header.level;
|
||||
targetLevel = header.m_level;
|
||||
if (p_relativeLevel < 0) {
|
||||
targetLevel += p_relativeLevel;
|
||||
if (targetLevel < 1) {
|
||||
@ -781,9 +755,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
|
||||
}
|
||||
}
|
||||
|
||||
if (targetLevel == -1 || header.level == targetLevel) {
|
||||
if (targetLevel == -1 || header.m_level == targetLevel) {
|
||||
if (firstHeader
|
||||
&& (cursorLine == header.lineNumber
|
||||
&& (cursorLine == header.m_blockNumber
|
||||
|| p_forward)
|
||||
&& idx != -1) {
|
||||
// This header is not counted for the repeat.
|
||||
@ -795,7 +769,7 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
|
||||
// Found.
|
||||
break;
|
||||
}
|
||||
} else if (header.level < targetLevel) {
|
||||
} else if (header.m_level < targetLevel) {
|
||||
// Stop by higher level.
|
||||
return false;
|
||||
}
|
||||
@ -808,9 +782,9 @@ bool VMdEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
|
||||
}
|
||||
|
||||
// Jump to target header.
|
||||
int line = m_headers[targetIdx].lineNumber;
|
||||
int line = m_headers[targetIdx].m_blockNumber;
|
||||
if (line > -1) {
|
||||
QTextBlock block = document()->findBlockByLineNumber(line);
|
||||
QTextBlock block = document()->findBlockByNumber(line);
|
||||
if (block.isValid()) {
|
||||
cursor.setPosition(block.position());
|
||||
setTextCursor(cursor);
|
||||
@ -837,6 +811,8 @@ void VMdEdit::finishOneAsyncJob(int p_idx)
|
||||
m_freshEdit = false;
|
||||
emit statusChanged();
|
||||
|
||||
updateOutline(m_mdHighlighter->getHeaderRegions());
|
||||
updateHeaders(m_mdHighlighter->getHeaderRegions());
|
||||
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include <QColor>
|
||||
#include <QClipboard>
|
||||
#include <QImage>
|
||||
#include "vtoc.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "veditoperations.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
@ -32,31 +32,34 @@ public:
|
||||
// @p_path is the absolute path of the inserted image.
|
||||
void imageInserted(const QString &p_path);
|
||||
|
||||
void scrollToHeader(const VAnchor &p_anchor);
|
||||
// Scroll to header @p_blockNumber.
|
||||
// Return true if @p_blockNumber is valid to scroll to.
|
||||
bool scrollToHeader(int p_blockNumber);
|
||||
|
||||
// Like toPlainText(), but remove image preview characters.
|
||||
QString toPlainTextWithoutImg();
|
||||
|
||||
const QVector<VHeader> &getHeaders() const;
|
||||
|
||||
public slots:
|
||||
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
|
||||
|
||||
signals:
|
||||
void headersChanged(const QVector<VHeader> &headers);
|
||||
// Signal when headers change.
|
||||
void headersChanged(const QVector<VTableOfContentItem> &p_headers);
|
||||
|
||||
// Signal when current header change.
|
||||
void curHeaderChanged(VAnchor p_anchor);
|
||||
void currentHeaderChanged(int p_blockNumber);
|
||||
|
||||
// Signal when the status of VMdEdit changed.
|
||||
// Will be emitted by VImagePreviewer for now.
|
||||
void statusChanged();
|
||||
|
||||
private slots:
|
||||
void updateOutline(const QVector<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.
|
||||
void updateCurHeader();
|
||||
void updateCurrentHeader();
|
||||
|
||||
void handleClipboardChanged(QClipboard::Mode p_mode);
|
||||
|
||||
@ -95,9 +98,6 @@ private:
|
||||
// in the selection. Get the QImage.
|
||||
QImage tryGetSelectedImage();
|
||||
|
||||
// Return the header index in m_headers where current cursor locates.
|
||||
int currentCursorHeader() const;
|
||||
|
||||
QString getPlainTextWithoutPreviewImage() const;
|
||||
|
||||
// Try to get all the regions of preview image within @p_block.
|
||||
@ -107,6 +107,9 @@ private:
|
||||
|
||||
void finishOneAsyncJob(int p_idx);
|
||||
|
||||
// Index in m_headers of current header which contains the cursor.
|
||||
int indexOfCurrentHeader() const;
|
||||
|
||||
HGMarkdownHighlighter *m_mdHighlighter;
|
||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||
VImagePreviewer *m_imagePreviewer;
|
||||
@ -117,7 +120,8 @@ private:
|
||||
// Image links right at the beginning of the edit.
|
||||
QVector<ImageLink> m_initImages;
|
||||
|
||||
QVector<VHeader> m_headers;
|
||||
// Mainly used for title jump.
|
||||
QVector<VTableOfContentItem> m_headers;
|
||||
|
||||
bool m_freshEdit;
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
const QString VMdEditOperations::c_defaultImageTitle = "image";
|
||||
const QString VMdEditOperations::c_defaultImageTitle = "";
|
||||
|
||||
VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file)
|
||||
: VEditOperations(p_editor, p_file), m_autoIndentPos(-1)
|
||||
@ -278,6 +278,28 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_L:
|
||||
{
|
||||
if (modifiers == Qt::ControlModifier) {
|
||||
m_editor->insertLink();
|
||||
p_event->accept();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_M:
|
||||
{
|
||||
if (modifiers == Qt::ControlModifier) {
|
||||
decorateCodeBlock();
|
||||
p_event->accept();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_O:
|
||||
{
|
||||
if (modifiers == Qt::ControlModifier) {
|
||||
@ -626,33 +648,25 @@ void VMdEditOperations::changeListBlockSeqNumber(QTextBlock &p_block, int p_seq)
|
||||
|
||||
bool VMdEditOperations::insertTitle(int p_level)
|
||||
{
|
||||
Q_ASSERT(p_level > 0 && p_level < 7);
|
||||
QTextDocument *doc = m_editor->document();
|
||||
QString titleMark(p_level, '#');
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
int firstBlock = cursor.block().blockNumber();
|
||||
int lastBlock = firstBlock;
|
||||
|
||||
if (cursor.hasSelection()) {
|
||||
// Insert title # in front of the selected lines.
|
||||
// Insert title # in front of the selected blocks.
|
||||
int start = cursor.selectionStart();
|
||||
int end = cursor.selectionEnd();
|
||||
int startBlock = doc->findBlock(start).blockNumber();
|
||||
int endBlock = doc->findBlock(end).blockNumber();
|
||||
cursor.beginEditBlock();
|
||||
cursor.clearSelection();
|
||||
for (int i = startBlock; i <= endBlock; ++i) {
|
||||
QTextBlock block = doc->findBlockByNumber(i);
|
||||
cursor.setPosition(block.position(), QTextCursor::MoveAnchor);
|
||||
cursor.insertText(titleMark + " ");
|
||||
}
|
||||
cursor.movePosition(QTextCursor::EndOfBlock);
|
||||
cursor.endEditBlock();
|
||||
} else {
|
||||
// Insert title # in front of current block.
|
||||
cursor.beginEditBlock();
|
||||
cursor.movePosition(QTextCursor::StartOfBlock);
|
||||
cursor.insertText(titleMark + " ");
|
||||
cursor.movePosition(QTextCursor::EndOfBlock);
|
||||
cursor.endEditBlock();
|
||||
firstBlock = doc->findBlock(start).blockNumber();
|
||||
lastBlock = doc->findBlock(end).blockNumber();
|
||||
}
|
||||
|
||||
cursor.beginEditBlock();
|
||||
for (int i = firstBlock; i <= lastBlock; ++i) {
|
||||
VEditUtils::insertTitleMark(cursor, doc->findBlockByNumber(i), p_level);
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
return true;
|
||||
}
|
||||
@ -681,6 +695,10 @@ void VMdEditOperations::decorateText(TextDecoration p_decoration)
|
||||
decorateInlineCode();
|
||||
break;
|
||||
|
||||
case TextDecoration::CodeBlock:
|
||||
decorateCodeBlock();
|
||||
break;
|
||||
|
||||
default:
|
||||
validDecoration = false;
|
||||
qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
|
||||
@ -804,6 +822,86 @@ void VMdEditOperations::decorateInlineCode()
|
||||
m_editor->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void VMdEditOperations::decorateCodeBlock()
|
||||
{
|
||||
const QString marker("```");
|
||||
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
cursor.beginEditBlock();
|
||||
if (cursor.hasSelection()) {
|
||||
// Insert ``` around the selected text.
|
||||
int start = cursor.selectionStart();
|
||||
int end = cursor.selectionEnd();
|
||||
|
||||
QString indentation = VEditUtils::fetchIndentSpaces(cursor.block());
|
||||
|
||||
// Insert the end marker first.
|
||||
cursor.setPosition(end, QTextCursor::MoveAnchor);
|
||||
VEditUtils::insertBlock(cursor, false);
|
||||
VEditUtils::indentBlock(cursor, indentation);
|
||||
cursor.insertText(marker);
|
||||
|
||||
// Insert the start marker.
|
||||
cursor.setPosition(start, QTextCursor::MoveAnchor);
|
||||
VEditUtils::insertBlock(cursor, true);
|
||||
VEditUtils::indentBlock(cursor, indentation);
|
||||
cursor.insertText(marker);
|
||||
} else {
|
||||
// Insert ``` ``` and place cursor after the first marker.
|
||||
// Or if current block or next block is ```, we will skip it.
|
||||
QTextBlock block = cursor.block();
|
||||
int state = block.userState();
|
||||
if (state == HighlightBlockState::CodeBlock
|
||||
|| state == HighlightBlockState::CodeBlockStart
|
||||
|| state == HighlightBlockState::CodeBlockEnd) {
|
||||
// Find the block end.
|
||||
while (block.isValid()) {
|
||||
if (block.userState() == HighlightBlockState::CodeBlockEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
}
|
||||
|
||||
if (block.isValid()) {
|
||||
// It is CodeBlockEnd.
|
||||
cursor.setPosition(block.position());
|
||||
if (block.next().isValid()) {
|
||||
cursor.movePosition(QTextCursor::NextBlock);
|
||||
cursor.movePosition(QTextCursor::StartOfBlock);
|
||||
} else {
|
||||
cursor.movePosition(QTextCursor::EndOfBlock);
|
||||
}
|
||||
} else {
|
||||
// Reach the end of the document.
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
}
|
||||
} else {
|
||||
bool insertInline = false;
|
||||
if (!cursor.atBlockEnd()) {
|
||||
cursor.insertBlock();
|
||||
cursor.movePosition(QTextCursor::PreviousBlock);
|
||||
} else if (cursor.atBlockStart()) {
|
||||
insertInline = true;
|
||||
}
|
||||
|
||||
if (!insertInline) {
|
||||
VEditUtils::insertBlock(cursor, false);
|
||||
VEditUtils::indentBlockAsBlock(cursor, false);
|
||||
}
|
||||
|
||||
cursor.insertText(marker);
|
||||
|
||||
VEditUtils::insertBlock(cursor, true);
|
||||
VEditUtils::indentBlockAsBlock(cursor, true);
|
||||
cursor.insertText(marker);
|
||||
}
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void VMdEditOperations::decorateStrikethrough()
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
@ -840,3 +938,16 @@ void VMdEditOperations::decorateStrikethrough()
|
||||
cursor.endEditBlock();
|
||||
m_editor->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
bool VMdEditOperations::insertLink(const QString &p_linkText,
|
||||
const QString &p_linkUrl)
|
||||
{
|
||||
QString link = QString("[%1](%2)").arg(p_linkText).arg(p_linkUrl);
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
cursor.insertText(link);
|
||||
m_editor->setTextCursor(cursor);
|
||||
|
||||
setVimMode(VimMode::Insert);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -16,11 +16,18 @@ class VMdEditOperations : public VEditOperations
|
||||
Q_OBJECT
|
||||
public:
|
||||
VMdEditOperations(VEdit *p_editor, VFile *p_file);
|
||||
|
||||
bool insertImageFromMimeData(const QMimeData *source) Q_DECL_OVERRIDE;
|
||||
|
||||
bool insertImage() Q_DECL_OVERRIDE;
|
||||
|
||||
bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE;
|
||||
|
||||
bool insertLink(const QString &p_linkText,
|
||||
const QString &p_linkUrl);
|
||||
|
||||
// Insert decoration markers or decorate selected text.
|
||||
// If it is Vim Normal mode, change to Insert mode first.
|
||||
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
|
||||
@ -47,6 +54,10 @@ private:
|
||||
bool handleKeyEsc(QKeyEvent *p_event);
|
||||
bool handleKeyReturn(QKeyEvent *p_event);
|
||||
bool handleKeyBracketLeft(QKeyEvent *p_event);
|
||||
|
||||
// Insert title of level @p_level.
|
||||
// Will detect if current block already has some leading #s. If yes,
|
||||
// will delete it and insert the correct #s.
|
||||
bool insertTitle(int p_level);
|
||||
|
||||
// Change the sequence number of a list block.
|
||||
@ -61,6 +72,9 @@ private:
|
||||
// Insert inline-code marker or set selected text inline-coded.
|
||||
void decorateInlineCode();
|
||||
|
||||
// Insert inline-code marker or set selected text inline-coded.
|
||||
void decorateCodeBlock();
|
||||
|
||||
// Insert strikethrough marker or set selected text strikethrough.
|
||||
void decorateStrikethrough();
|
||||
|
||||
|
432
src/vmdtab.cpp
@ -11,7 +11,7 @@
|
||||
#include "vconfigmanager.h"
|
||||
#include "vmarkdownconverter.h"
|
||||
#include "vnotebook.h"
|
||||
#include "vtoc.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "vmdedit.h"
|
||||
#include "dialog/vfindreplacedialog.h"
|
||||
#include "veditarea.h"
|
||||
@ -22,13 +22,25 @@ extern VConfigManager *g_config;
|
||||
|
||||
VMdTab::VMdTab(VFile *p_file, VEditArea *p_editArea,
|
||||
OpenFileMode p_mode, QWidget *p_parent)
|
||||
: VEditTab(p_file, p_editArea, p_parent), m_editor(NULL), m_webViewer(NULL),
|
||||
m_document(NULL), m_mdConType(g_config->getMdConverterType())
|
||||
: VEditTab(p_file, p_editArea, p_parent),
|
||||
m_editor(NULL),
|
||||
m_webViewer(NULL),
|
||||
m_document(NULL),
|
||||
m_mdConType(g_config->getMdConverterType()),
|
||||
m_enableHeadingSequence(false)
|
||||
{
|
||||
V_ASSERT(m_file->getDocType() == DocType::Markdown);
|
||||
|
||||
m_file->open();
|
||||
|
||||
HeadingSequenceType headingSequenceType = g_config->getHeadingSequenceType();
|
||||
if (headingSequenceType == HeadingSequenceType::Enabled) {
|
||||
m_enableHeadingSequence = true;
|
||||
} else if (headingSequenceType == HeadingSequenceType::EnabledNoteOnly
|
||||
&& m_file->getType() == FileType::Note) {
|
||||
m_enableHeadingSequence = true;
|
||||
}
|
||||
|
||||
setupUI();
|
||||
|
||||
if (p_mode == OpenFileMode::Edit) {
|
||||
@ -50,54 +62,110 @@ void VMdTab::setupUI()
|
||||
setLayout(m_stacks);
|
||||
}
|
||||
|
||||
void VMdTab::handleTextChanged()
|
||||
{
|
||||
V_ASSERT(m_file->isModifiable());
|
||||
|
||||
if (m_modified) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void VMdTab::showFileReadMode()
|
||||
{
|
||||
m_isEditMode = false;
|
||||
|
||||
int outlineIndex = m_curHeader.m_outlineIndex;
|
||||
VHeaderPointer header(m_currentHeader);
|
||||
|
||||
if (m_mdConType == MarkdownConverterType::Hoedown) {
|
||||
viewWebByConverter();
|
||||
} else {
|
||||
m_document->updateText();
|
||||
updateTocFromHtml(m_document->getToc());
|
||||
updateOutlineFromHtml(m_document->getToc());
|
||||
}
|
||||
|
||||
m_stacks->setCurrentWidget(m_webViewer);
|
||||
clearSearchedWordHighlight();
|
||||
|
||||
scrollWebViewToHeader(outlineIndex);
|
||||
scrollWebViewToHeader(header);
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void VMdTab::scrollWebViewToHeader(int p_outlineIndex)
|
||||
bool VMdTab::scrollWebViewToHeader(const VHeaderPointer &p_header)
|
||||
{
|
||||
QString anchor;
|
||||
|
||||
m_curHeader = VAnchor(m_file, anchor, -1, p_outlineIndex);
|
||||
|
||||
if (p_outlineIndex < m_toc.headers.size() && p_outlineIndex >= 0) {
|
||||
QString tmp = m_toc.headers[p_outlineIndex].anchor;
|
||||
V_ASSERT(!tmp.isEmpty());
|
||||
m_curHeader.anchor = tmp;
|
||||
anchor = tmp.mid(1);
|
||||
if (!m_outline.isMatched(p_header)
|
||||
|| m_outline.getType() != VTableOfContentType::Anchor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_document->scrollToAnchor(anchor);
|
||||
if (p_header.isValid()) {
|
||||
const VTableOfContentItem *item = m_outline.getItem(p_header);
|
||||
if (item) {
|
||||
if (item->m_anchor.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
emit curHeaderChanged(m_curHeader);
|
||||
m_currentHeader = p_header;
|
||||
m_document->scrollToAnchor(item->m_anchor);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (m_outline.isEmpty()) {
|
||||
// Let it be.
|
||||
m_currentHeader = p_header;
|
||||
} else {
|
||||
// Scroll to top.
|
||||
m_currentHeader = p_header;
|
||||
m_document->scrollToAnchor("");
|
||||
}
|
||||
}
|
||||
|
||||
emit currentHeaderChanged(m_currentHeader);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMdTab::scrollEditorToHeader(const VHeaderPointer &p_header)
|
||||
{
|
||||
if (!m_outline.isMatched(p_header)
|
||||
|| m_outline.getType() != VTableOfContentType::BlockNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VMdEdit *mdEdit = dynamic_cast<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()
|
||||
@ -108,7 +176,7 @@ void VMdTab::viewWebByConverter()
|
||||
g_config->getMarkdownExtensions(),
|
||||
toc);
|
||||
m_document->setHtml(html);
|
||||
updateTocFromHtml(toc);
|
||||
updateOutlineFromHtml(toc);
|
||||
}
|
||||
|
||||
void VMdTab::showFileEditMode()
|
||||
@ -117,42 +185,28 @@ void VMdTab::showFileEditMode()
|
||||
return;
|
||||
}
|
||||
|
||||
VHeaderPointer header(m_currentHeader);
|
||||
|
||||
m_isEditMode = true;
|
||||
|
||||
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(getEditor());
|
||||
V_ASSERT(mdEdit);
|
||||
|
||||
// beginEdit() may change m_curHeader.
|
||||
int outlineIndex = m_curHeader.m_outlineIndex;
|
||||
|
||||
mdEdit->beginEdit();
|
||||
m_stacks->setCurrentWidget(mdEdit);
|
||||
|
||||
int lineNumber = -1;
|
||||
const QVector<VHeader> &headers = mdEdit->getHeaders();
|
||||
// If editor is not init, we need to wait for it to init headers.
|
||||
// Generally, beginEdit() will generate the headers. Wait is needed when
|
||||
// highlight completion is going to re-generate the headers.
|
||||
int nrRetry = 5;
|
||||
while (outlineIndex > -1 && headers.isEmpty() && nrRetry-- > 0) {
|
||||
while (header.m_index > -1 && m_outline.isEmpty() && nrRetry-- > 0) {
|
||||
qDebug() << "wait another 100 ms for editor's headers ready";
|
||||
VUtils::sleepWait(100);
|
||||
}
|
||||
|
||||
if (outlineIndex < 0 || outlineIndex >= headers.size()) {
|
||||
lineNumber = -1;
|
||||
outlineIndex = -1;
|
||||
} else {
|
||||
lineNumber = headers[outlineIndex].lineNumber;
|
||||
}
|
||||
|
||||
VAnchor anchor(m_file, "", lineNumber, outlineIndex);
|
||||
|
||||
mdEdit->scrollToHeader(anchor);
|
||||
scrollEditorToHeader(header);
|
||||
|
||||
mdEdit->setFocus();
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
bool VMdTab::closeFile(bool p_forced)
|
||||
@ -232,7 +286,7 @@ bool VMdTab::saveFile()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ret;
|
||||
bool ret = true;
|
||||
// Make sure the file already exists. Temporary deal with cases when user delete or move
|
||||
// a file.
|
||||
QString filePath = m_file->fetchPath();
|
||||
@ -242,16 +296,16 @@ bool VMdTab::saveFile()
|
||||
tr("File <span style=\"%1\">%2</span> being written has been removed.")
|
||||
.arg(g_config->c_dataTextStyle).arg(filePath),
|
||||
QMessageBox::Ok, QMessageBox::Ok, this);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_editor->saveFile();
|
||||
ret = m_file->save();
|
||||
if (!ret) {
|
||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note."),
|
||||
tr("Fail to write to disk when saving a note. Please try it again."),
|
||||
QMessageBox::Ok, QMessageBox::Ok, this);
|
||||
m_editor->setModified(true);
|
||||
ret = false;
|
||||
} else {
|
||||
m_editor->saveFile();
|
||||
ret = m_file->save();
|
||||
if (!ret) {
|
||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note."),
|
||||
tr("Fail to write to disk when saving a note. Please try it again."),
|
||||
QMessageBox::Ok, QMessageBox::Ok, this);
|
||||
m_editor->setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
@ -285,11 +339,13 @@ void VMdTab::setupMarkdownViewer()
|
||||
QWebChannel *channel = new QWebChannel(m_webViewer);
|
||||
channel->registerObject(QStringLiteral("content"), m_document);
|
||||
connect(m_document, &VDocument::tocChanged,
|
||||
this, &VMdTab::updateTocFromHtml);
|
||||
connect(m_document, SIGNAL(headerChanged(const QString&)),
|
||||
this, SLOT(updateCurHeader(const QString &)));
|
||||
this, &VMdTab::updateOutlineFromHtml);
|
||||
connect(m_document, SIGNAL(headerChanged(const QString &)),
|
||||
this, SLOT(updateCurrentHeader(const QString &)));
|
||||
connect(m_document, &VDocument::keyPressed,
|
||||
this, &VMdTab::handleWebKeyPressed);
|
||||
connect(m_document, SIGNAL(logicsFinished(void)),
|
||||
this, SLOT(restoreFromTabInfo(void)));
|
||||
page->setWebChannel(channel);
|
||||
|
||||
m_webViewer->setHtml(VUtils::generateHtmlTemplate(m_mdConType, false),
|
||||
@ -302,15 +358,16 @@ void VMdTab::setupMarkdownEditor()
|
||||
{
|
||||
Q_ASSERT(m_file->isModifiable() && !m_editor);
|
||||
qDebug() << "create Markdown editor";
|
||||
|
||||
m_editor = new VMdEdit(m_file, m_document, m_mdConType, this);
|
||||
connect(dynamic_cast<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,
|
||||
this, &VMdTab::updateStatus);
|
||||
connect(m_editor, SIGNAL(curHeaderChanged(VAnchor)),
|
||||
this, SLOT(updateCurHeader(VAnchor)));
|
||||
connect(m_editor, &VEdit::textChanged,
|
||||
this, &VMdTab::handleTextChanged);
|
||||
this, &VMdTab::updateStatus);
|
||||
connect(m_editor, &VEdit::cursorPositionChanged,
|
||||
this, &VMdTab::updateStatus);
|
||||
connect(m_editor, &VEdit::saveAndRead,
|
||||
@ -324,193 +381,82 @@ void VMdTab::setupMarkdownEditor()
|
||||
connect(m_editor, &VEdit::vimStatusUpdated,
|
||||
this, &VEditTab::vimStatusUpdated);
|
||||
connect(m_editor, &VEdit::requestCloseFindReplaceDialog,
|
||||
this, [this](){
|
||||
this, [this]() {
|
||||
this->m_editArea->getFindReplaceDialog()->closeDialog();
|
||||
});
|
||||
connect(m_editor, SIGNAL(ready(void)),
|
||||
this, SLOT(restoreFromTabInfo(void)));
|
||||
|
||||
enableHeadingSequence(m_enableHeadingSequence);
|
||||
m_editor->reloadFile();
|
||||
m_stacks->addWidget(m_editor);
|
||||
}
|
||||
|
||||
static void parseTocUl(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers,
|
||||
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)
|
||||
void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml)
|
||||
{
|
||||
if (m_isEditMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_toc.type = VHeaderType::Anchor;
|
||||
m_toc.headers.clear();
|
||||
m_outline.clear();
|
||||
|
||||
if (!parseTocHtml(p_tocHtml, m_toc.headers)) {
|
||||
return;
|
||||
if (m_outline.parseTableFromHtml(p_tocHtml)) {
|
||||
m_outline.setFile(m_file);
|
||||
m_outline.setType(VTableOfContentType::Anchor);
|
||||
}
|
||||
|
||||
m_toc.m_file = m_file;
|
||||
m_toc.valid = true;
|
||||
m_currentHeader.reset();
|
||||
|
||||
emit outlineChanged(m_toc);
|
||||
emit outlineChanged(m_outline);
|
||||
}
|
||||
|
||||
void VMdTab::updateTocFromHeaders(const QVector<VHeader> &p_headers)
|
||||
void VMdTab::updateOutlineFromHeaders(const QVector<VTableOfContentItem> &p_headers)
|
||||
{
|
||||
if (!m_isEditMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_toc.type = VHeaderType::LineNumber;
|
||||
m_toc.headers = p_headers;
|
||||
m_toc.m_file = m_file;
|
||||
m_toc.valid = true;
|
||||
m_outline.update(m_file,
|
||||
p_headers,
|
||||
VTableOfContentType::BlockNumber);
|
||||
|
||||
// Clear current header.
|
||||
m_curHeader = VAnchor(m_file, "", -1, -1);
|
||||
emit curHeaderChanged(m_curHeader);
|
||||
m_currentHeader.reset();
|
||||
|
||||
emit outlineChanged(m_toc);
|
||||
emit outlineChanged(m_outline);
|
||||
}
|
||||
|
||||
void VMdTab::scrollToAnchor(const VAnchor &p_anchor)
|
||||
void VMdTab::scrollToHeader(const VHeaderPointer &p_header)
|
||||
{
|
||||
if (p_anchor == m_curHeader) {
|
||||
if (m_outline.isMatched(p_header)) {
|
||||
// Scroll only when @p_header is valid.
|
||||
scrollToHeaderInternal(p_header);
|
||||
}
|
||||
}
|
||||
|
||||
void VMdTab::updateCurrentHeader(const QString &p_anchor)
|
||||
{
|
||||
if (m_isEditMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_curHeader = p_anchor;
|
||||
// Find the index of the anchor in outline.
|
||||
int idx = m_outline.indexOfItemByAnchor(p_anchor);
|
||||
m_currentHeader.update(m_file, idx);
|
||||
|
||||
if (m_isEditMode) {
|
||||
dynamic_cast<VMdEdit *>(getEditor())->scrollToHeader(p_anchor);
|
||||
} else {
|
||||
if (!p_anchor.anchor.isEmpty()) {
|
||||
m_document->scrollToAnchor(p_anchor.anchor.mid(1));
|
||||
}
|
||||
}
|
||||
emit currentHeaderChanged(m_currentHeader);
|
||||
}
|
||||
|
||||
void VMdTab::updateCurHeader(const QString &p_anchor)
|
||||
void VMdTab::updateCurrentHeader(int p_blockNumber)
|
||||
{
|
||||
if (m_isEditMode || m_curHeader.anchor.mid(1) == p_anchor) {
|
||||
if (!m_isEditMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_curHeader = VAnchor(m_file, "#" + p_anchor, -1);
|
||||
if (!p_anchor.isEmpty()) {
|
||||
const QVector<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find the index of the block number in outline.
|
||||
int idx = m_outline.indexOfItemByBlockNumber(p_blockNumber);
|
||||
m_currentHeader.update(m_file, idx);
|
||||
|
||||
emit curHeaderChanged(m_curHeader);
|
||||
}
|
||||
|
||||
void VMdTab::updateCurHeader(VAnchor p_anchor)
|
||||
{
|
||||
if (m_isEditMode) {
|
||||
if (!p_anchor.anchor.isEmpty() || p_anchor.lineNumber == m_curHeader.lineNumber) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (p_anchor.lineNumber != -1 || p_anchor.anchor == m_curHeader.anchor) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_curHeader = p_anchor;
|
||||
|
||||
emit curHeaderChanged(m_curHeader);
|
||||
emit currentHeaderChanged(m_currentHeader);
|
||||
}
|
||||
|
||||
void VMdTab::insertImage()
|
||||
@ -523,6 +469,16 @@ void VMdTab::insertImage()
|
||||
m_editor->insertImage();
|
||||
}
|
||||
|
||||
void VMdTab::insertLink()
|
||||
{
|
||||
if (!m_isEditMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_editor);
|
||||
m_editor->insertLink();
|
||||
}
|
||||
|
||||
void VMdTab::findText(const QString &p_text, uint p_options, bool p_peek,
|
||||
bool p_forward)
|
||||
{
|
||||
@ -682,9 +638,9 @@ void VMdTab::requestUpdateVimStatus()
|
||||
}
|
||||
}
|
||||
|
||||
VEditTabInfo VMdTab::createEditTabInfo()
|
||||
VEditTabInfo VMdTab::fetchTabInfo() const
|
||||
{
|
||||
VEditTabInfo info = VEditTab::createEditTabInfo();
|
||||
VEditTabInfo info = VEditTab::fetchTabInfo();
|
||||
|
||||
if (m_editor) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
@ -693,6 +649,8 @@ VEditTabInfo VMdTab::createEditTabInfo()
|
||||
info.m_blockCount = m_editor->document()->blockCount();
|
||||
}
|
||||
|
||||
info.m_headerIndex = m_currentHeader.m_index;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -702,3 +660,45 @@ void VMdTab::decorateText(TextDecoration p_decoration)
|
||||
m_editor->decorateText(p_decoration);
|
||||
}
|
||||
}
|
||||
|
||||
bool VMdTab::restoreFromTabInfo(const VEditTabInfo &p_info)
|
||||
{
|
||||
if (p_info.m_editTab != this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore header.
|
||||
VHeaderPointer header(m_file, p_info.m_headerIndex);
|
||||
bool ret = scrollToHeaderInternal(header);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VMdTab::restoreFromTabInfo()
|
||||
{
|
||||
restoreFromTabInfo(m_infoToRestore);
|
||||
|
||||
// Clear it anyway.
|
||||
m_infoToRestore.clear();
|
||||
}
|
||||
|
||||
void VMdTab::enableHeadingSequence(bool p_enabled)
|
||||
{
|
||||
m_enableHeadingSequence = p_enabled;
|
||||
|
||||
if (m_editor) {
|
||||
VEditConfig &config = m_editor->getConfig();
|
||||
config.m_enableHeadingSequence = m_enableHeadingSequence;
|
||||
}
|
||||
}
|
||||
|
||||
bool VMdTab::isHeadingSequenceEnabled() const
|
||||
{
|
||||
return m_enableHeadingSequence;
|
||||
}
|
||||
|
||||
void VMdTab::evaluateMagicWords()
|
||||
{
|
||||
if (isEditMode() && m_file->isModifiable()) {
|
||||
getEditor()->evaluateMagicWords();
|
||||
}
|
||||
}
|
||||
|
56
src/vmdtab.h
@ -31,11 +31,13 @@ public:
|
||||
// Save file.
|
||||
bool saveFile() Q_DECL_OVERRIDE;
|
||||
|
||||
// Scroll to anchor @p_anchor.
|
||||
void scrollToAnchor(const VAnchor& p_anchor) Q_DECL_OVERRIDE;
|
||||
// Scroll to @p_header.
|
||||
void scrollToHeader(const VHeaderPointer &p_header) Q_DECL_OVERRIDE;
|
||||
|
||||
void insertImage() Q_DECL_OVERRIDE;
|
||||
|
||||
void insertLink() Q_DECL_OVERRIDE;
|
||||
|
||||
// Search @p_text in current note.
|
||||
void findText(const QString &p_text, uint p_options, bool p_peek,
|
||||
bool p_forward = true) Q_DECL_OVERRIDE;
|
||||
@ -62,25 +64,34 @@ public:
|
||||
// Insert decoration markers or decorate selected text.
|
||||
void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
|
||||
|
||||
// Create a filled VEditTabInfo.
|
||||
VEditTabInfo fetchTabInfo() const Q_DECL_OVERRIDE;
|
||||
|
||||
// Enable or disable heading sequence.
|
||||
void enableHeadingSequence(bool p_enabled);
|
||||
|
||||
bool isHeadingSequenceEnabled() const;
|
||||
|
||||
// Evaluate magic words.
|
||||
void evaluateMagicWords() Q_DECL_OVERRIDE;
|
||||
|
||||
public slots:
|
||||
// Enter edit mode.
|
||||
void editFile() Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
// Handle text changed in m_editor.
|
||||
void handleTextChanged();
|
||||
// Update m_outline according to @p_tocHtml for read mode.
|
||||
void updateOutlineFromHtml(const QString &p_tocHtml);
|
||||
|
||||
// Update m_toc according to @p_tocHtml for read mode.
|
||||
void updateTocFromHtml(const QString &p_tocHtml);
|
||||
|
||||
// Update m_toc accroding to @p_headers for edit mode.
|
||||
void updateTocFromHeaders(const QVector<VHeader> &p_headers);
|
||||
// Update m_outline accroding to @p_headers for edit mode.
|
||||
void updateOutlineFromHeaders(const QVector<VTableOfContentItem> &p_headers);
|
||||
|
||||
// Web viewer requests to update current header.
|
||||
void updateCurHeader(const QString &p_anchor);
|
||||
// @p_anchor is the anchor of the header, like "toc_1".
|
||||
void updateCurrentHeader(const QString &p_anchor);
|
||||
|
||||
// Editor requests to update current header.
|
||||
void updateCurHeader(VAnchor p_anchor);
|
||||
void updateCurrentHeader(int p_blockNumber);
|
||||
|
||||
// Handle key press event in Web view.
|
||||
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift);
|
||||
@ -91,6 +102,9 @@ private slots:
|
||||
// m_editor requests to discard changes and enter read mode.
|
||||
void discardAndRead();
|
||||
|
||||
// Restore from m_infoToRestore.
|
||||
void restoreFromTabInfo();
|
||||
|
||||
private:
|
||||
// Setup UI.
|
||||
void setupUI();
|
||||
@ -111,8 +125,14 @@ private:
|
||||
void viewWebByConverter();
|
||||
|
||||
// Scroll Web view to given header.
|
||||
// @p_outlineIndex is the index in m_toc.headers.
|
||||
void scrollWebViewToHeader(int p_outlineIndex);
|
||||
// Return true if scroll was made.
|
||||
bool scrollWebViewToHeader(const VHeaderPointer &p_header);
|
||||
|
||||
bool scrollEditorToHeader(const VHeaderPointer &p_header);
|
||||
|
||||
// Scroll web/editor to given header.
|
||||
// Return true if scroll was made.
|
||||
bool scrollToHeaderInternal(const VHeaderPointer &p_header);
|
||||
|
||||
// Search text in Web view.
|
||||
void findTextInWebView(const QString &p_text, uint p_options, bool p_peek,
|
||||
@ -127,17 +147,21 @@ private:
|
||||
// Focus the proper child widget.
|
||||
void focusChild() Q_DECL_OVERRIDE;
|
||||
|
||||
// Create a filled VEditTabInfo.
|
||||
VEditTabInfo createEditTabInfo() Q_DECL_OVERRIDE;
|
||||
|
||||
// Get the markdown editor. If not init yet, init and return it.
|
||||
VEdit *getEditor();
|
||||
|
||||
// Restore from @p_fino.
|
||||
// Return true if succeed.
|
||||
bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
|
||||
|
||||
VEdit *m_editor;
|
||||
VWebView *m_webViewer;
|
||||
VDocument *m_document;
|
||||
MarkdownConverterType m_mdConType;
|
||||
|
||||
// Whether heading sequence is enabled.
|
||||
bool m_enableHeadingSequence;
|
||||
|
||||
QStackedLayout *m_stacks;
|
||||
};
|
||||
|
||||
|
@ -16,6 +16,10 @@
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
// Meta word manager.
|
||||
VMetaWordManager *g_mwMgr;
|
||||
|
||||
|
||||
QString VNote::s_markdownTemplate;
|
||||
QString VNote::s_markdownTemplatePDF;
|
||||
|
||||
@ -53,10 +57,15 @@ const QString VNote::c_markdownGuideDocFile_en = ":/resources/docs/markdown_guid
|
||||
const QString VNote::c_markdownGuideDocFile_zh = ":/resources/docs/markdown_guide_zh.md";
|
||||
|
||||
VNote::VNote(QObject *parent)
|
||||
: QObject(parent), m_mainWindow(dynamic_cast<VMainWindow *>(parent))
|
||||
: QObject(parent)
|
||||
{
|
||||
initTemplate();
|
||||
|
||||
g_config->getNotebooks(m_notebooks, this);
|
||||
|
||||
m_metaWordMgr.init();
|
||||
|
||||
g_mwMgr = &m_metaWordMgr;
|
||||
}
|
||||
|
||||
void VNote::initPalette(QPalette palette)
|
||||
@ -330,6 +339,16 @@ VNoteFile *VNote::getInternalFile(const QString &p_path)
|
||||
return file;
|
||||
}
|
||||
|
||||
VFile *VNote::getFile(const QString &p_path)
|
||||
{
|
||||
VFile *file = getInternalFile(p_path);
|
||||
if (!file) {
|
||||
file = getOrphanFile(p_path, true, false);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
VDirectory *VNote::getInternalDirectory(const QString &p_path)
|
||||
{
|
||||
VDirectory *dir = NULL;
|
||||
|
16
src/vnote.h
@ -12,11 +12,12 @@
|
||||
#include <QPalette>
|
||||
#include "vnotebook.h"
|
||||
#include "vconstants.h"
|
||||
#include "utils/vmetawordmanager.h"
|
||||
|
||||
class VMainWindow;
|
||||
class VOrphanFile;
|
||||
class VNoteFile;
|
||||
|
||||
|
||||
class VNote : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -74,10 +75,13 @@ public:
|
||||
const QVector<QPair<QString, QString> > &getPalette() const;
|
||||
void initPalette(QPalette palette);
|
||||
QString getColorFromPalette(const QString &p_name) const;
|
||||
VMainWindow *getMainWindow() const;
|
||||
|
||||
QString getNavigationLabelStyle(const QString &p_str) const;
|
||||
|
||||
// Given the path of a file, first try to open it as note file,
|
||||
// then try to open it as orphan file.
|
||||
VFile *getFile(const QString &p_path);
|
||||
|
||||
// Given the path of an external file, create a VOrphanFile struct.
|
||||
VOrphanFile *getOrphanFile(const QString &p_path,
|
||||
bool p_modifiable,
|
||||
@ -102,7 +106,8 @@ private:
|
||||
// Maintain all the notebooks. Other holder should use QPointer.
|
||||
QVector<VNotebook *> m_notebooks;
|
||||
QVector<QPair<QString, QString> > m_palette;
|
||||
VMainWindow *m_mainWindow;
|
||||
|
||||
VMetaWordManager m_metaWordMgr;
|
||||
|
||||
// Hold all external file: Orphan File.
|
||||
// Need to clean up periodly.
|
||||
@ -114,9 +119,4 @@ inline const QVector<QPair<QString, QString> >& VNote::getPalette() const
|
||||
return m_palette;
|
||||
}
|
||||
|
||||
inline VMainWindow *VNote::getMainWindow() const
|
||||
{
|
||||
return m_mainWindow;
|
||||
}
|
||||
|
||||
#endif // VNOTE_H
|
||||
|
@ -130,5 +130,9 @@
|
||||
<file>resources/icons/delete_attachment.svg</file>
|
||||
<file>resources/icons/sort.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>
|
||||
</RCC>
|
||||
|
@ -9,7 +9,7 @@
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
|
||||
: QObject(parent), m_name(name)
|
||||
: QObject(parent), m_name(name), m_valid(false)
|
||||
{
|
||||
m_path = QDir::cleanPath(path);
|
||||
m_recycleBinFolder = g_config->getRecycleBinFolder();
|
||||
@ -29,6 +29,7 @@ bool VNotebook::readConfigNotebook()
|
||||
QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path);
|
||||
if (configJson.isEmpty()) {
|
||||
qWarning() << "fail to read notebook configuration" << m_path;
|
||||
m_valid = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -58,6 +59,7 @@ bool VNotebook::readConfigNotebook()
|
||||
writeConfigNotebook();
|
||||
}
|
||||
|
||||
m_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -93,6 +93,8 @@ public:
|
||||
// Need to check if this notebook has been opened.
|
||||
QDateTime getCreatedTimeUtc();
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
// Serialize current instance to json.
|
||||
QJsonObject toConfigJson() const;
|
||||
@ -118,6 +120,10 @@ private:
|
||||
|
||||
// Parent is NULL for root directory
|
||||
VDirectory *m_rootDir;
|
||||
|
||||
// Whether this notebook is valid.
|
||||
// Will set to true after readConfigNotebook().
|
||||
bool m_valid;
|
||||
};
|
||||
|
||||
inline VDirectory *VNotebook::getRootDir() const
|
||||
@ -130,4 +136,9 @@ inline const QString &VNotebook::getRecycleBinFolder() const
|
||||
return m_recycleBinFolder;
|
||||
}
|
||||
|
||||
inline bool VNotebook::isValid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
#endif // VNOTEBOOK_H
|
||||
|
@ -20,22 +20,27 @@
|
||||
#include "vnote.h"
|
||||
#include "veditarea.h"
|
||||
#include "vnofocusitemdelegate.h"
|
||||
#include "vmainwindow.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
extern VNote *g_vnote;
|
||||
|
||||
const int VNotebookSelector::c_notebookStartIdx = 1;
|
||||
extern VMainWindow *g_mainWin;
|
||||
|
||||
VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent)
|
||||
: QComboBox(p_parent), VNavigationMode(),
|
||||
m_vnote(vnote), m_notebooks(m_vnote->getNotebooks()),
|
||||
m_editArea(NULL), m_lastValidIndex(-1), m_naviLabel(NULL)
|
||||
VNotebookSelector::VNotebookSelector(QWidget *p_parent)
|
||||
: QComboBox(p_parent),
|
||||
VNavigationMode(),
|
||||
m_notebooks(g_vnote->getNotebooks()),
|
||||
m_lastValidIndex(-1),
|
||||
m_muted(false),
|
||||
m_naviLabel(NULL)
|
||||
{
|
||||
m_listWidget = new QListWidget(this);
|
||||
m_listWidget->setItemDelegate(new VNoFocusItemDelegate(this));
|
||||
m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_listWidget, &QListWidget::customContextMenuRequested,
|
||||
this, &VNotebookSelector::requestPopupListContextMenu);
|
||||
this, &VNotebookSelector::popupListContextMenuRequested);
|
||||
|
||||
setModel(m_listWidget->model());
|
||||
setView(m_listWidget);
|
||||
@ -47,8 +52,6 @@ VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent)
|
||||
|
||||
connect(this, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(handleCurIndexChanged(int)));
|
||||
connect(this, SIGNAL(activated(int)),
|
||||
this, SLOT(handleItemActivated(int)));
|
||||
}
|
||||
|
||||
void VNotebookSelector::initActions()
|
||||
@ -75,9 +78,7 @@ void VNotebookSelector::initActions()
|
||||
}
|
||||
|
||||
Q_ASSERT(items.size() == 1);
|
||||
QListWidgetItem *item = items[0];
|
||||
int index = this->indexOfListItem(item);
|
||||
VNotebook *notebook = this->getNotebookFromComboIndex(index);
|
||||
VNotebook *notebook = getNotebook(items[0]);
|
||||
QUrl url = QUrl::fromLocalFile(notebook->getPath());
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
@ -93,9 +94,7 @@ void VNotebookSelector::initActions()
|
||||
}
|
||||
|
||||
Q_ASSERT(items.size() == 1);
|
||||
QListWidgetItem *item = items[0];
|
||||
int index = this->indexOfListItem(item);
|
||||
VNotebook *notebook = this->getNotebookFromComboIndex(index);
|
||||
VNotebook *notebook = getNotebook(items[0]);
|
||||
QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
@ -111,9 +110,7 @@ void VNotebookSelector::initActions()
|
||||
}
|
||||
|
||||
Q_ASSERT(items.size() == 1);
|
||||
QListWidgetItem *item = items[0];
|
||||
int index = this->indexOfListItem(item);
|
||||
VNotebook *notebook = this->getNotebookFromComboIndex(index);
|
||||
VNotebook *notebook = getNotebook(items[0]);
|
||||
QString binPath = notebook->getRecycleBinFolderPath();
|
||||
|
||||
int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||
@ -129,7 +126,9 @@ void VNotebookSelector::initActions()
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(binPath),
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
QMessageBox::Ok, this, MessageBoxType::Danger);
|
||||
QMessageBox::Ok,
|
||||
this,
|
||||
MessageBoxType::Danger);
|
||||
if (ret == QMessageBox::Ok) {
|
||||
QString info;
|
||||
if (VUtils::emptyDirectory(notebook, binPath, true)) {
|
||||
@ -157,37 +156,60 @@ void VNotebookSelector::initActions()
|
||||
|
||||
void VNotebookSelector::updateComboBox()
|
||||
{
|
||||
m_muted = true;
|
||||
|
||||
int index = g_config->getCurNotebookIndex();
|
||||
|
||||
disconnect(this, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(handleCurIndexChanged(int)));
|
||||
clear();
|
||||
|
||||
m_listWidget->clear();
|
||||
|
||||
insertAddNotebookItem();
|
||||
|
||||
for (int i = 0; i < m_notebooks.size(); ++i) {
|
||||
addNotebookItem(m_notebooks[i]->getName());
|
||||
addNotebookItem(m_notebooks[i]);
|
||||
}
|
||||
|
||||
setCurrentIndex(-1);
|
||||
connect(this, SIGNAL(currentIndexChanged(int)),
|
||||
this, SLOT(handleCurIndexChanged(int)));
|
||||
|
||||
m_muted = false;
|
||||
|
||||
if (m_notebooks.isEmpty()) {
|
||||
g_config->setCurNotebookIndex(-1);
|
||||
setCurrentIndex(0);
|
||||
} else {
|
||||
setCurrentIndexNotebook(index);
|
||||
const VNotebook *nb = NULL;
|
||||
if (index >= 0 && index < m_notebooks.size()) {
|
||||
nb = m_notebooks[index];
|
||||
}
|
||||
|
||||
setCurrentItemToNotebook(nb);
|
||||
}
|
||||
|
||||
qDebug() << "notebooks" << m_notebooks.size() << "current index" << index;
|
||||
}
|
||||
|
||||
void VNotebookSelector::setCurrentIndexNotebook(int p_index)
|
||||
void VNotebookSelector::setCurrentItemToNotebook(const VNotebook *p_notebook)
|
||||
{
|
||||
if (p_index > -1) {
|
||||
p_index += c_notebookStartIdx;
|
||||
setCurrentIndex(itemIndexOfNotebook(p_notebook));
|
||||
}
|
||||
|
||||
int VNotebookSelector::itemIndexOfNotebook(const VNotebook *p_notebook) const
|
||||
{
|
||||
if (!p_notebook) {
|
||||
return -1;
|
||||
}
|
||||
setCurrentIndex(p_index);
|
||||
|
||||
qulonglong ptr = (qulonglong)p_notebook;
|
||||
int cnt = m_listWidget->count();
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
QListWidgetItem *item = m_listWidget->item(i);
|
||||
if (item->data(Qt::UserRole).toULongLong() == ptr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void VNotebookSelector::insertAddNotebookItem()
|
||||
@ -199,49 +221,51 @@ void VNotebookSelector::insertAddNotebookItem()
|
||||
font.setItalic(true);
|
||||
item->setData(Qt::FontRole, font);
|
||||
item->setToolTip(tr("Create or import a notebook"));
|
||||
|
||||
m_listWidget->insertItem(0, item);
|
||||
}
|
||||
|
||||
void VNotebookSelector::handleCurIndexChanged(int p_index)
|
||||
{
|
||||
qDebug() << "current index changed" << p_index << "startIdx" << c_notebookStartIdx;
|
||||
if (m_muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString tooltip = tr("View and edit notebooks");
|
||||
VNotebook *nb = NULL;
|
||||
if (p_index > -1) {
|
||||
if (p_index < c_notebookStartIdx) {
|
||||
// Click a special action item.
|
||||
if (m_listWidget->count() == c_notebookStartIdx) {
|
||||
// There is no regular notebook item. Just let it be selected.
|
||||
p_index = -1;
|
||||
} else {
|
||||
// handleItemActivated() will handle the logics.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
int nbIdx = p_index - c_notebookStartIdx;
|
||||
Q_ASSERT(nbIdx >= 0);
|
||||
nb = m_notebooks[nbIdx];
|
||||
}
|
||||
}
|
||||
m_lastValidIndex = p_index;
|
||||
QString tooltip;
|
||||
if (p_index > -1) {
|
||||
p_index -= c_notebookStartIdx;
|
||||
tooltip = nb->getName();
|
||||
}
|
||||
setToolTip(tooltip);
|
||||
g_config->setCurNotebookIndex(p_index);
|
||||
emit curNotebookChanged(nb);
|
||||
}
|
||||
nb = getNotebook(p_index);
|
||||
if (!nb) {
|
||||
// Add notebook.
|
||||
setToolTip(tooltip);
|
||||
|
||||
void VNotebookSelector::handleItemActivated(int p_index)
|
||||
{
|
||||
if (p_index > -1 && p_index < c_notebookStartIdx) {
|
||||
// Click a special action item
|
||||
if (m_lastValidIndex > -1) {
|
||||
setCurrentIndex(m_lastValidIndex);
|
||||
if (m_lastValidIndex != p_index && m_lastValidIndex > -1) {
|
||||
setCurrentIndex(m_lastValidIndex);
|
||||
}
|
||||
|
||||
if (!m_notebooks.isEmpty()) {
|
||||
newNotebook();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
newNotebook();
|
||||
}
|
||||
|
||||
m_lastValidIndex = p_index;
|
||||
|
||||
int nbIdx = -1;
|
||||
if (nb) {
|
||||
tooltip = nb->getName();
|
||||
|
||||
nbIdx = m_notebooks.indexOf(nb);
|
||||
Q_ASSERT(nbIdx > -1);
|
||||
}
|
||||
|
||||
g_config->setCurNotebookIndex(nbIdx);
|
||||
|
||||
setToolTip(tooltip);
|
||||
|
||||
emit curNotebookChanged(nb);
|
||||
}
|
||||
|
||||
void VNotebookSelector::update()
|
||||
@ -260,13 +284,14 @@ bool VNotebookSelector::newNotebook()
|
||||
info += tr("* A previously created notebook could be imported into VNote "
|
||||
"by choosing its root folder.");
|
||||
|
||||
QString defaultName;
|
||||
QString defaultPath;
|
||||
|
||||
// Use empty default name and path to let the dialog to auto generate a name
|
||||
// under the default VNote notebook folder.
|
||||
VNewNotebookDialog dialog(tr("Add Notebook"), info, defaultName,
|
||||
defaultPath, m_notebooks, this);
|
||||
VNewNotebookDialog dialog(tr("Add Notebook"),
|
||||
info,
|
||||
"",
|
||||
"",
|
||||
m_notebooks,
|
||||
this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
createNotebook(dialog.getNameInput(),
|
||||
dialog.getPathInput(),
|
||||
@ -289,7 +314,7 @@ void VNotebookSelector::createNotebook(const QString &p_name,
|
||||
{
|
||||
VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import,
|
||||
p_imageFolder, p_attachmentFolder,
|
||||
m_vnote);
|
||||
g_vnote);
|
||||
if (!nb) {
|
||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||
tr("Fail to create notebook "
|
||||
@ -302,8 +327,8 @@ void VNotebookSelector::createNotebook(const QString &p_name,
|
||||
m_notebooks.append(nb);
|
||||
g_config->setNotebooks(m_notebooks);
|
||||
|
||||
addNotebookItem(nb->getName());
|
||||
setCurrentIndexNotebook(m_notebooks.size() - 1);
|
||||
addNotebookItem(nb);
|
||||
setCurrentItemToNotebook(nb);
|
||||
}
|
||||
|
||||
void VNotebookSelector::deleteNotebook()
|
||||
@ -312,86 +337,83 @@ void VNotebookSelector::deleteNotebook()
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(items.size() == 1);
|
||||
QListWidgetItem *item = items[0];
|
||||
int index = indexOfListItem(item);
|
||||
|
||||
VNotebook *notebook = getNotebookFromComboIndex(index);
|
||||
Q_ASSERT(items.size() == 1);
|
||||
|
||||
VNotebook *notebook = getNotebook(items[0]);
|
||||
Q_ASSERT(notebook);
|
||||
|
||||
VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook, this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
bool deleteFiles = dialog.getDeleteFiles();
|
||||
m_editArea->closeFile(notebook, true);
|
||||
g_mainWin->getEditArea()->closeFile(notebook, true);
|
||||
deleteNotebook(notebook, deleteFiles);
|
||||
}
|
||||
}
|
||||
|
||||
void VNotebookSelector::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles)
|
||||
{
|
||||
V_ASSERT(p_notebook);
|
||||
Q_ASSERT(p_notebook);
|
||||
|
||||
int idx = indexOfNotebook(p_notebook);
|
||||
|
||||
m_notebooks.remove(idx);
|
||||
m_notebooks.removeOne(p_notebook);
|
||||
g_config->setNotebooks(m_notebooks);
|
||||
|
||||
removeNotebookItem(idx);
|
||||
int idx = itemIndexOfNotebook(p_notebook);
|
||||
QListWidgetItem *item = m_listWidget->takeItem(idx);
|
||||
Q_ASSERT(item);
|
||||
delete item;
|
||||
|
||||
QString name(p_notebook->getName());
|
||||
QString path(p_notebook->getPath());
|
||||
bool ret = VNotebook::deleteNotebook(p_notebook, p_deleteFiles);
|
||||
if (!ret) {
|
||||
// Notebook could not be deleted completely.
|
||||
int cho = VUtils::showMessage(QMessageBox::Information, tr("Delete Notebook Folder From Disk"),
|
||||
int cho = VUtils::showMessage(QMessageBox::Information,
|
||||
tr("Delete Notebook Folder From Disk"),
|
||||
tr("Fail to delete the root folder of notebook "
|
||||
"<span style=\"%1\">%2</span> from disk. You may open "
|
||||
"the folder and check it manually.")
|
||||
.arg(g_config->c_dataTextStyle).arg(name), "",
|
||||
.arg(g_config->c_dataTextStyle).arg(name),
|
||||
"",
|
||||
QMessageBox::Open | QMessageBox::Ok,
|
||||
QMessageBox::Ok, this);
|
||||
QMessageBox::Ok,
|
||||
this);
|
||||
if (cho == QMessageBox::Open) {
|
||||
// Open the notebook location.
|
||||
QUrl url = QUrl::fromLocalFile(path);
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int VNotebookSelector::indexOfNotebook(const VNotebook *p_notebook)
|
||||
{
|
||||
for (int i = 0; i < m_notebooks.size(); ++i) {
|
||||
if (m_notebooks[i] == p_notebook) {
|
||||
return i;
|
||||
}
|
||||
if (m_notebooks.isEmpty()) {
|
||||
m_muted = true;
|
||||
setCurrentIndex(0);
|
||||
m_muted = false;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void VNotebookSelector::editNotebookInfo()
|
||||
{
|
||||
QList<QListWidgetItem *> items = m_listWidget->selectedItems();
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(items.size() == 1);
|
||||
QListWidgetItem *item = items[0];
|
||||
int index = indexOfListItem(item);
|
||||
|
||||
VNotebook *notebook = getNotebookFromComboIndex(index);
|
||||
QString curName = notebook->getName();
|
||||
|
||||
VNotebookInfoDialog dialog(tr("Notebook Information"), "", notebook,
|
||||
m_notebooks, this);
|
||||
VNotebook *notebook = getNotebook(items[0]);
|
||||
VNotebookInfoDialog dialog(tr("Notebook Information"),
|
||||
"",
|
||||
notebook,
|
||||
m_notebooks,
|
||||
this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
bool updated = false;
|
||||
bool configUpdated = false;
|
||||
QString name = dialog.getName();
|
||||
if (name != curName) {
|
||||
if (name != notebook->getName()) {
|
||||
updated = true;
|
||||
notebook->rename(name);
|
||||
updateComboBoxItem(index, name);
|
||||
g_config->setNotebooks(m_notebooks);
|
||||
}
|
||||
|
||||
@ -407,54 +429,58 @@ void VNotebookSelector::editNotebookInfo()
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
fillItem(items[0], notebook);
|
||||
emit notebookUpdated(notebook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VNotebookSelector::addNotebookItem(const QString &p_name)
|
||||
void VNotebookSelector::addNotebookItem(const VNotebook *p_notebook)
|
||||
{
|
||||
QListWidgetItem *item = new QListWidgetItem(m_listWidget);
|
||||
item->setText(p_name);
|
||||
item->setToolTip(p_name);
|
||||
item->setIcon(QIcon(":/resources/icons/notebook_item.svg"));
|
||||
fillItem(item, p_notebook);
|
||||
}
|
||||
|
||||
void VNotebookSelector::removeNotebookItem(int p_index)
|
||||
void VNotebookSelector::fillItem(QListWidgetItem *p_item,
|
||||
const VNotebook *p_notebook) const
|
||||
{
|
||||
QListWidgetItem *item = m_listWidget->item(p_index + c_notebookStartIdx);
|
||||
m_listWidget->removeItemWidget(item);
|
||||
delete item;
|
||||
p_item->setText(p_notebook->getName());
|
||||
p_item->setToolTip(p_notebook->getName());
|
||||
p_item->setIcon(QIcon(":/resources/icons/notebook_item.svg"));
|
||||
p_item->setData(Qt::UserRole, (qulonglong)p_notebook);
|
||||
}
|
||||
|
||||
void VNotebookSelector::updateComboBoxItem(int p_index, const QString &p_name)
|
||||
{
|
||||
QListWidgetItem *item = m_listWidget->item(p_index);
|
||||
item->setText(p_name);
|
||||
item->setToolTip(p_name);
|
||||
}
|
||||
|
||||
void VNotebookSelector::requestPopupListContextMenu(QPoint p_pos)
|
||||
void VNotebookSelector::popupListContextMenuRequested(QPoint p_pos)
|
||||
{
|
||||
QModelIndex index = m_listWidget->indexAt(p_pos);
|
||||
if (!index.isValid() || index.row() < c_notebookStartIdx) {
|
||||
QListWidgetItem *item = m_listWidget->itemAt(p_pos);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VNotebook *nb = getNotebook(item);
|
||||
if (!nb) {
|
||||
return;
|
||||
}
|
||||
|
||||
QListWidgetItem *item = m_listWidget->itemAt(p_pos);
|
||||
Q_ASSERT(item);
|
||||
m_listWidget->clearSelection();
|
||||
item->setSelected(true);
|
||||
|
||||
QMenu menu(this);
|
||||
menu.setToolTipsVisible(true);
|
||||
menu.addAction(m_deleteNotebookAct);
|
||||
menu.addSeparator();
|
||||
menu.addAction(m_recycleBinAct);
|
||||
menu.addAction(m_emptyRecycleBinAct);
|
||||
if (nb->isValid()) {
|
||||
menu.addSeparator();
|
||||
menu.addAction(m_recycleBinAct);
|
||||
menu.addAction(m_emptyRecycleBinAct);
|
||||
}
|
||||
|
||||
menu.addSeparator();
|
||||
menu.addAction(m_openLocationAct);
|
||||
menu.addAction(m_notebookInfoAct);
|
||||
|
||||
if (nb->isValid()) {
|
||||
menu.addAction(m_notebookInfoAct);
|
||||
}
|
||||
|
||||
menu.exec(m_listWidget->mapToGlobal(p_pos));
|
||||
}
|
||||
@ -471,40 +497,30 @@ bool VNotebookSelector::eventFilter(QObject *watched, QEvent *event)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QComboBox::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
int VNotebookSelector::indexOfListItem(const QListWidgetItem *p_item)
|
||||
{
|
||||
int nrItems = m_listWidget->count();
|
||||
for (int i = 0; i < nrItems; ++i) {
|
||||
if (m_listWidget->item(i) == p_item) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return QComboBox::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
bool VNotebookSelector::locateNotebook(const VNotebook *p_notebook)
|
||||
{
|
||||
if (p_notebook) {
|
||||
for (int i = 0; i < m_notebooks.size(); ++i) {
|
||||
if (m_notebooks[i] == p_notebook) {
|
||||
setCurrentIndexNotebook(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool ret = false;
|
||||
int index = itemIndexOfNotebook(p_notebook);
|
||||
if (index > -1) {
|
||||
setCurrentIndex(index);
|
||||
ret = true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VNotebookSelector::showPopup()
|
||||
{
|
||||
if (count() <= c_notebookStartIdx) {
|
||||
if (m_notebooks.isEmpty()) {
|
||||
// No normal notebook items. Just add notebook.
|
||||
newNotebook();
|
||||
return;
|
||||
}
|
||||
|
||||
resizeListWidgetToContent();
|
||||
QComboBox::showPopup();
|
||||
}
|
||||
@ -526,6 +542,7 @@ void VNotebookSelector::resizeListWidgetToContent()
|
||||
minHeight = m_listWidget->sizeHintForRow(0) * m_listWidget->count() + 10;
|
||||
minHeight = qMin(minHeight, maxMinHeight);
|
||||
}
|
||||
|
||||
m_listWidget->setMinimumSize(minWidth, minHeight);
|
||||
}
|
||||
|
||||
@ -616,3 +633,23 @@ bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
VNotebook *VNotebookSelector::getNotebook(int p_itemIdx) const
|
||||
{
|
||||
VNotebook *nb = NULL;
|
||||
QListWidgetItem *item = m_listWidget->item(p_itemIdx);
|
||||
if (item) {
|
||||
nb = (VNotebook *)item->data(Qt::UserRole).toULongLong();
|
||||
}
|
||||
|
||||
return nb;
|
||||
}
|
||||
|
||||
VNotebook *VNotebookSelector::getNotebook(const QListWidgetItem *p_item) const
|
||||
{
|
||||
if (p_item) {
|
||||
return (VNotebook *)p_item->data(Qt::UserRole).toULongLong();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
@ -7,8 +7,6 @@
|
||||
#include "vnavigationmode.h"
|
||||
|
||||
class VNotebook;
|
||||
class VNote;
|
||||
class VEditArea;
|
||||
class QListWidget;
|
||||
class QAction;
|
||||
class QListWidgetItem;
|
||||
@ -18,11 +16,15 @@ class VNotebookSelector : public QComboBox, public VNavigationMode
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VNotebookSelector(VNote *vnote, QWidget *p_parent = 0);
|
||||
explicit VNotebookSelector(QWidget *p_parent = 0);
|
||||
|
||||
// Update Combox from m_notebooks.
|
||||
void update();
|
||||
inline void setEditArea(VEditArea *p_editArea);
|
||||
|
||||
// Select notebook @p_notebook.
|
||||
bool locateNotebook(const VNotebook *p_notebook);
|
||||
|
||||
// Add notebook on popup if no notebooks currently.
|
||||
void showPopup() Q_DECL_OVERRIDE;
|
||||
|
||||
// Implementations for VNavigationMode.
|
||||
@ -41,24 +43,32 @@ signals:
|
||||
void notebookCreated(const QString &p_name, bool p_import);
|
||||
|
||||
public slots:
|
||||
// Popup a dialog to prompt user to create a notebook.
|
||||
bool newNotebook();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
// Act to currentIndexChanged() signal if m_muted is false.
|
||||
void handleCurIndexChanged(int p_index);
|
||||
void handleItemActivated(int p_index);
|
||||
void requestPopupListContextMenu(QPoint p_pos);
|
||||
|
||||
void popupListContextMenuRequested(QPoint p_pos);
|
||||
|
||||
// Delete currently selected notebook.
|
||||
void deleteNotebook();
|
||||
|
||||
// View and edit notebook information of selected notebook.
|
||||
void editNotebookInfo();
|
||||
|
||||
private:
|
||||
void initActions();
|
||||
|
||||
// Update Combox from m_notebooks.
|
||||
void updateComboBox();
|
||||
|
||||
// Return the index of @p_notebook in m_noteboks.
|
||||
int indexOfNotebook(const VNotebook *p_notebook);
|
||||
// Return the item index of @p_notebook.
|
||||
int itemIndexOfNotebook(const VNotebook *p_notebook) const;
|
||||
|
||||
// If @p_import is true, we will use the existing config file.
|
||||
// If @p_imageFolder is empty, we will use the global one.
|
||||
@ -68,26 +78,37 @@ private:
|
||||
const QString &p_attachmentFolder);
|
||||
|
||||
void deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles);
|
||||
void addNotebookItem(const QString &p_name);
|
||||
// @p_index is the index of m_notebooks, NOT of QComboBox.
|
||||
void removeNotebookItem(int p_index);
|
||||
// @p_index is the index of QComboBox.
|
||||
void updateComboBoxItem(int p_index, const QString &p_name);
|
||||
|
||||
// Add an item corresponding to @p_notebook to combo box.
|
||||
void addNotebookItem(const VNotebook *p_notebook);
|
||||
|
||||
void fillItem(QListWidgetItem *p_item, const VNotebook *p_notebook) const;
|
||||
|
||||
// Insert "Add Notebook" item to combo box.
|
||||
void insertAddNotebookItem();
|
||||
// @p_index is the index of m_notebooks.
|
||||
void setCurrentIndexNotebook(int p_index);
|
||||
int indexOfListItem(const QListWidgetItem *p_item);
|
||||
// @p_index is the idnex of QComboBox.
|
||||
inline VNotebook *getNotebookFromComboIndex(int p_index);
|
||||
|
||||
// Set current item corresponding to @p_notebook.
|
||||
void setCurrentItemToNotebook(const VNotebook *p_notebook);
|
||||
|
||||
// Get VNotebook from @p_itemIdx, the index of m_listWidget.
|
||||
VNotebook *getNotebook(int p_itemIdx) const;
|
||||
|
||||
VNotebook *getNotebook(const QListWidgetItem *p_item) const;
|
||||
|
||||
void resizeListWidgetToContent();
|
||||
|
||||
bool handlePopupKeyPress(QKeyEvent *p_event);
|
||||
|
||||
VNote *m_vnote;
|
||||
QVector<VNotebook *> &m_notebooks;
|
||||
VEditArea *m_editArea;
|
||||
|
||||
QListWidget *m_listWidget;
|
||||
|
||||
// Used to restore after clicking Add Notebook item.
|
||||
int m_lastValidIndex;
|
||||
|
||||
// Whether it is muted from currentIndexChanged().
|
||||
bool m_muted;
|
||||
|
||||
// Actions
|
||||
QAction *m_deleteNotebookAct;
|
||||
QAction *m_notebookInfoAct;
|
||||
@ -95,28 +116,7 @@ private:
|
||||
QAction *m_recycleBinAct;
|
||||
QAction *m_emptyRecycleBinAct;
|
||||
|
||||
// We will add several special action item in the combobox. This is the start index
|
||||
// of the real notebook items related to m_notebooks.
|
||||
static const int c_notebookStartIdx;
|
||||
|
||||
QLabel *m_naviLabel;
|
||||
};
|
||||
|
||||
inline void VNotebookSelector::setEditArea(VEditArea *p_editArea)
|
||||
{
|
||||
m_editArea = p_editArea;
|
||||
}
|
||||
|
||||
inline VNotebook *VNotebookSelector::getNotebookFromComboIndex(int p_index)
|
||||
{
|
||||
if (p_index < c_notebookStartIdx) {
|
||||
return NULL;
|
||||
}
|
||||
int nbIdx = p_index - c_notebookStartIdx;
|
||||
if (nbIdx >= m_notebooks.size()) {
|
||||
return NULL;
|
||||
}
|
||||
return m_notebooks[nbIdx];
|
||||
}
|
||||
|
||||
#endif // VNOTEBOOKSELECTOR_H
|
||||
|
@ -233,7 +233,9 @@ bool VNoteFile::addAttachment(const QString &p_file)
|
||||
QString folderPath = fetchAttachmentFolderPath();
|
||||
QString name = VUtils::fileNameFromPath(p_file);
|
||||
Q_ASSERT(!name.isEmpty());
|
||||
name = VUtils::getFileNameWithSequence(folderPath, name);
|
||||
// For attachments, we do not use complete base name.
|
||||
// abc.tar.gz should be abc_001.tar.gz instead of abc.tar_001.gz.
|
||||
name = VUtils::getFileNameWithSequence(folderPath, name, false);
|
||||
QString destPath = QDir(folderPath).filePath(name);
|
||||
if (!VUtils::copyFile(p_file, destPath, false)) {
|
||||
return false;
|
||||
@ -543,7 +545,7 @@ bool VNoteFile::copyFile(VDirectory *p_destDir,
|
||||
if (!attaFolderPath.isEmpty()) {
|
||||
QDir dir(destFile->fetchBasePath());
|
||||
QString folderPath = dir.filePath(destFile->getNotebook()->getAttachmentFolder());
|
||||
attaFolder = VUtils::getFileNameWithSequence(folderPath, attaFolder);
|
||||
attaFolder = VUtils::getDirNameWithSequence(folderPath, attaFolder);
|
||||
folderPath = QDir(folderPath).filePath(attaFolder);
|
||||
|
||||
// Copy attaFolderPath to folderPath.
|
||||
|
167
src/voutline.cpp
@ -1,44 +1,39 @@
|
||||
#include <QDebug>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QCoreApplication>
|
||||
#include "voutline.h"
|
||||
#include "vtoc.h"
|
||||
#include "utils/vutils.h"
|
||||
#include "vnote.h"
|
||||
#include "vfile.h"
|
||||
|
||||
extern VNote *g_vnote;
|
||||
|
||||
VOutline::VOutline(QWidget *parent)
|
||||
: QTreeWidget(parent), VNavigationMode()
|
||||
: QTreeWidget(parent),
|
||||
VNavigationMode(),
|
||||
m_muted(false)
|
||||
{
|
||||
setColumnCount(1);
|
||||
setHeaderHidden(true);
|
||||
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
||||
// TODO: jump to the header when user click the same item twice.
|
||||
connect(this, &VOutline::currentItemChanged,
|
||||
this, &VOutline::handleCurItemChanged);
|
||||
this, &VOutline::handleCurrentItemChanged);
|
||||
}
|
||||
|
||||
void VOutline::checkOutline(const VToc &p_toc) const
|
||||
void VOutline::updateOutline(const VTableOfContent &p_outline)
|
||||
{
|
||||
const QVector<VHeader> &headers = p_toc.headers;
|
||||
|
||||
for (int i = 0; i < headers.size(); ++i) {
|
||||
V_ASSERT(headers[i].index == i);
|
||||
if (p_outline == m_outline) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void VOutline::updateOutline(const VToc &toc)
|
||||
{
|
||||
// Clear current header
|
||||
curHeader = VAnchor();
|
||||
m_currentHeader.clear();
|
||||
|
||||
checkOutline(toc);
|
||||
|
||||
outline = toc;
|
||||
m_outline = p_outline;
|
||||
|
||||
updateTreeFromOutline();
|
||||
|
||||
@ -49,22 +44,25 @@ void VOutline::updateTreeFromOutline()
|
||||
{
|
||||
clear();
|
||||
|
||||
if (!outline.valid) {
|
||||
if (m_outline.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QVector<VHeader> &headers = outline.headers;
|
||||
const QVector<VTableOfContentItem> &headers = m_outline.getTable();
|
||||
int idx = 0;
|
||||
updateTreeByLevel(headers, idx, NULL, NULL, 1);
|
||||
}
|
||||
|
||||
void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
|
||||
QTreeWidgetItem *parent, QTreeWidgetItem *last, int level)
|
||||
void VOutline::updateTreeByLevel(const QVector<VTableOfContentItem> &headers,
|
||||
int &index,
|
||||
QTreeWidgetItem *parent,
|
||||
QTreeWidgetItem *last,
|
||||
int level)
|
||||
{
|
||||
while (index < headers.size()) {
|
||||
const VHeader &header = headers[index];
|
||||
const VTableOfContentItem &header = headers[index];
|
||||
QTreeWidgetItem *item;
|
||||
if (header.level == level) {
|
||||
if (header.m_level == level) {
|
||||
if (parent) {
|
||||
item = new QTreeWidgetItem(parent);
|
||||
} else {
|
||||
@ -75,7 +73,7 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
|
||||
|
||||
last = item;
|
||||
++index;
|
||||
} else if (header.level < level) {
|
||||
} else if (header.m_level < level) {
|
||||
return;
|
||||
} else {
|
||||
updateTreeByLevel(headers, index, last, NULL, level + 1);
|
||||
@ -83,11 +81,11 @@ void VOutline::updateTreeByLevel(const QVector<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->setText(0, p_header.name);
|
||||
p_item->setToolTip(0, p_header.name);
|
||||
p_item->setData(0, Qt::UserRole, p_header.m_index);
|
||||
p_item->setText(0, p_header.m_name);
|
||||
p_item->setToolTip(0, p_header.m_name);
|
||||
|
||||
if (p_header.isEmpty()) {
|
||||
p_item->setForeground(0, QColor("grey"));
|
||||
@ -99,127 +97,81 @@ void VOutline::expandTree()
|
||||
if (topLevelItemCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
expandAll();
|
||||
}
|
||||
|
||||
void VOutline::handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem * /*p_preItem*/)
|
||||
void VOutline::handleCurrentItemChanged(QTreeWidgetItem *p_curItem,
|
||||
QTreeWidgetItem * p_preItem)
|
||||
{
|
||||
Q_UNUSED(p_preItem);
|
||||
|
||||
if (!p_curItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VHeader *header = getHeaderFromItem(p_curItem);
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
const VTableOfContentItem *header = getHeaderFromItem(p_curItem);
|
||||
Q_ASSERT(header);
|
||||
m_currentHeader.update(m_outline.getFile(), header->m_index);
|
||||
|
||||
VAnchor tmp(outline.m_file, header->anchor, header->lineNumber, header->index);
|
||||
if (tmp == curHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
curHeader = tmp;
|
||||
|
||||
if (!header->isEmpty()) {
|
||||
emit outlineItemActivated(curHeader);
|
||||
if (!header->isEmpty() && !m_muted) {
|
||||
emit outlineItemActivated(m_currentHeader);
|
||||
}
|
||||
}
|
||||
|
||||
void VOutline::updateCurHeader(const VAnchor &anchor)
|
||||
void VOutline::updateCurrentHeader(const VHeaderPointer &p_header)
|
||||
{
|
||||
if (anchor == curHeader) {
|
||||
if (p_header == m_currentHeader
|
||||
|| !m_outline.isMatched(p_header)) {
|
||||
return;
|
||||
}
|
||||
|
||||
curHeader = anchor;
|
||||
if (outline.type == VHeaderType::Anchor) {
|
||||
selectAnchor(anchor.anchor);
|
||||
} else {
|
||||
// Select by lineNumber.
|
||||
selectLineNumber(anchor.lineNumber);
|
||||
}
|
||||
// Item change should not emit the signal.
|
||||
m_muted = true;
|
||||
m_currentHeader = p_header;
|
||||
selectHeader(m_currentHeader);
|
||||
m_muted = false;
|
||||
}
|
||||
|
||||
void VOutline::selectAnchor(const QString &anchor)
|
||||
void VOutline::selectHeader(const VHeaderPointer &p_header)
|
||||
{
|
||||
setCurrentItem(NULL);
|
||||
|
||||
if (anchor.isEmpty()) {
|
||||
if (!m_outline.getItem(p_header)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int nrTop = topLevelItemCount();
|
||||
for (int i = 0; i < nrTop; ++i) {
|
||||
if (selectAnchorOne(topLevelItem(i), anchor)) {
|
||||
if (selectHeaderOne(topLevelItem(i), p_header)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor)
|
||||
bool VOutline::selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header)
|
||||
{
|
||||
if (!item) {
|
||||
if (!p_item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const VHeader *header = getHeaderFromItem(item);
|
||||
const VTableOfContentItem *header = getHeaderFromItem(p_item);
|
||||
if (!header) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header->anchor == anchor) {
|
||||
setCurrentItem(item);
|
||||
if (header->isMatched(p_header)) {
|
||||
setCurrentItem(p_item);
|
||||
return true;
|
||||
}
|
||||
|
||||
int nrChild = item->childCount();
|
||||
int nrChild = p_item->childCount();
|
||||
for (int i = 0; i < nrChild; ++i) {
|
||||
if (selectAnchorOne(item->child(i), anchor)) {
|
||||
if (selectHeaderOne(p_item->child(i), p_header)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void VOutline::selectLineNumber(int lineNumber)
|
||||
{
|
||||
setCurrentItem(NULL);
|
||||
|
||||
if (lineNumber == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int nrTop = topLevelItemCount();
|
||||
for (int i = 0; i < nrTop; ++i) {
|
||||
if (selectLineNumberOne(topLevelItem(i), lineNumber)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber)
|
||||
{
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const VHeader *header = getHeaderFromItem(item);
|
||||
if (!header) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header->lineNumber == lineNumber) {
|
||||
// Select this item
|
||||
setCurrentItem(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
int nrChild = item->childCount();
|
||||
for (int i = 0; i < nrChild; ++i) {
|
||||
if (selectLineNumberOne(item->child(i), lineNumber)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -373,17 +325,8 @@ QList<QTreeWidgetItem *> VOutline::getVisibleChildItems(const QTreeWidgetItem *p
|
||||
return items;
|
||||
}
|
||||
|
||||
const VHeader *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const
|
||||
const VTableOfContentItem *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const
|
||||
{
|
||||
const VHeader *header = NULL;
|
||||
|
||||
int index = p_item->data(0, Qt::UserRole).toInt();
|
||||
if (index < 0 || index >= outline.headers.size()) {
|
||||
return header;
|
||||
}
|
||||
|
||||
header = &(outline.headers[index]);
|
||||
Q_ASSERT(header->index == index);
|
||||
|
||||
return header;
|
||||
return m_outline.getItem(index);
|
||||
}
|
||||
|
@ -5,11 +5,13 @@
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
#include <QChar>
|
||||
#include "vtoc.h"
|
||||
#include "vtableofcontent.h"
|
||||
#include "vnavigationmode.h"
|
||||
|
||||
class QLabel;
|
||||
|
||||
// Display table of content as a tree and enable user to click an item to
|
||||
// jump to that header.
|
||||
class VOutline : public QTreeWidget, public VNavigationMode
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -23,45 +25,60 @@ public:
|
||||
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
|
||||
|
||||
signals:
|
||||
void outlineItemActivated(const VAnchor &anchor);
|
||||
// Emit when current item changed by user and header of that item is not empty.
|
||||
// Do not worry about infinite recursion.
|
||||
void outlineItemActivated(const VHeaderPointer &p_header);
|
||||
|
||||
public slots:
|
||||
void updateOutline(const VToc &toc);
|
||||
void updateCurHeader(const VAnchor &anchor);
|
||||
// Called to update outline and the tree.
|
||||
// Just clear the tree if @p_outline is empty.
|
||||
void updateOutline(const VTableOfContent &p_outline);
|
||||
|
||||
// Called to update current header in the tree.
|
||||
// Will not emit outlineItemActivated().
|
||||
void updateCurrentHeader(const VHeaderPointer &p_header);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
void handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
|
||||
// Handle current item change even of the tree.
|
||||
// Do not response if m_muted is true.
|
||||
void handleCurrentItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
|
||||
|
||||
private:
|
||||
// Update tree according to outline.
|
||||
void updateTreeFromOutline();
|
||||
|
||||
// @index: the index in @headers.
|
||||
void updateTreeByLevel(const QVector<VHeader> &headers, int &index, QTreeWidgetItem *parent,
|
||||
QTreeWidgetItem *last, int level);
|
||||
void updateTreeByLevel(const QVector<VTableOfContentItem> &headers,
|
||||
int &index,
|
||||
QTreeWidgetItem *parent,
|
||||
QTreeWidgetItem *last,
|
||||
int level);
|
||||
|
||||
void expandTree();
|
||||
void selectAnchor(const QString &anchor);
|
||||
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
|
||||
void selectLineNumber(int lineNumber);
|
||||
bool selectLineNumberOne(QTreeWidgetItem *item, int lineNumber);
|
||||
|
||||
// Set the item corresponding to @p_header as current item.
|
||||
void selectHeader(const VHeaderPointer &p_header);
|
||||
|
||||
bool selectHeaderOne(QTreeWidgetItem *p_item, const VHeaderPointer &p_header);
|
||||
|
||||
QList<QTreeWidgetItem *> getVisibleItems() const;
|
||||
QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
|
||||
|
||||
// Fill the info of @p_item.
|
||||
void fillItem(QTreeWidgetItem *p_item, const VHeader &p_header);
|
||||
|
||||
// Check if @p_toc is valid.
|
||||
void checkOutline(const VToc &p_toc) const;
|
||||
void fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header);
|
||||
|
||||
// Return NULL if no corresponding header in outline.
|
||||
const VHeader *getHeaderFromItem(QTreeWidgetItem *p_item) const;
|
||||
const VTableOfContentItem *getHeaderFromItem(QTreeWidgetItem *p_item) const;
|
||||
|
||||
VToc outline;
|
||||
VAnchor curHeader;
|
||||
VTableOfContent m_outline;
|
||||
|
||||
VHeaderPointer m_currentHeader;
|
||||
|
||||
// When true, won't emit outlineItemActivated().
|
||||
bool m_muted;
|
||||
|
||||
// Navigation Mode.
|
||||
// Map second key to QTreeWidgetItem.
|
||||
|
@ -2,10 +2,9 @@
|
||||
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "vnote.h"
|
||||
#include "vmainwindow.h"
|
||||
|
||||
extern VNote *g_vnote;
|
||||
extern VMainWindow *g_mainWin;
|
||||
|
||||
VPreviewPage::VPreviewPage(QWidget *parent) : QWebEnginePage(parent)
|
||||
{
|
||||
@ -21,7 +20,7 @@ bool VPreviewPage::acceptNavigationRequest(const QUrl &p_url,
|
||||
|
||||
if (p_url.isLocalFile()) {
|
||||
QString filePath = p_url.toLocalFile();
|
||||
if (g_vnote->getMainWindow()->tryOpenInternalFile(filePath)) {
|
||||
if (g_mainWin->tryOpenInternalFile(filePath)) {
|
||||
qDebug() << "internal notes jump" << filePath;
|
||||
return false;
|
||||
}
|
||||
|