#include "qcodeedit.h" #include "qcodeedit_completer_p.h" #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) # include #endif #include "ecombobox.h" #include "qad_types.h" #include "ui_qcodeedit.h" Q_DECLARE_METATYPE(QTextCursor) Q_DECLARE_METATYPE(QCodeEdit::ACEntry) class _QCE_Viewport: public QWidget { public: _QCE_Viewport(QWidget * p = 0): QWidget(p) { setObjectName("__qcodeedit_viewport__"); //setCursor(Qt::IBeamCursor); } void paintEvent(QPaintEvent * e) override { if (!isEnabled()) return; ce->drawCursor(); } QCodeEdit * ce; }; QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) { overlay = 0; prev_lc = auto_comp_pl = cur_search_ind = pos_press = pos_el_press = -1; timer_parse = 0; _ignore_focus_out = _destructor = _replacing = cursor_state = block_sel_state = false; _first = true; qRegisterMetaType(); qRegisterMetaType(); ui = new Ui::QCodeEdit(); ui->setupUi(this); overlay = new _QCE_Viewport(ui->textCode->viewport()); overlay->ce = this; overlay->show(); ui->widgetSearch->hide(); ui->comboSearch->installEventFilter(this); ui->comboReplace->installEventFilter(this); es_line.format.setBackground(QColor(240, 245, 240)); es_line.format.setProperty(QTextFormat::FullWidthSelection, true); es_cursor.format.setBackground(QColor(220, 255, 200)); es_bracket.format.setBackground(QColor(180, 238, 180)); es_bracket.format.setForeground(Qt::red); 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); widget_help->setFrameShadow(QFrame::Sunken); 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 < lh_last; ++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[lhF1]->setIcon(QIcon(":/icons/f1.png")); lbl_help[lhF1]->setText(tr("Press F1 for details")); help_visible = true; completer = new QCodeEditCompleter(); cursor_width = qMax(qRound(fontHeight() / 10.), 1); ui->textCode->setCursorWidth(0); 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) ui->textLines->setFixedWidth(ui->textLines->fontMetrics().horizontalAdvance(" ")); #else ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width(" ")); #endif QAction * a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Shift+Tab")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(deindent())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+D")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(deleteLine())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Return")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(newLine())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Up")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(scrollUp())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Down")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(scrollDown())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Shift+Return")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(newLineBefore())); ui->frame->setFocusProxy(ui->textCode); QTextOption to = ui->textLines->document()->defaultTextOption(); to.setAlignment(Qt::AlignTop | Qt::AlignRight); ui->textLines->document()->setDefaultTextOption(to); /*to = ui->textCode->document()->defaultTextOption(); to.setFlags(QTextOption::SuppressColors); ui->textCode->document()->setDefaultTextOption(to);*/ setShowSpaces(true); a = new QAction(this); a->setShortcut(QKeySequence("Ctrl+F")); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a, SIGNAL(triggered(bool)), this, SLOT(search_triggered())); addAction(a); /*a = new QAction(this); a->setShortcut(QKeySequence("Esc")); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a, SIGNAL(triggered(bool)), this, SLOT(hideSearch())); addAction(a);*/ connect(completer, SIGNAL(itemDoubleClicked(QTreeWidgetItem * ,int)), this, SLOT(commitCompletition())); connect(completer, SIGNAL(commit()), this, SLOT(commitCompletition())); connect(completer, SIGNAL(gotoHRef(QCodeEdit::ACEntry)), this, SLOT(gotoHelpHRef(QCodeEdit::ACEntry))); connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), ui->textLines->verticalScrollBar(), SLOT(setValue(int))); connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(hideHelp())); connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(resizeOverlay())); connect(ui->textCode->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(resizeOverlay())); connect(ui->textCode, SIGNAL(textChanged()), this, SLOT(textEdit_textChanged())); connect(ui->textCode, SIGNAL(textChanged()), this, SIGNAL(textChanged())); connect(ui->textCode, SIGNAL(cursorPositionChanged()), this, SLOT(textEdit_cursorPositionChanged())); connect(ui->textCode, SIGNAL(selectionChanged()), this, SLOT(textEdit_selectionChanged())); connect(ui->textCode, SIGNAL(redoAvailable(bool)), this, SLOT(textEdit_redoAvailable(bool))); connect(ui->comboSearch->lineEdit(), SIGNAL(returnPressed()), this, SLOT(searchNext())); connect(ui->comboReplace->lineEdit(), SIGNAL(returnPressed()), this, SLOT(on_buttonReplaceSearch_clicked())); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](QWindow*w){if (w == 0) {hideHelp(); completer->hide();}}); #endif updateLines(); timer_blink = startTimer(QApplication::cursorFlashTime() / 2); registerAutoCompletitionClass(-1, QCodeEdit::Keyword, "Words", QIcon(":/icons/code-word.png")); } QCodeEdit::~QCodeEdit() { _destructor = true; delete completer; //for (int i = 0; i < 2; ++i) // delete lbl_help[i]; delete widget_help; //delete ui; } QTextCursor QCodeEdit::textCursor() const { return ui->textCode->textCursor(); } QTextDocument * QCodeEdit::document() const { return ui->textCode->document(); } void QCodeEdit::setDocument(QTextDocument * doc) { cancelBlockSelection(); if (document()) { document()->setProperty("_cursor", QVariant::fromValue(textCursor())); document()->setProperty("_vpos", textEdit()->verticalScrollBar()->value()); } ui->textCode->setEnabled(doc); ui->textLines->setEnabled(doc); documentUnset(); if (!doc) { ui->textCode->setDocument(0); documentChanged(0); return; } if (!qobject_cast(doc->documentLayout())) doc->setDocumentLayout(new QPlainTextDocumentLayout(doc)); ui->textCode->setDocument(doc); cursor_width = qMax(qRound(fontHeight() / 10.), 1); ui->textCode->setCursorWidth(0); //ui->textCode->setCursorWidth(qMax(qRound(fontHeight() / 10.), 1)); setShowSpaces(spaces_); if (doc->property("_cursor").isValid()) { setTextCursor(doc->property("_cursor").value()); textEdit()->verticalScrollBar()->setValue(doc->property("_vpos").toInt()); } documentChanged(doc); doc->setDefaultFont(editorFont()); updateLines(); } void QCodeEdit::setTextCursor(const QTextCursor & c) { ui->textCode->setTextCursor(c); } void QCodeEdit::centerCursor() { ui->textCode->centerCursor(); updateLines(); } void QCodeEdit::insertText(const QString & text) { ui->textCode->insertPlainText(text); updateLines(); } void QCodeEdit::appendText(const QString & text) { ui->textCode->appendPlainText(text); updateLines(); } void QCodeEdit::setCustomExtraSelection(const QList & es) { es_custom = es; applyExtraSelection(); } QRect QCodeEdit::cursorRect() const { return ui->textCode->cursorRect(); } QRect QCodeEdit::cursorRect(const QTextCursor & cursor) const { return ui->textCode->cursorRect(cursor); } QString QCodeEdit::text() const { return ui->textCode->toPlainText(); } QStringList QCodeEdit::cursorScope() const { return cursor_scope; } bool QCodeEdit::showLineNumbers() const { return ui->textLines->isVisible(); } void QCodeEdit::setHelpHintVisible(bool on) { help_visible = on; } bool QCodeEdit::isHelpHintVisible() const { return help_visible; } void QCodeEdit::setEditorFont(QFont f) { ui->textCode->setFont(f); ui->textLines->setFont(f); } QFont QCodeEdit::editorFont() const { return ui->textCode->font(); } QPlainTextEdit * QCodeEdit::textEdit() const { return ui->textCode; } 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; for (int i = pos - 1; i >= 0; --i) { QChar c = s[i]; if (skip) {skip = false; continue;} if (c == sc) {skip = true; continue;} if (c == cc) {cnt++; continue;} if (c == oc) {cnt--; if (cnt == 0) return i;} } return -1; } int QCodeEdit::skipCWord(const QString & s, int pos) { QChar pc(0), c(0); for (int i = pos - 1; i >= 0; --i) { pc = c; c = s[i]; if (c.isLetterOrNumber() || (c.toLatin1() == '_')) continue; if (pc.isLetter() || (pc.toLatin1() == '_')) return i + 1; return -1; } return -1; } bool QCodeEdit::matchWritten(QString s, QString w) { if (s.isEmpty() || w.isEmpty()) return true; if (s.contains(w, Qt::CaseInsensitive)) return true; int sp(0); for (int i = 0; i < w.size(); ++i, ++sp) { if (sp >= s.size()) return false; QChar wc(w[i].toLower()); bool ns = false, bl = true; while (sp < s.size()) { if (ns || s[sp].toLatin1() == '_') { if (s[sp].toLatin1() == '_') {sp++; bl = false; continue;} if (s[sp].isLower() && bl) {sp++; continue;} if (s[sp].toLower() != wc) return false; } if (s[sp].toLower() == wc) break; ns = true; sp++; } if (sp >= s.size()) return false; } return true; } QChar QCodeEdit::pairChar(QChar c) { switch (c.toLatin1()) { case '\"': return '\"'; case '(': return ')'; case ')': return '('; case '[': return ']'; case ']': return '['; default: break; } return QChar(); } bool QCodeEdit::eventFilter(QObject * o, QEvent * e) { if (_destructor) return QWidget::eventFilter(o, e); if (e->type() == QEvent::Destroy) { completer->removeEventFilter(this); ui->textCode->removeEventFilter(this); ui->textCode->viewport()->removeEventFilter(this); ui->textLines->viewport()->removeEventFilter(this); return QWidget::eventFilter(o, e); } if (ui->textLines) { if (o == ui->textLines->viewport()) {/* if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease || e->type() == QEvent::MouseMove || e->type() == QEvent::MouseButtonDblClick) { #if (QT_VERSION < 0x050000) const_cast(((QMouseEvent*)e)->pos()) = QPoint(0, ((QMouseEvent*)e)->pos().y()); #else const_cast(((QMouseEvent*)e)->localPos()) = QPointF(0, ((QMouseEvent*)e)->localPos().y()); #endif QApplication::sendEvent(ui->textCode->viewport(), e); return true; }*/ QTextCursor tc; int tcpos = 0; switch (e->type()) { case QEvent::MouseButtonPress: if (!isEnabled()) break; tc = ui->textCode->cursorForPosition(((QMouseEvent*)e)->pos()); tc.movePosition(QTextCursor::EndOfLine); pos_el_press = tc.anchor(); tc.movePosition(QTextCursor::StartOfLine); pos_press = tc.anchor(); if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); ui->textCode->setFocus(); ui->textCode->setTextCursor(tc); return true; case QEvent::MouseMove: if (!isEnabled()) break; tc = ui->textCode->cursorForPosition(((QMouseEvent*)e)->pos()); tc.movePosition(QTextCursor::StartOfLine); if (pos_press == tc.anchor()) { if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); return true; } if (pos_press < tc.anchor()) { if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); } tcpos = tc.position(); tc.setPosition(pos_press < tc.anchor() ? pos_press : pos_el_press); tc.setPosition(tcpos, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); return true; case QEvent::Wheel: if (!isEnabled()) break; QApplication::sendEvent(ui->textCode->viewport(), e); return true; default: break; } } } if (o == completer) { //qDebug() << o << e; if (e->type() == QEvent::WindowActivate) _ignore_focus_out = true; //qDebug() << e; return QWidget::eventFilter(o, e); } if (o == ui->comboSearch || o == ui->comboReplace) { //qDebug() << o << e; if (e->type() == QEvent::KeyPress) { if (((QKeyEvent * )e)->key() == Qt::Key_Escape) { hideHelp(); if (completer->isVisible()) completer->hide(); else hideSearch(); } } //qDebug() << e; return QWidget::eventFilter(o, e); } if (ui->textCode) { if (o == ui->textCode->viewport()) { switch (e->type()) { case QEvent::MouseButtonPress: { cancelBlockSelection(); completer->hide(); hideHelp(); QMouseEvent * me = (QMouseEvent*)e; if (me->modifiers().testFlag(Qt::ControlModifier) && (me->button() == Qt::LeftButton)) gotoLink(); } break; case QEvent::MouseMove: { if (!completer->isHidden()) break; QMouseEvent * me = (QMouseEvent*)e; switchBlockSelection(); if (me->modifiers().testFlag(Qt::ControlModifier)) showLink(); } break; case QEvent::Paint: resizeOverlay(); break; case QEvent::DragMove: if (!isEnabled()) break; drag_cursor = ui->textCode->cursorForPosition(((QDragMoveEvent*)e)->pos()); repaintCursor(); break; case QEvent::MouseButtonRelease: case QEvent::DragLeave: case QEvent::Drop: cancelDragCursor(); break; default: break; } return QWidget::eventFilter(o, e); } if (o == ui->textCode) { //qDebug() << e; QMetaObject::invokeMethod(this, "syncScrolls", Qt::QueuedConnection); switch (e->type()) { case QEvent::ToolTip: if (completer->isHidden()) { QTextCursor tc = ui->textCode->cursorForPosition(((QHelpEvent*)e)->pos()); tc.select(QTextCursor::WordUnderCursor); raiseHelp(tc); } break; case QEvent::KeyPress: //qDebug() << "key" << ke; if (codeKeyEvent((QKeyEvent * )e)) return true; break; case QEvent::KeyRelease: hideLink(); break; case QEvent::FocusOut: if (_ignore_focus_out) { _ignore_focus_out = false; break; } case QEvent::FocusIn: createBlockSelection(); break; case QEvent::Hide: case QEvent::HideToParent: hideLink(); case QEvent::MouseButtonPress: //qDebug() << e; completer->hide(); hideHelp(); default: break; } } } return QWidget::eventFilter(o, e); } void QCodeEdit::showEvent(QShowEvent * ) { if (!_first) return; _first = false; completer->installEventFilter(this); ui->textCode->installEventFilter(this); ui->textCode->viewport()->installEventFilter(this); ui->textLines->viewport()->installEventFilter(this); } void QCodeEdit::timerEvent(QTimerEvent * e) { if (e->timerId() == timer_parse) { parse(); emit parseRequest(); killTimer(timer_parse); timer_parse = 0; } if (e->timerId() == timer_blink) { cursor_state = !cursor_state; repaintCursor(); } } void QCodeEdit::leaveEvent(QEvent * e) { hideHelp(); QWidget::leaveEvent(e); } void QCodeEdit::changeEvent(QEvent * e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); lbl_help[lhF1]->setText(tr("Press F1 for details")); break; default: break; } } bool QCodeEdit::codeKeyEvent(QKeyEvent * ke) { QChar kc; switch (ke->key()) { case Qt::Key_Space: if (ke->modifiers().testFlag(Qt::ControlModifier)) { invokeAutoCompletition(true); return true; } break; case Qt::Key_Escape: hideHelp(); if (completer->isVisible()) completer->hide(); else hideSearch(); break; case Qt::Key_Up: switchBlockSelection(ke); if (completer->isVisible()) { completer->previousCompletition(); return true; } completer->hide(); hideHelp(); if (ke->modifiers().testFlag(Qt::AltModifier)) { if (ke->modifiers().testFlag(Qt::ShiftModifier)) return false; else copyLineUp(); return true; } if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { moveLineUp(); return true; } break; case Qt::Key_Down: switchBlockSelection(ke); if (completer->isVisible()) { completer->nextCompletition(); return true; } completer->hide(); hideHelp(); if (ke->modifiers().testFlag(Qt::AltModifier)) { if (ke->modifiers().testFlag(Qt::ShiftModifier)) return false; else copyLineDown(); return true; } if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { moveLineDown(); return true; } break; case Qt::Key_Home: case Qt::Key_End: case Qt::Key_PageUp: case Qt::Key_PageDown: switchBlockSelection(ke); if (completer->isVisible()) { qApp->sendEvent(completer, new QKeyEvent(ke->type(), ke->key(), ke->modifiers())); return true; } break; case Qt::Key_Left: case Qt::Key_Right: switchBlockSelection(ke); if (hasBlockSelection()) break; case Qt::Key_Backspace: case Qt::Key_Delete: if (completer->isVisible()) QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); if (removeBlockSelection(ke->key() == Qt::Key_Delete)) return true; break; case Qt::Key_Return: if (hasBlockSelection()) { cancelBlockSelection(); return true; } if (completer->isVisible()) { commitCompletition(); completer->hide(); return true; } if (ui->textCode->textCursor().selectedText().isEmpty()) QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); break; case Qt::Key_Tab: if (!ui->textCode->textCursor().selectedText().isEmpty()) { if (ke->modifiers().testFlag(Qt::ShiftModifier)) deindent(); else indent(); return true; } break; case Qt::Key_D: if (ke->modifiers().testFlag(Qt::ControlModifier)) { completer->hide(); return true; } break; case Qt::Key_Control: showLink(); break; case Qt::Key_F1: if (widget_help->isVisible()) gotoHelpHRef(help_entry); break; default: break; } if (!ke->text().isEmpty()) { if (hasBlockSelection() && (ke->modifiers() == 0 || ke->modifiers() == Qt::ShiftModifier)) { insertBlockSelection(ke->text()); return true; } kc = ke->text()[0]; } if (kc == '.') { completer->hide(); QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); } else { if ((kc.isLetterOrNumber() || kc.toLatin1() == '_') && completer->isVisible()) QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); } return false; } char antiBracket(char c) { switch (c) { case '(': return ')'; case '[': return ']'; case '{': return '}'; case '<': return '>'; case ')': return '('; case ']': return '['; case '}': return '{'; case '>': return '<'; } return 0; } void QCodeEdit::highlightBrackets() { es_brackets.clear(); QTextCursor stc = ui->textCode->textCursor(), tc; QTextEdit::ExtraSelection es; stc.setPosition(stc.position()); QTextCursor::MoveOperation mop[2] = {QTextCursor::Left, QTextCursor::Right}; QString mbr[2] = {")]}>", "([{<"}; for (int d = 0; d < 2; ++d) { tc = stc; tc.movePosition(mop[d], QTextCursor::KeepAnchor); if (!tc.selectedText().isEmpty()) { char ch = tc.selectedText()[0].toLatin1(); if (mbr[d].contains(ch)) { es = es_bracket; es.cursor = tc; es_brackets << es; QTextCursor ftc = tc; int bcnt = 1; char fch = antiBracket(ch); while (bcnt > 0) { ftc.setPosition(ftc.position()); if (!ftc.movePosition(mop[d], QTextCursor::KeepAnchor)) break; //qDebug() << tc.selectedText(); if (ftc.selectedText().isEmpty()) break; if (ftc.selectedText()[0].toLatin1() == ch) ++bcnt; if (ftc.selectedText()[0].toLatin1() == fch) --bcnt; } if (bcnt == 0) { es.cursor = ftc; es_brackets << es; es.format = es_range.format; es.cursor.setPosition(tc.position(), QTextCursor::KeepAnchor); if (!es.cursor.selection().isEmpty()) es_brackets << es; } } } } } void QCodeEdit::applyExtraSelection() { ui->textCode->setExtraSelections(QList() << es_line << es_selected << es_custom << es_brackets << es_search_list << es_cursor << es_link << es_blockselection); } void QCodeEdit::clearSearch() { es_search_list.clear(); applyExtraSelection(); } void QCodeEdit::moveToSearch() { if (es_search_list.isEmpty()) return; if (cur_search_ind < 0) cur_search_ind += es_search_list.size(); if (cur_search_ind >= es_search_list.size()) cur_search_ind = 0; if (cur_search_ind < 0 || cur_search_ind >= es_search_list.size()) return; ui->textCode->setTextCursor(es_search_list[cur_search_ind].cursor); } int QCodeEdit::searchIndFromCursor() { if (es_search_list.isEmpty()) return -1; int ci = ui->textCode->textCursor().anchor(); for (int i = 0; i < es_search_list.size(); ++i) if (es_search_list[i].cursor.anchor() > ci) return i - 1; return -1; } QRect QCodeEdit::cursorRect() { QRect r = ui->textCode->cursorRect(textCursor()), lr = r; if (hasBlockSelection()) { r |= ui->textCode->cursorRect(block_start_cursor); lr.setY(r.y()); lr.setHeight(r.height()); } lr.setWidth(cursor_width); return lr; } QRect QCodeEdit::blockSelectionRect() { QTextCursor tc = ui->textCode->textCursor(); QPoint ps(block_start_cursor.positionInBlock(), block_start_cursor.blockNumber()), pe(tc.positionInBlock(), tc.blockNumber()); //QRect bsr(QPoint(qMin(ps.x(), pe.x()), qMin(ps.y(), pe.y())), // QSize(qAbs(ps.x() - pe.x()), qAbs(ps.y() - pe.y()) + 1)); //return bsr; return (ui->textCode->cursorRect(tc) | ui->textCode->cursorRect(block_start_cursor)) .translated(ui->textCode->horizontalScrollBar()->value(), 0); } QVector QCodeEdit::blockSelectionCursors(QRect bsr) { QVector ret; //qDebug() << bsr; int sline = ui->textCode->cursorForPosition(bsr.topLeft()).blockNumber(); int eline = ui->textCode->cursorForPosition(bsr.bottomRight()).blockNumber(); for (int l = sline; l <= eline; ++l) { QTextCursor stc(ui->textCode->document()->findBlockByNumber(l)), etc; QRect stc_rect = ui->textCode->cursorRect(stc); stc = ui->textCode->cursorForPosition(stc_rect.center() + QPoint(bsr.left() , 0)); etc = ui->textCode->cursorForPosition(stc_rect.center() + QPoint(bsr.right(), 0)); stc.setPosition(etc.position(), QTextCursor::KeepAnchor); ret << stc; } return ret; } void QCodeEdit::repaintCursor() { overlay->update(cursorRect()); } void QCodeEdit::drawCursor() { if (!isEnabled() || !ui->textCode->hasFocus()) return; QPainter p(overlay); QTextCursor tc = textCursor(); //qDebug() << block_start_cursor.position() << tc.position(); QRect line = cursorRect(); if (cursor_state && ui->textCode->hasFocus()) { line.adjust(0, 1, 0, -1); p.setCompositionMode(QPainter::CompositionMode_Difference); p.fillRect(line, Qt::white); } if (!drag_cursor.isNull()) { line = ui->textCode->cursorRect(drag_cursor); line.setWidth(cursor_width); p.setCompositionMode(QPainter::CompositionMode_Difference); p.fillRect(line, Qt::white); } } bool QCodeEdit::hasBlockSelection() const { return !block_start_cursor.isNull(); } void QCodeEdit::startBlockSelection() { if (!hasBlockSelection()) { QTextCursor tc = textCursor(); block_start_cursor = tc; block_start_cursor.setPosition(tc.selectionStart()); } } void QCodeEdit::cancelBlockSelection() { block_start_cursor = QTextCursor(); es_blockselection.clear(); applyExtraSelection(); } void QCodeEdit::switchBlockSelection(QKeyEvent * ke) { bool alt = QApplication::keyboardModifiers().testFlag(Qt::AltModifier); if (ke) alt = ke->modifiers().testFlag(Qt::AltModifier) && ke->modifiers().testFlag(Qt::ShiftModifier); if (alt) { startBlockSelection(); QTextCursor tc = ui->textCode->textCursor(); QTextCursor::MoveOperation op = QTextCursor::NoMove; if (!ke) return; switch (ke->key()) { case Qt::Key_Left : op = QTextCursor::Left ; break; case Qt::Key_Right: op = QTextCursor::Right; break; case Qt::Key_Up : op = QTextCursor::Up ; break; case Qt::Key_Down : op = QTextCursor::Down ; break; default: break; } if (op != QTextCursor::NoMove) { tc.movePosition(op); ui->textCode->setTextCursor(tc); } repaintCursor(); } else cancelBlockSelection(); } bool QCodeEdit::removeBlockSelection(bool is_del) { if (!hasBlockSelection()) return false; QRect bsr = blockSelectionRect(); bool del = false, back = false; if (bsr.width() <= 1) { if (is_del) del = true; else back = true; } QVector clist = blockSelectionCursors(bsr); QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int bspx = ui->textCode->cursorRect(block_start_cursor).center().x(); int nullw = ui->textCode->fontMetrics(). #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) horizontalAdvance #else width #endif (" "); int min_dist = nullw * 0.5; int new_pos = -1; foreach (QTextCursor c, clist) { if (del || back) { if (qAbs(bspx - ui->textCode->cursorRect(c).center().x()) > min_dist) continue; int line = c.blockNumber(); c.movePosition(del ? QTextCursor::Right : QTextCursor::Left, QTextCursor::KeepAnchor); if (line != c.blockNumber()) continue; //qDebug() << qAbs(bspx - ui->textCode->cursorRect(c).center().x()) << min_dist; } c.removeSelectedText(); if (c.blockNumber() == tc.blockNumber()) new_pos = c.position(); } tc.setPosition(new_pos); ui->textCode->setTextCursor(tc); tc.endEditBlock(); return true; } void QCodeEdit::insertBlockSelection(QString text) { if (!hasBlockSelection()) return; QTextCursor tc = ui->textCode->textCursor(); QRect bsr = blockSelectionRect(); //qDebug() << "___" << bsr; int scrl = ui->textCode->horizontalScrollBar()->value(); if (bsr.width() > 1) { removeBlockSelection(false); bsr = blockSelectionRect(); //qDebug() << "del" << bsr; } tc.beginEditBlock(); //tc.joinPreviousEditBlock(); int nullw = ui->textCode->fontMetrics(). #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) horizontalAdvance #else width #endif (" "); QVector clist = blockSelectionCursors(bsr); foreach (QTextCursor c, clist) { c.removeSelectedText(); int spcnt = (bsr.left() - ui->textCode->cursorRect(c).center().x() - scrl) / nullw; if (spcnt > 0) { c.insertText(QString(spcnt, QChar(' '))); } c.insertText(text); } //tc.setPosition(tc.block().position() + block_start_cursor.positionInBlock()); //ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::createBlockSelection() { if (!hasBlockSelection()) return; es_blockselection.clear(); QTextEdit::ExtraSelection es; es.format.setForeground(palette().brush(QPalette::HighlightedText)); es.format.setBackground(palette().brush(QPalette::Highlight)); QVector clist = blockSelectionCursors(blockSelectionRect()); foreach (QTextCursor c, clist) { es.cursor = c; es_blockselection << es; } applyExtraSelection(); } void QCodeEdit::cancelDragCursor() { drag_cursor = QTextCursor(); overlay->update(); } void QCodeEdit::searchAll() { QString st = ui->comboSearch->currentText(); es_search_list.clear(); if (!st.isEmpty() && !ui->widgetSearch->isHidden()) { QTextDocument::FindFlags ff = QTextDocument::FindFlags(); if (ui->buttonSearchCase->isChecked()) ff |= QTextDocument::FindCaseSensitively; if (ui->buttonSearchWord->isChecked()) ff |= QTextDocument::FindWholeWords; QTextCursor tc(ui->textCode->document()->begin()); QTextEdit::ExtraSelection es = es_search; while (true) { tc = ui->textCode->document()->find(st, tc, ff); if (tc.isNull()) break; es.cursor = tc; es_search_list << es; } } applyExtraSelection(); QString ss; if (es_search_list.isEmpty()) ss = "color: rgb(180, 0, 0);"; ui->comboSearch->lineEdit()->setStyleSheet(ss); } void QCodeEdit::search_triggered() { QTextCursor tc = ui->textCode->textCursor(); QString st = tc.selectedText(); if (st.isEmpty()) { tc.select(QTextCursor::WordUnderCursor); st = tc.selectedText(); } search(st); //QMetaObject::invokeMethod(ui->comboSearch->lineEdit(), "returnPressed"); if (ui->comboSearch->findText(st) < 0) ui->comboSearch->insertItem(0, st); } void QCodeEdit::syncScrolls() { ui->textLines->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value()); ui->textLines->setHorizontalScrollBarPolicy(ui->textCode->horizontalScrollBar()->isVisible() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); //qDebug() << "!!!"; } void QCodeEdit::scrollUp() { ui->textCode->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value() - 1); } void QCodeEdit::scrollDown() { ui->textCode->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value() + 1); } void QCodeEdit::deleteLine() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); tc.movePosition(QTextCursor::EndOfLine); tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); bool md = true; if (!tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::StartOfLine); tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); md = false; } tc.removeSelectedText(); tc.movePosition(QTextCursor::StartOfLine); if (md) tc.movePosition(QTextCursor::Down); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::copyLineUp() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; se_--; } tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); tc.setPosition(se, QTextCursor::KeepAnchor); bool ins_nl = false; if (!tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor)) ins_nl = true; QString l = tc.selectedText(); tc.beginEditBlock(); tc.setPosition(ss); if (ins_nl) l.append("\n"); tc.insertText(l); tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::copyLineDown() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; se_--; } tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); tc.setPosition(se, QTextCursor::KeepAnchor); bool ins_nl = false; if (!tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor)) ins_nl = true; QString l = tc.selectedText(); tc.beginEditBlock(); tc.setPosition(ss); ss_ += l.size(); se_ += l.size(); if (ins_nl) { l.append("\n"); ss_++; se_++; } tc.insertText(l); tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::moveLineUp() { QTextCursor tc = ui->textCode->textCursor(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; se_--; } tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); if (!tc.movePosition(QTextCursor::Up)) return; tc.beginEditBlock(); tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); QString l = tc.selectedText(); ss -= l.size(); se -= l.size(); ss_ -= l.size(); se_ -= l.size(); tc.beginEditBlock(); tc.removeSelectedText(); tc.setPosition(se); bool de = false; if (!tc.movePosition(QTextCursor::Right)) { l.prepend("\n"); de = true; } tc.insertText(l); if (de) { tc.movePosition(QTextCursor::End); tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); tc.removeSelectedText(); } tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::moveLineDown() { QTextCursor tc = ui->textCode->textCursor(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; se_--; } tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(se); if (!tc.movePosition(QTextCursor::Right)) return; tc.beginEditBlock(); bool de = false; if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); de = true; } QString l = tc.selectedText(); tc.beginEditBlock(); tc.removeSelectedText(); tc.setPosition(ss); if (de) l += "\n"; tc.insertText(l); if (de) { tc.movePosition(QTextCursor::End); tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); tc.removeSelectedText(); } ss += l.size(); se += l.size(); ss_ += l.size(); se_ += l.size(); tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::indent() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; se_--; } tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); while (tc.position() < se_) { tc.insertText("\t"); se_++; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Down)) break; } tc.setPosition(ss_ + 1); tc.setPosition(se_, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::deindent() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; se_--; } tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); bool first = true; while (tc.position() < se_) { tc.movePosition(QTextCursor::StartOfLine); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); int rs = 0; if (tc.selectedText() == "\t") { tc.removeSelectedText(); rs = 1; } else { for (int i = 0; i < 4; ++i) { tc.movePosition(QTextCursor::StartOfLine); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); if (tc.selectedText() == " ") { tc.removeSelectedText(); rs++; } } } if (first) { first = false; ss_ -= rs; } se_ -= rs; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Down)) break; } tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::autoIndent() { QTextCursor tc = ui->textCode->textCursor(), stc = tc; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Up)) return; tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); QString line = tc.selectedText(), tabs; int i = 0; for (; i < line.size(); ++i) if (!line[i].isSpace()) { tabs = line.left(i); break; } if (i >= line.size()) tabs = line.left(line.size() - 1); int nt = qMax(0, line.count(QChar('{')) - line.count(QChar('}'))); tabs.append(QString("\t").repeated(nt)); if (tabs.isEmpty()) return; stc.beginEditBlock(); stc.insertText(tabs); ui->textCode->setTextCursor(stc); stc.endEditBlock(); } 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; overlay->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(); overlay->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(); ui->textCode->verticalScrollBar()->setValue(0); ui->textLines->verticalScrollBar()->setValue(0); } void QCodeEdit::newLine() { QTextCursor tc = ui->textCode->textCursor(); tc.movePosition(QTextCursor::EndOfLine); tc.insertText("\n"); ui->textCode->setTextCursor(tc); QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); } void QCodeEdit::newLineBefore() { QTextCursor tc = ui->textCode->textCursor(); tc.movePosition(QTextCursor::StartOfLine); tc.insertText("\n"); tc.movePosition(QTextCursor::Up); ui->textCode->setTextCursor(tc); QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); } void QCodeEdit::setFocus() { ui->textCode->setFocus(); } void QCodeEdit::setText(const QString & t) { ui->textCode->setPlainText(t); } void QCodeEdit::updateLines() { if (timer_parse > 0) killTimer(timer_parse); timer_parse = startTimer(500); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) # if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) ui->textCode->setTabStopDistance(ui->textCode->fontMetrics().horizontalAdvance(" ")); # else ui->textCode->setTabStopDistance(ui->textCode->fontMetrics().width(" ")); # endif #else ui->textCode->setTabStopWidth(ui->textCode->fontMetrics().width(" ")); #endif int lc = ui->textCode->document()->lineCount(); if (prev_lc == lc) return; prev_lc = lc; #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) ui->textLines->setFixedWidth(ui->textLines->fontMetrics().horizontalAdvance(QString(" %1").arg(lc))); #else ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width(QString(" %1").arg(lc))); #endif ui->textLines->clear(); for (int i = 1; i <= lc; ++i) ui->textLines->appendPlainText(QString("%1").arg(i)); ui->textLines->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value()); } QString QCodeEdit::selectArg(QString s, int arg) { if (!s.contains('(') || arg < 0) return s; QString ss = s.left(s.indexOf('(')); s.remove(0, ss.size()); if (s.startsWith('(')) s.remove(0, 1); if (s.endsWith(')')) s.chop(1); QStringList al = s.split(","); QString ret = ss + "("; for (int i = 0; i < al.size(); ++i) { if (i > 0) ret += ", "; if (i == arg) ret += ""; ret += al[i].trimmed(); if (i == arg) ret += ""; } ret += ")"; return ret; } QCodeEdit::ACEntry QCodeEdit::findEntryOnCursor(QTextCursor tc, int arg, ACClass * acc, QPair * scope) { bool ok = false; QPair sc = getScope(tc, &ok); if (scope) *scope = sc; QString st = tc.selectedText(); if (arg >= 0) st = sc.second; if (!ok || st.isEmpty()) { return ACEntry(); } ok = false; 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)) { ts.remove(0, st.size()); ts = ts.trimmed(); if (!ts.isEmpty()) { if (ts[0] != '(') continue; } } else continue; } //qDebug() << s.second << st; if (acc) *acc = ac_classes.value(i.first); return s; } } return ACEntry(); } void QCodeEdit::raiseHelp(QTextCursor tc, int arg) { ACClass acc; QPair scope; ACEntry e = findEntryOnCursor(tc, arg, &acc, &scope); if (e.isNull()) { hideHelp(); return; } lbl_help[lhMain]->setText(QString("[%1] %2 %3").arg(acc.name, e.type, selectArg(e.name, arg))); lbl_help[lhMain]->setIcon(acc.icon); QString hint; if (!e.hint.isEmpty()) hint = QString("%1").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[lhMain]->setFont(font()); lbl_help[lhF1]->setVisible(!e.help_href.isEmpty() && help_visible); 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(tc.selectedText())); #else whr.setWidth(ui->textCode->fontMetrics().width(tc.selectedText())); #endif QPoint whp; whp.setX(whr.left() - whr.width() - (widget_help->width() - whr.width()) / 2); whp.setY(whr.top() - widget_help->height() - (fontHeight() / 3)); //qDebug() << whr << whp << widget_help->width() << ", " << st; widget_help->move(ui->textCode->viewport()->mapToGlobal(whp)); widget_help->show(); widget_help->raise(); help_entry = e; cursor_scope = scope.first; cursor_scope << scope.second; //qDebug() << "tooltip" << st; } void QCodeEdit::gotoHelpHRef(QCodeEdit::ACEntry e) { if (e.help_href.isEmpty()) return; QDesktopServices::openUrl(e.help_href); } void QCodeEdit::resizeOverlay() { if (overlay) overlay->setGeometry(ui->textCode->viewport()->geometry()); } void QCodeEdit::hideHelp() { help_entry = ACEntry(); widget_help->hide(); es_cursor.cursor = QTextCursor(); cursor_scope.clear(); applyExtraSelection(); } QTextCursor QCodeEdit::functionStart(QTextCursor tc, int * arg) { QString doc = ui->textCode->toPlainText(); int bcnt = 0, a = 0, i = -1; for (i = tc.position() - 1; i >= 0; --i) { if (doc[i] == ')') bcnt++; if (doc[i] == '(') { if (bcnt == 0) break; else bcnt--; } //if (doc[i] == '(') bcnt--; if (doc[i] == ',' && bcnt == 0) a++; } if (i < 0) return QTextCursor(); if (arg) *arg = a; QTextCursor ret(ui->textCode->document()); ret.setPosition(i); //qDebug() << "found" << i << a; return ret; } QCodeEdit::ACList QCodeEdit::wordsCompletitionList(const QString & written) const { QCodeEdit::ACList ret; if (!written.isEmpty()) { QTextCursor tc = QTextCursor(ui->textCode->document()->begin()), stc; QStringList acwl; tc = QTextCursor(ui->textCode->document()->begin()); while (true) { tc = ui->textCode->document()->find(written, tc); if (tc.isNull()) break; stc = tc; stc.movePosition(QTextCursor::Left); stc.select(QTextCursor::WordUnderCursor); if (!stc.selectedText().isEmpty() && stc.selectedText().trimmed() != written) acwl << stc.selectedText(); } acwl.removeDuplicates(); ACSection acl; acl.first = -1; foreach (const QString & s, acwl) acl.second << ACEntry("", s); ret << acl; } return ret; } QPair QCodeEdit::getScope(QTextCursor tc, bool * ok) { QPair ret; QTextCursor stc = tc; if (tc.isNull()) { completer->hide(); if (ok) *ok = false; return ret; } int line = tc.block().firstLineNumber(); if (completer->isVisible()) { if (auto_comp_pl != line) { completer->hide(); auto_comp_pl = line; if (ok) *ok = false; return ret; } } QString doc = ui->textCode->toPlainText(); auto_comp_pl = line; completer->clear(); int spos = tc.position(), cpos = spos; tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); QStringList scope; QString written = tc.selectedText().trimmed(); //qDebug() << "\n*** invokeAutoCompletition ***"; if (written != "_" && !written.leftJustified(1)[0].isLetterOrNumber()) { written.clear(); } else { cpos = skipCWord(doc, spos); if (cpos >= 0) written = doc.mid(cpos, spos - cpos).trimmed(); } while (cpos >= 0) { cpos--; //qDebug() << "char =" << doc.mid(cpos, 1); if (doc.mid(cpos, 1) != ".") break; QChar c = doc.mid(cpos - 1, 1).leftJustified(1)[0]; int ppos = cpos; if (c == '\"' || c == ')' || c == ']') { cpos = skipRange(doc, cpos, pairChar(c), c, '\\'); //qDebug() << "range" << cpos; if (cpos < 0) break; } int npos = skipCWord(doc, cpos); if (npos < 0) break; scope.push_front(doc.mid(npos, ppos - npos)); cpos = npos; } ret.first = scope; ret.second = written; if (ok) *ok = true; return ret; } void QCodeEdit::invokeAutoCompletition(bool force) { int arg = -1; QTextCursor htc = functionStart(ui->textCode->textCursor(), &arg); if (!htc.isNull()) { //qDebug() << "raise"; raiseHelp(htc, arg); } bool ok; QPair scope = getScope(ui->textCode->textCursor(), &ok); if (!ok) return; ACList acl(autoCompletitionList(scope.first, scope.second)); //qDebug() << written << scope << acl.size(); if (scope.first.isEmpty() && scope.second.isEmpty() && !force) { completer->hide(); hideHelp(); return; } if (word_complete) acl << wordsCompletitionList(scope.second); QFont bf(font()); bf.setBold(true); foreach (const ACSection & ac, acl) { if (ac.second.isEmpty()) continue; ACClass acc = ac_classes.value(ac.first); completer->addItems(bf, acc, ac); } completer->invoke(ui->textCode->mapToGlobal(ui->textCode->cursorRect().bottomRight())); } void QCodeEdit::commitCompletition() { if (completer->currentItem() == 0) return; if (!completer->currentItem()->flags().testFlag(Qt::ItemIsSelectable)) return; QString ins = completer->currentValue(), ret = completer->currentReturn(); QTextCursor tc = ui->textCode->textCursor(), stc = tc; tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); bool ins_br = true, shifted = false; if (!tc.selectedText().isEmpty()) { // if (tc.selectedText()[0].isSpace()) { if (!tc.selectedText()[0].isLetterOrNumber() && !tc.selectedText()[0].isSpace() && !(tc.selectedText()[0] == '_')) { stc.movePosition(QTextCursor::Left); shifted = true; } else { tc.movePosition(QTextCursor::Left); tc.movePosition(QTextCursor::EndOfWord); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); } if (!tc.selectedText().isEmpty()) if (tc.selectedText()[0].toLatin1() == '(') ins_br = false; } if (ins.contains("(")) ins = ins.left(ins.indexOf("(")) + "()"; if (!ins_br && ins.endsWith("()")) ins.chop(2); tc = stc; tc.select(QTextCursor::WordUnderCursor); if (!tc.selectedText().leftJustified(1)[0].isLetterOrNumber() && !(tc.selectedText().leftJustified(1)[0] == '_')) { tc = stc; if (shifted) tc.movePosition(QTextCursor::Right); } ui->textCode->setTextCursor(tc); ui->textCode->textCursor().insertText(ins); tc = ui->textCode->textCursor(); if (ins_br) { if (ret == "void") { tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); if (tc.selectedText() != ";") { ui->textCode->textCursor().insertText(";"); tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2); } } if (ins.endsWith(")") && !completer->currentValue().endsWith("()")) { tc.movePosition(QTextCursor::Left); ui->textCode->setTextCursor(tc); } } else { if (completer->currentValue().endsWith(")")) { tc.movePosition(QTextCursor::Right); ui->textCode->setTextCursor(tc); } if (completer->currentValue().endsWith("()")) { tc.movePosition(QTextCursor::Right); ui->textCode->setTextCursor(tc); } } completer->hide(); } void QCodeEdit::textEdit_cursorPositionChanged() { es_line.cursor = ui->textCode->textCursor(); //qDebug() << "cursorPositionChanged" << es_line.cursor.position(); es_line.cursor.select(QTextCursor::LineUnderCursor); es_line.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); highlightBrackets(); applyExtraSelection(); if (timer_blink) killTimer(timer_blink); timer_blink = startTimer(QApplication::cursorFlashTime() / 2); cursor_state = true; createBlockSelection(); } void QCodeEdit::textEdit_textChanged() { if (_replacing) return; searchAll(); updateLines(); } void QCodeEdit::textEdit_selectionChanged() { if (hasBlockSelection()) { QTextCursor tc = ui->textCode->textCursor(); //qDebug() << block_start_cursor.selectionStart() << tc.selectionEnd(); bool bs = ui->textCode->blockSignals(true); tc.clearSelection(); ui->textCode->setTextCursor(tc); ui->textCode->blockSignals(bs); applyExtraSelection(); return; } block_start_cursor = QTextCursor(); es_selected.clear(); QString sf = ui->textCode->textCursor().selectedText(); if (sf.trimmed().isEmpty() || sf.contains("\n")) { applyExtraSelection(); return; } QTextCursor tc(ui->textCode->document()->begin()); QTextEdit::ExtraSelection es; es.format.setBackground(QColor(251, 250, 150)); while (true) { tc = ui->textCode->document()->find(sf, tc, QTextDocument::FindCaseSensitively | QTextDocument::FindWholeWords); if (tc.isNull()) break; es.cursor = tc; es_selected << es; } applyExtraSelection(); } void QCodeEdit::textEdit_redoAvailable(bool available) { if (available) cancelBlockSelection(); } void QCodeEdit::setShowSpaces(bool yes) { spaces_ = yes; QTextOption to = ui->textCode->document()->defaultTextOption(); QTextOption::Flags tof = to.flags(); if (yes) tof |= QTextOption::ShowTabsAndSpaces; else tof &= ~QTextOption::ShowTabsAndSpaces; to.setFlags(tof); ui->textCode->document()->setDefaultTextOption(to); } void QCodeEdit::setShowLineNumbers(bool yes) { ui->textLines->setVisible(yes); } void QCodeEdit::search(const QString & t) { ui->widgetSearch->show(); ui->comboSearch->setEditText(QString()); ui->comboSearch->setEditText(t); ui->comboSearch->setFocus(); //searchAll(); searchNext(false); } void QCodeEdit::searchNext(bool next) { if (es_search_list.isEmpty()) return; cur_search_ind = searchIndFromCursor() + (next ? 1 : 0); moveToSearch(); } void QCodeEdit::searchPrevious() { if (es_search_list.isEmpty()) return; cur_search_ind = searchIndFromCursor() - 1; moveToSearch(); } void QCodeEdit::hideSearch() { ui->widgetSearch->hide(); searchAll(); } void QCodeEdit::on_comboSearch_currentTextChanged(const QString & t) { searchAll(); searchNext(false); } void QCodeEdit::on_buttonReplace_clicked() { if (es_search_list.isEmpty() || cur_search_ind < 0 || cur_search_ind >= es_search_list.size()) return; if (ui->textCode->textCursor() != es_search_list[cur_search_ind].cursor) return; if (ui->textCode->textCursor().selectedText().size() != es_search_list[cur_search_ind].cursor.selectedText().size()) return; ui->textCode->textCursor().insertText(ui->comboReplace->currentText()); } void QCodeEdit::on_buttonReplaceSearch_clicked() { on_buttonReplace_clicked(); searchNext(); } void QCodeEdit::on_buttonReplaceAll_clicked() { _replacing = true; QString rt = ui->comboReplace->currentText(); textCursor().beginEditBlock(); for (int i = es_search_list.size() - 1; i >= 0; --i) es_search_list[i].cursor.insertText(rt); _replacing = false; textCursor().endEditBlock(); textEdit_textChanged(); } QString QCodeEdit::ACEntry::declaration() const { if (declaration_pos < 0 || declaration_file.isEmpty()) return QString(); return (declaration_file + ": %1").arg(declaration_pos); }