Files
qad/libs/widgets/qcodeedit.cpp
Andrey 4497123421 refactoring qad widgets part 2
c++ cast, nullptr, forward declaration, agregate ui, connect to member functions, order and clear includes
2022-12-12 10:18:34 +03:00

2133 lines
55 KiB
C++

#include "qcodeedit.h"
#include "ui_qcodeedit.h"
#include "qcodeedit_completer_p.h"
#include "ecombobox.h"
#include "qad_types.h"
#include "iconedlabel.h"
#include <QLayout>
#include <QBoxLayout>
#include <QScrollBar>
#include <QLineEdit>
#include <QTextDocument>
#include <QTextDocumentFragment>
#include <QTextOption>
#include <QTextCursor>
#include <QTextBlock>
#include <QAction>
#include <QApplication>
#include <QDesktopServices>
#include <QPainter>
#include <QPlainTextEdit>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
# include <QWindow>
#endif
Q_DECLARE_METATYPE(QTextCursor)
Q_DECLARE_METATYPE(QCodeEdit::ACEntry)
class _QCE_Viewport: public QWidget {
public:
_QCE_Viewport(QWidget * p = nullptr): QWidget(p) {
setObjectName("__qcodeedit_viewport__");
setMouseTracking(true);
}
void paintEvent(QPaintEvent *) override {
if (!isEnabled()) return;
ce->drawCursor();
}
QCodeEdit * ce;
};
QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent), ui(new Ui::QCodeEdit) {
overlay = nullptr;
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;
comment_text = "//";
qRegisterMetaType<QTextCursor>();
qRegisterMetaType<QCodeEdit::ACEntry>();
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<int>(qRound(fontHeight(this) / 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);
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);
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 == nullptr) {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;
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(nullptr);
documentChanged(nullptr);
return;
}
if (!qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()))
doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
ui->textCode->setDocument(doc);
cursor_width = qMax<int>(qRound(fontHeight(this) / 10.), 1);
ui->textCode->setCursorWidth(0);
setShowSpaces(spaces_);
if (doc->property("_cursor").isValid()) {
setTextCursor(doc->property("_cursor").value<QTextCursor>());
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<QTextEdit::ExtraSelection> & 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;
}
QString QCodeEdit::commentText() const {
return comment_text;
}
void QCodeEdit::setCommentText(QString t) {
comment_text = t;
}
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.startsWith(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()) {
QTextCursor tc;
int tcpos = 0;
switch (e->type()) {
case QEvent::MouseButtonPress:
if (!isEnabled()) {
break;
}
tc = ui->textCode->cursorForPosition(static_cast<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(static_cast<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 (static_cast<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();
auto me = static_cast<QMouseEvent*>(e);
if (me->modifiers().testFlag(Qt::ControlModifier) && (me->button() == Qt::LeftButton))
if (!hasBlockSelection() && !ui->textCode->textCursor().hasSelection())
gotoLink();
} break;
case QEvent::MouseMove: {
if (!completer->isHidden()) break;
auto me = static_cast<QMouseEvent*>(e);
if (me->buttons() != 0)
switchBlockSelection();
if (me->modifiers().testFlag(Qt::ControlModifier))
if (!hasBlockSelection() && !ui->textCode->textCursor().hasSelection())
showLink();
} break;
case QEvent::Paint:
resizeOverlay();
break;
case QEvent::DragMove:
if (!isEnabled()) break;
drag_cursor = ui->textCode->cursorForPosition(
#if QT_VERSION_MAJOR <= 5
static_cast<QDragMoveEvent*>(e)->pos()
#else
static_cast<QDragMoveEvent*>(e)->position().toPoint()
#endif
);
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(static_cast<QHelpEvent*>(e)->pos());
tc.select(QTextCursor::WordUnderCursor);
raiseHelp(tc);
}
break;
case QEvent::KeyPress:
//qDebug() << "key" << ke;
if (codeKeyEvent(static_cast<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_Slash:
if (ke->modifiers().testFlag(Qt::ControlModifier)) {
toggleComment();
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:
if (!hasBlockSelection() && !ui->textCode->textCursor().hasSelection()) {
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 || ke->modifiers() == Qt::KeypadModifier)) {
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;
}
void QCodeEdit::toggleComment() {
QTextCursor tc = ui->textCode->textCursor();
tc.beginEditBlock();
int ss = tc.selectionStart();
int ss_ = ss;
int se = tc.selectionEnd();
QString st_ = tc.selection().toPlainText();
if (st_.endsWith("\n")) {
st_.chop(1);
se--;
}
int 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 need_comment = false;
QMap<int, bool> comms;
while (tc.position() <= se_) {
int line = tc.blockNumber();
tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, comment_text.size());
if (line == tc.blockNumber()) {
if (tc.selectedText() == comment_text) {
comms[line] = true;
} else {
need_comment = true;
comms[line] = false;
}
} else {
comms[line] = false;
}
tc.movePosition(QTextCursor::StartOfLine);
if (!tc.movePosition(QTextCursor::Down)) {
break;
}
}
tc.setPosition(ss);
bool first = true;
while (tc.position() <= se_) {
int line = tc.blockNumber();
if (need_comment) {
if (!comms[line]) {
tc.insertText(comment_text);
se_ += comment_text.size();
if (first) ss_ += comment_text.size();
}
} else {
if (comms[line]) {
tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, comment_text.size());
tc.removeSelectedText();
se_ -= comment_text.size();
if (first) ss_ -= comment_text.size();
}
}
first = false;
tc.movePosition(QTextCursor::StartOfLine);
if (!tc.movePosition(QTextCursor::Down)) {
break;
}
}
tc.setPosition(ss_);
tc.setPosition(se_, QTextCursor::KeepAnchor);
ui->textCode->setTextCursor(tc);
tc.endEditBlock();
}
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<QTextEdit::ExtraSelection>() << 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());
QPoint pe(tc.positionInBlock(), tc.blockNumber());
return (ui->textCode->cursorRect(tc) | ui->textCode->cursorRect(block_start_cursor))
.translated(ui->textCode->horizontalScrollBar()->value(), 0);
}
QVector<QTextCursor> QCodeEdit::blockSelectionCursors(QRect bsr) {
QVector<QTextCursor> 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;
bool back = false;
if (bsr.width() <= 1) {
if (is_del) {
del = true;
} else {
back = true;
}
}
QVector<QTextCursor> 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 / 2;
int new_pos = -1;
for (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();
int nullw = ui->textCode->fontMetrics().
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
horizontalAdvance
#else
width
#endif
(" ");
QVector<QTextCursor> clist = blockSelectionCursors(bsr);
for (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.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<QTextCursor> clist = blockSelectionCursors(blockSelectionRect());
for (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);
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);
}
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();
int ss_ = ss;
int se = tc.selectionEnd();
QString st_ = tc.selection().toPlainText();
if (st_.endsWith("\n")) {
st_.chop(1);
se--;
}
int 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();
int ss_ = ss;
int se = tc.selectionEnd();
QString st_ = tc.selection().toPlainText();
if (st_.endsWith("\n")) {
st_.chop(1);
se--;
}
int 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();
int ss_ = ss;
int se = tc.selectionEnd();
QString st_ = tc.selection().toPlainText();
if (st_.endsWith("\n")) {
st_.chop(1);
se--;
}
int 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();
int ss_ = ss;
int se = tc.selectionEnd();
QString st_ = tc.selection().toPlainText();
if (st_.endsWith("\n")) {
st_.chop(1);
se--;
}
int 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();
int ss_ = ss;
int se = tc.selectionEnd();
QString st_ = tc.selection().toPlainText();
if (st_.endsWith("\n")) {
st_.chop(1);
se--;
}
int 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();
int ss_ = ss;
int se = tc.selectionEnd();
QString st_ = tc.selection().toPlainText();
if (st_.endsWith("\n")) {
st_.chop(1);
se--;
}
int 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();
QTextCursor stc = tc;
tc.movePosition(QTextCursor::StartOfLine);
if (!tc.movePosition(QTextCursor::Up)) {
return;
}
tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
QString line = tc.selectedText();
QString 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<int>(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 += "<span style=\"font-weight:600;\">";
}
ret += al[i].trimmed();
if (i == arg) {
ret += "</span>";
}
}
ret += ")";
return ret;
}
QCodeEdit::ACEntry QCodeEdit::findEntryOnCursor(QTextCursor tc, int arg, ACClass * acc, QPair<QStringList, QString> * scope) {
bool ok = false;
QPair<QStringList, QString> sc = getScope(tc, &ok);
if (scope) {
*scope = sc;
}
QString st = tc.selectedText();
if (arg >= 0) {
st = sc.second;
}
if (!ok || st.isEmpty()) {
return ACEntry();
}
ok = false;
ACList acl(autoCompletitionList(sc.first, sc.second));
for (const ACSection & i : acl) {
for (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<QStringList, QString> scope;
ACEntry e = findEntryOnCursor(tc, arg, &acc, &scope);
if (e.isNull()) {
hideHelp();
return;
}
lbl_help[lhMain]->setText(QString("<html><body>[%1] %2 %3</html></body>").arg(acc.name, e.type, selectArg(e.name, arg)));
lbl_help[lhMain]->setIcon(acc.icon);
QString hint;
if (!e.hint.isEmpty()) {
hint = QString("<span style=\"font-style:italic;\">%1</span>").arg(e.hint);
}
lbl_help[lhHint]->setText(hint);
lbl_help[lhHint]->setHidden(hint.isEmpty());
//qDebug() << "help found" << tc.selectionStart() << tc.selectionEnd();
es_cursor.cursor = tc;
applyExtraSelection();
//tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
lbl_help[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(this) / 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 == 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());
QTextCursor 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;
for (const QString & s : acwl) {
acl.second << ACEntry("", s);
}
ret << acl;
}
return ret;
}
QPair<QStringList, QString> QCodeEdit::getScope(QTextCursor tc, bool * ok) {
QPair<QStringList, QString> 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<QStringList, QString> 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() == nullptr) 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].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();
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);
}