Merge remote-tracking branch 'origin/dev'

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

View File

@ -1,11 +1,11 @@
#!/bin/bash
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

View File

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

View File

@ -54,7 +54,8 @@ Utilizing Qt, VNote could run on **Linux**, **Windows**, and **macOS** (due to t
- Supports infinite levels of folders;
- Supports 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.
![VNote Edit](screenshots/vnote_edit.gif)
@ -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).

View File

@ -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/)
- 支持高分辨率;
- 支持笔记附件。
![VNote Edit](screenshots/vnote_edit.gif)
@ -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)。

View File

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

View File

@ -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();
}

View File

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

View File

@ -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();
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,132 @@
#include "vinsertlinkdialog.h"
#include <QtWidgets>
#include "vlineedit.h"
VInsertLinkDialog::VInsertLinkDialog(const QString &p_title,
const QString &p_text,
const QString &p_info,
const QString &p_linkText,
const QString &p_linkUrl,
QWidget *p_parent)
: QDialog(p_parent)
{
setupUI(p_title, p_text, p_info, p_linkText, p_linkUrl);
fetchLinkFromClipboard();
handleInputChanged();
}
void VInsertLinkDialog::setupUI(const QString &p_title,
const QString &p_text,
const QString &p_info,
const QString &p_linkText,
const QString &p_linkUrl)
{
QLabel *textLabel = NULL;
if (!p_text.isEmpty()) {
textLabel = new QLabel(p_text);
textLabel->setWordWrap(true);
}
QLabel *infoLabel = NULL;
if (!p_info.isEmpty()) {
infoLabel = new QLabel(p_info);
infoLabel->setWordWrap(true);
}
m_linkTextEdit = new VLineEdit(p_linkText);
m_linkTextEdit->selectAll();
m_linkUrlEdit = new QLineEdit(p_linkUrl);
m_linkUrlEdit->setToolTip(tr("Absolute or relative path of the link"));
QFormLayout *inputLayout = new QFormLayout();
inputLayout->addRow(tr("&Text:"), m_linkTextEdit);
inputLayout->addRow(tr("&URL:"), m_linkUrlEdit);
// Ok is the default button.
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
m_linkTextEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout;
if (textLabel) {
mainLayout->addWidget(textLabel);
}
if (infoLabel) {
mainLayout->addWidget(infoLabel);
}
mainLayout->addLayout(inputLayout);
mainLayout->addWidget(m_btnBox);
setLayout(mainLayout);
setWindowTitle(p_title);
connect(m_linkTextEdit, &QLineEdit::textChanged,
this, &VInsertLinkDialog::handleInputChanged);
connect(m_linkUrlEdit, &QLineEdit::textChanged,
this, &VInsertLinkDialog::handleInputChanged);
}
void VInsertLinkDialog::handleInputChanged()
{
bool textOk = true;
if (m_linkTextEdit->getEvaluatedText().isEmpty()) {
textOk = false;
}
bool urlOk = true;
if (m_linkUrlEdit->text().isEmpty()) {
urlOk = false;
}
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
okBtn->setEnabled(textOk && urlOk);
}
void VInsertLinkDialog::fetchLinkFromClipboard()
{
if (!m_linkUrlEdit->text().isEmpty()
|| !m_linkTextEdit->text().isEmpty()) {
return;
}
QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (!mimeData->hasText()) {
return;
}
QString text = mimeData->text();
if (text.isEmpty()) {
return;
}
QUrl url = QUrl::fromUserInput(text);
if (url.isValid()) {
if (m_linkUrlEdit->text().isEmpty()) {
m_linkUrlEdit->setText(text);
}
} else if (m_linkTextEdit->text().isEmpty()) {
m_linkTextEdit->setText(text);
}
}
QString VInsertLinkDialog::getLinkText() const
{
return m_linkTextEdit->getEvaluatedText();
}
QString VInsertLinkDialog::getLinkUrl() const
{
return m_linkUrlEdit->text();
}

View File

@ -0,0 +1,46 @@
#ifndef VINSERTLINKDIALOG_H
#define VINSERTLINKDIALOG_H
#include <QDialog>
#include <QString>
class VLineEdit;
class QLineEdit;
class QDialogButtonBox;
class VInsertLinkDialog : public QDialog
{
Q_OBJECT
public:
VInsertLinkDialog(const QString &p_title,
const QString &p_text,
const QString &p_info,
const QString &p_linkText,
const QString &p_linkUrl,
QWidget *p_parent = nullptr);
QString getLinkText() const;
QString getLinkUrl() const;
private slots:
void handleInputChanged();
private:
void setupUI(const QString &p_title,
const QString &p_text,
const QString &p_info,
const QString &p_linkText,
const QString &p_linkUrl);
// Infer link text/url from clipboard only when text and url are both empty.
void fetchLinkFromClipboard();
VLineEdit *m_linkTextEdit;
QLineEdit *m_linkUrlEdit;
QDialogButtonBox *m_btnBox;
};
#endif // VINSERTLINKDIALOG_H

View File

@ -2,6 +2,8 @@
#include "vnewdirdialog.h"
#include "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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

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

After

Width:  |  Height:  |  Size: 470 B

View File

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

After

Width:  |  Height:  |  Size: 780 B

View File

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

After

Width:  |  Height:  |  Size: 502 B

View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,17 +1 @@
<?xml version="1.0"?>
<svg 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

@ -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() {}

View File

@ -0,0 +1,596 @@
#include "vmetawordmanager.h"
#include <QDebug>
#include <QWidget>
#include <QApplication>
#include <QToolTip>
#include "vconfigmanager.h"
extern VConfigManager *g_config;
// Used as the function template for some date/time related meta words.
static QString formattedDateTime(const VMetaWord *p_metaWord,
const QString &p_format)
{
return p_metaWord->getManager()->getDateTime().toString(p_format);
}
static QString allMetaWordsInfo(const VMetaWord *p_metaWord)
{
QString msg = QObject::tr("All magic words:");
const VMetaWordManager *mgr = p_metaWord->getManager();
QList<QString> keys = mgr->getAllMetaWords().keys();
keys.sort(Qt::CaseInsensitive);
for (auto const & key : keys) {
const VMetaWord *word = mgr->findMetaWord(key);
Q_ASSERT(word);
msg += QString("\n%1:\t%2").arg(word->getWord()).arg(word->getDefinition());
}
QWidget *focusWid = QApplication::focusWidget();
if (focusWid) {
QPoint pos = focusWid->mapToGlobal(QPoint(0, focusWid->height()));
QToolTip::showText(pos, msg, focusWid);
}
// Just return the same word.
return QString("%1help%1").arg(VMetaWordManager::c_delimiter);
}
const QChar VMetaWordManager::c_delimiter = '%';
VMetaWordManager::VMetaWordManager(QObject *p_parent)
: QObject(p_parent)
{
}
void VMetaWordManager::init()
{
using namespace std::placeholders;
// %d%.
addMetaWord(MetaWordType::FunctionBased,
"d",
tr("the day as number without a leading zero (`1` to `31`)"),
std::bind(formattedDateTime, _1, "d"));
// %dd%.
addMetaWord(MetaWordType::FunctionBased,
"dd",
tr("the day as number with a leading zero (`01` to `31`)"),
std::bind(formattedDateTime, _1, "dd"));
// %ddd%.
addMetaWord(MetaWordType::FunctionBased,
"ddd",
tr("the abbreviated localized day name (e.g. `Mon` to `Sun`)"),
std::bind(formattedDateTime, _1, "ddd"));
// %dddd%.
addMetaWord(MetaWordType::FunctionBased,
"dddd",
tr("the long localized day name (e.g. `Monday` to `Sunday`)"),
std::bind(formattedDateTime, _1, "dddd"));
// %M%.
addMetaWord(MetaWordType::FunctionBased,
"M",
tr("the month as number without a leading zero (`1` to `12`)"),
std::bind(formattedDateTime, _1, "M"));
// %MM%.
addMetaWord(MetaWordType::FunctionBased,
"MM",
tr("the month as number with a leading zero (`01` to `12`)"),
std::bind(formattedDateTime, _1, "MM"));
// %MMM%.
addMetaWord(MetaWordType::FunctionBased,
"MMM",
tr("the abbreviated localized month name (e.g. `Jan` to `Dec`)"),
std::bind(formattedDateTime, _1, "MMM"));
// %MMMM%.
addMetaWord(MetaWordType::FunctionBased,
"MMMM",
tr("the long localized month name (e.g. `January` to `December`"),
std::bind(formattedDateTime, _1, "MMMM"));
// %yy%.
addMetaWord(MetaWordType::FunctionBased,
"yy",
tr("the year as two digit number (`00` to `99`)"),
std::bind(formattedDateTime, _1, "yy"));
// %yyyy%.
addMetaWord(MetaWordType::FunctionBased,
"yyyy",
tr("the year as four digit number"),
std::bind(formattedDateTime, _1, "yyyy"));
// %h%.
addMetaWord(MetaWordType::FunctionBased,
"h",
tr("the hour without a leading zero (`0` to `23` or `1` to `12` if AM/PM display"),
std::bind(formattedDateTime, _1, "h"));
// %hh%.
addMetaWord(MetaWordType::FunctionBased,
"hh",
tr("the hour with a leading zero (`00` to `23` or `01` to `12` if AM/PM display"),
std::bind(formattedDateTime, _1, "hh"));
// %H%.
addMetaWord(MetaWordType::FunctionBased,
"H",
tr("the hour without a leading zero (`0` to `23` even with AM/PM display"),
std::bind(formattedDateTime, _1, "H"));
// %HH%.
addMetaWord(MetaWordType::FunctionBased,
"HH",
tr("the hour with a leading zero (`00` to `23` even with AM/PM display"),
std::bind(formattedDateTime, _1, "HH"));
// %m%.
addMetaWord(MetaWordType::FunctionBased,
"m",
tr("the minute without a leading zero (`0` to `59`)"),
std::bind(formattedDateTime, _1, "m"));
// %mm%.
addMetaWord(MetaWordType::FunctionBased,
"mm",
tr("the minute with a leading zero (`00` to `59`)"),
std::bind(formattedDateTime, _1, "mm"));
// %s%.
addMetaWord(MetaWordType::FunctionBased,
"s",
tr("the second without a leading zero (`0` to `59`)"),
std::bind(formattedDateTime, _1, "s"));
// %ss%.
addMetaWord(MetaWordType::FunctionBased,
"ss",
tr("the second with a leading zero (`00` to `59`)"),
std::bind(formattedDateTime, _1, "ss"));
// %z%.
addMetaWord(MetaWordType::FunctionBased,
"z",
tr("the milliseconds without leading zeroes (`0` to `999`)"),
std::bind(formattedDateTime, _1, "z"));
// %zzz%.
addMetaWord(MetaWordType::FunctionBased,
"zzz",
tr("the milliseconds with leading zeroes (`000` to `999`)"),
std::bind(formattedDateTime, _1, "zzz"));
// %AP%.
addMetaWord(MetaWordType::FunctionBased,
"AP",
tr("use AM/PM display (`AM` or `PM`)"),
std::bind(formattedDateTime, _1, "AP"));
// %A%.
addMetaWord(MetaWordType::FunctionBased,
"A",
tr("use AM/PM display (`AM` or `PM`)"),
std::bind(formattedDateTime, _1, "A"));
// %ap%.
addMetaWord(MetaWordType::FunctionBased,
"ap",
tr("use am/pm display (`am` or `pm`)"),
std::bind(formattedDateTime, _1, "ap"));
// %a%.
addMetaWord(MetaWordType::FunctionBased,
"a",
tr("use am/pm display (`am` or `pm`)"),
std::bind(formattedDateTime, _1, "a"));
// %t%.
addMetaWord(MetaWordType::FunctionBased,
"t",
tr("the timezone (e.g. `CEST`)"),
std::bind(formattedDateTime, _1, "t"));
// %random%.
addMetaWord(MetaWordType::FunctionBased,
"random",
tr("a random number"),
[](const VMetaWord *) {
return QString::number(qrand());
});
// %random_d%.
addMetaWord(MetaWordType::Dynamic,
"random_d",
tr("dynamic version of `random`"),
[](const VMetaWord *) {
return QString::number(qrand());
});
// %date%.
addMetaWord(MetaWordType::Compound,
"date",
QString("%1yyyy%1-%1MM%1-%1dd%1").arg(c_delimiter));
// %da%.
addMetaWord(MetaWordType::Compound,
"da",
QString("%1yyyy%1%1MM%1%1dd%1").arg(c_delimiter));
// %time%.
addMetaWord(MetaWordType::Compound,
"time",
QString("%1hh%1:%1mm%1:%1ss%1").arg(c_delimiter));
// %datetime%.
addMetaWord(MetaWordType::Compound,
"datetime",
QString("%1date%1 %1time%1").arg(c_delimiter));
// Custom meta words.
initCustomMetaWords();
// %help% to print all metaword info.
addMetaWord(MetaWordType::FunctionBased,
"help",
tr("information about all defined magic words"),
allMetaWordsInfo);
}
void VMetaWordManager::initCustomMetaWords()
{
QVector<VMagicWord> words = g_config->getCustomMagicWords();
for (auto const & item : words) {
addMetaWord(MetaWordType::Compound,
item.m_name,
item.m_definition);
}
}
QString VMetaWordManager::evaluate(const QString &p_text) const
{
if (p_text.isEmpty()) {
return p_text;
}
// Update datetime for later parse.
const_cast<VMetaWordManager *>(this)->m_dateTime = QDateTime::currentDateTime();
// Treat the text as a Compound meta word.
const QString tmpWord("vnote_tmp_metaword");
Q_ASSERT(!contains(tmpWord));
VMetaWord metaWord(this,
MetaWordType::Compound,
tmpWord,
p_text,
nullptr,
true);
if (metaWord.isValid()) {
return metaWord.evaluate();
} else {
return p_text;
}
}
bool VMetaWordManager::contains(const QString &p_word) const
{
return m_metaWords.contains(p_word);
}
const VMetaWord *VMetaWordManager::findMetaWord(const QString &p_word) const
{
auto it = m_metaWords.find(p_word);
if (it != m_metaWords.end()) {
return &it.value();
}
return NULL;
}
void VMetaWordManager::addMetaWord(MetaWordType p_type,
const QString &p_word,
const QString &p_definition,
MetaWordFunc p_function)
{
if (p_word.isEmpty() || contains(p_word)) {
return;
}
VMetaWord metaWord(this,
p_type,
p_word,
p_definition,
p_function);
if (metaWord.isValid()) {
m_metaWords.insert(p_word, metaWord);
qDebug() << QString("MetaWord %1%2%1[%3] added")
.arg(c_delimiter).arg(p_word).arg(p_definition);
}
}
VMetaWord::VMetaWord(const VMetaWordManager *p_manager,
MetaWordType p_type,
const QString &p_word,
const QString &p_definition,
MetaWordFunc p_function,
bool p_allowAllSpaces)
: m_manager(p_manager),
m_type(p_type),
m_word(p_word),
m_definition(p_definition),
m_valid(false)
{
m_function = p_function;
if (checkType(MetaWordType::Simple)
|| checkType(MetaWordType::Compound)) {
Q_ASSERT(!m_function);
} else {
Q_ASSERT(m_function);
}
checkAndParseDefinition(p_allowAllSpaces);
}
bool VMetaWord::checkType(MetaWordType p_type)
{
return m_type == p_type;
}
void VMetaWord::checkAndParseDefinition(bool p_allowAllSpaces)
{
if (m_word.contains(VMetaWordManager::c_delimiter)) {
m_valid = false;
return;
}
m_valid = true;
m_tokens.clear();
// We do not accept \n and \t in the definition.
QRegExp defReg("[\\S ]*");
if (!defReg.exactMatch(m_definition) && !p_allowAllSpaces) {
m_valid = false;
return;
}
if (checkType(MetaWordType::FunctionBased)
|| checkType(MetaWordType::Dynamic)) {
if (!m_function) {
m_valid = false;
}
} else if (checkType(MetaWordType::Compound)) {
m_tokens = parseToTokens(m_definition);
if (m_tokens.isEmpty()) {
m_valid = false;
return;
}
for (auto const & to : m_tokens) {
if (to.isMetaWord()) {
if (!m_manager->contains(to.m_value)) {
// Dependency not defined.
m_valid = false;
break;
}
}
}
}
}
bool VMetaWord::isValid() const
{
return m_valid;
}
QString VMetaWord::evaluate() const
{
Q_ASSERT(m_valid);
qDebug() << "evaluate meta word" << m_word;
switch (m_type) {
case MetaWordType::Simple:
return m_definition;
case MetaWordType::FunctionBased:
case MetaWordType::Dynamic:
return m_function(this);
case MetaWordType::Compound:
{
QHash<QString, QString> cache;
return evaluateTokens(m_tokens, cache);
}
default:
return "";
}
}
MetaWordType VMetaWord::getType() const
{
return m_type;
}
const QString &VMetaWord::getWord() const
{
return m_word;
}
const QString &VMetaWord::getDefinition() const
{
return m_definition;
}
const VMetaWordManager *VMetaWord::getManager() const
{
return m_manager;
}
QString VMetaWord::toString() const
{
QChar typeChar('U');
switch (m_type) {
case MetaWordType::Simple:
typeChar = 'S';
break;
case MetaWordType::FunctionBased:
typeChar = 'F';
break;
case MetaWordType::Dynamic:
typeChar = 'D';
break;
case MetaWordType::Compound:
typeChar = 'C';
break;
default:
break;
}
return QString("%1%2%1[%3]: %4").arg(VMetaWordManager::c_delimiter)
.arg(m_word)
.arg(typeChar)
.arg(m_definition);
}
QVector<VMetaWord::Token> VMetaWord::parseToTokens(const QString &p_text)
{
QVector<Token> tokens;
TokenType type = TokenType::Raw;
QString value;
value.reserve(p_text.size());
for (int idx = 0; idx < p_text.size(); ++idx) {
const QChar &ch = p_text[idx];
if (ch == VMetaWordManager::c_delimiter) {
// Check if it is single or double.
int next = idx + 1;
if (next == p_text.size()
|| p_text[next] != VMetaWordManager::c_delimiter) {
// Single delimiter.
if (type == TokenType::Raw) {
// End of a raw token, begin of a MetaWord token.
if (!value.isEmpty()) {
tokens.push_back(Token(type, value));
}
type = TokenType::MetaWord;
} else {
// End of a MetaWord token, begin of a Raw token.
Q_ASSERT(!value.isEmpty());
tokens.push_back(Token(type, value));
type = TokenType::Raw;
}
value.clear();
} else {
// Double delimiter.
// If now is parsing a MetaWord token, treat the first delimiter
// as the end of a token.
// Otherwise, store one single delimiter in value and skip next char.
if (type == TokenType::MetaWord) {
Q_ASSERT(!value.isEmpty());
tokens.push_back(Token(type, value));
type = TokenType::Raw;
value.clear();
} else {
value.push_back(ch);
++idx;
}
}
} else {
// Push ch in value.
value.push_back(ch);
}
}
if (!value.isEmpty()) {
if (type == TokenType::Raw) {
tokens.push_back(Token(type, value));
} else {
// An imcomplete metaword token.
// Treat it as raw.
tokens.push_back(Token(TokenType::Raw, "%" + value));
}
value.clear();
}
return tokens;
}
QString VMetaWord::evaluateTokens(const QVector<VMetaWord::Token> &p_tokens,
QHash<QString, QString> &p_cache) const
{
QString val;
for (auto const & to : p_tokens) {
switch (to.m_type) {
case TokenType::Raw:
val += to.m_value;
break;
case TokenType::MetaWord:
{
const VMetaWord *metaWord = m_manager->findMetaWord(to.m_value);
if (!metaWord) {
// Invalid meta word. Treat it as literal value.
val += VMetaWordManager::c_delimiter + to.m_value + VMetaWordManager::c_delimiter;
break;
}
QString wordVal;
switch (metaWord->getType()) {
case MetaWordType::FunctionBased:
{
auto it = p_cache.find(metaWord->getWord());
if (it != p_cache.end()) {
// Find it in the cache.
wordVal = it.value();
} else {
// First evaluate this meta word.
wordVal = metaWord->evaluate();
p_cache.insert(metaWord->getWord(), wordVal);
}
break;
}
case MetaWordType::Compound:
wordVal = evaluateTokens(metaWord->m_tokens, p_cache);
break;
default:
wordVal = metaWord->evaluate();
break;
}
val += wordVal;
break;
}
default:
Q_ASSERT(false);
break;
}
}
return val;
}

View File

@ -0,0 +1,205 @@
#ifndef VMETAWORDMANAGER_H
#define VMETAWORDMANAGER_H
#include <functional>
#include <QObject>
#include <QString>
#include <QVector>
#include <QHash>
#include <QDateTime>
enum class MetaWordType
{
// Definition is plain text.
Simple = 0,
// Definition is a function call to get the value.
FunctionBased,
// Like FunctionBased, but should re-evaluate the value for each occurence.
Dynamic,
// Consists of other meta words.
Compound,
Invalid
};
// We call meta word "magic word" in user interaction.
struct VMagicWord
{
QString m_name;
QString m_definition;
};
class VMetaWordManager;
class VMetaWord;
typedef std::function<QString(const VMetaWord *)> MetaWordFunc;
// A Meta Word is surrounded by %.
// - Use %% for an escaped %;
// - Built-in or user-defined;
// - A meta word could contain other meta words as definition.
class VMetaWord
{
public:
VMetaWord(const VMetaWordManager *p_manager,
MetaWordType p_type,
const QString &p_word,
const QString &p_definition,
MetaWordFunc p_function = nullptr,
bool p_allowAllSpaces = false);
bool isValid() const;
QString evaluate() const;
MetaWordType getType() const;
const QString &getWord() const;
const QString &getDefinition() const;
const VMetaWordManager *getManager() const;
QString toString() const;
enum class TokenType
{
Raw = 0,
MetaWord
};
struct Token
{
Token()
: m_type(TokenType::Raw)
{
}
Token(VMetaWord::TokenType p_type, const QString &p_value)
: m_type(p_type),
m_value(p_value)
{
}
QString toString() const
{
return QString("token %1[%2]").arg(m_type == TokenType::Raw
? "Raw" : "MetaWord")
.arg(m_value);
}
bool isRaw() const
{
return m_type == TokenType::Raw;
}
bool isMetaWord() const
{
return m_type == TokenType::MetaWord;
}
TokenType m_type;
// For Raw type, m_value is the raw string of this token;
// For MetaWord type, m_value is the word of the meta word pointed to by
// this token.
QString m_value;
};
private:
// Check if m_type is @p_type.
bool checkType(MetaWordType p_type);
// Parse children word from definition.
// Children word MUST be defined in m_manager already.
// @p_allowAllSpaces: if true then we allow all spaces including \n and \t
// to appear in the definition.
void checkAndParseDefinition(bool p_allowAllSpaces);
// Parse @p_text to a list of tokens.
static QVector<VMetaWord::Token> parseToTokens(const QString &p_text);
// Used for Compound meta word with cache @p_cache.
// @p_cache: value cache for FunctionBased Token.
// For a meta word occuring more than once, we should evaluate it once for
// FunctionBased meta word.
QString evaluateTokens(const QVector<VMetaWord::Token> &p_tokens,
QHash<QString, QString> &p_cache) const;
const VMetaWordManager *m_manager;
MetaWordType m_type;
// Word could contains spaces but no %.
QString m_word;
// For Simple/Compound meta word, this contains the definition;
// For FunctionBased/Dynamic meta word, this makes no sense and is used
// for description.
QString m_definition;
// For FunctionBased and Dynamic meta word.
MetaWordFunc m_function;
bool m_valid;
// Tokens used for Compound meta word.
QVector<Token> m_tokens;
};
// Manager of meta word.
class VMetaWordManager : public QObject
{
Q_OBJECT
public:
explicit VMetaWordManager(QObject *p_parent = nullptr);
void init();
// Expand meta words in @p_text and return the expanded text.
QString evaluate(const QString &p_text) const;
const VMetaWord *findMetaWord(const QString &p_word) const;
bool contains(const QString &p_word) const;
const QDateTime &getDateTime() const;
const QHash<QString, VMetaWord> &getAllMetaWords() const;
// % by default.
static const QChar c_delimiter;
private:
void addMetaWord(MetaWordType p_type,
const QString &p_word,
const QString &p_definition,
MetaWordFunc p_function = nullptr);
void initCustomMetaWords();
// Map using word as key.
QHash<QString, VMetaWord> m_metaWords;
// Used for data/time related evaluate.
// Will be updated before each evaluation.
QDateTime m_dateTime;
};
inline const QDateTime &VMetaWordManager::getDateTime() const
{
return m_dateTime;
}
inline const QHash<QString, VMetaWord> &VMetaWordManager::getAllMetaWords() const
{
return m_metaWords;
}
#endif // VMETAWORDMANAGER_H

View File

@ -33,6 +33,8 @@ QVector<QPair<QString, QString>> VUtils::s_availableLanguages;
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)");
const QString VUtils::c_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();
}

View File

@ -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.
// ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" )
// 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;

View File

@ -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 &reg = m_registers[m_regName];
Register &reg = 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 &reg = m_registers[m_regName];
Register &reg = 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 "";

View File

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

View File

@ -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();

View File

@ -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();
}

View File

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

View File

@ -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 &notebook = *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 &section, 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 &section, const QSt
void VConfigManager::setConfigToSettings(const QString &section, 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;
}

View File

@ -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 &section, const QString &key) const;
// Set a config to user settings.
void setConfigToSettings(const QString &section, 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

View File

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

View File

@ -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();

View File

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

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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();
}
}

View File

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

View File

@ -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);
}
}

View File

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

View File

@ -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()
{
}

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

@ -0,0 +1,74 @@
#include "vfilesessioninfo.h"
#include <QSettings>
#include "vedittabinfo.h"
#include "vtableofcontent.h"
#include "vedittab.h"
VFileSessionInfo::VFileSessionInfo()
: m_mode(OpenFileMode::Read),
m_headerIndex(-1),
m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-1)
{
}
VFileSessionInfo::VFileSessionInfo(const QString &p_file,
OpenFileMode p_mode)
: m_file(p_file),
m_mode(p_mode),
m_headerIndex(-1),
m_cursorBlockNumber(-1),
m_cursorPositionInBlock(-1)
{
}
// Fetch VFileSessionInfo from @p_tabInfo.
VFileSessionInfo VFileSessionInfo::fromEditTabInfo(const VEditTabInfo *p_tabInfo)
{
Q_ASSERT(p_tabInfo);
VEditTab *tab = p_tabInfo->m_editTab;
VFileSessionInfo info(tab->getFile()->fetchPath(),
tab->isEditMode() ? OpenFileMode::Edit : OpenFileMode::Read);
info.m_headerIndex = p_tabInfo->m_headerIndex;
info.m_cursorBlockNumber = p_tabInfo->m_cursorBlockNumber;
info.m_cursorPositionInBlock = p_tabInfo->m_cursorPositionInBlock;
return info;
}
void VFileSessionInfo::toEditTabInfo(VEditTabInfo *p_tabInfo) const
{
p_tabInfo->m_headerIndex = m_headerIndex;
p_tabInfo->m_cursorBlockNumber = m_cursorBlockNumber;
p_tabInfo->m_cursorPositionInBlock = m_cursorPositionInBlock;
}
VFileSessionInfo VFileSessionInfo::fromSettings(const QSettings *p_settings)
{
VFileSessionInfo info;
info.m_file = p_settings->value(FileSessionConfig::c_file).toString();
int tmpMode = p_settings->value(FileSessionConfig::c_mode).toInt();
if (tmpMode >= (int)OpenFileMode::Read && tmpMode < (int)OpenFileMode::Invalid) {
info.m_mode = (OpenFileMode)tmpMode;
} else {
info.m_mode = OpenFileMode::Read;
}
info.m_headerIndex = p_settings->value(FileSessionConfig::c_headerIndex).toInt();
info.m_cursorBlockNumber = p_settings->value(FileSessionConfig::c_cursorBlockNumber).toInt();
info.m_cursorPositionInBlock = p_settings->value(FileSessionConfig::c_cursorPositionInBlock).toInt();
return info;
}
void VFileSessionInfo::toSettings(QSettings *p_settings) const
{
p_settings->setValue(FileSessionConfig::c_file, m_file);
p_settings->setValue(FileSessionConfig::c_mode, (int)m_mode);
p_settings->setValue(FileSessionConfig::c_headerIndex, m_headerIndex);
p_settings->setValue(FileSessionConfig::c_cursorBlockNumber, m_cursorBlockNumber);
p_settings->setValue(FileSessionConfig::c_cursorPositionInBlock, m_cursorPositionInBlock);
}

57
src/vfilesessioninfo.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef VFILESESSIONINFO_H
#define VFILESESSIONINFO_H
#include "vconstants.h"
struct VEditTabInfo;
class QSettings;
namespace FileSessionConfig
{
static const QString c_file = "file";
static const QString c_mode = "mode";
// Index in outline of the anchor.
static const QString c_headerIndex = "header_index";
static const QString c_cursorBlockNumber = "cursor_block_number";
static const QString c_cursorPositionInBlock = "cursor_position_in_block";
}
// Information about an opened file (session).
class VFileSessionInfo
{
public:
VFileSessionInfo();
VFileSessionInfo(const QString &p_file,
OpenFileMode p_mode);
// Fetch VFileSessionInfo from @p_tabInfo.
static VFileSessionInfo fromEditTabInfo(const VEditTabInfo *p_tabInfo);
// Fill corresponding fields of @p_tabInfo.
void toEditTabInfo(VEditTabInfo *p_tabInfo) const;
// Fetch VFileSessionInfo from @p_settings.
static VFileSessionInfo fromSettings(const QSettings *p_settings);
void toSettings(QSettings *p_settings) const;
// Absolute path of the file.
QString m_file;
// Mode of this file in this session.
OpenFileMode m_mode;
// Index in outline of the header.
int m_headerIndex;
// Block number of cursor block.
int m_cursorBlockNumber;
// Position in block of cursor.
int m_cursorPositionInBlock;
};
#endif // VFILESESSIONINFO_H

View File

@ -32,7 +32,7 @@ void VHtmlTab::setupUI()
{
m_editor = new VEdit(m_file, this);
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;
}

View File

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

@ -0,0 +1,48 @@
#include "vlineedit.h"
#include <QDebug>
#include <QToolTip>
#include "utils/vmetawordmanager.h"
extern VMetaWordManager *g_mwMgr;
VLineEdit::VLineEdit(QWidget *p_parent)
: QLineEdit(p_parent)
{
init();
}
VLineEdit::VLineEdit(const QString &p_contents, QWidget *p_parent)
: QLineEdit(p_contents, p_parent)
{
init();
}
void VLineEdit::handleTextChanged(const QString &p_text)
{
m_evaluatedText = g_mwMgr->evaluate(p_text);
qDebug() << "evaluate text:" << m_evaluatedText;
if (m_evaluatedText == p_text) {
return;
}
// Display tooltip at bottom-left.
QPoint pos = mapToGlobal(QPoint(0, height()));
QToolTip::showText(pos, m_evaluatedText, this);
}
void VLineEdit::init()
{
m_evaluatedText = g_mwMgr->evaluate(text());
connect(this, &QLineEdit::textChanged,
this, &VLineEdit::handleTextChanged);
}
const QString VLineEdit::getEvaluatedText() const
{
return m_evaluatedText;
}

29
src/vlineedit.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef VLINEEDIT_H
#define VLINEEDIT_H
#include <QLineEdit>
// QLineEdit with meta word support.
class VLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit VLineEdit(QWidget *p_parent = nullptr);
VLineEdit(const QString &p_contents, QWidget *p_parent = Q_NULLPTR);
// Return the evaluated text.
const QString getEvaluatedText() const;
private slots:
void handleTextChanged(const QString &p_text);
private:
void init();
// We should keep the evaluated text identical with what's displayed.
QString m_evaluatedText;
};
#endif // VLINEEDIT_H

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,15 @@ class QShortcut;
class VButtonWithWidget;
class 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

View File

@ -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();
}
}

View File

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

View File

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

View File

@ -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();

View File

@ -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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

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

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