Big QCodeEdit update: supports for autocomplete/help hint and custom data. Add dynamic links on Ctrl+mouse by 2 new virtual functions.

This commit is contained in:
2020-09-12 01:08:13 +03:00
parent be2870e116
commit aa695f8494
5 changed files with 144 additions and 45 deletions

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
cmake_policy(SET CMP0017 NEW) # need include() with .cmake cmake_policy(SET CMP0017 NEW) # need include() with .cmake
project(qad) project(qad)
set(qad_MAJOR 1) set(qad_MAJOR 1)
set(qad_MINOR 11) set(qad_MINOR 12)
set(qad_REVISION 0) set(qad_REVISION 0)
set(qad_SUFFIX ) set(qad_SUFFIX )
set(qad_COMPANY SHS) set(qad_COMPANY SHS)

View File

@@ -18,9 +18,11 @@
#include "ui_qcodeedit.h" #include "ui_qcodeedit.h"
Q_DECLARE_METATYPE(QTextCursor) Q_DECLARE_METATYPE(QTextCursor)
Q_DECLARE_METATYPE(QCodeEdit::ACEntry)
QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) { QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
qRegisterMetaType<QTextCursor>(); qRegisterMetaType<QTextCursor>();
qRegisterMetaType<QCodeEdit::ACEntry>();
ui = new Ui::QCodeEdit(); ui = new Ui::QCodeEdit();
ui->setupUi(this); ui->setupUi(this);
ui->widgetSearch->hide(); ui->widgetSearch->hide();
@@ -38,6 +40,8 @@ QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
es_search.format.setBackground(QColor(255, 240, 10)); es_search.format.setBackground(QColor(255, 240, 10));
es_range.format.setBackground(QColor(230, 246, 255)); es_range.format.setBackground(QColor(230, 246, 255));
es_range.format.setProperty(QTextFormat::FullWidthSelection, true); es_range.format.setProperty(QTextFormat::FullWidthSelection, true);
es_link.format.setForeground(Qt::blue);
es_link.format.setFontUnderline(true);
widget_help = new QFrame(); widget_help = new QFrame();
widget_help->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); widget_help->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
widget_help->setFocusPolicy(Qt::NoFocus); widget_help->setFocusPolicy(Qt::NoFocus);
@@ -45,18 +49,19 @@ QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
widget_help->setFrameShape(QFrame::StyledPanel); widget_help->setFrameShape(QFrame::StyledPanel);
widget_help->setLayout(new QBoxLayout(QBoxLayout::TopToBottom)); widget_help->setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
widget_help->layout()->setContentsMargins(0, 0, 0, 0); widget_help->layout()->setContentsMargins(0, 0, 0, 0);
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 3; ++i) {
lbl_help[i] = new IconedLabel(); lbl_help[i] = new IconedLabel();
lbl_help[i]->setFrameShadow(QFrame::Plain); lbl_help[i]->setFrameShadow(QFrame::Plain);
lbl_help[i]->setFrameShape(QFrame::NoFrame); lbl_help[i]->setFrameShape(QFrame::NoFrame);
lbl_help[i]->setDirection(IconedLabel::RightToLeft); lbl_help[i]->setDirection(IconedLabel::RightToLeft);
widget_help->layout()->addWidget(lbl_help[i]); widget_help->layout()->addWidget(lbl_help[i]);
} }
lbl_help[1]->setIcon(QIcon(":/icons/f1.png")); lbl_help[lhF1]->setIcon(QIcon(":/icons/f1.png"));
lbl_help[1]->setText(tr("Press F1 for details")); lbl_help[lhF1]->setText(tr("Press F1 for details"));
completer = new QCodeEditCompleter(); completer = new QCodeEditCompleter();
ui->textCode->setCursorWidth(qMax<int>(qRound(fontHeight() / 10.), 1)); ui->textCode->setCursorWidth(qMax<int>(qRound(fontHeight() / 10.), 1));
ui->textCode->viewport()->setMouseTracking(true);
ui->textLines->viewport()->setAutoFillBackground(false); ui->textLines->viewport()->setAutoFillBackground(false);
ui->textLines->viewport()->setCursor(Qt::ArrowCursor); ui->textLines->viewport()->setCursor(Qt::ArrowCursor);
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
@@ -246,6 +251,11 @@ QPlainTextEdit * QCodeEdit::textEdit() const {
} }
void QCodeEdit::registerAutoCompletitionClass(int id, QCodeEdit::ACClassType ac_class, const QString & name, const QIcon & icon) {
ac_classes[id] = ACClass(id, ac_class, name, icon);
}
int QCodeEdit::skipRange(const QString & s, int pos, QChar oc, QChar cc, QChar sc) { int QCodeEdit::skipRange(const QString & s, int pos, QChar oc, QChar cc, QChar sc) {
int cnt = 0; int cnt = 0;
bool skip = false; bool skip = false;
@@ -400,6 +410,18 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
completer->hide(); completer->hide();
hideHelp(); hideHelp();
} }
if (e->type() == QEvent::MouseMove) {
QMouseEvent * me = (QMouseEvent*)e;
if (me->modifiers().testFlag(Qt::ControlModifier)) {
showLink();
}
}
if (e->type() == QEvent::MouseButtonPress) {
QMouseEvent * me = (QMouseEvent*)e;
if (me->modifiers().testFlag(Qt::ControlModifier) && (me->button() == Qt::LeftButton)) {
gotoLink();
}
}
return QWidget::eventFilter(o, e); return QWidget::eventFilter(o, e);
} }
if (o == ui->textCode) { if (o == ui->textCode) {
@@ -503,6 +525,9 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
return true; return true;
} }
break; break;
case Qt::Key_Control:
showLink();
break;
default: break; default: break;
} }
if (!ke->text().isEmpty()) if (!ke->text().isEmpty())
@@ -515,6 +540,9 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false));
} }
break; break;
case QEvent::KeyRelease:
hideLink();
break;
case QEvent::FocusOut: case QEvent::FocusOut:
if (_ignore_focus_out) { if (_ignore_focus_out) {
_ignore_focus_out = false; _ignore_focus_out = false;
@@ -522,6 +550,7 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
} }
case QEvent::Hide: case QEvent::Hide:
case QEvent::HideToParent: case QEvent::HideToParent:
hideLink();
case QEvent::MouseButtonPress: case QEvent::MouseButtonPress:
//qDebug() << e; //qDebug() << e;
completer->hide(); completer->hide();
@@ -563,7 +592,7 @@ void QCodeEdit::changeEvent(QEvent * e) {
switch (e->type()) { switch (e->type()) {
case QEvent::LanguageChange: case QEvent::LanguageChange:
ui->retranslateUi(this); ui->retranslateUi(this);
lbl_help[1]->setText(tr("Press F1 for details")); lbl_help[lhF1]->setText(tr("Press F1 for details"));
break; break;
default: default:
break; break;
@@ -628,7 +657,7 @@ void QCodeEdit::highlightBrackets() {
void QCodeEdit::applyExtraSelection() { void QCodeEdit::applyExtraSelection() {
ui->textCode->setExtraSelections(QList<QTextEdit::ExtraSelection>() << es_line << es_selected ui->textCode->setExtraSelections(QList<QTextEdit::ExtraSelection>() << es_line << es_selected
<< es_custom << es_brackets << es_search_list << es_cursor); << es_custom << es_brackets << es_search_list << es_cursor << es_link);
} }
@@ -953,6 +982,41 @@ void QCodeEdit::autoIndent() {
} }
void QCodeEdit::showLink() {
QTextCursor tc = ui->textCode->cursorForPosition(ui->textCode->mapFromGlobal(QCursor::pos()));
tc.select(QTextCursor::WordUnderCursor);
link_entry = findEntryOnCursor(tc);
if (link_entry.isNull()) {
hideLink();
return;
}
if (!linkValidate(link_entry)) {
hideLink();
return;
}
es_link.cursor = tc;
ui->textCode->viewport()->setCursor(tc.isNull() ? Qt::IBeamCursor : Qt::PointingHandCursor);
applyExtraSelection();
//qDebug() << "showLink" << tc.selectedText() << link_entry.type << link_entry.name;
}
void QCodeEdit::hideLink() {
es_link.cursor = QTextCursor();
ui->textCode->viewport()->setCursor(Qt::IBeamCursor);
link_entry = ACEntry();
applyExtraSelection();
//qDebug() << "hideLink";
}
void QCodeEdit::gotoLink() {
if (link_entry.isNull()) return;
QMetaObject::invokeMethod(this, "_activateLink", Qt::QueuedConnection, Q_ARG(QCodeEdit::ACEntry, link_entry));
hideLink();
}
void QCodeEdit::scrollToTop() { void QCodeEdit::scrollToTop() {
prev_lc = -1; prev_lc = -1;
updateLines(); updateLines();
@@ -1036,20 +1100,20 @@ QString QCodeEdit::selectArg(QString s, int arg) {
} }
void QCodeEdit::raiseHelp(QTextCursor tc, int arg) { QCodeEdit::ACEntry QCodeEdit::findEntryOnCursor(QTextCursor tc, int arg, ACClass * acc, QPair<QStringList, QString> * scope) {
bool ok; bool ok = false;
QPair<QStringList, QString> scope = getScope(tc, &ok); QPair<QStringList, QString> sc = getScope(tc, &ok);
if (scope) *scope = sc;
QString st = tc.selectedText(); QString st = tc.selectedText();
if (arg >= 0) st = scope.second; if (arg >= 0) st = sc.second;
if (!ok || st.isEmpty()) { if (!ok || st.isEmpty()) {
hideHelp(); return ACEntry();
return;
} }
ok = false; ok = false;
ACList acl(autoCompletitionList(scope.first, scope.second)); ACList acl(autoCompletitionList(sc.first, sc.second));
foreach (const ACPair & i, acl) { foreach (const ACSection & i, acl) {
foreach (const StringsPair & s, i.second) { foreach (const ACEntry & s, i.second) {
QString ts = s.second; QString ts = s.name;
//qDebug() << ts << st; //qDebug() << ts << st;
if (ts != st) { if (ts != st) {
if (ts.startsWith(st)) { if (ts.startsWith(st)) {
@@ -1063,31 +1127,43 @@ void QCodeEdit::raiseHelp(QTextCursor tc, int arg) {
continue; continue;
} }
//qDebug() << s.second << st; //qDebug() << s.second << st;
ACClass acc = ac_classes.value(i.first); if (acc) *acc = ac_classes.value(i.first);
lbl_help[0]->setIcon(acc.icon); return s;
lbl_help[0]->setText(QString("<html><body>[%1] %2 %3</html></body>").arg(acc.name, s.first, selectArg(s.second, arg)));
ok = true;
break;
} }
if (ok) break;
} }
if (!ok) { return ACEntry();
}
void QCodeEdit::raiseHelp(QTextCursor tc, int arg) {
ACClass acc;
QPair<QStringList, QString> scope;
ACEntry e = findEntryOnCursor(tc, arg, &acc, &scope);
if (e.isNull()) {
hideHelp(); hideHelp();
return; return;
} }
lbl_help[lhMain]->setText(QString("<html><body>[%1] %2 %3</html></body>").arg(acc.name, e.type, selectArg(e.name, arg)));
lbl_help[lhMain]->setIcon(acc.icon);
QString hint;
if (!e.hint.isEmpty())
hint = QString("<span style=\"font-style:italic;\">%1</span>").arg(e.hint);
lbl_help[lhHint]->setText(hint);
lbl_help[lhHint]->setHidden(hint.isEmpty());
//qDebug() << "help found" << tc.selectionStart() << tc.selectionEnd(); //qDebug() << "help found" << tc.selectionStart() << tc.selectionEnd();
es_cursor.cursor = tc; es_cursor.cursor = tc;
applyExtraSelection(); applyExtraSelection();
//tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); //tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
lbl_help[0]->setFont(font()); lbl_help[lhMain]->setFont(font());
qApp->processEvents(); qApp->processEvents();
widget_help->adjustSize();
widget_help->resize(widget_help->sizeHint()); widget_help->resize(widget_help->sizeHint());
qApp->processEvents(); qApp->processEvents();
QRect whr = ui->textCode->cursorRect(tc); QRect whr = ui->textCode->cursorRect(tc);
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
whr.setWidth(ui->textCode->fontMetrics().horizontalAdvance(st)); whr.setWidth(ui->textCode->fontMetrics().horizontalAdvance(tc.selectedText()));
#else #else
whr.setWidth(ui->textCode->fontMetrics().width(st)); whr.setWidth(ui->textCode->fontMetrics().width(tc.selectedText()));
#endif #endif
QPoint whp; QPoint whp;
whp.setX(whr.left() - whr.width() - (widget_help->width() - whr.width()) / 2); whp.setX(whr.left() - whr.width() - (widget_help->width() - whr.width()) / 2);
@@ -1149,10 +1225,10 @@ QCodeEdit::ACList QCodeEdit::wordsCompletitionList(const QString & written) cons
acwl << stc.selectedText(); acwl << stc.selectedText();
} }
acwl.removeDuplicates(); acwl.removeDuplicates();
ACPair acl; ACSection acl;
acl.first = -1; acl.first = -1;
foreach (const QString & s, acwl) foreach (const QString & s, acwl)
acl.second << StringsPair("", s); acl.second << ACEntry("", s);
ret << acl; ret << acl;
} }
return ret; return ret;
@@ -1234,7 +1310,7 @@ void QCodeEdit::invokeAutoCompletition(bool force) {
if (word_complet) acl << wordsCompletitionList(scope.second); if (word_complet) acl << wordsCompletitionList(scope.second);
QFont bf(font()); QFont bf(font());
bf.setBold(true); bf.setBold(true);
foreach (const ACPair & ac, acl) { foreach (const ACSection & ac, acl) {
if (ac.second.isEmpty()) continue; if (ac.second.isEmpty()) continue;
ACClass acc = ac_classes.value(ac.first); ACClass acc = ac_classes.value(ac.first);
completer->addItems(bf, acc, ac); completer->addItems(bf, acc, ac);

View File

@@ -54,7 +54,17 @@ public:
Function, Function,
Namespace Namespace
}; };
struct QAD_WIDGETS_EXPORT ACEntry {
ACEntry(const QString & t = QString(), const QString & n = QString(), const QString & h = QString()):
type(t), name(n), hint(h) {declaration_pos = -1; custom_data = 0;}
bool isNull() const {return type.isEmpty() && name.isEmpty();}
QString type;
QString name;
QString hint;
int declaration_pos;
void * custom_data;
};
QTextCursor textCursor() const; QTextCursor textCursor() const;
QTextDocument * document() const; QTextDocument * document() const;
void setDocument(QTextDocument * doc); void setDocument(QTextDocument * doc);
@@ -75,19 +85,22 @@ public:
QPlainTextEdit * textEdit() const; QPlainTextEdit * textEdit() const;
void registerAutoCompletitionClass(int id, ACClassType ac_class, const QString & name, const QIcon & icon = QIcon()) {ac_classes[id] = ACClass(id, ac_class, name, icon);} void registerAutoCompletitionClass(int id, ACClassType ac_class, const QString & name, const QIcon & icon = QIcon());
bool wordCompletitionEnabled() const {return word_complet;} bool wordCompletitionEnabled() const {return word_complet;}
protected: protected:
typedef QPair<QString, QString> StringsPair; typedef QPair<QString, QString> StringsPair;
typedef QPair<int, QList<StringsPair> > ACPair; typedef QPair<int, QList<ACEntry> > ACSection; // section, ACClass.id -> list of entries
typedef QList<ACPair> ACList; typedef QList<ACSection> ACList; // list of sections
virtual ACList autoCompletitionList(const QStringList & scope, const QString & written) const {return ACList();} virtual ACList autoCompletitionList(const QStringList & scope, const QString & written) const {return ACList();}
virtual void parse() {} virtual void parse() {}
virtual void documentUnset() {} virtual void documentUnset() {}
virtual void documentChanged(QTextDocument * d) {} virtual void documentChanged(QTextDocument * d) {}
virtual void linkClicked(ACEntry entry) {}
virtual bool linkValidate(ACEntry e) {return true;}
QString selectArg(QString s, int arg); QString selectArg(QString s, int arg);
void raiseHelp(QTextCursor tc, int arg = -1); void raiseHelp(QTextCursor tc, int arg = -1);
QTextCursor functionStart(QTextCursor tc, int * arg); QTextCursor functionStart(QTextCursor tc, int * arg);
@@ -108,30 +121,38 @@ private:
QString name; QString name;
QIcon icon; QIcon icon;
}; };
enum LabelHelpType {
lhMain,
lhHint,
lhF1
};
QCodeEditCompleter * completer; QCodeEditCompleter * completer;
IconedLabel * lbl_help[2]; IconedLabel * lbl_help[3];
QFrame * widget_help; QFrame * widget_help;
QTextEdit::ExtraSelection es_line, es_cursor, es_bracket, es_range, es_search; QTextEdit::ExtraSelection es_line, es_cursor, es_bracket, es_range, es_search, es_link;
QList<QTextEdit::ExtraSelection> es_selected, es_custom, es_brackets, es_search_list; QList<QTextEdit::ExtraSelection> es_selected, es_custom, es_brackets, es_search_list;
QMap<int, ACClass> ac_classes; QMap<int, ACClass> ac_classes;
QStringList cursor_scope; QStringList cursor_scope;
ACEntry link_entry;
int prev_lc, auto_comp_pl, timer, cur_search_ind, pos_press, pos_el_press; int prev_lc, auto_comp_pl, timer, cur_search_ind, pos_press, pos_el_press;
bool spaces_, _ignore_focus_out, _first, _destructor, _replacing; bool spaces_, _ignore_focus_out, _first, _destructor, _replacing;
bool word_complet; bool word_complet;
bool eventFilter(QObject * o, QEvent * e); bool eventFilter(QObject * o, QEvent * e) override;
void showEvent(QShowEvent * ); void showEvent(QShowEvent * ) override;
void timerEvent(QTimerEvent * ); void timerEvent(QTimerEvent * ) override;
void leaveEvent(QEvent * ); void leaveEvent(QEvent * ) override;
void changeEvent(QEvent * e); void changeEvent(QEvent * e) override;
void highlightBrackets(); void highlightBrackets();
void applyExtraSelection(); void applyExtraSelection();
void clearSearch(); void clearSearch();
void moveToSearch(); void moveToSearch();
int searchIndFromCursor(); int searchIndFromCursor();
ACEntry findEntryOnCursor(QTextCursor tc, int arg = -1, ACClass * acc = 0, QPair<QStringList, QString> * scope = 0);
private slots: private slots:
void _activateLink(QCodeEdit::ACEntry e) {linkClicked(e);}
void syncScrolls(); void syncScrolls();
void scrollUp(); void scrollUp();
void scrollDown(); void scrollDown();
@@ -144,6 +165,9 @@ private slots:
void indent(); void indent();
void deindent(); void deindent();
void autoIndent(); void autoIndent();
void showLink();
void hideLink();
void gotoLink();
void invokeAutoCompletition(bool force = false); void invokeAutoCompletition(bool force = false);
void commitCompletition(); void commitCompletition();
void searchAll(); void searchAll();
@@ -170,7 +194,6 @@ public slots:
void searchPrevious(); void searchPrevious();
void hideSearch(); void hideSearch();
void setWordCompletitionEnabled(bool on) {word_complet = on;} void setWordCompletitionEnabled(bool on) {word_complet = on;}
signals: signals:
void textChanged(); void textChanged();

View File

@@ -50,7 +50,7 @@ void QCodeEditCompleter::previousCompletition() {
} }
} }
void QCodeEditCompleter::addItems(QFont f, const QCodeEdit::ACClass & cl, const QCodeEdit::ACPair & items) { void QCodeEditCompleter::addItems(QFont f, const QCodeEdit::ACClass & cl, const QCodeEdit::ACSection & items) {
QTreeWidgetItem * gi = new QTreeWidgetItem(); QTreeWidgetItem * gi = new QTreeWidgetItem();
gi->setText(0, cl.name); gi->setText(0, cl.name);
gi->setTextAlignment(0, Qt::AlignCenter); gi->setTextAlignment(0, Qt::AlignCenter);
@@ -63,8 +63,8 @@ void QCodeEditCompleter::addItems(QFont f, const QCodeEdit::ACClass & cl, const
foreach (const auto & s, items.second) { foreach (const auto & s, items.second) {
QTreeWidgetItem * ni = new QTreeWidgetItem(); QTreeWidgetItem * ni = new QTreeWidgetItem();
ni->setIcon(0, cl.icon); ni->setIcon(0, cl.icon);
ni->setText(0, s.first); ni->setText(0, s.type);
ni->setText(1, s.second); ni->setText(1, s.name);
addTopLevelItem(ni); addTopLevelItem(ni);
} }
} }

View File

@@ -33,7 +33,7 @@ public:
void nextCompletition(); void nextCompletition();
void previousCompletition(); void previousCompletition();
void addItems(QFont f, const QCodeEdit::ACClass & cl, const QCodeEdit::ACPair & items); void addItems(QFont f, const QCodeEdit::ACClass & cl, const QCodeEdit::ACSection & items);
bool isEmpty() const; bool isEmpty() const;
void invoke(QPoint global_pos); void invoke(QPoint global_pos);