#include "qcodeedit.h" #include #include #include #include #include #include #include #include #include #include #include #include "ecombobox.h" #include "qad_types.h" #include "ui_qcodeedit.h" Q_DECLARE_METATYPE(QTextCursor) QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) { qRegisterMetaType(); ui = new Ui::QCodeEdit(); ui->setupUi(this); ui->widgetSearch->hide(); ui->comboSearch->installEventFilter(this); ui->comboReplace->installEventFilter(this); prev_lc = auto_comp_pl = cur_search_ind = pos_press = pos_el_press = -1; timer = 0; _ignore_focus_out = _destructor = _replacing = false; _first = true; 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); 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 < 2; ++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")); completer = new QTreeWidget(); completer->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); completer->setFocusPolicy(Qt::NoFocus); completer->setColumnCount(2); completer->setRootIsDecorated(false); completer->setHeaderHidden(true); completer->header()->setDefaultAlignment(Qt::AlignCenter); completer->header()-> #if (QT_VERSION >= 0x050000) setSectionResizeMode #else setResizeMode #endif (QHeaderView::ResizeToContents); completer->header()->setStretchLastSection(true); ui->textCode->setCursorWidth(qMax(qRound(fontHeight() / 10.), 1)); ui->textLines->viewport()->setAutoFillBackground(false); ui->textLines->viewport()->setCursor(Qt::ArrowCursor); ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width(" ")); 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(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, 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->comboSearch->lineEdit(), SIGNAL(returnPressed()), this, SLOT(searchNext())); connect(ui->comboReplace->lineEdit(), SIGNAL(returnPressed()), this, SLOT(on_buttonReplaceSearch_clicked())); updateLines(); 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) { 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); 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::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; } 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()) { if (e->type() == QEvent::MouseButtonPress) { completer->hide(); hideHelp(); } return QWidget::eventFilter(o, e); } if (o == ui->textCode) { //qDebug() << e; QMetaObject::invokeMethod(this, "syncScrolls", Qt::QueuedConnection); QKeyEvent * ke; QChar kc(0); switch (e->type()) { case QEvent::ToolTip: { QTextCursor tc = ui->textCode->cursorForPosition(((QHelpEvent*)e)->pos()); tc.select(QTextCursor::WordUnderCursor); raiseHelp(tc); } break; case QEvent::KeyPress: ke = (QKeyEvent * )e; //qDebug() << "key" << ke; 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: if (completer->isVisible()) { previousCompletition(); return true; } completer->hide(); hideHelp(); if (ke->modifiers().testFlag(Qt::AltModifier)) { copyLineUp(); return true; } if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { moveLineUp(); return true; } break; case Qt::Key_Down: if (completer->isVisible()) { nextCompletition(); return true; } completer->hide(); hideHelp(); if (ke->modifiers().testFlag(Qt::AltModifier)) { 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: if (completer->isVisible()) { qApp->sendEvent(completer, new QKeyEvent(e->type(), ke->key(), ke->modifiers())); return true; } break; case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Backspace: case Qt::Key_Delete: if (completer->isVisible()) QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); break; case Qt::Key_Return: 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; default: break; } if (!ke->text().isEmpty()) 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)); } break; case QEvent::FocusOut: if (_ignore_focus_out) { _ignore_focus_out = false; break; } case QEvent::Hide: case QEvent::HideToParent: 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 * ) { parse(); emit parseRequest(); killTimer(timer); timer = 0; } 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[1]->setText(tr("Press F1 for details")); break; default: break; } } 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); } void QCodeEdit::nextCompletition() { int ci = completer->currentIndex().row(); if (ci >= completer->topLevelItemCount() - 1) return; if (completer->topLevelItem(ci + 1)->flags().testFlag(Qt::ItemIsSelectable)) completer->setCurrentItem(completer->topLevelItem(ci + 1)); else { if (ci >= completer->topLevelItemCount() - 2) return; completer->setCurrentItem(completer->topLevelItem(ci + 2)); } } void QCodeEdit::previousCompletition() { int ci = completer->currentIndex().row(); if (ci <= 0) return; if (completer->topLevelItem(ci - 1)->flags().testFlag(Qt::ItemIsSelectable)) completer->setCurrentItem(completer->topLevelItem(ci - 1)); else { if (ci <= 1) return; completer->setCurrentItem(completer->topLevelItem(ci - 2)); } } 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; } void QCodeEdit::searchAll() { QString st = ui->comboSearch->currentText(); es_search_list.clear(); if (!st.isEmpty() && !ui->widgetSearch->isHidden()) { QTextDocument::FindFlags ff = 0; 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.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); } void QCodeEdit::copyLineUp() { 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); 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); } void QCodeEdit::copyLineDown() { 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); 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); } 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.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); } 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; 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); } void QCodeEdit::indent() { 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); 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); } void QCodeEdit::deindent() { 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); 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); } 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.insertText(tabs); ui->textCode->setTextCursor(stc); } 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 > 0) killTimer(timer); timer = startTimer(500); ui->textCode->setTabStopWidth(ui->textCode->fontMetrics().width(" ")); int lc = ui->textCode->document()->lineCount(); if (prev_lc == lc) return; prev_lc = lc; ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width(QString(" %1").arg(lc))); 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; } void QCodeEdit::raiseHelp(QTextCursor tc, int arg) { bool ok; QPair scope = getScope(tc, &ok); QString st = tc.selectedText(); if (arg >= 0) st = scope.second; if (!ok || st.isEmpty()) { hideHelp(); return; } ok = false; ACList acl(autoCompletitionList(scope.first, scope.second)); foreach (const ACPair & i, acl) { foreach (const StringsPair & s, i.second) { QString ts = s.second; //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; ACClass acc = ac_classes.value(i.first); lbl_help[0]->setIcon(acc.icon); lbl_help[0]->setText(QString("[%1] %2 %3").arg(acc.name, s.first, selectArg(s.second, arg))); ok = true; break; } if (ok) break; } if (!ok) { hideHelp(); return; } //qDebug() << "help found" << tc.selectionStart() << tc.selectionEnd(); es_cursor.cursor = tc; applyExtraSelection(); //tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); lbl_help[0]->setFont(font()); qApp->processEvents(); widget_help->resize(widget_help->sizeHint()); qApp->processEvents(); QRect whr = ui->textCode->cursorRect(tc); whr.setWidth(ui->textCode->fontMetrics().width(st)); 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(); cursor_scope = scope.first; cursor_scope << scope.second; //qDebug() << "tooltip" << st; } void QCodeEdit::hideHelp() { 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(); ACPair acl; acl.first = -1; foreach (const QString & s, acwl) acl.second << StringsPair("", 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; } acl << wordsCompletitionList(scope.second); QFont bf(font()); bf.setBold(true); foreach (const ACPair & ac, acl) { if (ac.second.isEmpty()) continue; ACClass acc = ac_classes.value(ac.first); QTreeWidgetItem * gi = new QTreeWidgetItem(); gi->setText(0, acc.name); gi->setTextAlignment(0, Qt::AlignCenter); gi->setTextAlignment(1, Qt::AlignCenter); gi->setFont(0, bf); gi->setBackgroundColor(0, Qt::lightGray); gi->setFlags(Qt::ItemIsEnabled); completer->addTopLevelItem(gi); gi->setFirstColumnSpanned(true); foreach (const StringsPair & s, ac.second) { QTreeWidgetItem * ni = new QTreeWidgetItem(); ni->setIcon(0, acc.icon); ni->setText(0, s.first); ni->setText(1, s.second); completer->addTopLevelItem(ni); } } if (completer->topLevelItemCount() > 1) completer->setCurrentItem(completer->topLevelItem(1)); if (completer->isHidden()) completer->move(ui->textCode->mapToGlobal(ui->textCode->cursorRect().bottomRight())); if (completer->topLevelItemCount() > 0) { completer->setVisible(true); //qApp->processEvents(); int sz = completer->verticalScrollBar()->width(); for (int i = 0; i < completer->header()->count(); ++i) sz += qMax(sz, ((QAbstractItemView*)completer)->sizeHintForColumn(i)); completer->resize(sz, fontHeight() * 16); } else completer->hide(); } void QCodeEdit::commitCompletition() { if (completer->currentItem() == 0) return; if (!completer->currentItem()->flags().testFlag(Qt::ItemIsSelectable)) return; QString ins = completer->currentItem()->text(1), ret = completer->currentItem()->text(0); 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->currentItem()->text(1).endsWith("()")) { tc.movePosition(QTextCursor::Left); ui->textCode->setTextCursor(tc); } } else { if (completer->currentItem()->text(1).endsWith(")")) { tc.movePosition(QTextCursor::Right); ui->textCode->setTextCursor(tc); } if (completer->currentItem()->text(1).endsWith("()")) { tc.movePosition(QTextCursor::Right); ui->textCode->setTextCursor(tc); } } completer->hide(); } void QCodeEdit::textEdit_cursorPositionChanged() { es_line.cursor = ui->textCode->textCursor(); es_line.cursor.select(QTextCursor::LineUnderCursor); es_line.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); highlightBrackets(); applyExtraSelection(); } void QCodeEdit::textEdit_textChanged() { if (_replacing) return; searchAll(); updateLines(); } void QCodeEdit::textEdit_selectionChanged() { 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::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(); for (int i = es_search_list.size() - 1; i >= 0; --i) es_search_list[i].cursor.insertText(rt); _replacing = false; textEdit_textChanged(); }