From effa8027cc63a05aa18dc02858888c14f3530895 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 14 Feb 2021 23:12:50 +0100 Subject: [PATCH] Implemented completer for macro editor. --- src/lay/lay/layMacroEditorPage.cc | 153 ++++++++++++++++++++++++++++-- src/lay/lay/layMacroEditorPage.h | 10 ++ 2 files changed, 155 insertions(+), 8 deletions(-) diff --git a/src/lay/lay/layMacroEditorPage.cc b/src/lay/lay/layMacroEditorPage.cc index 03c41abdc..0eb04be59 100644 --- a/src/lay/lay/layMacroEditorPage.cc +++ b/src/lay/lay/layMacroEditorPage.cc @@ -42,6 +42,10 @@ #include #include #include +#include +#include +#include +#include namespace lay { @@ -499,11 +503,26 @@ MacroEditorPage::MacroEditorPage (QWidget * /*parent*/, MacroEditorHighlighters connect (mp_text, SIGNAL (textChanged ()), this, SLOT (text_changed ())); connect (mp_text, SIGNAL (cursorPositionChanged ()), this, SLOT (cursor_position_changed ())); + connect (mp_text->horizontalScrollBar (), SIGNAL (valueChanged (int)), this, SLOT (hide_completer ())); + connect (mp_text->verticalScrollBar (), SIGNAL (valueChanged (int)), this, SLOT (hide_completer ())); connect (mp_exec_model, SIGNAL (breakpoints_changed ()), this, SLOT (breakpoints_changed ())); connect (mp_exec_model, SIGNAL (current_line_changed ()), this, SLOT (current_line_changed ())); connect (mp_exec_model, SIGNAL (run_mode_changed ()), this, SLOT (run_mode_changed ())); mp_text->installEventFilter (this); + + mp_completer_popup = new QWidget (window (), Qt::ToolTip | Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput); + mp_completer_popup->setWindowModality (Qt::NonModal); + QHBoxLayout *ly = new QHBoxLayout (mp_completer_popup); + ly->setMargin (0); + mp_completer_list = new QListWidget (mp_completer_popup); + ly->addWidget (mp_completer_list); + mp_completer_popup->hide (); + + mp_completer_timer = new QTimer (this); + mp_completer_timer->setInterval (1000); + mp_completer_timer->setSingleShot (true); + connect (mp_completer_timer, SIGNAL (timeout ()), this, SLOT (completer_timer ())); } void MacroEditorPage::update () @@ -598,12 +617,107 @@ static bool valid_element (const SyntaxHighlighterElement &e) return e.basic_attribute_id != lay::dsComment && e.basic_attribute_id != lay::dsString; } +void MacroEditorPage::complete () +{ + QTextCursor c = mp_text->textCursor (); + if (c.selectionStart () != c.selectionEnd ()) { + return; + } + + c.select (QTextCursor::WordUnderCursor); + if (mp_completer_list->currentItem ()) { + QString s = mp_completer_list->currentItem ()->text (); + c.insertText (s); + } +} + +void MacroEditorPage::fill_completer_list () +{ + QTextCursor c = mp_text->textCursor (); + if (c.selectionStart () != c.selectionEnd ()) { + return; + } + + int pos = c.anchor (); + c.select (QTextCursor::WordUnderCursor); + int pos0 = c.selectionStart (); + if (pos0 >= pos) { + return; + } + + QString s = c.selectedText ().mid (0, pos - pos0); + + QString text = mp_text->toPlainText (); + + std::set words; + + int i = 0; + while (i >= 0) { + i = text.indexOf (s, i); + if (i >= 0) { + QString::iterator c = text.begin () + i; + QString w; + while (c->isLetterOrNumber () || c->toLatin1 () == '_') { + w += *c; + ++c; + } + if (! w.isEmpty () && w != s) { + words.insert (w); + } + ++i; + } + } + + for (std::set::const_iterator w = words.begin (); w != words.end (); ++w) { + mp_completer_list->addItem (*w); + } +} + +void MacroEditorPage::completer_timer () +{ + if (! mp_text->hasFocus ()) { + return; + } + + mp_completer_list->clear (); + fill_completer_list (); + + if (mp_completer_list->count () > 0) { + + mp_completer_list->setCurrentRow (0); + + QTextCursor c = mp_text->textCursor (); + c.clearSelection (); + QRect r = mp_text->cursorRect (c); + QPoint pos = mp_text->mapToGlobal (r.bottomLeft ()); + + QSize sz = mp_completer_list->sizeHint (); + QFontMetrics fm (mp_completer_list->font ()); + mp_completer_popup->setGeometry (pos.x (), pos.y () + r.height () / 3, sz.width (), 4 + 4 * fm.height () /*sz.height ()*/); + mp_completer_popup->show (); + + mp_text->setFocus (); + + } else { + mp_completer_popup->hide (); + } +} + +void MacroEditorPage::hide_completer () +{ + mp_completer_popup->hide (); +} + void MacroEditorPage::cursor_position_changed () { if (m_ignore_cursor_changed_event) { return; } + mp_completer_popup->hide (); + mp_completer_timer->stop (); + mp_completer_timer->start (); + QTextCursor cursor = mp_text->textCursor (); m_edit_cursor = cursor; @@ -1433,6 +1547,11 @@ MacroEditorPage::eventFilter (QObject *watched, QEvent *event) event->accept (); return true; + } else if (event->type () == QEvent::FocusOut) { + + hide_completer (); + return true; + } else if (event->type () == QEvent::KeyPress) { m_error_line = -1; @@ -1445,7 +1564,12 @@ MacroEditorPage::eventFilter (QObject *watched, QEvent *event) if (ke->key () == Qt::Key_Tab && (ke->modifiers () & Qt::ShiftModifier) == 0) { - return tab_key_pressed (); + if (mp_completer_popup->isVisible ()) { + complete (); + return true; + } else { + return tab_key_pressed (); + } } else if ((ke->key () == Qt::Key_Backtab || (ke->key () == Qt::Key_Tab && (ke->modifiers () & Qt::ShiftModifier) != 0))) { @@ -1457,19 +1581,27 @@ MacroEditorPage::eventFilter (QObject *watched, QEvent *event) } else if (ke->key () == Qt::Key_Escape) { - // Handle Esc to return to the before-find position and clear the selection + // Handle Esc to return to the before-find position and clear the selection or to hide popup - find_reset (); - - QTextCursor c = mp_text->textCursor (); - c.clearSelection (); - mp_text->setTextCursor (c); + if (mp_completer_popup->isVisible ()) { + mp_completer_popup->hide (); + } else { + find_reset (); + QTextCursor c = mp_text->textCursor (); + c.clearSelection (); + mp_text->setTextCursor (c); + } return true; } else if (ke->key () == Qt::Key_Return) { - return return_pressed (); + if (mp_completer_popup->isVisible ()) { + complete (); + return true; + } else { + return return_pressed (); + } } else if (ke->key () == Qt::Key_F1) { @@ -1481,6 +1613,11 @@ MacroEditorPage::eventFilter (QObject *watched, QEvent *event) return true; + } else if (mp_completer_popup->isVisible () && (ke->key () == Qt::Key_Up || ke->key () == Qt::Key_Down)) { + + QApplication::sendEvent (mp_completer_list, event); + return true; + } else if (ke->key () == Qt::Key_F && (ke->modifiers () & Qt::ControlModifier) != 0) { QTextCursor c = mp_text->textCursor (); diff --git a/src/lay/lay/layMacroEditorPage.h b/src/lay/lay/layMacroEditorPage.h index 8eee34048..732160dfc 100644 --- a/src/lay/lay/layMacroEditorPage.h +++ b/src/lay/lay/layMacroEditorPage.h @@ -45,6 +45,9 @@ typedef QTextEdit TextEditWidget; class QLabel; class QSyntaxHighlighter; +class QTimer; +class QWindow; +class QListWidget; namespace lay { @@ -281,6 +284,8 @@ protected slots: void breakpoints_changed (); void current_line_changed (); void run_mode_changed (); + void completer_timer (); + void hide_completer (); private: lym::Macro *mp_macro; @@ -297,12 +302,17 @@ private: QRegExp m_current_search; QTextCursor m_edit_cursor; bool m_ignore_cursor_changed_event; + QTimer *mp_completer_timer; + QWidget *mp_completer_popup; + QListWidget *mp_completer_list; void update_extra_selections (); bool return_pressed (); bool backspace_pressed (); bool back_tab_key_pressed (); bool tab_key_pressed (); + void fill_completer_list (); + void complete (); bool eventFilter (QObject *watched, QEvent *event); };