improve Snippet support

This commit is contained in:
Le Tan 2021-07-08 21:31:13 +08:00
parent d1d8fabb60
commit 58e8ea5ee8
29 changed files with 694 additions and 35 deletions

@ -1 +1 @@
Subproject commit 98274148a0e1ad371f29abe072fac35bf5d7b6df
Subproject commit 7045758b2c9c10f6b72b97f15c40ded97db6ac0d

View File

@ -49,6 +49,7 @@ namespace vnotex
FindAndReplace,
FindNext,
FindPrevious,
ApplySnippet,
MaxShortcut
};
Q_ENUM(Shortcut)

View File

@ -117,4 +117,5 @@ QString MainConfig::getVersion(const QJsonObject &p_jobj)
void MainConfig::doVersionSpecificOverride()
{
// In a new version, we may want to change one value by force.
m_coreConfig->m_shortcuts[CoreConfig::Shortcut::SearchDock].clear();
}

View File

@ -17,8 +17,8 @@
"CloseTab" : "Ctrl+G, X",
"NavigationDock" : "Ctrl+G, A",
"OutlineDock" : "Ctrl+G, U",
"SearchDock" : "Ctrl+G, S",
"SnippetDock" : "",
"SearchDock" : "",
"SnippetDock" : "Ctrl+G, S",
"LocationListDock" : "Ctrl+G, L",
"Search" : "Ctrl+Alt+F",
"NavigationMode" : "Ctrl+G, W",
@ -93,7 +93,8 @@
"RichPaste" : "Ctrl+Shift+V",
"FindAndReplace" : "Ctrl+F",
"FindNext" : "F3",
"FindPrevious" : "Shift+F3"
"FindPrevious" : "Shift+F3",
"ApplySnippet" : "Ctrl+G, I"
},
"spell_check_auto_detect_language" : false,
"spell_check_default_dictionary" : "en_US"

View File

@ -9,6 +9,7 @@ DynamicSnippet::DynamicSnippet(const QString &p_name,
const Callback &p_callback)
: Snippet(p_name,
p_description,
QString(),
Snippet::InvalidShortcut,
false,
QString(),

View File

@ -16,6 +16,7 @@ Snippet::Snippet(const QString &p_name)
}
Snippet::Snippet(const QString &p_name,
const QString &p_description,
const QString &p_content,
int p_shortcut,
bool p_indentAsFirstLine,
@ -23,6 +24,7 @@ Snippet::Snippet(const QString &p_name,
const QString &p_selectionMark)
: m_type(Type::Text),
m_name(p_name),
m_description(p_description),
m_content(p_content),
m_shortcut(p_shortcut),
m_indentAsFirstLine(p_indentAsFirstLine),
@ -36,6 +38,7 @@ QJsonObject Snippet::toJson() const
QJsonObject obj;
obj[QStringLiteral("type")] = static_cast<int>(m_type);
obj[QStringLiteral("description")] = m_description;
obj[QStringLiteral("content")] = m_content;
obj[QStringLiteral("shortcut")] = m_shortcut;
obj[QStringLiteral("indent_as_first_line")] = m_indentAsFirstLine;
@ -48,6 +51,7 @@ QJsonObject Snippet::toJson() const
void Snippet::fromJson(const QJsonObject &p_jobj)
{
m_type = static_cast<Type>(p_jobj[QStringLiteral("type")].toInt());
m_description = p_jobj[QStringLiteral("description")].toString();
m_content = p_jobj[QStringLiteral("content")].toString();
m_shortcut = p_jobj[QStringLiteral("shortcut")].toInt();
m_indentAsFirstLine = p_jobj[QStringLiteral("indent_as_first_line")].toBool();
@ -104,6 +108,11 @@ const QString &Snippet::getContent() const
return m_content;
}
const QString &Snippet::getDescription() const
{
return m_description;
}
QString Snippet::apply(const QString &p_selectedText,
const QString &p_indentationSpaces,
int &p_cursorOffset)

View File

@ -24,6 +24,7 @@ namespace vnotex
explicit Snippet(const QString &p_name);
Snippet(const QString &p_name,
const QString &p_description,
const QString &p_content,
int p_shortcut,
bool p_indentAsFirstLine,
@ -43,6 +44,8 @@ namespace vnotex
const QString &getName() const;
const QString &getDescription() const;
Type getType() const;
int getShortcut() const;
@ -78,6 +81,8 @@ namespace vnotex
// To avoid mixed with shortcut, the name should not contain digits.
QString m_name;
QString m_description;
// Content of the snippet if it is Text.
// Embedded snippet is supported.
QString m_content;

View File

@ -2,6 +2,9 @@
#include <QRegExp>
#include <QFileInfo>
#include <QPixmap>
#include <QPainter>
#include <QDebug>
#include "fileutils.h"
@ -81,3 +84,40 @@ QIcon IconUtils::fetchIconWithDisabledState(const QString &p_iconFile)
colors.push_back(OverriddenColor(s_defaultIconDisabledForeground, QIcon::Disabled, QIcon::Off));
return fetchIcon(p_iconFile, colors);
}
QIcon IconUtils::drawTextIcon(const QString &p_text,
const QString &p_fg,
const QString &p_border)
{
const int wid = 64;
QPixmap pixmap(wid, wid);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
auto pen = painter.pen();
pen.setColor(p_border);
pen.setWidth(3);
painter.setPen(pen);
painter.drawRoundedRect(4, 4, wid - 8, wid - 8, 8, 8);
if (!p_text.isEmpty()) {
pen.setColor(p_fg);
painter.setPen(pen);
auto font = painter.font();
font.setPointSize(36);
painter.setFont(font);
auto requriedRect = painter.boundingRect(4, 4, wid - 8, wid - 8,
Qt::AlignCenter,
p_text);
painter.drawText(requriedRect, p_text);
}
QIcon icon;
icon.addPixmap(pixmap);
return icon;
}

View File

@ -46,6 +46,10 @@ namespace vnotex
static QIcon fetchIconWithDisabledState(const QString &p_iconFile);
static QIcon drawTextIcon(const QString &p_text,
const QString &p_fg,
const QString &p_border);
private:
static QString replaceForegroundOfIcon(const QString &p_iconContent, const QString &p_foreground);

View File

@ -130,6 +130,32 @@ bool WidgetUtils::processKeyEventLikeVi(QWidget *p_widget,
break;
}
case Qt::Key_H:
{
if (isViControlModifier(modifiers)) {
auto upEvent = new QKeyEvent(QEvent::KeyPress,
Qt::Key_Left,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, upEvent);
eventHandled = true;
}
break;
}
case Qt::Key_L:
{
if (isViControlModifier(modifiers)) {
auto upEvent = new QKeyEvent(QEvent::KeyPress,
Qt::Key_Right,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, upEvent);
eventHandled = true;
}
break;
}
default:
break;
}

View File

@ -20,12 +20,12 @@ DeleteConfirmDialog::DeleteConfirmDialog(const QString &p_title,
const QString &p_info,
const QVector<ConfirmItemInfo> &p_items,
DeleteConfirmDialog::Flags p_flags,
bool p_noAskChecked,
bool p_noAskAgainChecked,
QWidget *p_parent)
: ScrollDialog(p_parent),
m_items(p_items)
{
setupUI(p_title, p_text, p_info, p_flags, p_noAskChecked);
setupUI(p_title, p_text, p_info, p_flags, p_noAskAgainChecked);
updateItemsList();
@ -36,7 +36,7 @@ void DeleteConfirmDialog::setupUI(const QString &p_title,
const QString &p_text,
const QString &p_info,
DeleteConfirmDialog::Flags p_flags,
bool p_noAskChecked)
bool p_noAskAgainChecked)
{
auto mainWidget = new QWidget(this);
setCentralWidget(mainWidget);
@ -60,7 +60,7 @@ void DeleteConfirmDialog::setupUI(const QString &p_title,
// Ask again.
if (p_flags & Flag::AskAgain) {
m_noAskCB = new QCheckBox(tr("Do not ask again"), mainWidget);
m_noAskCB->setChecked(p_noAskChecked);
m_noAskCB->setChecked(p_noAskAgainChecked);
mainLayout->addWidget(m_noAskCB);
}

View File

@ -66,12 +66,12 @@ namespace vnotex
Q_DECLARE_FLAGS(Flags, Flag)
DeleteConfirmDialog(const QString &p_title,
const QString &p_text,
const QString &p_info,
const QVector<ConfirmItemInfo> &p_items,
DeleteConfirmDialog::Flags p_flags,
bool p_noAskChecked,
QWidget *p_parent = nullptr);
const QString &p_text,
const QString &p_info,
const QVector<ConfirmItemInfo> &p_items,
DeleteConfirmDialog::Flags p_flags,
bool p_noAskAgainChecked,
QWidget *p_parent = nullptr);
QVector<ConfirmItemInfo> getConfirmedItems() const;
@ -87,7 +87,7 @@ namespace vnotex
const QString &p_text,
const QString &p_info,
DeleteConfirmDialog::Flags p_flags,
bool p_noAskChecked);
bool p_noAskAgainChecked);
void updateItemsList();

View File

@ -51,6 +51,7 @@ void NewSnippetDialog::acceptedButtonClicked()
bool NewSnippetDialog::newSnippet()
{
auto snip = QSharedPointer<Snippet>::create(m_infoWidget->getName(),
m_infoWidget->getDescription(),
m_infoWidget->getContent(),
m_infoWidget->getShortcut(),
m_infoWidget->shouldIndentAsFirstLine(),

View File

@ -44,6 +44,11 @@ void SnippetInfoWidget::setupUI()
setFocusProxy(m_nameLineEdit);
m_descriptionLineEdit = WidgetsFactory::createLineEdit(this);
connect(m_descriptionLineEdit, &QLineEdit::textEdited,
this, &SnippetInfoWidget::inputEdited);
mainLayout->addRow(tr("Description:"), m_descriptionLineEdit);
setupTypeComboBox(this);
mainLayout->addRow(tr("Type:"), m_typeComboBox);
@ -134,6 +139,11 @@ QString SnippetInfoWidget::getContent() const
return m_contentTextEdit->toPlainText();
}
QString SnippetInfoWidget::getDescription() const
{
return m_descriptionLineEdit->text();
}
void SnippetInfoWidget::setSnippet(const Snippet *p_snippet)
{
if (m_snippet == p_snippet) {
@ -144,15 +154,26 @@ void SnippetInfoWidget::setSnippet(const Snippet *p_snippet)
m_snippet = p_snippet;
initShortcutComboBox();
if (m_snippet) {
const bool readOnly = m_snippet->isReadOnly();
m_nameLineEdit->setText(m_snippet->getName());
m_nameLineEdit->setEnabled(!readOnly);
m_descriptionLineEdit->setText(m_snippet->getDescription());
m_descriptionLineEdit->setEnabled(!readOnly);
m_typeComboBox->setCurrentIndex(m_typeComboBox->findData(static_cast<int>(m_snippet->getType())));
m_typeComboBox->setEnabled(!readOnly);
m_shortcutComboBox->setCurrentIndex(m_shortcutComboBox->findData(m_snippet->getShortcut()));
m_shortcutComboBox->setEnabled(!readOnly);
m_cursorMarkLineEdit->setText(m_snippet->getCursorMark());
m_cursorMarkLineEdit->setEnabled(!readOnly);
m_selectionMarkLineEdit->setText(m_snippet->getSelectionMark());
m_selectionMarkLineEdit->setEnabled(!readOnly);
m_indentAsFirstLineCheckBox->setChecked(m_snippet->isIndentAsFirstLineEnabled());
m_indentAsFirstLineCheckBox->setEnabled(!readOnly);
m_contentTextEdit->setPlainText(m_snippet->getContent());
m_contentTextEdit->setEnabled(!readOnly);
} else {
m_nameLineEdit->clear();
m_descriptionLineEdit->clear();
m_typeComboBox->setCurrentIndex(m_typeComboBox->findData(static_cast<int>(Snippet::Type::Text)));
m_shortcutComboBox->setCurrentIndex(m_shortcutComboBox->findData(Snippet::InvalidShortcut));
m_cursorMarkLineEdit->setText(Snippet::c_defaultCursorMark);

View File

@ -36,6 +36,8 @@ namespace vnotex
QString getContent() const;
QString getDescription() const;
signals:
void inputEdited();
@ -56,6 +58,8 @@ namespace vnotex
QLineEdit *m_nameLineEdit = nullptr;
QLineEdit *m_descriptionLineEdit = nullptr;
QComboBox *m_typeComboBox = nullptr;
QComboBox *m_shortcutComboBox = nullptr;

View File

@ -79,6 +79,7 @@ void SnippetPropertiesDialog::acceptedButtonClicked()
bool SnippetPropertiesDialog::saveSnippetProperties()
{
auto snip = QSharedPointer<Snippet>::create(m_infoWidget->getName(),
m_infoWidget->getDescription(),
m_infoWidget->getContent(),
m_infoWidget->getShortcut(),
m_infoWidget->shouldIndentAsFirstLine(),

View File

@ -0,0 +1,32 @@
#include "floatingwidget.h"
#include <QMenu>
using namespace vnotex;
FloatingWidget::FloatingWidget(QWidget *p_parent)
: QWidget(p_parent)
{
}
void FloatingWidget::showEvent(QShowEvent *p_event)
{
QWidget::showEvent(p_event);
// May fix potential input method issue.
activateWindow();
setFocus();
}
void FloatingWidget::finish()
{
if (m_menu) {
m_menu->hide();
}
}
void FloatingWidget::setMenu(QMenu *p_menu)
{
m_menu = p_menu;
}

View File

@ -0,0 +1,33 @@
#ifndef FLOATINGWIDGET_H
#define FLOATINGWIDGET_H
#include <QWidget>
#include <QVariant>
class QMenu;
namespace vnotex
{
// Used for ViewWindow to show as a floating widget (usually via QMenu).
class FloatingWidget : public QWidget
{
Q_OBJECT
public:
void setMenu(QMenu *p_menu);
virtual QVariant result() const = 0;
protected:
FloatingWidget(QWidget *p_parent = nullptr);
// Sub-class calls this to indicates completion.
void finish();
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
private:
QMenu *m_menu = nullptr;
};
}
#endif // FLOATINGWIDGET_H

View File

@ -31,7 +31,6 @@
#include "editors/statuswidget.h"
#include "editors/plantumlhelper.h"
#include "editors/graphvizhelper.h"
#include <snippet/snippetmgr.h>
using namespace vnotex;
@ -979,13 +978,25 @@ void MarkdownViewWindow::setupPreviewHelper()
void MarkdownViewWindow::applySnippet(const QString &p_name)
{
if (isReadMode() || m_editor->isReadOnly()) {
qWarning() << "failed to apply snippet in read mode or to a read-only buffer" << p_name;
if (isReadMode()) {
qWarning() << "failed to apply snippet in read mode" << p_name;
return;
}
m_editor->enterInsertModeIfApplicable();
SnippetMgr::getInst().applySnippet(p_name,
m_editor->getTextEdit(),
SnippetMgr::generateOverrides(getBuffer()));
TextViewWindowHelper::applySnippet(this, p_name);
}
void MarkdownViewWindow::applySnippet()
{
if (isReadMode()) {
qWarning() << "failed to apply snippet in read mode";
return;
}
TextViewWindowHelper::applySnippet(this);
}
QPoint MarkdownViewWindow::getFloatingWidgetPosition()
{
return TextViewWindowHelper::getFloatingWidgetPosition(this);
}

View File

@ -46,6 +46,8 @@ namespace vnotex
void applySnippet(const QString &p_name) Q_DECL_OVERRIDE;
void applySnippet() Q_DECL_OVERRIDE;
public slots:
void handleEditorConfigChange() Q_DECL_OVERRIDE;
@ -85,6 +87,8 @@ namespace vnotex
void zoom(bool p_zoomIn) Q_DECL_OVERRIDE;
QPoint getFloatingWidgetPosition() Q_DECL_OVERRIDE;
private:
void setupUI();

View File

@ -0,0 +1,233 @@
#include "quickselector.h"
#include <QVBoxLayout>
#include <QWidgetAction>
#include <QMenu>
#include <QLabel>
#include <QListWidgetItem>
#include <QKeyEvent>
#include <QDebug>
#include <QRegularExpression>
#include <utils/widgetutils.h>
#include <utils/iconutils.h>
#include "lineedit.h"
#include "listwidget.h"
#include "widgetsfactory.h"
using namespace vnotex;
QuickSelectorItem::QuickSelectorItem(const QVariant &p_key,
const QString &p_name,
const QString &p_tip,
const QString &p_shortcut)
: m_key(p_key),
m_name(p_name),
m_tip(p_tip),
m_shortcut(p_shortcut)
{
Q_ASSERT(m_shortcut.size() < 3);
}
static bool selectorItemCmp(const QuickSelectorItem &p_a, const QuickSelectorItem &p_b)
{
if (p_a.m_shortcut.isEmpty()) {
if (p_b.m_shortcut.isEmpty()) {
return p_a.m_name < p_b.m_name;
}
return false;
} else {
if (p_b.m_shortcut.isEmpty()) {
return true;
}
return p_a.m_shortcut < p_b.m_shortcut;
}
}
QuickSelector::QuickSelector(const QString &p_title,
const QVector<QuickSelectorItem> &p_items,
bool p_sortByShortcut,
QWidget *p_parent)
: FloatingWidget(p_parent),
m_items(p_items)
{
if (p_sortByShortcut) {
std::sort(m_items.begin(), m_items.end(), selectorItemCmp);
}
setupUI(p_title);
updateItemList();
}
void QuickSelector::setupUI(const QString &p_title)
{
auto mainLayout = new QVBoxLayout(this);
if (!p_title.isEmpty()) {
mainLayout->addWidget(new QLabel(p_title, this));
}
m_searchLineEdit = WidgetsFactory::createLineEdit(this);
connect(m_searchLineEdit, &QLineEdit::textEdited,
this, &QuickSelector::searchAndFilter);
mainLayout->addWidget(m_searchLineEdit);
setFocusProxy(m_searchLineEdit);
m_searchLineEdit->installEventFilter(this);
m_itemList = new ListWidget(this);
m_itemList->setWrapping(true);
m_itemList->setFlow(QListView::LeftToRight);
m_itemList->setIconSize(QSize(18, 18));
connect(m_itemList, &QListWidget::itemActivated,
this, &QuickSelector::activateItem);
mainLayout->addWidget(m_itemList);
m_itemList->installEventFilter(this);
}
void QuickSelector::updateItemList()
{
m_itemList->clear();
for (int i = 0; i < m_items.size(); ++i) {
const auto &item = m_items[i];
auto listItem = new QListWidgetItem(m_itemList);
auto icon = IconUtils::drawTextIcon(item.m_shortcut, "blue", "darkgreen");
listItem->setIcon(icon);
listItem->setText(item.m_name);
listItem->setToolTip(item.m_tip);
listItem->setData(Qt::UserRole, i);
}
Q_ASSERT(!m_items.isEmpty());
m_itemList->setCurrentRow(0);
}
void QuickSelector::activateItem(const QListWidgetItem *p_item)
{
if (p_item) {
m_selectedKey = getSelectorItem(p_item).m_key;
}
finish();
}
void QuickSelector::activate(const QuickSelectorItem *p_item)
{
m_selectedKey = p_item->m_key;
finish();
}
QuickSelectorItem &QuickSelector::getSelectorItem(const QListWidgetItem *p_item)
{
Q_ASSERT(p_item);
return m_items[p_item->data(Qt::UserRole).toInt()];
}
QVariant QuickSelector::result() const
{
return m_selectedKey;
}
bool QuickSelector::eventFilter(QObject *p_obj, QEvent *p_event)
{
if ((p_obj == m_searchLineEdit || p_obj == m_itemList)
&& p_event->type() == QEvent::KeyPress) {
auto keyEve = static_cast<QKeyEvent *>(p_event);
const auto key = keyEve->key();
if (key == Qt::Key_Tab || key == Qt::Key_Backtab) {
// Change focus.
if (p_obj == m_searchLineEdit) {
m_itemList->setFocus();
} else {
m_searchLineEdit->setFocus();
}
return true;
} else if (key == Qt::Key_Enter || key == Qt::Key_Return) {
if (p_obj == m_searchLineEdit) {
activateItem(m_itemList->currentItem());
return true;
}
}
}
return FloatingWidget::eventFilter(p_obj, p_event);
}
void QuickSelector::searchAndFilter(const QString &p_text)
{
auto text = p_text.trimmed();
if (text.isEmpty()) {
// Show all items.
filterItems([](const QuickSelectorItem &) {
return true;
});
return;
} else if (text.size() < 3) {
// Check shortcut first.
const QuickSelectorItem *hitItem = nullptr;
int ret = filterItems([&text, &hitItem](const QuickSelectorItem &p_item) {
if (p_item.m_shortcut == text) {
hitItem = &p_item;
return true;
} else if (p_item.m_shortcut.startsWith(text)) {
return true;
}
return false;
});
if (hitItem) {
activate(hitItem);
return;
}
if (ret > 0) {
return;
}
}
// Check name.
auto parts = text.split(QLatin1Char(' '), QString::SkipEmptyParts);
Q_ASSERT(!parts.isEmpty());
QRegularExpression regExp;
regExp.setPatternOptions(regExp.patternOptions() | QRegularExpression::CaseInsensitiveOption);
if (parts.size() == 1) {
regExp.setPattern(QRegularExpression::escape(parts[0]));
} else {
QString pattern = QRegularExpression::escape(parts[0]);
for (int i = 1; i < parts.size(); ++i) {
pattern += ".*" + QRegularExpression::escape(parts[i]);
}
regExp.setPattern(pattern);
}
filterItems([&regExp](const QuickSelectorItem &p_item) {
if (p_item.m_name.indexOf(regExp) != -1) {
return true;
}
return false;
});
}
int QuickSelector::filterItems(const std::function<bool(const QuickSelectorItem &)> &p_judge)
{
const int cnt = m_itemList->count();
int matchedCnt = 0;
int firstHit = -1;
for (int i = 0; i < cnt; ++i) {
auto item = m_itemList->item(i);
bool hit = p_judge(getSelectorItem(item));
if (hit) {
if (matchedCnt == 0) {
firstHit = i;
}
++matchedCnt;
}
item->setHidden(!hit);
}
m_itemList->setCurrentRow(firstHit);
return matchedCnt;
}

View File

@ -0,0 +1,74 @@
#ifndef QUICKSELECTOR_H
#define QUICKSELECTOR_H
#include "floatingwidget.h"
#include <QVariant>
#include <QVector>
class QLineEdit;
class QListWidget;
class QListWidgetItem;
namespace vnotex
{
struct QuickSelectorItem
{
QuickSelectorItem() = default;
QuickSelectorItem(const QVariant &p_key,
const QString &p_name,
const QString &p_tip,
const QString &p_shortcut);
QVariant m_key;
QString m_name;
QString m_tip;
// Empty or size < 3.
QString m_shortcut;
};
class QuickSelector : public FloatingWidget
{
Q_OBJECT
public:
QuickSelector(const QString &p_title,
const QVector<QuickSelectorItem> &p_items,
bool p_sortByShortcut,
QWidget *p_parent = nullptr);
QVariant result() const Q_DECL_OVERRIDE;
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
private:
void setupUI(const QString &p_title);
void updateItemList();
void activateItem(const QListWidgetItem *p_item);
void activate(const QuickSelectorItem *p_item);
void searchAndFilter(const QString &p_text);
// Return the number of items that hit @p_judge.
int filterItems(const std::function<bool(const QuickSelectorItem &)> &p_judge);
QuickSelectorItem &getSelectorItem(const QListWidgetItem *p_item);
QVector<QuickSelectorItem> m_items;
QLineEdit *m_searchLineEdit = nullptr;
QListWidget *m_itemList = nullptr;
QVariant m_selectedKey;
};
}
#endif // QUICKSELECTOR_H

View File

@ -103,6 +103,7 @@ void SnippetPanel::updateSnippetList()
}
item->setData(Qt::UserRole, snippet->getName());
item->setToolTip(snippet->getDescription());
}
updateItemsCountLabel();

View File

@ -15,7 +15,6 @@
#include <core/thememgr.h>
#include "editors/statuswidget.h"
#include <core/fileopenparameters.h>
#include <snippet/snippetmgr.h>
using namespace vnotex;
@ -257,13 +256,15 @@ ViewWindowSession TextViewWindow::saveSession() const
void TextViewWindow::applySnippet(const QString &p_name)
{
if (m_editor->isReadOnly()) {
qWarning() << "failed to apply snippet to a read-only buffer" << p_name;
return;
}
m_editor->enterInsertModeIfApplicable();
SnippetMgr::getInst().applySnippet(p_name,
m_editor->getTextEdit(),
SnippetMgr::generateOverrides(getBuffer()));
TextViewWindowHelper::applySnippet(this, p_name);
}
void TextViewWindow::applySnippet()
{
TextViewWindowHelper::applySnippet(this);
}
QPoint TextViewWindow::getFloatingWidgetPosition()
{
return TextViewWindowHelper::getFloatingWidgetPosition(this);
}

View File

@ -33,6 +33,8 @@ namespace vnotex
void applySnippet(const QString &p_name) Q_DECL_OVERRIDE;
void applySnippet() Q_DECL_OVERRIDE;
public slots:
void handleEditorConfigChange() Q_DECL_OVERRIDE;
@ -62,6 +64,8 @@ namespace vnotex
void zoom(bool p_zoomIn) Q_DECL_OVERRIDE;
QPoint getFloatingWidgetPosition() Q_DECL_OVERRIDE;
private:
void setupUI();

View File

@ -2,11 +2,17 @@
#define TEXTVIEWWINDOWHELPER_H
#include <QFileInfo>
#include <QTextCursor>
#include <QRegularExpression>
#include <QTextBlock>
#include <vtextedit/texteditorconfig.h>
#include <core/texteditorconfig.h>
#include <core/configmgr.h>
#include <utils/widgetutils.h>
#include <snippet/snippetmgr.h>
#include "quickselector.h"
namespace vnotex
{
@ -181,6 +187,104 @@ namespace vnotex
p_win->m_editor->clearIncrementalSearchHighlight();
p_win->m_editor->clearSearchHighlight();
}
template <typename _ViewWindow>
static void applySnippet(_ViewWindow *p_win, const QString &p_name)
{
if (p_win->m_editor->isReadOnly() || p_name.isEmpty()) {
qWarning() << "failed to apply snippet" << p_name << "to a read-only buffer";
return;
}
SnippetMgr::getInst().applySnippet(p_name,
p_win->m_editor->getTextEdit(),
SnippetMgr::generateOverrides(p_win->getBuffer()));
p_win->m_editor->enterInsertModeIfApplicable();
p_win->showMessage(ViewWindow::tr("Snippet applied: %1").arg(p_name));
}
template <typename _ViewWindow>
static void applySnippet(_ViewWindow *p_win)
{
if (p_win->m_editor->isReadOnly()) {
qWarning() << "failed to apply snippet to a read-only buffer";
return;
}
QString snippetName;
auto textEdit = p_win->m_editor->getTextEdit();
if (!textEdit->hasSelection()) {
// Fetch the snippet symbol containing current cursor.
auto cursor = textEdit->textCursor();
const auto block = cursor.block();
const auto text = block.text();
const int pib = cursor.positionInBlock();
QRegularExpression regExp(SnippetMgr::c_snippetSymbolRegExp);
QRegularExpressionMatch match;
int idx = text.lastIndexOf(regExp, pib, &match);
if (idx >= 0 && (idx + match.capturedLength(0) >= pib)) {
// Found one symbol under current cursor.
snippetName = match.captured(1);
if (!SnippetMgr::getInst().find(snippetName)) {
p_win->showMessage(ViewWindow::tr("Snippet (%1) not found").arg(snippetName));
return;
}
// Remove the symbol and apply snippet later.
cursor.setPosition(block.position() + idx);
cursor.setPosition(block.position() + idx + match.capturedLength(0), QTextCursor::KeepAnchor);
cursor.removeSelectedText();
textEdit->setTextCursor(cursor);
}
}
if (snippetName.isEmpty()) {
// Prompt for snippet.
snippetName = promptForSnippet(p_win);
}
if (!snippetName.isEmpty()) {
applySnippet(p_win, snippetName);
}
}
template <typename _ViewWindow>
static QString promptForSnippet(_ViewWindow *p_win)
{
const auto snippets = SnippetMgr::getInst().getSnippets();
if (snippets.isEmpty()) {
p_win->showMessage(ViewWindow::tr("Snippet not available"));
return QString();
}
QVector<QuickSelectorItem> items;
for (const auto &snip : snippets) {
items.push_back(QuickSelectorItem(snip->getName(),
snip->getName(),
snip->getDescription(),
snip->getShortcutString()));
}
// Ownership will be transferred to showFloatingWidget().
auto selector = new QuickSelector(ViewWindow::tr("Select Snippet"),
items,
true,
p_win);
auto ret = p_win->showFloatingWidget(selector);
return ret.toString();
}
template <typename _ViewWindow>
static QPoint getFloatingWidgetPosition(_ViewWindow *p_win)
{
auto textEdit = p_win->m_editor->getTextEdit();
auto localPos = textEdit->cursorRect().bottomRight();
if (!textEdit->rect().contains(localPos)) {
localPos = QPoint(5, 5);
}
return textEdit->mapToGlobal(localPos);
}
};
}

View File

@ -13,6 +13,7 @@
#include <QFocusEvent>
#include <QShortcut>
#include <QWheelEvent>
#include <QWidgetAction>
#include <core/fileopenparameters.h>
#include "toolbarhelper.h"
@ -33,6 +34,7 @@
#include "findandreplacewidget.h"
#include "editors/statuswidget.h"
#include "propertydefs.h"
#include "floatingwidget.h"
using namespace vnotex;
@ -865,6 +867,17 @@ void ViewWindow::setupShortcuts()
});
}
}
// ApplySnippet.
{
auto shortcut = WidgetUtils::createShortcut(editorConfig.getShortcut(EditorConfig::ApplySnippet), this, Qt::WidgetWithChildrenShortcut);
if (shortcut) {
connect(shortcut, &QShortcut::activated,
this, [this]() {
applySnippet();
});
}
}
}
void ViewWindow::wheelEvent(QWheelEvent *p_event)
@ -1108,3 +1121,24 @@ void ViewWindow::setWindowFlags(WindowFlags p_flags)
{
m_flags = p_flags;
}
QVariant ViewWindow::showFloatingWidget(FloatingWidget *p_widget)
{
// Show the widget through a QWidgetAction in menu.
QMenu menu;
auto act = new QWidgetAction(&menu);
// @act will own @p_widget.
act->setDefaultWidget(p_widget);
menu.addAction(act);
p_widget->setMenu(&menu);
menu.exec(getFloatingWidgetPosition());
return p_widget->result();
}
QPoint ViewWindow::getFloatingWidgetPosition()
{
return mapToGlobal(QPoint(5, 5));
}

View File

@ -25,6 +25,7 @@ namespace vnotex
class EditReadDiscardAction;
class FindAndReplaceWidget;
class StatusWidget;
class FloatingWidget;
class ViewWindow : public QFrame
{
@ -86,6 +87,12 @@ namespace vnotex
virtual void applySnippet(const QString &p_name) = 0;
virtual void applySnippet() = 0;
// Take ownership of @p_widget.
// Return the result from the FloatingWidget.
QVariant showFloatingWidget(FloatingWidget *p_widget);
public slots:
virtual void handleEditorConfigChange() = 0;
@ -163,9 +170,6 @@ namespace vnotex
virtual void handleFindAndReplaceWidgetOpened();
// Show message in status widget if exists. Otherwise, show it in the mainwindow's status widget.
void showMessage(const QString p_msg);
protected:
void setCentralWidget(QWidget *p_widget);
@ -228,6 +232,11 @@ namespace vnotex
void read(bool p_save);
// Show message in status widget if exists. Otherwise, show it in the mainwindow's status widget.
void showMessage(const QString p_msg);
virtual QPoint getFloatingWidgetPosition();
static QToolBar *createToolBar(QWidget *p_parent = nullptr);
// The revision of the buffer of the last sync content.

View File

@ -47,6 +47,7 @@ SOURCES += \
$$PWD/filesystemviewer.cpp \
$$PWD/dialogs/folderfilesfilterwidget.cpp \
$$PWD/findandreplacewidget.cpp \
$$PWD/floatingwidget.cpp \
$$PWD/fullscreentoggleaction.cpp \
$$PWD/lineedit.cpp \
$$PWD/lineeditdelegate.cpp \
@ -62,6 +63,7 @@ SOURCES += \
$$PWD/outlineprovider.cpp \
$$PWD/outlineviewer.cpp \
$$PWD/propertydefs.cpp \
$$PWD/quickselector.cpp \
$$PWD/searchinfoprovider.cpp \
$$PWD/searchpanel.cpp \
$$PWD/snippetpanel.cpp \
@ -151,6 +153,7 @@ HEADERS += \
$$PWD/filesystemviewer.h \
$$PWD/dialogs/folderfilesfilterwidget.h \
$$PWD/findandreplacewidget.h \
$$PWD/floatingwidget.h \
$$PWD/fullscreentoggleaction.h \
$$PWD/lineedit.h \
$$PWD/lineeditdelegate.h \
@ -167,6 +170,7 @@ HEADERS += \
$$PWD/outlineprovider.h \
$$PWD/outlineviewer.h \
$$PWD/propertydefs.h \
$$PWD/quickselector.h \
$$PWD/searchinfoprovider.h \
$$PWD/searchpanel.h \
$$PWD/snippetpanel.h \