mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 05:49:53 +08:00
improve Snippet support
This commit is contained in:
parent
d1d8fabb60
commit
58e8ea5ee8
@ -1 +1 @@
|
||||
Subproject commit 98274148a0e1ad371f29abe072fac35bf5d7b6df
|
||||
Subproject commit 7045758b2c9c10f6b72b97f15c40ded97db6ac0d
|
@ -49,6 +49,7 @@ namespace vnotex
|
||||
FindAndReplace,
|
||||
FindNext,
|
||||
FindPrevious,
|
||||
ApplySnippet,
|
||||
MaxShortcut
|
||||
};
|
||||
Q_ENUM(Shortcut)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -9,6 +9,7 @@ DynamicSnippet::DynamicSnippet(const QString &p_name,
|
||||
const Callback &p_callback)
|
||||
: Snippet(p_name,
|
||||
p_description,
|
||||
QString(),
|
||||
Snippet::InvalidShortcut,
|
||||
false,
|
||||
QString(),
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
32
src/widgets/floatingwidget.cpp
Normal file
32
src/widgets/floatingwidget.cpp
Normal 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;
|
||||
}
|
33
src/widgets/floatingwidget.h
Normal file
33
src/widgets/floatingwidget.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
233
src/widgets/quickselector.cpp
Normal file
233
src/widgets/quickselector.cpp
Normal 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([®Exp](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;
|
||||
}
|
74
src/widgets/quickselector.h
Normal file
74
src/widgets/quickselector.h
Normal 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
|
@ -103,6 +103,7 @@ void SnippetPanel::updateSnippetList()
|
||||
}
|
||||
|
||||
item->setData(Qt::UserRole, snippet->getName());
|
||||
item->setToolTip(snippet->getDescription());
|
||||
}
|
||||
|
||||
updateItemsCountLabel();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 \
|
||||
|
Loading…
x
Reference in New Issue
Block a user