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
project(qad)
set(qad_MAJOR 1)
set(qad_MINOR 11)
set(qad_MINOR 12)
set(qad_REVISION 0)
set(qad_SUFFIX )
set(qad_COMPANY SHS)

View File

@@ -18,9 +18,11 @@
#include "ui_qcodeedit.h"
Q_DECLARE_METATYPE(QTextCursor)
Q_DECLARE_METATYPE(QCodeEdit::ACEntry)
QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
qRegisterMetaType<QTextCursor>();
qRegisterMetaType<QCodeEdit::ACEntry>();
ui = new Ui::QCodeEdit();
ui->setupUi(this);
ui->widgetSearch->hide();
@@ -38,6 +40,8 @@ QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
es_search.format.setBackground(QColor(255, 240, 10));
es_range.format.setBackground(QColor(230, 246, 255));
es_range.format.setProperty(QTextFormat::FullWidthSelection, true);
es_link.format.setForeground(Qt::blue);
es_link.format.setFontUnderline(true);
widget_help = new QFrame();
widget_help->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
widget_help->setFocusPolicy(Qt::NoFocus);
@@ -45,18 +49,19 @@ QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
widget_help->setFrameShape(QFrame::StyledPanel);
widget_help->setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
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]->setFrameShadow(QFrame::Plain);
lbl_help[i]->setFrameShape(QFrame::NoFrame);
lbl_help[i]->setDirection(IconedLabel::RightToLeft);
widget_help->layout()->addWidget(lbl_help[i]);
}
lbl_help[1]->setIcon(QIcon(":/icons/f1.png"));
lbl_help[1]->setText(tr("Press F1 for details"));
lbl_help[lhF1]->setIcon(QIcon(":/icons/f1.png"));
lbl_help[lhF1]->setText(tr("Press F1 for details"));
completer = new QCodeEditCompleter();
ui->textCode->setCursorWidth(qMax<int>(qRound(fontHeight() / 10.), 1));
ui->textCode->viewport()->setMouseTracking(true);
ui->textLines->viewport()->setAutoFillBackground(false);
ui->textLines->viewport()->setCursor(Qt::ArrowCursor);
#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 cnt = 0;
bool skip = false;
@@ -400,6 +410,18 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
completer->hide();
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);
}
if (o == ui->textCode) {
@@ -503,6 +525,9 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
return true;
}
break;
case Qt::Key_Control:
showLink();
break;
default: break;
}
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));
}
break;
case QEvent::KeyRelease:
hideLink();
break;
case QEvent::FocusOut:
if (_ignore_focus_out) {
_ignore_focus_out = false;
@@ -522,6 +550,7 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
}
case QEvent::Hide:
case QEvent::HideToParent:
hideLink();
case QEvent::MouseButtonPress:
//qDebug() << e;
completer->hide();
@@ -563,7 +592,7 @@ void QCodeEdit::changeEvent(QEvent * e) {
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
lbl_help[1]->setText(tr("Press F1 for details"));
lbl_help[lhF1]->setText(tr("Press F1 for details"));
break;
default:
break;
@@ -628,7 +657,7 @@ void QCodeEdit::highlightBrackets() {
void QCodeEdit::applyExtraSelection() {
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() {
prev_lc = -1;
updateLines();
@@ -1036,20 +1100,20 @@ QString QCodeEdit::selectArg(QString s, int arg) {
}
void QCodeEdit::raiseHelp(QTextCursor tc, int arg) {
bool ok;
QPair<QStringList, QString> scope = getScope(tc, &ok);
QCodeEdit::ACEntry QCodeEdit::findEntryOnCursor(QTextCursor tc, int arg, ACClass * acc, QPair<QStringList, QString> * scope) {
bool ok = false;
QPair<QStringList, QString> sc = getScope(tc, &ok);
if (scope) *scope = sc;
QString st = tc.selectedText();
if (arg >= 0) st = scope.second;
if (arg >= 0) st = sc.second;
if (!ok || st.isEmpty()) {
hideHelp();
return;
return ACEntry();
}
ok = false;
ACList acl(autoCompletitionList(scope.first, scope.second));
foreach (const ACPair & i, acl) {
foreach (const StringsPair & s, i.second) {
QString ts = s.second;
ACList acl(autoCompletitionList(sc.first, sc.second));
foreach (const ACSection & i, acl) {
foreach (const ACEntry & s, i.second) {
QString ts = s.name;
//qDebug() << ts << st;
if (ts != st) {
if (ts.startsWith(st)) {
@@ -1063,31 +1127,43 @@ void QCodeEdit::raiseHelp(QTextCursor tc, int arg) {
continue;
}
//qDebug() << s.second << st;
ACClass acc = ac_classes.value(i.first);
lbl_help[0]->setIcon(acc.icon);
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 (acc) *acc = ac_classes.value(i.first);
return s;
}
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();
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();
es_cursor.cursor = tc;
applyExtraSelection();
//tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
lbl_help[0]->setFont(font());
lbl_help[lhMain]->setFont(font());
qApp->processEvents();
widget_help->adjustSize();
widget_help->resize(widget_help->sizeHint());
qApp->processEvents();
QRect whr = ui->textCode->cursorRect(tc);
#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
whr.setWidth(ui->textCode->fontMetrics().width(st));
whr.setWidth(ui->textCode->fontMetrics().width(tc.selectedText()));
#endif
QPoint whp;
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.removeDuplicates();
ACPair acl;
ACSection acl;
acl.first = -1;
foreach (const QString & s, acwl)
acl.second << StringsPair("", s);
acl.second << ACEntry("", s);
ret << acl;
}
return ret;
@@ -1234,7 +1310,7 @@ void QCodeEdit::invokeAutoCompletition(bool force) {
if (word_complet) acl << wordsCompletitionList(scope.second);
QFont bf(font());
bf.setBold(true);
foreach (const ACPair & ac, acl) {
foreach (const ACSection & ac, acl) {
if (ac.second.isEmpty()) continue;
ACClass acc = ac_classes.value(ac.first);
completer->addItems(bf, acc, ac);

View File

@@ -54,7 +54,17 @@ public:
Function,
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;
QTextDocument * document() const;
void setDocument(QTextDocument * doc);
@@ -75,19 +85,22 @@ public:
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;}
protected:
typedef QPair<QString, QString> StringsPair;
typedef QPair<int, QList<StringsPair> > ACPair;
typedef QList<ACPair> ACList;
typedef QPair<int, QList<ACEntry> > ACSection; // section, ACClass.id -> list of entries
typedef QList<ACSection> ACList; // list of sections
virtual ACList autoCompletitionList(const QStringList & scope, const QString & written) const {return ACList();}
virtual void parse() {}
virtual void documentUnset() {}
virtual void documentChanged(QTextDocument * d) {}
virtual void linkClicked(ACEntry entry) {}
virtual bool linkValidate(ACEntry e) {return true;}
QString selectArg(QString s, int arg);
void raiseHelp(QTextCursor tc, int arg = -1);
QTextCursor functionStart(QTextCursor tc, int * arg);
@@ -108,30 +121,38 @@ private:
QString name;
QIcon icon;
};
enum LabelHelpType {
lhMain,
lhHint,
lhF1
};
QCodeEditCompleter * completer;
IconedLabel * lbl_help[2];
IconedLabel * lbl_help[3];
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;
QMap<int, ACClass> ac_classes;
QStringList cursor_scope;
ACEntry link_entry;
int prev_lc, auto_comp_pl, timer, cur_search_ind, pos_press, pos_el_press;
bool spaces_, _ignore_focus_out, _first, _destructor, _replacing;
bool word_complet;
bool eventFilter(QObject * o, QEvent * e);
void showEvent(QShowEvent * );
void timerEvent(QTimerEvent * );
void leaveEvent(QEvent * );
void changeEvent(QEvent * e);
bool eventFilter(QObject * o, QEvent * e) override;
void showEvent(QShowEvent * ) override;
void timerEvent(QTimerEvent * ) override;
void leaveEvent(QEvent * ) override;
void changeEvent(QEvent * e) override;
void highlightBrackets();
void applyExtraSelection();
void clearSearch();
void moveToSearch();
int searchIndFromCursor();
ACEntry findEntryOnCursor(QTextCursor tc, int arg = -1, ACClass * acc = 0, QPair<QStringList, QString> * scope = 0);
private slots:
void _activateLink(QCodeEdit::ACEntry e) {linkClicked(e);}
void syncScrolls();
void scrollUp();
void scrollDown();
@@ -144,6 +165,9 @@ private slots:
void indent();
void deindent();
void autoIndent();
void showLink();
void hideLink();
void gotoLink();
void invokeAutoCompletition(bool force = false);
void commitCompletition();
void searchAll();
@@ -170,7 +194,6 @@ public slots:
void searchPrevious();
void hideSearch();
void setWordCompletitionEnabled(bool on) {word_complet = on;}
signals:
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();
gi->setText(0, cl.name);
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) {
QTreeWidgetItem * ni = new QTreeWidgetItem();
ni->setIcon(0, cl.icon);
ni->setText(0, s.first);
ni->setText(1, s.second);
ni->setText(0, s.type);
ni->setText(1, s.name);
addTopLevelItem(ni);
}
}

View File

@@ -33,7 +33,7 @@ public:
void nextCompletition();
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;
void invoke(QPoint global_pos);