support snippets

Shortcuts are not supported yet.
This commit is contained in:
Le Tan 2017-11-07 19:52:54 +08:00
parent 7131b483f3
commit 6ac33d2bd0
44 changed files with 2041 additions and 489 deletions

View File

@ -0,0 +1,296 @@
#include "veditsnippetdialog.h"
#include <QtWidgets>
#include "utils/vutils.h"
#include "vlineedit.h"
#include "vconfigmanager.h"
#include "utils/vmetawordmanager.h"
extern VMetaWordManager *g_mwMgr;
extern VConfigManager *g_config;
VEditSnippetDialog::VEditSnippetDialog(const QString &p_title,
const QString &p_info,
const QVector<VSnippet> &p_snippets,
const VSnippet &p_snippet,
QWidget *p_parent)
: QDialog(p_parent),
m_snippets(p_snippets),
m_snippet(p_snippet)
{
setupUI(p_title, p_info);
handleInputChanged();
}
void VEditSnippetDialog::setupUI(const QString &p_title, const QString &p_info)
{
QLabel *infoLabel = NULL;
if (!p_info.isEmpty()) {
infoLabel = new QLabel(p_info);
infoLabel->setWordWrap(true);
}
// Name.
m_nameEdit = new VLineEdit(m_snippet.getName());
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
m_nameEdit);
m_nameEdit->setValidator(validator);
// Type.
m_typeCB = new QComboBox();
for (int i = 0; i < VSnippet::Type::Invalid; ++i) {
m_typeCB->addItem(VSnippet::typeStr(static_cast<VSnippet::Type>(i)), i);
}
int typeIdx = m_typeCB->findData((int)m_snippet.getType());
Q_ASSERT(typeIdx > -1);
m_typeCB->setCurrentIndex(typeIdx);
// Shortcut.
m_shortcutCB = new QComboBox();
m_shortcutCB->addItem(tr("None"), QChar());
auto shortcuts = getAvailableShortcuts();
for (auto it : shortcuts) {
m_shortcutCB->addItem(it, it);
}
QChar sh = m_snippet.getShortcut();
if (sh.isNull()) {
m_shortcutCB->setCurrentIndex(0);
} else {
int shortcutIdx = m_shortcutCB->findData(sh);
m_shortcutCB->setCurrentIndex(shortcutIdx < 0 ? 0 : shortcutIdx);
}
// Cursor mark.
m_cursorMarkEdit = new QLineEdit(m_snippet.getCursorMark());
m_cursorMarkEdit->setToolTip(tr("String in the content to mark the cursor position"));
// Selection mark.
m_selectionMarkEdit = new QLineEdit(m_snippet.getSelectionMark());
m_selectionMarkEdit->setToolTip(tr("String in the content to be replaced with selected text"));
// Content.
m_contentEdit = new QTextEdit();
setContentEditByType();
QFormLayout *topLayout = new QFormLayout();
topLayout->addRow(tr("Snippet &name:"), m_nameEdit);
topLayout->addRow(tr("Snippet &type:"), m_typeCB);
topLayout->addRow(tr("Shortc&ut:"), m_shortcutCB);
topLayout->addRow(tr("Cursor &mark:"), m_cursorMarkEdit);
topLayout->addRow(tr("&Selection mark:"), m_selectionMarkEdit);
topLayout->addRow(tr("&Content:"), m_contentEdit);
m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true);
m_warnLabel->hide();
// 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_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) {
mainLayout->addWidget(infoLabel);
}
mainLayout->addLayout(topLayout);
mainLayout->addWidget(m_warnLabel);
mainLayout->addWidget(m_btnBox);
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(mainLayout);
setWindowTitle(p_title);
connect(m_nameEdit, &QLineEdit::textChanged,
this, &VEditSnippetDialog::handleInputChanged);
connect(m_typeCB, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &VEditSnippetDialog::handleInputChanged);
connect(m_cursorMarkEdit, &QLineEdit::textChanged,
this, &VEditSnippetDialog::handleInputChanged);
connect(m_selectionMarkEdit, &QLineEdit::textChanged,
this, &VEditSnippetDialog::handleInputChanged);
connect(m_contentEdit, &QTextEdit::textChanged,
this, &VEditSnippetDialog::handleInputChanged);
}
void VEditSnippetDialog::handleInputChanged()
{
bool showWarnLabel = false;
QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk && name != m_snippet.getName()) {
// Check if the name conflicts with existing snippet name.
// Case-insensitive.
QString lowerName = name.toLower();
bool conflicted = false;
for (auto const & item : m_snippets) {
if (item.getName() == name) {
conflicted = true;
break;
}
}
QString warnText;
if (conflicted) {
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;
m_warnLabel->setText(warnText);
}
}
QString cursorMark = m_cursorMarkEdit->text();
bool cursorMarkOk = true;
if (nameOk && !cursorMark.isEmpty()) {
// Check if the mark appears more than once in the content.
QString selectionMark = m_selectionMarkEdit->text();
QString content = getContentEditByType();
content = g_mwMgr->evaluate(content);
QString warnText;
if (content.count(cursorMark) > 1) {
cursorMarkOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Cursor mark <span style=\"%2\">%3</span> occurs more than once "
"in the content (after magic word evaluation). "
"Please choose another mark.")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(cursorMark);
} else if ((cursorMark == selectionMark
|| cursorMark.contains(selectionMark)
|| selectionMark.contains(cursorMark))
&& !selectionMark.isEmpty()) {
cursorMarkOk = false;
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Cursor mark <span style=\"%2\">%3</span> conflicts with selection mark. "
"Please choose another mark.")
.arg(g_config->c_warningTextStyle)
.arg(g_config->c_dataTextStyle)
.arg(cursorMark);
}
if (!cursorMarkOk) {
showWarnLabel = true;
m_warnLabel->setText(warnText);
}
}
m_warnLabel->setVisible(showWarnLabel);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
okBtn->setEnabled(nameOk && cursorMarkOk);
}
void VEditSnippetDialog::setContentEditByType()
{
switch (m_snippet.getType()) {
case VSnippet::Type::PlainText:
m_contentEdit->setPlainText(m_snippet.getContent());
break;
case VSnippet::Type::Html:
m_contentEdit->setHtml(m_snippet.getContent());
break;
default:
m_contentEdit->setPlainText(m_snippet.getContent());
break;
}
}
QString VEditSnippetDialog::getContentEditByType() const
{
if (m_typeCB->currentIndex() == -1) {
return QString();
}
switch (static_cast<VSnippet::Type>(m_typeCB->currentData().toInt())) {
case VSnippet::Type::PlainText:
return m_contentEdit->toPlainText();
case VSnippet::Type::Html:
return m_contentEdit->toHtml();
default:
return m_contentEdit->toPlainText();
}
}
QString VEditSnippetDialog::getNameInput() const
{
return m_nameEdit->getEvaluatedText();
}
VSnippet::Type VEditSnippetDialog::getTypeInput() const
{
return static_cast<VSnippet::Type>(m_typeCB->currentData().toInt());
}
QString VEditSnippetDialog::getCursorMarkInput() const
{
return m_cursorMarkEdit->text();
}
QString VEditSnippetDialog::getSelectionMarkInput() const
{
return m_selectionMarkEdit->text();
}
QString VEditSnippetDialog::getContentInput() const
{
return getContentEditByType();
}
QChar VEditSnippetDialog::getShortcutInput() const
{
return m_shortcutCB->currentData().toChar();
}
QVector<QChar> VEditSnippetDialog::getAvailableShortcuts() const
{
QVector<QChar> ret = VSnippet::getAllShortcuts();
QChar curCh = m_snippet.getShortcut();
// Remove those have already been assigned to snippets.
for (auto const & snip : m_snippets) {
QChar ch = snip.getShortcut();
if (ch.isNull()) {
continue;
}
if (ch != curCh) {
ret.removeOne(ch);
}
}
return ret;
}

View File

@ -0,0 +1,66 @@
#ifndef VEDITSNIPPETDIALOG_H
#define VEDITSNIPPETDIALOG_H
#include <QDialog>
#include <QVector>
#include "vsnippet.h"
class VLineEdit;
class QLineEdit;
class QLabel;
class QDialogButtonBox;
class QComboBox;
class QTextEdit;
class VEditSnippetDialog : public QDialog
{
Q_OBJECT
public:
VEditSnippetDialog(const QString &p_title,
const QString &p_info,
const QVector<VSnippet> &p_snippets,
const VSnippet &p_snippet,
QWidget *p_parent = nullptr);
QString getNameInput() const;
VSnippet::Type getTypeInput() const;
QString getCursorMarkInput() const;
QString getSelectionMarkInput() const;
QString getContentInput() const;
QChar getShortcutInput() const;
private slots:
void handleInputChanged();
private:
void setupUI(const QString &p_title, const QString &p_info);
void setContentEditByType();
QString getContentEditByType() const;
QVector<QChar> getAvailableShortcuts() const;
VLineEdit *m_nameEdit;
QComboBox *m_typeCB;
QComboBox *m_shortcutCB;
QLineEdit *m_cursorMarkEdit;
QLineEdit *m_selectionMarkEdit;
QTextEdit *m_contentEdit;
QLabel *m_warnLabel;
QDialogButtonBox *m_btnBox;
const QVector<VSnippet> &m_snippets;
const VSnippet &m_snippet;
};
#endif // VEDITSNIPPETDIALOG_H

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<polygon points="448,224 288,224 288,64 224,64 224,224 64,224 64,288 224,288 224,448 288,448 288,288 448,288 "/>
</svg>

After

Width:  |  Height:  |  Size: 597 B

View File

@ -0,0 +1,16 @@
<?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" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<polygon points="198.011,159.22 163.968,193.337 420.064,450 454,415.883 "/>
<rect x="182" y="62" width="32" height="64"/>
<rect x="182" y="266" width="32" height="64"/>
<rect x="274" y="178" width="64" height="32"/>
<polygon points="303.941,112.143 281.314,89.465 236.06,134.82 258.687,157.498 "/>
<polygon points="92.06,112.143 137.314,157.498 159.941,134.82 114.687,89.465 "/>
<polygon points="92.06,279.141 114.687,301.816 159.941,256.462 137.314,233.784 "/>
<rect x="58" y="178" width="64" height="32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1006 B

View File

@ -0,0 +1,10 @@
<?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">
<path d="M341,128V99c0-19.1-14.5-35-34.5-35H205.4C185.5,64,171,79.9,171,99v29H80v32h9.2c0,0,5.4,0.6,8.2,3.4c2.8,2.8,3.9,9,3.9,9
l19,241.7c1.5,29.4,1.5,33.9,36,33.9h199.4c34.5,0,34.5-4.4,36-33.8l19-241.6c0,0,1.1-6.3,3.9-9.1c2.8-2.8,8.2-3.4,8.2-3.4h9.2v-32
h-91V128z M192,99c0-9.6,7.8-15,17.7-15h91.7c9.9,0,18.6,5.5,18.6,15v29H192V99z M183.5,384l-10.3-192h20.3L204,384H183.5z
M267.1,384h-22V192h22V384z M328.7,384h-20.4l10.5-192h20.3L328.7,384z"/>
</svg>

After

Width:  |  Height:  |  Size: 934 B

View File

@ -0,0 +1,10 @@
<?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" enable-background="new 0 0 512 512" xml:space="preserve">
<path d="M437.334,144H256.006l-42.668-48H74.666C51.197,96,32,115.198,32,138.667v234.666C32,396.802,51.197,416,74.666,416h362.668
C460.803,416,480,396.802,480,373.333V186.667C480,163.198,460.803,144,437.334,144z M448,373.333
c0,5.782-4.885,10.667-10.666,10.667H74.666C68.884,384,64,379.115,64,373.333V176h373.334c5.781,0,10.666,4.885,10.666,10.667
V373.333z"/>
</svg>

After

Width:  |  Height:  |  Size: 840 B

View File

@ -0,0 +1,10 @@
<?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>
<polygon points="288,448 288,192 192,192 192,208 224,208 224,448 192,448 192,464 320,464 320,448 "/>
<path d="M255.8,144.5c26.6,0,48.2-21.6,48.2-48.2s-21.6-48.2-48.2-48.2c-26.6,0-48.2,21.6-48.2,48.2S229.2,144.5,255.8,144.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 723 B

View File

@ -0,0 +1,16 @@
<?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" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<polygon points="198.011,159.22 163.968,193.337 420.064,450 454,415.883 "/>
<rect x="182" y="62" width="32" height="64"/>
<rect x="182" y="266" width="32" height="64"/>
<rect x="274" y="178" width="64" height="32"/>
<polygon points="303.941,112.143 281.314,89.465 236.06,134.82 258.687,157.498 "/>
<polygon points="92.06,112.143 137.314,157.498 159.941,134.82 114.687,89.465 "/>
<polygon points="92.06,279.141 114.687,301.816 159.941,256.462 137.314,233.784 "/>
<rect x="58" y="178" width="64" height="32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1022 B

View File

@ -57,6 +57,7 @@ SOURCES += main.cpp\
dialog/vselectdialog.cpp \ dialog/vselectdialog.cpp \
vcaptain.cpp \ vcaptain.cpp \
vopenedlistmenu.cpp \ vopenedlistmenu.cpp \
vnavigationmode.cpp \
vorphanfile.cpp \ vorphanfile.cpp \
vcodeblockhighlighthelper.cpp \ vcodeblockhighlighthelper.cpp \
vwebview.cpp \ vwebview.cpp \
@ -91,7 +92,11 @@ SOURCES += main.cpp\
vpreviewmanager.cpp \ vpreviewmanager.cpp \
vimageresourcemanager2.cpp \ vimageresourcemanager2.cpp \
vtextdocumentlayout.cpp \ vtextdocumentlayout.cpp \
vtextedit.cpp vtextedit.cpp \
vsnippetlist.cpp \
vsnippet.cpp \
dialog/veditsnippetdialog.cpp \
utils/vimnavigationforwidget.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -170,7 +175,11 @@ HEADERS += vmainwindow.h \
vpreviewmanager.h \ vpreviewmanager.h \
vimageresourcemanager2.h \ vimageresourcemanager2.h \
vtextdocumentlayout.h \ vtextdocumentlayout.h \
vtextedit.h vtextedit.h \
vsnippetlist.h \
vsnippet.h \
dialog/veditsnippetdialog.h \
utils/vimnavigationforwidget.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

View File

@ -0,0 +1,69 @@
#include "vimnavigationforwidget.h"
#include <QWidget>
#include <QCoreApplication>
#include <QKeyEvent>
#include <QDebug>
#include "vutils.h"
VimNavigationForWidget::VimNavigationForWidget()
{
}
bool VimNavigationForWidget::injectKeyPressEventForVim(QWidget *p_widget,
QKeyEvent *p_event,
QWidget *p_escWidget)
{
Q_ASSERT(p_widget);
bool ret = false;
int key = p_event->key();
int modifiers = p_event->modifiers();
if (!p_escWidget) {
p_escWidget = p_widget;
}
switch (key) {
case Qt::Key_BracketLeft:
{
if (VUtils::isControlModifierForVim(modifiers)) {
QKeyEvent *escEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape,
Qt::NoModifier);
QCoreApplication::postEvent(p_escWidget, escEvent);
ret = true;
}
break;
}
case Qt::Key_J:
{
if (VUtils::isControlModifierForVim(modifiers)) {
QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, downEvent);
ret = true;
}
break;
}
case Qt::Key_K:
{
if (VUtils::isControlModifierForVim(modifiers)) {
QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, upEvent);
ret = true;
}
break;
}
default:
break;
}
return ret;
}

View File

@ -0,0 +1,24 @@
#ifndef VIMNAVIGATIONFORWIDGET_H
#define VIMNAVIGATIONFORWIDGET_H
class QWidget;
class QKeyEvent;
// Provide simple Vim mode navigation for widgets.
class VimNavigationForWidget
{
public:
// Try to handle @p_event and inject proper event instead if it triggers
// Vim operation.
// Return true if @p_event is handled properly.
// @p_escWidget: the widget to accept the ESC event.
static bool injectKeyPressEventForVim(QWidget *p_widget,
QKeyEvent *p_event,
QWidget *p_escWidget = nullptr);
private:
VimNavigationForWidget();
};
#endif // VIMNAVIGATIONFORWIDGET_H

View File

@ -61,7 +61,7 @@ QString VUtils::readFileFromDisk(const QString &filePath)
{ {
QFile file(filePath); QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "fail to read file" << filePath; qWarning() << "fail to open file" << filePath << "to read";
return QString(); return QString();
} }
QString fileText(file.readAll()); QString fileText(file.readAll());
@ -84,6 +84,34 @@ bool VUtils::writeFileToDisk(const QString &filePath, const QString &text)
return true; return true;
} }
bool VUtils::writeJsonToDisk(const QString &p_filePath, const QJsonObject &p_json)
{
QFile file(p_filePath);
// We use Unix LF for config file.
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << "fail to open file" << p_filePath << "to write";
return false;
}
QJsonDocument doc(p_json);
if (-1 == file.write(doc.toJson())) {
return false;
}
return true;
}
QJsonObject VUtils::readJsonFromDisk(const QString &p_filePath)
{
QFile file(p_filePath);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "fail to open file" << p_filePath << "to read";
return QJsonObject();
}
return QJsonDocument::fromJson(file.readAll()).object();
}
QRgb VUtils::QRgbFromString(const QString &str) QRgb VUtils::QRgbFromString(const QString &str)
{ {
Q_ASSERT(str.length() == 6); Q_ASSERT(str.length() == 6);
@ -859,6 +887,12 @@ bool VUtils::deleteFile(const VNotebook *p_notebook,
} }
} }
bool VUtils::deleteFile(const QString &p_path)
{
QFile file(p_path);
return file.remove();
}
bool VUtils::deleteFile(const VOrphanFile *p_file, bool VUtils::deleteFile(const VOrphanFile *p_file,
const QString &p_path, const QString &p_path,
bool p_skipRecycleBin) bool p_skipRecycleBin)
@ -1004,3 +1038,12 @@ QString VUtils::validFilePathToOpen(const QString &p_file)
return QString(); return QString();
} }
bool VUtils::isControlModifierForVim(int p_modifiers)
{
#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
return p_modifiers == Qt::MetaModifier;
#else
return p_modifiers == Qt::ControlModifier;
#endif
}

View File

@ -72,9 +72,16 @@ class VUtils
{ {
public: public:
static QString readFileFromDisk(const QString &filePath); static QString readFileFromDisk(const QString &filePath);
static bool writeFileToDisk(const QString &filePath, const QString &text); static bool writeFileToDisk(const QString &filePath, const QString &text);
static bool writeJsonToDisk(const QString &p_filePath, const QJsonObject &p_json);
static QJsonObject readJsonFromDisk(const QString &p_filePath);
// Transform FFFFFF string to QRgb // Transform FFFFFF string to QRgb
static QRgb QRgbFromString(const QString &str); static QRgb QRgbFromString(const QString &str);
static QString generateImageFileName(const QString &path, const QString &title, static QString generateImageFileName(const QString &path, const QString &title,
const QString &format = "png"); const QString &format = "png");
@ -226,6 +233,9 @@ public:
const QString &p_path, const QString &p_path,
bool p_skipRecycleBin = false); bool p_skipRecycleBin = false);
// Delete file specified by @p_path.
static bool deleteFile(const QString &p_path);
static QString displayDateTime(const QDateTime &p_dateTime); static QString displayDateTime(const QDateTime &p_dateTime);
// Check if file @p_name exists in @p_dir. // Check if file @p_name exists in @p_dir.
@ -242,6 +252,9 @@ public:
// Return empty if it is not valid. // Return empty if it is not valid.
static QString validFilePathToOpen(const QString &p_file); static QString validFilePathToOpen(const QString &p_file);
// See if @p_modifiers is Control which is different on macOs and Windows.
static bool isControlModifierForVim(int p_modifiers);
// Regular expression for image link. // Regular expression for image link.
// ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" ) // ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" )
// Captured texts (need to be trimmed): // Captured texts (need to be trimmed):

View File

@ -24,16 +24,6 @@ const int VVim::SearchHistory::c_capacity = 50;
#define ADDKEY(x, y) case (x): {ch = (y); break;} #define ADDKEY(x, y) case (x): {ch = (y); break;}
// See if @p_modifiers is Control which is different on macOs and Windows.
static bool isControlModifier(int p_modifiers)
{
#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
return p_modifiers == Qt::MetaModifier;
#else
return p_modifiers == Qt::ControlModifier;
#endif
}
// Returns NULL QChar if invalid. // Returns NULL QChar if invalid.
static QChar keyToChar(int p_key, int p_modifiers) static QChar keyToChar(int p_key, int p_modifiers)
{ {
@ -41,7 +31,7 @@ static QChar keyToChar(int p_key, int p_modifiers)
return QChar('0' + (p_key - Qt::Key_0)); return QChar('0' + (p_key - Qt::Key_0));
} else if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) { } else if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) {
if (p_modifiers == Qt::ShiftModifier if (p_modifiers == Qt::ShiftModifier
|| isControlModifier(p_modifiers)) { || VUtils::isControlModifierForVim(p_modifiers)) {
return QChar('A' + (p_key - Qt::Key_A)); return QChar('A' + (p_key - Qt::Key_A));
} else { } else {
return QChar('a' + (p_key - Qt::Key_A)); return QChar('a' + (p_key - Qt::Key_A));
@ -99,7 +89,7 @@ static QString keyToString(int p_key, int p_modifiers)
return QString(); return QString();
} }
if (isControlModifier(p_modifiers)) { if (VUtils::isControlModifierForVim(p_modifiers)) {
return QString("^") + ch; return QString("^") + ch;
} else { } else {
return ch; return ch;
@ -473,7 +463,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
// Handle Insert mode key press. // Handle Insert mode key press.
if (VimMode::Insert == m_mode) { if (VimMode::Insert == m_mode) {
if (key == Qt::Key_Escape if (key == Qt::Key_Escape
|| (key == Qt::Key_BracketLeft && isControlModifier(modifiers))) { || (key == Qt::Key_BracketLeft && VUtils::isControlModifierForVim(modifiers))) {
// See if we need to cancel auto indent. // See if we need to cancel auto indent.
bool cancelAutoIndent = false; bool cancelAutoIndent = false;
if (p_autoIndentPos && *p_autoIndentPos > -1) { if (p_autoIndentPos && *p_autoIndentPos > -1) {
@ -510,14 +500,14 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
} }
goto clear_accept; goto clear_accept;
} else if (key == Qt::Key_R && isControlModifier(modifiers)) { } else if (key == Qt::Key_R && VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+R, insert the content of a register. // Ctrl+R, insert the content of a register.
m_pendingKeys.append(keyInfo); m_pendingKeys.append(keyInfo);
m_registerPending = true; m_registerPending = true;
goto accept; goto accept;
} }
if (key == Qt::Key_O && isControlModifier(modifiers)) { if (key == Qt::Key_O && VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+O, enter normal mode, execute one command, then return to insert mode. // Ctrl+O, enter normal mode, execute one command, then return to insert mode.
m_insertModeAfterCommand = true; m_insertModeAfterCommand = true;
clearSelection(); clearSelection();
@ -823,7 +813,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
m_editor->setTextCursorW(cursor); m_editor->setTextCursorW(cursor);
setMode(VimMode::Insert); setMode(VimMode::Insert);
} else if (isControlModifier(modifiers)) { } else if (VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+I, jump to next location. // Ctrl+I, jump to next location.
if (!m_tokens.isEmpty() if (!m_tokens.isEmpty()
|| !checkMode(VimMode::Normal)) { || !checkMode(VimMode::Normal)) {
@ -935,7 +925,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
} }
break; break;
} else if (isControlModifier(modifiers)) { } else if (VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+O, jump to previous location. // Ctrl+O, jump to previous location.
if (!m_tokens.isEmpty() if (!m_tokens.isEmpty()
|| !checkMode(VimMode::Normal)) { || !checkMode(VimMode::Normal)) {
@ -1037,7 +1027,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
// Should be kept together with Qt::Key_PageUp. // Should be kept together with Qt::Key_PageUp.
case Qt::Key_B: case Qt::Key_B:
{ {
if (isControlModifier(modifiers)) { if (VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+B, page up, fall through. // Ctrl+B, page up, fall through.
modifiers = Qt::NoModifier; modifiers = Qt::NoModifier;
} else if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) { } else if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
@ -1095,7 +1085,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
bool toLower = modifiers == Qt::NoModifier; bool toLower = modifiers == Qt::NoModifier;
if (isControlModifier(modifiers)) { if (VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+U, HalfPageUp. // Ctrl+U, HalfPageUp.
if (!m_keys.isEmpty()) { if (!m_keys.isEmpty()) {
// Not a valid sequence. // Not a valid sequence.
@ -1200,7 +1190,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
case Qt::Key_D: case Qt::Key_D:
{ {
if (isControlModifier(modifiers)) { if (VUtils::isControlModifierForVim(modifiers)) {
// Ctrl+D, HalfPageDown. // Ctrl+D, HalfPageDown.
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (!m_keys.isEmpty()) { if (!m_keys.isEmpty()) {
@ -1301,7 +1291,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
// Should be kept together with Qt::Key_Escape. // Should be kept together with Qt::Key_Escape.
case Qt::Key_BracketLeft: case Qt::Key_BracketLeft:
{ {
if (isControlModifier(modifiers)) { if (VUtils::isControlModifierForVim(modifiers)) {
// fallthrough. // fallthrough.
} else if (modifiers == Qt::NoModifier) { } else if (modifiers == Qt::NoModifier) {
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
@ -1792,7 +1782,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
break; break;
} }
if (isControlModifier(modifiers)) { if (VUtils::isControlModifierForVim(modifiers)) {
// Redo. // Redo.
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (!m_keys.isEmpty() || hasActionToken()) { if (!m_keys.isEmpty() || hasActionToken()) {

View File

@ -8,6 +8,7 @@
#include "vmainwindow.h" #include "vmainwindow.h"
#include "dialog/vconfirmdeletiondialog.h" #include "dialog/vconfirmdeletiondialog.h"
#include "dialog/vsortdialog.h" #include "dialog/vsortdialog.h"
#include "utils/vimnavigationforwidget.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VMainWindow *g_mainWin; extern VMainWindow *g_mainWin;
@ -462,47 +463,10 @@ void VAttachmentList::handleListItemCommitData(QWidget *p_itemEdit)
void VAttachmentList::keyPressEvent(QKeyEvent *p_event) void VAttachmentList::keyPressEvent(QKeyEvent *p_event)
{ {
int key = p_event->key(); if (VimNavigationForWidget::injectKeyPressEventForVim(m_attachmentList,
int modifiers = p_event->modifiers(); p_event,
switch (key) { this)) {
case Qt::Key_BracketLeft: return;
{
if (modifiers == Qt::ControlModifier) {
QKeyEvent *escEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape,
Qt::NoModifier);
QCoreApplication::postEvent(this, escEvent);
return;
}
break;
}
case Qt::Key_J:
{
if (modifiers == Qt::ControlModifier) {
QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
Qt::NoModifier);
QCoreApplication::postEvent(m_attachmentList, downEvent);
return;
}
break;
}
case Qt::Key_K:
{
if (modifiers == Qt::ControlModifier) {
QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
Qt::NoModifier);
QCoreApplication::postEvent(m_attachmentList, upEvent);
return;
}
break;
}
default:
break;
} }
QWidget::keyPressEvent(p_event); QWidget::keyPressEvent(p_event);

View File

@ -28,12 +28,16 @@ const QString VConfigManager::c_defaultConfigFile = QString("vnote.ini");
const QString VConfigManager::c_sessionConfigFile = QString("session.ini"); const QString VConfigManager::c_sessionConfigFile = QString("session.ini");
const QString VConfigManager::c_snippetConfigFile = QString("snippet.json");
const QString VConfigManager::c_styleConfigFolder = QString("styles"); const QString VConfigManager::c_styleConfigFolder = QString("styles");
const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles"); const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles");
const QString VConfigManager::c_templateConfigFolder = QString("templates"); const QString VConfigManager::c_templateConfigFolder = QString("templates");
const QString VConfigManager::c_snippetConfigFolder = QString("snippets");
const QString VConfigManager::c_defaultCssFile = QString(":/resources/styles/default.css"); const QString VConfigManager::c_defaultCssFile = QString(":/resources/styles/default.css");
const QString VConfigManager::c_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.css"); const QString VConfigManager::c_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.css");
@ -483,6 +487,7 @@ bool VConfigManager::writeDirectoryConfig(const QString &path, const QJsonObject
QString configFile = fetchDirConfigFilePath(path); QString configFile = fetchDirConfigFilePath(path);
QFile config(configFile); QFile config(configFile);
// We use Unix LF for config file.
if (!config.open(QIODevice::WriteOnly)) { if (!config.open(QIODevice::WriteOnly)) {
qWarning() << "fail to open directory configuration file for write:" qWarning() << "fail to open directory configuration file for write:"
<< configFile; << configFile;
@ -734,17 +739,32 @@ QString VConfigManager::getConfigFilePath() const
QString VConfigManager::getStyleConfigFolder() const QString VConfigManager::getStyleConfigFolder() const
{ {
return getConfigFolder() + QDir::separator() + c_styleConfigFolder; static QString path = QDir(getConfigFolder()).filePath(c_styleConfigFolder);
return path;
} }
QString VConfigManager::getCodeBlockStyleConfigFolder() const QString VConfigManager::getCodeBlockStyleConfigFolder() const
{ {
return getStyleConfigFolder() + QDir::separator() + c_codeBlockStyleConfigFolder; static QString path = QDir(getStyleConfigFolder()).filePath(c_codeBlockStyleConfigFolder);
return path;
} }
QString VConfigManager::getTemplateConfigFolder() const QString VConfigManager::getTemplateConfigFolder() const
{ {
return getConfigFolder() + QDir::separator() + c_templateConfigFolder; static QString path = QDir(getConfigFolder()).filePath(c_templateConfigFolder);
return path;
}
QString VConfigManager::getSnippetConfigFolder() const
{
static QString path = QDir(getConfigFolder()).filePath(c_snippetConfigFolder);
return path;
}
QString VConfigManager::getSnippetConfigFilePath() const
{
static QString path = QDir(getSnippetConfigFolder()).filePath(c_snippetConfigFile);
return path;
} }
QVector<QString> VConfigManager::getCssStyles() const QVector<QString> VConfigManager::getCssStyles() const

View File

@ -352,6 +352,11 @@ public:
// Get the folder c_templateConfigFolder in the config folder. // Get the folder c_templateConfigFolder in the config folder.
QString getTemplateConfigFolder() const; QString getTemplateConfigFolder() const;
// Get the folder c_snippetConfigFolder in the config folder.
QString getSnippetConfigFolder() const;
QString getSnippetConfigFilePath() const;
// Read all available css files in c_styleConfigFolder. // Read all available css files in c_styleConfigFolder.
QVector<QString> getCssStyles() const; QVector<QString> getCssStyles() const;
@ -714,6 +719,9 @@ private:
// The name of the config file for session information. // The name of the config file for session information.
static const QString c_sessionConfigFile; static const QString c_sessionConfigFile;
// The name of the config file for snippets folder.
static const QString c_snippetConfigFile;
// QSettings for the user configuration // QSettings for the user configuration
QSettings *userSettings; QSettings *userSettings;
@ -733,6 +741,9 @@ private:
// The folder name of template files. // The folder name of template files.
static const QString c_templateConfigFolder; static const QString c_templateConfigFolder;
// The folder name of snippet files.
static const QString c_snippetConfigFolder;
// Default CSS file in resource system. // Default CSS file in resource system.
static const QString c_defaultCssFile; static const QString c_defaultCssFile;

View File

@ -50,6 +50,18 @@ namespace DirConfig
static const QString c_modifiedTime = "modified_time"; static const QString c_modifiedTime = "modified_time";
} }
// Snippet Cofnig file items.
namespace SnippetConfig
{
static const QString c_version = "version";
static const QString c_snippets = "snippets";
static const QString c_name = "name";
static const QString c_type = "type";
static const QString c_cursorMark = "cursor_mark";
static const QString c_selectionMark = "selection_mark";
static const QString c_shortcut = "shortcut";
}
static const QString c_emptyHeaderName = "[EMPTY]"; static const QString c_emptyHeaderName = "[EMPTY]";
enum class TextDecoration enum class TextDecoration

View File

@ -10,6 +10,7 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vmainwindow.h" #include "vmainwindow.h"
#include "dialog/vsortdialog.h" #include "dialog/vsortdialog.h"
#include "utils/vimnavigationforwidget.h"
extern VMainWindow *g_mainWin; extern VMainWindow *g_mainWin;
@ -910,6 +911,10 @@ void VDirectoryTree::mousePressEvent(QMouseEvent *event)
void VDirectoryTree::keyPressEvent(QKeyEvent *event) void VDirectoryTree::keyPressEvent(QKeyEvent *event)
{ {
if (VimNavigationForWidget::injectKeyPressEventForVim(this, event)) {
return;
}
int key = event->key(); int key = event->key();
int modifiers = event->modifiers(); int modifiers = event->modifiers();
@ -924,32 +929,6 @@ void VDirectoryTree::keyPressEvent(QKeyEvent *event)
break; break;
} }
case Qt::Key_J:
{
if (modifiers == Qt::ControlModifier) {
event->accept();
QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
Qt::NoModifier);
QCoreApplication::postEvent(this, downEvent);
return;
}
break;
}
case Qt::Key_K:
{
if (modifiers == Qt::ControlModifier) {
event->accept();
QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
Qt::NoModifier);
QCoreApplication::postEvent(this, upEvent);
return;
}
break;
}
case Qt::Key_Asterisk: case Qt::Key_Asterisk:
{ {
if (modifiers == Qt::ShiftModifier) { if (modifiers == Qt::ShiftModifier) {
@ -1089,110 +1068,18 @@ void VDirectoryTree::expandSubTree(QTreeWidgetItem *p_item)
} }
} }
void VDirectoryTree::registerNavigation(QChar p_majorKey)
{
m_majorKey = p_majorKey;
V_ASSERT(m_keyMap.empty());
V_ASSERT(m_naviLabels.empty());
}
void VDirectoryTree::showNavigation() void VDirectoryTree::showNavigation()
{ {
// Clean up. VNavigationMode::showNavigation(this);
m_keyMap.clear();
for (auto label : m_naviLabels) {
delete label;
}
m_naviLabels.clear();
if (!isVisible()) {
return;
}
// Generate labels for visible items.
auto items = getVisibleItems();
for (int i = 0; i < 26 && i < items.size(); ++i) {
QChar key('a' + i);
m_keyMap[key] = items[i];
QString str = QString(m_majorKey) + key;
QLabel *label = new QLabel(str, this);
label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
label->move(visualItemRect(items[i]).topLeft());
label->show();
m_naviLabels.append(label);
}
}
void VDirectoryTree::hideNavigation()
{
m_keyMap.clear();
for (auto label : m_naviLabels) {
delete label;
}
m_naviLabels.clear();
} }
bool VDirectoryTree::handleKeyNavigation(int p_key, bool &p_succeed) bool VDirectoryTree::handleKeyNavigation(int p_key, bool &p_succeed)
{ {
static bool secondKey = false; static bool secondKey = false;
bool ret = false; return VNavigationMode::handleKeyNavigation(this,
p_succeed = false; secondKey,
QChar keyChar = VUtils::keyToChar(p_key); p_key,
if (secondKey && !keyChar.isNull()) { p_succeed);
secondKey = false;
p_succeed = true;
ret = true;
auto it = m_keyMap.find(keyChar);
if (it != m_keyMap.end()) {
setCurrentItem(it.value());
setFocus();
}
} else if (keyChar == m_majorKey) {
// Major key pressed.
// Need second key if m_keyMap is not empty.
if (m_keyMap.isEmpty()) {
p_succeed = true;
} else {
secondKey = true;
}
ret = true;
}
return ret;
}
QList<QTreeWidgetItem *> VDirectoryTree::getVisibleItems() const
{
QList<QTreeWidgetItem *> items;
for (int i = 0; i < topLevelItemCount(); ++i) {
QTreeWidgetItem *item = topLevelItem(i);
if (!item->isHidden()) {
items.append(item);
if (item->isExpanded()) {
items.append(getVisibleChildItems(item));
}
}
}
return items;
}
QList<QTreeWidgetItem *> VDirectoryTree::getVisibleChildItems(const QTreeWidgetItem *p_item) const
{
QList<QTreeWidgetItem *> items;
if (p_item && !p_item->isHidden() && p_item->isExpanded()) {
for (int i = 0; i < p_item->childCount(); ++i) {
QTreeWidgetItem *child = p_item->child(i);
if (!child->isHidden()) {
items.append(child);
if (child->isExpanded()) {
items.append(getVisibleChildItems(child));
}
}
}
}
return items;
} }
int VDirectoryTree::getNewMagic() int VDirectoryTree::getNewMagic()

View File

@ -29,9 +29,7 @@ public:
const VNotebook *currentNotebook() const; const VNotebook *currentNotebook() const;
// Implementations for VNavigationMode. // Implementations for VNavigationMode.
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
void showNavigation() Q_DECL_OVERRIDE; void showNavigation() Q_DECL_OVERRIDE;
void hideNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
signals: signals:
@ -134,10 +132,6 @@ private:
// Expand the currently-built subtree of @p_item according to VDirectory.isExpanded(). // Expand the currently-built subtree of @p_item according to VDirectory.isExpanded().
void expandSubTree(QTreeWidgetItem *p_item); void expandSubTree(QTreeWidgetItem *p_item);
QList<QTreeWidgetItem *> getVisibleItems() const;
QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
// We use a map to save and restore current directory of each notebook. // We use a map to save and restore current directory of each notebook.
// Try to restore current directory after changing notebook. // Try to restore current directory after changing notebook.
// Return false if no cache item found for current notebook. // Return false if no cache item found for current notebook.
@ -180,11 +174,6 @@ private:
// Reload content from disk. // Reload content from disk.
QAction *m_reloadAct; QAction *m_reloadAct;
// Navigation Mode.
// Map second key to QTreeWidgetItem.
QMap<QChar, QTreeWidgetItem *> m_keyMap;
QVector<QLabel *> m_naviLabels;
static const QString c_infoShortcutSequence; static const QString c_infoShortcutSequence;
static const QString c_copyShortcutSequence; static const QString c_copyShortcutSequence;
static const QString c_cutShortcutSequence; static const QString c_cutShortcutSequence;

View File

@ -756,7 +756,8 @@ bool VEditArea::handleKeyNavigation(int p_key, bool &p_succeed)
ret = true; ret = true;
auto it = m_keyMap.find(keyChar); auto it = m_keyMap.find(keyChar);
if (it != m_keyMap.end()) { if (it != m_keyMap.end()) {
setCurrentWindow(splitter->indexOf(it.value()), true); setCurrentWindow(splitter->indexOf(static_cast<VEditWindow *>(it.value())),
true);
} }
} else if (keyChar == m_majorKey) { } else if (keyChar == m_majorKey) {
// Major key pressed. // Major key pressed.

View File

@ -205,11 +205,6 @@ private:
// Last closed files stack. // Last closed files stack.
QStack<VFileSessionInfo> m_lastClosedFiles; QStack<VFileSessionInfo> m_lastClosedFiles;
// Navigation Mode.
// Map second key to VEditWindow.
QMap<QChar, VEditWindow *> m_keyMap;
QVector<QLabel *> m_naviLabels;
}; };
inline VEditWindow* VEditArea::getWindow(int windowIndex) const inline VEditWindow* VEditArea::getWindow(int windowIndex) const

View File

@ -9,6 +9,7 @@
#include "veditoperations.h" #include "veditoperations.h"
#include "dialog/vinsertlinkdialog.h" #include "dialog/vinsertlinkdialog.h"
#include "utils/vmetawordmanager.h" #include "utils/vmetawordmanager.h"
#include "utils/vvim.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -915,3 +916,10 @@ void VEditor::updateConfig()
{ {
updateEditConfig(); updateEditConfig();
} }
void VEditor::setVimMode(VimMode p_mode)
{
if (m_editOps) {
m_editOps->setVimMode(p_mode);
}
}

View File

@ -16,6 +16,7 @@ class VEditOperations;
class QTimer; class QTimer;
class QLabel; class QLabel;
class VVim; class VVim;
enum class VimMode;
enum class SelectionId { enum class SelectionId {
@ -136,6 +137,8 @@ public:
// Update config according to global configurations. // Update config according to global configurations.
virtual void updateConfig(); virtual void updateConfig();
void setVimMode(VimMode p_mode);
// Wrapper functions for QPlainTextEdit/QTextEdit. // Wrapper functions for QPlainTextEdit/QTextEdit.
// Ends with W to distinguish it from the original interfaces. // Ends with W to distinguish it from the original interfaces.
public: public:

View File

@ -124,3 +124,8 @@ bool VEditTab::tabHasFocus() const
void VEditTab::insertLink() void VEditTab::insertLink()
{ {
} }
void VEditTab::applySnippet(const VSnippet *p_snippet)
{
Q_UNUSED(p_snippet);
}

View File

@ -10,6 +10,7 @@
#include "vedittabinfo.h" #include "vedittabinfo.h"
class VEditArea; class VEditArea;
class VSnippet;
// VEditTab is the base class of an edit tab inside VEditWindow. // VEditTab is the base class of an edit tab inside VEditWindow.
class VEditTab : public QWidget class VEditTab : public QWidget
@ -91,6 +92,9 @@ public:
// Called by evaluateMagicWordsByCaptain() to evaluate the magic words. // Called by evaluateMagicWordsByCaptain() to evaluate the magic words.
virtual void evaluateMagicWords(); virtual void evaluateMagicWords();
// Insert snippet @p_snippet.
virtual void applySnippet(const VSnippet *p_snippet);
public slots: public slots:
// Enter edit mode // Enter edit mode
virtual void editFile() = 0; virtual void editFile() = 0;

View File

@ -15,6 +15,7 @@
#include "dialog/vconfirmdeletiondialog.h" #include "dialog/vconfirmdeletiondialog.h"
#include "dialog/vsortdialog.h" #include "dialog/vsortdialog.h"
#include "vmainwindow.h" #include "vmainwindow.h"
#include "utils/vimnavigationforwidget.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
extern VNote *g_vnote; extern VNote *g_vnote;
@ -825,50 +826,21 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
getNewMagic(); getNewMagic();
} }
void VFileList::keyPressEvent(QKeyEvent *event) void VFileList::keyPressEvent(QKeyEvent *p_event)
{ {
int key = event->key(); if (VimNavigationForWidget::injectKeyPressEventForVim(fileList,
int modifiers = event->modifiers(); p_event)) {
switch (key) { return;
case Qt::Key_Return: }
{
if (p_event->key() == Qt::Key_Return) {
QListWidgetItem *item = fileList->currentItem(); QListWidgetItem *item = fileList->currentItem();
if (item) { if (item) {
handleItemClicked(item); handleItemClicked(item);
} }
break;
} }
QWidget::keyPressEvent(p_event);
case Qt::Key_J:
{
if (modifiers == Qt::ControlModifier) {
event->accept();
QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
Qt::NoModifier);
QCoreApplication::postEvent(fileList, downEvent);
return;
}
break;
}
case Qt::Key_K:
{
if (modifiers == Qt::ControlModifier) {
event->accept();
QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
Qt::NoModifier);
QCoreApplication::postEvent(fileList, upEvent);
return;
}
break;
}
default:
break;
}
QWidget::keyPressEvent(event);
} }
void VFileList::focusInEvent(QFocusEvent * /* p_event */) void VFileList::focusInEvent(QFocusEvent * /* p_event */)
@ -893,91 +865,15 @@ bool VFileList::locateFile(const VNoteFile *p_file)
return false; return false;
} }
void VFileList::registerNavigation(QChar p_majorKey)
{
m_majorKey = p_majorKey;
V_ASSERT(m_keyMap.empty());
V_ASSERT(m_naviLabels.empty());
}
void VFileList::showNavigation() void VFileList::showNavigation()
{ {
// Clean up. VNavigationMode::showNavigation(fileList);
m_keyMap.clear();
for (auto label : m_naviLabels) {
delete label;
}
m_naviLabels.clear();
if (!isVisible()) {
return;
}
// Generate labels for visible items.
auto items = getVisibleItems();
int itemWidth = rect().width();
for (int i = 0; i < 26 && i < items.size(); ++i) {
QChar key('a' + i);
m_keyMap[key] = items[i];
QString str = QString(m_majorKey) + key;
QLabel *label = new QLabel(str, this);
label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
label->show();
QRect rect = fileList->visualItemRect(items[i]);
// Display the label at the end to show the file name.
label->move(rect.x() + itemWidth - label->width(), rect.y());
m_naviLabels.append(label);
}
}
void VFileList::hideNavigation()
{
m_keyMap.clear();
for (auto label : m_naviLabels) {
delete label;
}
m_naviLabels.clear();
} }
bool VFileList::handleKeyNavigation(int p_key, bool &p_succeed) bool VFileList::handleKeyNavigation(int p_key, bool &p_succeed)
{ {
static bool secondKey = false; static bool secondKey = false;
bool ret = false; return VNavigationMode::handleKeyNavigation(fileList, secondKey, p_key, p_succeed);
p_succeed = false;
QChar keyChar = VUtils::keyToChar(p_key);
if (secondKey && !keyChar.isNull()) {
secondKey = false;
p_succeed = true;
ret = true;
auto it = m_keyMap.find(keyChar);
if (it != m_keyMap.end()) {
fileList->setCurrentItem(it.value(), QItemSelectionModel::ClearAndSelect);
fileList->setFocus();
}
} else if (keyChar == m_majorKey) {
// Major key pressed.
// Need second key if m_keyMap is not empty.
if (m_keyMap.isEmpty()) {
p_succeed = true;
} else {
secondKey = true;
}
ret = true;
}
return ret;
}
QList<QListWidgetItem *> VFileList::getVisibleItems() const
{
QList<QListWidgetItem *> items;
for (int i = 0; i < fileList->count(); ++i) {
QListWidgetItem *item = fileList->item(i);
if (!item->isHidden()) {
items.append(item);
}
}
return items;
} }
int VFileList::getNewMagic() int VFileList::getNewMagic()

View File

@ -49,9 +49,7 @@ public:
QWidget *getContentWidget() const; QWidget *getContentWidget() const;
// Implementations for VNavigationMode. // Implementations for VNavigationMode.
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
void showNavigation() Q_DECL_OVERRIDE; void showNavigation() Q_DECL_OVERRIDE;
void hideNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
public slots: public slots:
@ -104,7 +102,8 @@ private slots:
void sortItems(); void sortItems();
protected: protected:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
void focusInEvent(QFocusEvent *p_event) Q_DECL_OVERRIDE; void focusInEvent(QFocusEvent *p_event) Q_DECL_OVERRIDE;
private: private:
@ -140,8 +139,6 @@ private:
inline QPointer<VNoteFile> getVFile(QListWidgetItem *p_item) const; inline QPointer<VNoteFile> getVFile(QListWidgetItem *p_item) const;
QList<QListWidgetItem *> getVisibleItems() const;
// Fill the info of @p_item according to @p_file. // Fill the info of @p_item according to @p_file.
void fillItem(QListWidgetItem *p_item, const VNoteFile *p_file); void fillItem(QListWidgetItem *p_item, const VNoteFile *p_file);
@ -174,11 +171,6 @@ private:
QAction *m_openLocationAct; QAction *m_openLocationAct;
QAction *m_sortAct; QAction *m_sortAct;
// Navigation Mode.
// Map second key to QListWidgetItem.
QMap<QChar, QListWidgetItem *> m_keyMap;
QVector<QLabel *> m_naviLabels;
static const QString c_infoShortcutSequence; static const QString c_infoShortcutSequence;
static const QString c_copyShortcutSequence; static const QString c_copyShortcutSequence;
static const QString c_cutShortcutSequence; static const QString c_cutShortcutSequence;

View File

@ -30,6 +30,7 @@
#include "vbuttonwithwidget.h" #include "vbuttonwithwidget.h"
#include "vattachmentlist.h" #include "vattachmentlist.h"
#include "vfilesessioninfo.h" #include "vfilesessioninfo.h"
#include "vsnippetlist.h"
VMainWindow *g_mainWin; VMainWindow *g_mainWin;
@ -111,6 +112,7 @@ void VMainWindow::registerCaptainAndNavigationTargets()
m_captain->registerNavigationTarget(m_fileList); m_captain->registerNavigationTarget(m_fileList);
m_captain->registerNavigationTarget(editArea); m_captain->registerNavigationTarget(editArea);
m_captain->registerNavigationTarget(outline); m_captain->registerNavigationTarget(outline);
m_captain->registerNavigationTarget(m_snippetList);
// Register Captain mode targets. // Register Captain mode targets.
m_captain->registerCaptainTarget(tr("AttachmentList"), m_captain->registerCaptainTarget(tr("AttachmentList"),
@ -1185,7 +1187,6 @@ void VMainWindow::initDockWindows()
toolDock = new QDockWidget(tr("Tools"), this); toolDock = new QDockWidget(tr("Tools"), this);
toolDock->setObjectName("ToolsDock"); toolDock->setObjectName("ToolsDock");
toolDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); toolDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
toolBox = new QToolBox(this);
// Outline tree. // Outline tree.
outline = new VOutline(this); outline = new VOutline(this);
@ -1196,8 +1197,18 @@ void VMainWindow::initDockWindows()
connect(outline, &VOutline::outlineItemActivated, connect(outline, &VOutline::outlineItemActivated,
editArea, &VEditArea::scrollToHeader); editArea, &VEditArea::scrollToHeader);
toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline")); // Snippets.
toolDock->setWidget(toolBox); m_snippetList = new VSnippetList(this);
m_toolBox = new QToolBox(this);
m_toolBox->addItem(outline,
QIcon(":/resources/icons/outline.svg"),
tr("Outline"));
m_toolBox->addItem(m_snippetList,
QIcon(":/resources/icons/snippets.svg"),
tr("Snippets"));
toolDock->setWidget(m_toolBox);
addDockWidget(Qt::RightDockWidgetArea, toolDock); addDockWidget(Qt::RightDockWidgetArea, toolDock);
QAction *toggleAct = toolDock->toggleViewAction(); QAction *toggleAct = toolDock->toggleViewAction();

View File

@ -37,6 +37,7 @@ class QSystemTrayIcon;
class QShortcut; class QShortcut;
class VButtonWithWidget; class VButtonWithWidget;
class VAttachmentList; class VAttachmentList;
class VSnippetList;
enum class PanelViewState enum class PanelViewState
{ {
@ -87,6 +88,8 @@ public:
VFile *getCurrentFile() const; VFile *getCurrentFile() const;
VEditTab *getCurrentTab() const;
signals: signals:
// Emit when editor related configurations were changed by user. // Emit when editor related configurations were changed by user.
void editorConfigUpdated(); void editorConfigUpdated();
@ -294,8 +297,15 @@ private:
VEditArea *editArea; VEditArea *editArea;
QDockWidget *toolDock; QDockWidget *toolDock;
QToolBox *toolBox;
// Tool box in the dock widget.
QToolBox *m_toolBox;
VOutline *outline; VOutline *outline;
// View and manage snippets.
VSnippetList *m_snippetList;
VAvatar *m_avatar; VAvatar *m_avatar;
VFindReplaceDialog *m_findReplaceDialog; VFindReplaceDialog *m_findReplaceDialog;
VVimIndicator *m_vimIndicator; VVimIndicator *m_vimIndicator;
@ -396,4 +406,9 @@ inline VFile *VMainWindow::getCurrentFile() const
return m_curFile; return m_curFile;
} }
inline VEditTab *VMainWindow::getCurrentTab() const
{
return m_curTab;
}
#endif // VMAINWINDOW_H #endif // VMAINWINDOW_H

View File

@ -10,7 +10,6 @@
#include "veditor.h" #include "veditor.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vtableofcontent.h" #include "vtableofcontent.h"
#include "veditoperations.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vutils.h" #include "utils/vutils.h"

View File

@ -18,6 +18,7 @@
#include "vwebview.h" #include "vwebview.h"
#include "vmdeditor.h" #include "vmdeditor.h"
#include "vmainwindow.h" #include "vmainwindow.h"
#include "vsnippet.h"
extern VMainWindow *g_mainWin; extern VMainWindow *g_mainWin;
@ -724,3 +725,19 @@ void VMdTab::evaluateMagicWords()
getEditor()->evaluateMagicWords(); getEditor()->evaluateMagicWords();
} }
} }
void VMdTab::applySnippet(const VSnippet *p_snippet)
{
if (isEditMode()
&& m_file->isModifiable()
&& p_snippet->getType() == VSnippet::Type::PlainText) {
Q_ASSERT(m_editor);
QTextCursor cursor = m_editor->textCursor();
bool changed = p_snippet->apply(cursor);
if (changed) {
m_editor->setTextCursor(cursor);
m_editor->setVimMode(VimMode::Insert);
}
}
}

View File

@ -75,6 +75,8 @@ public:
// Evaluate magic words. // Evaluate magic words.
void evaluateMagicWords() Q_DECL_OVERRIDE; void evaluateMagicWords() Q_DECL_OVERRIDE;
void applySnippet(const VSnippet *p_snippet) Q_DECL_OVERRIDE;
public slots: public slots:
// Enter edit mode. // Enter edit mode.
void editFile() Q_DECL_OVERRIDE; void editFile() Q_DECL_OVERRIDE;

202
src/vnavigationmode.cpp Normal file
View File

@ -0,0 +1,202 @@
#include "vnavigationmode.h"
#include <QDebug>
#include <QLabel>
#include <QListWidget>
#include <QTreeWidget>
#include "vnote.h"
#include "utils/vutils.h"
extern VNote *g_vnote;
VNavigationMode::VNavigationMode()
{
}
VNavigationMode::~VNavigationMode()
{
}
void VNavigationMode::registerNavigation(QChar p_majorKey)
{
m_majorKey = p_majorKey;
Q_ASSERT(m_keyMap.empty());
Q_ASSERT(m_naviLabels.empty());
}
void VNavigationMode::hideNavigation()
{
clearNavigation();
}
void VNavigationMode::showNavigation(QListWidget *p_widget)
{
clearNavigation();
if (!p_widget->isVisible()) {
return;
}
// Generate labels for visible items.
auto items = getVisibleItems(p_widget);
for (int i = 0; i < 26 && i < items.size(); ++i) {
QChar key('a' + i);
m_keyMap[key] = items[i];
QString str = QString(m_majorKey) + key;
QLabel *label = new QLabel(str, p_widget);
label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
label->show();
QRect rect = p_widget->visualItemRect(items[i]);
// Display the label at the end to show the file name.
label->move(rect.x() + p_widget->rect().width() - label->width() - 2,
rect.y());
m_naviLabels.append(label);
}
}
QList<QListWidgetItem *> VNavigationMode::getVisibleItems(const QListWidget *p_widget) const
{
QList<QListWidgetItem *> items;
for (int i = 0; i < p_widget->count(); ++i) {
QListWidgetItem *item = p_widget->item(i);
if (!item->isHidden()) {
items.append(item);
}
}
return items;
}
static QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item)
{
QList<QTreeWidgetItem *> items;
if (p_item && !p_item->isHidden() && p_item->isExpanded()) {
for (int i = 0; i < p_item->childCount(); ++i) {
QTreeWidgetItem *child = p_item->child(i);
if (!child->isHidden()) {
items.append(child);
if (child->isExpanded()) {
items.append(getVisibleChildItems(child));
}
}
}
}
return items;
}
QList<QTreeWidgetItem *> VNavigationMode::getVisibleItems(const QTreeWidget *p_widget) const
{
QList<QTreeWidgetItem *> items;
for (int i = 0; i < p_widget->topLevelItemCount(); ++i) {
QTreeWidgetItem *item = p_widget->topLevelItem(i);
if (!item->isHidden()) {
items.append(item);
if (item->isExpanded()) {
items.append(getVisibleChildItems(item));
}
}
}
return items;
}
bool VNavigationMode::handleKeyNavigation(QListWidget *p_widget,
bool &p_secondKey,
int p_key,
bool &p_succeed)
{
bool ret = false;
p_succeed = false;
QChar keyChar = VUtils::keyToChar(p_key);
if (p_secondKey && !keyChar.isNull()) {
p_secondKey = false;
p_succeed = true;
ret = true;
auto it = m_keyMap.find(keyChar);
if (it != m_keyMap.end()) {
p_widget->setCurrentItem(static_cast<QListWidgetItem *>(it.value()),
QItemSelectionModel::ClearAndSelect);
p_widget->setFocus();
}
} else if (keyChar == m_majorKey) {
// Major key pressed.
// Need second key if m_keyMap is not empty.
if (m_keyMap.isEmpty()) {
p_succeed = true;
} else {
p_secondKey = true;
}
ret = true;
}
return ret;
}
void VNavigationMode::showNavigation(QTreeWidget *p_widget)
{
clearNavigation();
if (!p_widget->isVisible()) {
return;
}
// Generate labels for visible items.
auto items = getVisibleItems(p_widget);
for (int i = 0; i < 26 && i < items.size(); ++i) {
QChar key('a' + i);
m_keyMap[key] = items[i];
QString str = QString(m_majorKey) + key;
QLabel *label = new QLabel(str, p_widget);
label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
label->move(p_widget->visualItemRect(items[i]).topLeft());
label->show();
m_naviLabels.append(label);
}
}
void VNavigationMode::clearNavigation()
{
m_keyMap.clear();
for (auto label : m_naviLabels) {
delete label;
}
m_naviLabels.clear();
}
bool VNavigationMode::handleKeyNavigation(QTreeWidget *p_widget,
bool &p_secondKey,
int p_key,
bool &p_succeed)
{
bool ret = false;
p_succeed = false;
QChar keyChar = VUtils::keyToChar(p_key);
if (p_secondKey && !keyChar.isNull()) {
p_secondKey = false;
p_succeed = true;
ret = true;
auto it = m_keyMap.find(keyChar);
if (it != m_keyMap.end()) {
p_widget->setCurrentItem(static_cast<QTreeWidgetItem *>(it.value()));
p_widget->setFocus();
}
} else if (keyChar == m_majorKey) {
// Major key pressed.
// Need second key if m_keyMap is not empty.
if (m_keyMap.isEmpty()) {
p_succeed = true;
} else {
p_secondKey = true;
}
ret = true;
}
return ret;
}

View File

@ -2,23 +2,63 @@
#define VNAVIGATIONMODE_H #define VNAVIGATIONMODE_H
#include <QChar> #include <QChar>
#include <QVector>
#include <QMap>
#include <QList>
class QLabel;
class QListWidget;
class QListWidgetItem;
class QTreeWidget;
class QTreeWidgetItem;
// Interface class for Navigation Mode in Captain Mode. // Interface class for Navigation Mode in Captain Mode.
class VNavigationMode class VNavigationMode
{ {
public: public:
VNavigationMode() {}; VNavigationMode();
virtual ~VNavigationMode() {};
virtual ~VNavigationMode();
virtual void registerNavigation(QChar p_majorKey);
virtual void registerNavigation(QChar p_majorKey) = 0;
virtual void showNavigation() = 0; virtual void showNavigation() = 0;
virtual void hideNavigation() = 0;
virtual void hideNavigation();
// Return true if this object could consume p_key. // Return true if this object could consume p_key.
// p_succeed indicates whether the keys hit a target successfully. // p_succeed indicates whether the keys hit a target successfully.
virtual bool handleKeyNavigation(int p_key, bool &p_succeed) = 0; virtual bool handleKeyNavigation(int p_key, bool &p_succeed) = 0;
protected: protected:
void clearNavigation();
void showNavigation(QListWidget *p_widget);
void showNavigation(QTreeWidget *p_widget);
bool handleKeyNavigation(QListWidget *p_widget,
bool &p_secondKey,
int p_key,
bool &p_succeed);
bool handleKeyNavigation(QTreeWidget *p_widget,
bool &p_secondKey,
int p_key,
bool &p_succeed);
QChar m_majorKey; QChar m_majorKey;
// Map second key to item.
QMap<QChar, void *> m_keyMap;
QVector<QLabel *> m_naviLabels;
private:
QList<QListWidgetItem *> getVisibleItems(const QListWidget *p_widget) const;
QList<QTreeWidgetItem *> getVisibleItems(const QTreeWidget *p_widget) const;
}; };
#endif // VNAVIGATIONMODE_H #endif // VNAVIGATIONMODE_H

View File

@ -135,5 +135,11 @@
<file>resources/icons/link.svg</file> <file>resources/icons/link.svg</file>
<file>resources/icons/code_block.svg</file> <file>resources/icons/code_block.svg</file>
<file>resources/icons/manage_template.svg</file> <file>resources/icons/manage_template.svg</file>
<file>resources/icons/snippets.svg</file>
<file>resources/icons/add_snippet.svg</file>
<file>resources/icons/locate_snippet.svg</file>
<file>resources/icons/delete_snippet.svg</file>
<file>resources/icons/snippet_info.svg</file>
<file>resources/icons/apply_snippet.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -21,6 +21,7 @@
#include "veditarea.h" #include "veditarea.h"
#include "vnofocusitemdelegate.h" #include "vnofocusitemdelegate.h"
#include "vmainwindow.h" #include "vmainwindow.h"
#include "utils/vimnavigationforwidget.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -590,45 +591,9 @@ bool VNotebookSelector::handleKeyNavigation(int p_key, bool &p_succeed)
bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event) bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event)
{ {
int key = p_event->key(); if (VimNavigationForWidget::injectKeyPressEventForVim(m_listWidget,
int modifiers = p_event->modifiers(); p_event)) {
switch (key) { return true;
case Qt::Key_BracketLeft:
{
if (modifiers == Qt::ControlModifier) {
p_event->accept();
hidePopup();
return true;
}
break;
}
case Qt::Key_J:
{
if (modifiers == Qt::ControlModifier) {
p_event->accept();
QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
Qt::NoModifier);
QCoreApplication::postEvent(m_listWidget, downEvent);
return true;
}
break;
}
case Qt::Key_K:
{
if (modifiers == Qt::ControlModifier) {
p_event->accept();
QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
Qt::NoModifier);
QCoreApplication::postEvent(m_listWidget, upEvent);
return true;
}
break;
}
default:
break;
} }
return false; return false;

View File

@ -194,6 +194,7 @@ void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
hide(); hide();
return; return;
} }
break; break;
} }
@ -201,7 +202,7 @@ void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
{ {
m_cmdTimer->stop(); m_cmdTimer->stop();
m_cmdNum = 0; m_cmdNum = 0;
if (modifiers == Qt::ControlModifier) { if (VUtils::isControlModifierForVim(modifiers)) {
QList<QAction *> acts = actions(); QList<QAction *> acts = actions();
if (acts.size() == 0) { if (acts.size() == 0) {
return; return;
@ -230,6 +231,7 @@ void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
setActiveAction(act); setActiveAction(act);
return; return;
} }
break; break;
} }
@ -237,11 +239,12 @@ void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
{ {
m_cmdTimer->stop(); m_cmdTimer->stop();
m_cmdNum = 0; m_cmdNum = 0;
if (modifiers == Qt::ControlModifier) { if (VUtils::isControlModifierForVim(modifiers)) {
QList<QAction *> acts = actions(); QList<QAction *> acts = actions();
if (acts.size() == 0) { if (acts.size() == 0) {
return; return;
} }
int idx = acts.size() - 1; int idx = acts.size() - 1;
QAction *act = activeAction(); QAction *act = activeAction();
if (act) { if (act) {
@ -266,6 +269,7 @@ void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
setActiveAction(act); setActiveAction(act);
return; return;
} }
break; break;
} }
@ -274,6 +278,7 @@ void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
m_cmdNum = 0; m_cmdNum = 0;
break; break;
} }
QMenu::keyPressEvent(p_event); QMenu::keyPressEvent(p_event);
} }

View File

@ -221,108 +221,18 @@ void VOutline::keyPressEvent(QKeyEvent *event)
QTreeWidget::keyPressEvent(event); QTreeWidget::keyPressEvent(event);
} }
void VOutline::registerNavigation(QChar p_majorKey)
{
m_majorKey = p_majorKey;
V_ASSERT(m_keyMap.empty());
V_ASSERT(m_naviLabels.empty());
}
void VOutline::showNavigation() void VOutline::showNavigation()
{ {
// Clean up. VNavigationMode::showNavigation(this);
m_keyMap.clear();
for (auto label : m_naviLabels) {
delete label;
}
m_naviLabels.clear();
if (!isVisible()) {
return;
}
// Generate labels for visible items.
auto items = getVisibleItems();
for (int i = 0; i < 26 && i < items.size(); ++i) {
QChar key('a' + i);
m_keyMap[key] = items[i];
QString str = QString(m_majorKey) + key;
QLabel *label = new QLabel(str, this);
label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
label->move(visualItemRect(items[i]).topLeft());
label->show();
m_naviLabels.append(label);
}
}
void VOutline::hideNavigation()
{
m_keyMap.clear();
for (auto label : m_naviLabels) {
delete label;
}
m_naviLabels.clear();
} }
bool VOutline::handleKeyNavigation(int p_key, bool &p_succeed) bool VOutline::handleKeyNavigation(int p_key, bool &p_succeed)
{ {
static bool secondKey = false; static bool secondKey = false;
bool ret = false; return VNavigationMode::handleKeyNavigation(this,
p_succeed = false; secondKey,
QChar keyChar = VUtils::keyToChar(p_key); p_key,
if (secondKey && !keyChar.isNull()) { p_succeed);
secondKey = false;
p_succeed = true;
ret = true;
auto it = m_keyMap.find(keyChar);
if (it != m_keyMap.end()) {
setCurrentItem(it.value());
setFocus();
}
} else if (keyChar == m_majorKey) {
// Major key pressed.
// Need second key if m_keyMap is not empty.
if (m_keyMap.isEmpty()) {
p_succeed = true;
} else {
secondKey = true;
}
ret = true;
}
return ret;
}
QList<QTreeWidgetItem *> VOutline::getVisibleItems() const
{
QList<QTreeWidgetItem *> items;
for (int i = 0; i < topLevelItemCount(); ++i) {
QTreeWidgetItem *item = topLevelItem(i);
if (!item->isHidden()) {
items.append(item);
if (item->isExpanded()) {
items.append(getVisibleChildItems(item));
}
}
}
return items;
}
QList<QTreeWidgetItem *> VOutline::getVisibleChildItems(const QTreeWidgetItem *p_item) const
{
QList<QTreeWidgetItem *> items;
if (p_item && !p_item->isHidden() && p_item->isExpanded()) {
for (int i = 0; i < p_item->childCount(); ++i) {
QTreeWidgetItem *child = p_item->child(i);
if (!child->isHidden()) {
items.append(child);
if (child->isExpanded()) {
items.append(getVisibleChildItems(child));
}
}
}
}
return items;
} }
const VTableOfContentItem *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const const VTableOfContentItem *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const

View File

@ -19,9 +19,7 @@ public:
VOutline(QWidget *parent = 0); VOutline(QWidget *parent = 0);
// Implementations for VNavigationMode. // Implementations for VNavigationMode.
void registerNavigation(QChar p_majorKey) Q_DECL_OVERRIDE;
void showNavigation() Q_DECL_OVERRIDE; void showNavigation() Q_DECL_OVERRIDE;
void hideNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
signals: signals:
@ -64,9 +62,6 @@ private:
bool selectHeaderOne(QTreeWidgetItem *p_item, 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. // Fill the info of @p_item.
void fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header); void fillItem(QTreeWidgetItem *p_item, const VTableOfContentItem &p_header);
@ -79,11 +74,6 @@ private:
// When true, won't emit outlineItemActivated(). // When true, won't emit outlineItemActivated().
bool m_muted; bool m_muted;
// Navigation Mode.
// Map second key to QTreeWidgetItem.
QMap<QChar, QTreeWidgetItem *> m_keyMap;
QVector<QLabel *> m_naviLabels;
}; };
#endif // VOUTLINE_H #endif // VOUTLINE_H

207
src/vsnippet.cpp Normal file
View File

@ -0,0 +1,207 @@
#include "vsnippet.h"
#include <QObject>
#include <QDebug>
#include "vconstants.h"
#include "utils/vutils.h"
#include "utils/veditutils.h"
#include "utils/vmetawordmanager.h"
extern VMetaWordManager *g_mwMgr;
const QString VSnippet::c_defaultCursorMark = "@@";
const QString VSnippet::c_defaultSelectionMark = "$$";
QVector<QChar> VSnippet::s_allShortcuts;
VSnippet::VSnippet()
: m_type(Type::PlainText),
m_cursorMark(c_defaultCursorMark)
{
}
VSnippet::VSnippet(const QString &p_name,
Type p_type,
const QString &p_content,
const QString &p_cursorMark,
const QString &p_selectionMark,
QChar p_shortcut)
: m_name(p_name),
m_type(p_type),
m_content(p_content),
m_cursorMark(p_cursorMark),
m_selectionMark(p_selectionMark),
m_shortcut(p_shortcut)
{
Q_ASSERT(m_selectionMark != m_cursorMark);
}
bool VSnippet::update(const QString &p_name,
Type p_type,
const QString &p_content,
const QString &p_cursorMark,
const QString &p_selectionMark,
QChar p_shortcut)
{
bool updated = false;
if (m_name != p_name) {
m_name = p_name;
updated = true;
}
if (m_type != p_type) {
m_type = p_type;
updated = true;
}
if (m_content != p_content) {
m_content = p_content;
updated = true;
}
if (m_cursorMark != p_cursorMark) {
m_cursorMark = p_cursorMark;
updated = true;
}
if (m_selectionMark != p_selectionMark) {
m_selectionMark = p_selectionMark;
updated = true;
}
if (m_shortcut != p_shortcut) {
m_shortcut = p_shortcut;
updated = true;
}
qDebug() << "snippet" << m_name << "updated" << updated;
return updated;
}
QString VSnippet::typeStr(VSnippet::Type p_type)
{
switch (p_type) {
case Type::PlainText:
return QObject::tr("PlainText");
case Type::Html:
return QObject::tr("Html");
default:
return QObject::tr("Invalid");
}
}
QJsonObject VSnippet::toJson() const
{
QJsonObject snip;
snip[SnippetConfig::c_name] = m_name;
snip[SnippetConfig::c_type] = (int)m_type;
snip[SnippetConfig::c_cursorMark] = m_cursorMark;
snip[SnippetConfig::c_selectionMark] = m_selectionMark;
snip[SnippetConfig::c_shortcut] = m_shortcut.isNull() ? "" : QString(m_shortcut);
return snip;
}
VSnippet VSnippet::fromJson(const QJsonObject &p_json)
{
QChar shortcut;
QString shortcutStr = p_json[SnippetConfig::c_shortcut].toString();
if (!shortcutStr.isEmpty() && isValidShortcut(shortcutStr[0])) {
shortcut = shortcutStr[0];
}
VSnippet snip(p_json[SnippetConfig::c_name].toString(),
static_cast<VSnippet::Type>(p_json[SnippetConfig::c_type].toInt()),
"",
p_json[SnippetConfig::c_cursorMark].toString(),
p_json[SnippetConfig::c_selectionMark].toString(),
shortcut);
return snip;
}
const QVector<QChar> &VSnippet::getAllShortcuts()
{
if (s_allShortcuts.isEmpty()) {
// Init.
char ch = 'a';
while (true) {
s_allShortcuts.append(ch);
if (ch == 'z') {
break;
}
ch++;
}
}
return s_allShortcuts;
}
bool VSnippet::isValidShortcut(QChar p_char)
{
if (p_char >= 'a' && p_char <= 'z') {
return true;
}
return false;
}
bool VSnippet::apply(QTextCursor &p_cursor) const
{
p_cursor.beginEditBlock();
// Delete selected text.
QString selection = VEditUtils::selectedText(p_cursor);
p_cursor.removeSelectedText();
// Evaluate the content.
QString content = g_mwMgr->evaluate(m_content);
// Find the cursor mark and break the content.
QString secondPart;
if (!m_cursorMark.isEmpty()) {
QStringList parts = content.split(m_cursorMark, QString::SkipEmptyParts);
Q_ASSERT(parts.size() < 3);
content = parts[0];
if (parts.size() == 2) {
secondPart = parts[1];
}
}
// Replace the selection mark.
if (!m_selectionMark.isEmpty()) {
content.replace(m_selectionMark, selection);
}
int pos = p_cursor.position() + content.size();
if (!secondPart.isEmpty()) {
secondPart.replace(m_selectionMark, selection);
content += secondPart;
}
// Insert it.
switch (m_type) {
case Type::Html:
p_cursor.insertHtml(content);
// TODO: set the position of the cursor.
break;
case Type::PlainText:
V_FALLTHROUGH;
default:
p_cursor.insertText(content);
p_cursor.setPosition(pos);
break;
}
p_cursor.endEditBlock();
return true;
}

113
src/vsnippet.h Normal file
View File

@ -0,0 +1,113 @@
#ifndef VSNIPPET_H
#define VSNIPPET_H
#include <QString>
#include <QJsonObject>
#include <QTextCursor>
class VSnippet
{
public:
enum Type
{
PlainText = 0,
Html,
Invalid
};
VSnippet();
VSnippet(const QString &p_name,
Type p_type = Type::PlainText,
const QString &p_content = QString(),
const QString &p_cursorMark = c_defaultCursorMark,
const QString &p_selectionMark = c_defaultSelectionMark,
QChar p_shortcut = QChar());
// Return true if there is any update.
bool update(const QString &p_name,
Type p_type,
const QString &p_content,
const QString &p_cursorMark,
const QString &p_selectionMark,
QChar p_shortcut);
const QString &getName() const
{
return m_name;
}
VSnippet::Type getType() const
{
return m_type;
}
const QString &getCursorMark() const
{
return m_cursorMark;
}
const QString &getSelectionMark() const
{
return m_selectionMark;
}
const QString &getContent() const
{
return m_content;
}
QChar getShortcut() const
{
return m_shortcut;
}
void setContent(const QString &p_content)
{
m_content = p_content;
}
// Not including m_content.
QJsonObject toJson() const;
// Apply this snippet via @p_cursor.
bool apply(QTextCursor &p_cursor) const;
// Not including m_content.
static VSnippet fromJson(const QJsonObject &p_json);
static QString typeStr(VSnippet::Type p_type);
static const QVector<QChar> &getAllShortcuts();
static bool isValidShortcut(QChar p_char);
private:
// File name in the snippet folder.
QString m_name;
Type m_type;
// Support magic word.
QString m_content;
// String in the content that mark the position of the cursor after insertion.
// If there is no such mark in the content, the cursor should be put at the
// end of the insertion.
QString m_cursorMark;
// Selection marks in the content will be replaced by selected text.
QString m_selectionMark;
// Shortcut to apply this snippet.
QChar m_shortcut;
static const QString c_defaultCursorMark;
static const QString c_defaultSelectionMark;
static QVector<QChar> s_allShortcuts;
};
#endif // VSNIPPET_H

606
src/vsnippetlist.cpp Normal file
View File

@ -0,0 +1,606 @@
#include "vsnippetlist.h"
#include <QtWidgets>
#include "vconfigmanager.h"
#include "dialog/veditsnippetdialog.h"
#include "utils/vutils.h"
#include "utils/vimnavigationforwidget.h"
#include "dialog/vsortdialog.h"
#include "dialog/vconfirmdeletiondialog.h"
#include "vmainwindow.h"
extern VConfigManager *g_config;
extern VMainWindow *g_mainWin;
const QString VSnippetList::c_infoShortcutSequence = "F2";
VSnippetList::VSnippetList(QWidget *p_parent)
: QWidget(p_parent)
{
setupUI();
initShortcuts();
initActions();
if (!readSnippetsFromConfig()) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to read snippets from <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(g_config->getSnippetConfigFolder()),
"",
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
updateContent();
}
void VSnippetList::setupUI()
{
m_addBtn = new QPushButton(QIcon(":/resources/icons/add_snippet.svg"), "");
m_addBtn->setToolTip(tr("New Snippet"));
m_addBtn->setProperty("FlatBtn", true);
connect(m_addBtn, &QPushButton::clicked,
this, &VSnippetList::newSnippet);
m_locateBtn = new QPushButton(QIcon(":/resources/icons/locate_snippet.svg"), "");
m_locateBtn->setToolTip(tr("Open Folder"));
m_locateBtn->setProperty("FlatBtn", true);
connect(m_locateBtn, &QPushButton::clicked,
this, [this]() {
makeSureFolderExist();
QUrl url = QUrl::fromLocalFile(g_config->getSnippetConfigFolder());
QDesktopServices::openUrl(url);
});
m_numLabel = new QLabel();
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addWidget(m_addBtn);
btnLayout->addWidget(m_locateBtn);
btnLayout->addStretch();
btnLayout->addWidget(m_numLabel);
btnLayout->setContentsMargins(0, 0, 3, 0);
m_snippetList = new QListWidget();
m_snippetList->setContextMenuPolicy(Qt::CustomContextMenu);
m_snippetList->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_snippetList->setEditTriggers(QAbstractItemView::SelectedClicked);
connect(m_snippetList, &QListWidget::customContextMenuRequested,
this, &VSnippetList::handleContextMenuRequested);
connect(m_snippetList, &QListWidget::itemActivated,
this, &VSnippetList::handleItemActivated);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addLayout(btnLayout);
mainLayout->addWidget(m_snippetList);
mainLayout->setContentsMargins(0, 0, 0, 0);
setLayout(mainLayout);
}
void VSnippetList::initActions()
{
m_applyAct = new QAction(QIcon(":/resources/icons/apply_snippet.svg"),
tr("&Apply"),
this);
m_applyAct->setToolTip(tr("Insert this snippet in editor"));
connect(m_applyAct, &QAction::triggered,
this, [this]() {
QListWidgetItem *item = m_snippetList->currentItem();
handleItemActivated(item);
});
m_infoAct = new QAction(QIcon(":/resources/icons/snippet_info.svg"),
tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)),
this);
m_infoAct->setToolTip(tr("View and edit snippet's information"));
connect(m_infoAct, &QAction::triggered,
this, &VSnippetList::snippetInfo);
m_deleteAct = new QAction(QIcon(":/resources/icons/delete_snippet.svg"),
tr("&Delete"),
this);
m_deleteAct->setToolTip(tr("Delete selected snippets"));
connect(m_deleteAct, &QAction::triggered,
this, &VSnippetList::deleteSelectedItems);
m_sortAct = new QAction(QIcon(":/resources/icons/sort.svg"),
tr("&Sort"),
this);
m_sortAct->setToolTip(tr("Sort snippets manually"));
connect(m_sortAct, &QAction::triggered,
this, &VSnippetList::sortItems);
}
void VSnippetList::initShortcuts()
{
QShortcut *infoShortcut = new QShortcut(QKeySequence(c_infoShortcutSequence), this);
infoShortcut->setContext(Qt::WidgetWithChildrenShortcut);
connect(infoShortcut, &QShortcut::activated,
this, &VSnippetList::snippetInfo);
}
void VSnippetList::newSnippet()
{
QString defaultName = VUtils::getFileNameWithSequence(g_config->getSnippetConfigFolder(),
"snippet");
VSnippet tmpSnippet(defaultName);
QString info = tr("Magic words are supported in the content of the snippet.");
VEditSnippetDialog dialog(tr("Create Snippet"),
info,
m_snippets,
tmpSnippet,
this);
if (dialog.exec() == QDialog::Accepted) {
makeSureFolderExist();
VSnippet snippet(dialog.getNameInput(),
dialog.getTypeInput(),
dialog.getContentInput(),
dialog.getCursorMarkInput(),
dialog.getSelectionMarkInput());
QString errMsg;
if (!addSnippet(snippet, &errMsg)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to create snippet <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(snippet.getName()),
errMsg,
QMessageBox::Ok,
QMessageBox::Ok,
this);
updateContent();
}
}
}
void VSnippetList::handleContextMenuRequested(QPoint p_pos)
{
QListWidgetItem *item = m_snippetList->itemAt(p_pos);
QMenu menu(this);
menu.setToolTipsVisible(true);
if (item) {
int itemCount = m_snippetList->selectedItems().size();
if (itemCount == 1) {
menu.addAction(m_applyAct);
menu.addAction(m_infoAct);
}
menu.addAction(m_deleteAct);
}
m_snippetList->update();
if (m_snippets.size() > 1) {
if (!menu.actions().isEmpty()) {
menu.addSeparator();
}
menu.addAction(m_sortAct);
}
if (!menu.actions().isEmpty()) {
menu.exec(m_snippetList->mapToGlobal(p_pos));
}
}
void VSnippetList::handleItemActivated(QListWidgetItem *p_item)
{
const VSnippet *snip = getSnippet(p_item);
if (snip) {
VEditTab *tab = g_mainWin->getCurrentTab();
if (tab) {
tab->applySnippet(snip);
}
}
}
void VSnippetList::deleteSelectedItems()
{
QVector<ConfirmItemInfo> items;
const QList<QListWidgetItem *> selectedItems = m_snippetList->selectedItems();
if (selectedItems.isEmpty()) {
return;
}
for (auto const & item : selectedItems) {
items.push_back(ConfirmItemInfo(item->text(),
item->text(),
"",
NULL));
}
QString text = tr("Are you sure to delete these snippets?");
QString info = tr("Click \"Cancel\" to leave them untouched.");
VConfirmDeletionDialog dialog(tr("Confirm Deleting Snippets"),
text,
info,
items,
false,
false,
false,
this);
if (dialog.exec()) {
items = dialog.getConfirmedItems();
QList<QString> names;
for (auto const & item : items) {
names.append(item.m_name);
}
QString errMsg;
if (!deleteSnippets(names, &errMsg)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to delete snippets."),
errMsg,
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
updateContent();
}
}
void VSnippetList::sortItems()
{
if (m_snippets.size() < 2) {
return;
}
VSortDialog dialog(tr("Sort Snippets"),
tr("Sort snippets in the configuration file."),
this);
QTreeWidget *tree = dialog.getTreeWidget();
tree->clear();
tree->setColumnCount(1);
QStringList headers;
headers << tr("Name");
tree->setHeaderLabels(headers);
for (int i = 0; i < m_snippets.size(); ++i) {
QTreeWidgetItem *item = new QTreeWidgetItem(tree, QStringList(m_snippets[i].getName()));
item->setData(0, Qt::UserRole, i);
}
dialog.treeUpdated();
if (dialog.exec()) {
QVector<QVariant> data = dialog.getSortedData();
Q_ASSERT(data.size() == m_snippets.size());
QVector<int> sortedIdx(data.size(), -1);
for (int i = 0; i < data.size(); ++i) {
sortedIdx[i] = data[i].toInt();
}
QString errMsg;
if (!sortSnippets(sortedIdx, &errMsg)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to sort snippets."),
errMsg,
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
updateContent();
}
}
void VSnippetList::snippetInfo()
{
if (m_snippetList->selectedItems().size() != 1) {
return;
}
QListWidgetItem *item = m_snippetList->currentItem();
VSnippet *snip = getSnippet(item);
if (!snip) {
return;
}
QString info = tr("Magic words are supported in the content of the snippet.");
VEditSnippetDialog dialog(tr("Snippet Information"),
info,
m_snippets,
*snip,
this);
if (dialog.exec() == QDialog::Accepted) {
QString errMsg;
bool ret = true;
if (snip->getName() != dialog.getNameInput()) {
// Delete the original snippet file.
if (!deleteSnippetFile(*snip, &errMsg)) {
ret = false;
}
}
if (snip->update(dialog.getNameInput(),
dialog.getTypeInput(),
dialog.getContentInput(),
dialog.getCursorMarkInput(),
dialog.getSelectionMarkInput(),
dialog.getShortcutInput())) {
if (!writeSnippetFile(*snip, &errMsg)) {
ret = false;
}
if (!writeSnippetsToConfig()) {
VUtils::addErrMsg(&errMsg,
tr("Fail to write snippets configuration file."));
ret = false;
}
updateContent();
}
if (!ret) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to update information of snippet <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(snip->getName()),
errMsg,
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
}
}
void VSnippetList::makeSureFolderExist() const
{
QString path = g_config->getSnippetConfigFolder();
if (!QFileInfo::exists(path)) {
QDir dir;
dir.mkpath(path);
}
}
void VSnippetList::updateContent()
{
m_snippetList->clear();
for (int i = 0; i < m_snippets.size(); ++i) {
const VSnippet &snip = m_snippets[i];
QListWidgetItem *item = new QListWidgetItem(snip.getName());
item->setToolTip(snip.getName());
item->setData(Qt::UserRole, snip.getName());
m_snippetList->addItem(item);
}
int cnt = m_snippetList->count();
if (cnt > 0) {
m_numLabel->setText(tr("%1 %2").arg(cnt)
.arg(cnt > 1 ? tr("Snippets") : tr("Snippet")));
m_snippetList->setFocus();
} else {
m_numLabel->setText("");
m_addBtn->setFocus();
}
}
bool VSnippetList::addSnippet(const VSnippet &p_snippet, QString *p_errMsg)
{
if (!writeSnippetFile(p_snippet, p_errMsg)) {
return false;
}
m_snippets.push_back(p_snippet);
bool ret = true;
if (!writeSnippetsToConfig()) {
VUtils::addErrMsg(p_errMsg,
tr("Fail to write snippets configuration file."));
m_snippets.pop_back();
ret = false;
}
updateContent();
return ret;
}
bool VSnippetList::writeSnippetsToConfig() const
{
makeSureFolderExist();
QJsonObject snippetJson;
snippetJson[SnippetConfig::c_version] = "1";
QJsonArray snippetArray;
for (int i = 0; i < m_snippets.size(); ++i) {
snippetArray.append(m_snippets[i].toJson());
}
snippetJson[SnippetConfig::c_snippets] = snippetArray;
return VUtils::writeJsonToDisk(g_config->getSnippetConfigFilePath(),
snippetJson);
}
bool VSnippetList::readSnippetsFromConfig()
{
m_snippets.clear();
if (!QFileInfo::exists(g_config->getSnippetConfigFilePath())) {
return true;
}
QJsonObject snippets = VUtils::readJsonFromDisk(g_config->getSnippetConfigFilePath());
if (snippets.isEmpty()) {
qWarning() << "invalid snippets configuration file" << g_config->getSnippetConfigFilePath();
return false;
}
// [snippets] section.
bool ret = true;
QJsonArray snippetArray = snippets[SnippetConfig::c_snippets].toArray();
for (int i = 0; i < snippetArray.size(); ++i) {
VSnippet snip = VSnippet::fromJson(snippetArray[i].toObject());
// Read the content.
QString filePath(QDir(g_config->getSnippetConfigFolder()).filePath(snip.getName()));
QString content = VUtils::readFileFromDisk(filePath);
if (content.isNull()) {
qWarning() << "fail to read snippet" << snip.getName();
ret = false;
continue;
}
snip.setContent(content);
m_snippets.push_back(snip);
}
return ret;
}
void VSnippetList::keyPressEvent(QKeyEvent *p_event)
{
if (VimNavigationForWidget::injectKeyPressEventForVim(m_snippetList,
p_event)) {
return;
}
QWidget::keyPressEvent(p_event);
}
void VSnippetList::showNavigation()
{
VNavigationMode::showNavigation(m_snippetList);
}
bool VSnippetList::handleKeyNavigation(int p_key, bool &p_succeed)
{
static bool secondKey = false;
return VNavigationMode::handleKeyNavigation(m_snippetList,
secondKey,
p_key,
p_succeed);
}
int VSnippetList::getSnippetIndex(QListWidgetItem *p_item) const
{
if (!p_item) {
return -1;
}
QString name = p_item->data(Qt::UserRole).toString();
for (int i = 0; i < m_snippets.size(); ++i) {
if (m_snippets[i].getName() == name) {
return i;
}
}
Q_ASSERT(false);
return -1;
}
VSnippet *VSnippetList::getSnippet(QListWidgetItem *p_item)
{
int idx = getSnippetIndex(p_item);
if (idx == -1) {
return NULL;
} else {
return &m_snippets[idx];
}
}
bool VSnippetList::writeSnippetFile(const VSnippet &p_snippet, QString *p_errMsg)
{
// Create and write to the snippet file.
QString filePath = getSnippetFilePath(p_snippet);
if (!VUtils::writeFileToDisk(filePath, p_snippet.getContent())) {
VUtils::addErrMsg(p_errMsg,
tr("Fail to add write the snippet file %1.")
.arg(filePath));
return false;
}
return true;
}
QString VSnippetList::getSnippetFilePath(const VSnippet &p_snippet) const
{
return QDir(g_config->getSnippetConfigFolder()).filePath(p_snippet.getName());
}
bool VSnippetList::sortSnippets(const QVector<int> &p_sortedIdx, QString *p_errMsg)
{
V_ASSERT(p_sortedIdx.size() == m_snippets.size());
auto ori = m_snippets;
for (int i = 0; i < p_sortedIdx.size(); ++i) {
m_snippets[i] = ori[p_sortedIdx[i]];
}
bool ret = true;
if (!writeSnippetsToConfig()) {
VUtils::addErrMsg(p_errMsg,
tr("Fail to write snippets configuration file."));
m_snippets = ori;
ret = false;
}
return ret;
}
bool VSnippetList::deleteSnippets(const QList<QString> &p_snippets,
QString *p_errMsg)
{
if (p_snippets.isEmpty()) {
return true;
}
bool ret = true;
QSet<QString> targets = QSet<QString>::fromList(p_snippets);
for (auto it = m_snippets.begin(); it != m_snippets.end();) {
if (targets.contains(it->getName())) {
// Remove it.
if (!deleteSnippetFile(*it, p_errMsg)) {
ret = false;
}
it = m_snippets.erase(it);
} else {
++it;
}
}
if (!writeSnippetsToConfig()) {
VUtils::addErrMsg(p_errMsg,
tr("Fail to write snippets configuration file."));
ret = false;
}
return ret;
}
bool VSnippetList::deleteSnippetFile(const VSnippet &p_snippet, QString *p_errMsg)
{
QString filePath = getSnippetFilePath(p_snippet);
if (!VUtils::deleteFile(filePath)) {
VUtils::addErrMsg(p_errMsg,
tr("Fail to remove snippet file %1.")
.arg(filePath));
return false;
}
return true;
}

98
src/vsnippetlist.h Normal file
View File

@ -0,0 +1,98 @@
#ifndef VSNIPPETLIST_H
#define VSNIPPETLIST_H
#include <QWidget>
#include <QVector>
#include <QPoint>
#include "vsnippet.h"
#include "vnavigationmode.h"
class QPushButton;
class QListWidget;
class QListWidgetItem;
class QLabel;
class QAction;
class QKeyEvent;
class VSnippetList : public QWidget, public VNavigationMode
{
Q_OBJECT
public:
explicit VSnippetList(QWidget *p_parent = nullptr);
// Implementations for VNavigationMode.
void showNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
private slots:
void newSnippet();
void handleContextMenuRequested(QPoint p_pos);
void handleItemActivated(QListWidgetItem *p_item);
void snippetInfo();
void deleteSelectedItems();
void sortItems();
private:
void setupUI();
void initActions();
void initShortcuts();
void makeSureFolderExist() const;
// Update list of snippets according to m_snippets.
void updateContent();
// Add @p_snippet.
bool addSnippet(const VSnippet &p_snippet, QString *p_errMsg = nullptr);
// Write m_snippets to config file.
bool writeSnippetsToConfig() const;
// Read from config file to m_snippets.
bool readSnippetsFromConfig();
// Get the snippet index in m_snippets of @p_item.
int getSnippetIndex(QListWidgetItem *p_item) const;
VSnippet *getSnippet(QListWidgetItem *p_item);
// Write the content of @p_snippet to file.
bool writeSnippetFile(const VSnippet &p_snippet, QString *p_errMsg = nullptr);
QString getSnippetFilePath(const VSnippet &p_snippet) const;
// Sort m_snippets according to @p_sortedIdx.
bool sortSnippets(const QVector<int> &p_sortedIdx, QString *p_errMsg = nullptr);
bool deleteSnippets(const QList<QString> &p_snippets, QString *p_errMsg = nullptr);
bool deleteSnippetFile(const VSnippet &p_snippet, QString *p_errMsg = nullptr);
QPushButton *m_addBtn;
QPushButton *m_locateBtn;
QLabel *m_numLabel;
QListWidget *m_snippetList;
QAction *m_applyAct;
QAction *m_infoAct;
QAction *m_deleteAct;
QAction *m_sortAct;
QVector<VSnippet> m_snippets;
static const QString c_infoShortcutSequence;
};
#endif // VSNIPPETLIST_H