Files
qad/qad_widgets/qcodeedit.cpp

989 lines
28 KiB
C++

#include "qcodeedit.h"
#include <QLayout>
#include <QBoxLayout>
#include <QScrollBar>
#include <QTextDocument>
#include <QTextDocumentFragment>
#include <QTextOption>
#include <QTextCursor>
#include <QTextBlock>
#include <QAction>
#include <QApplication>
#include <QHeaderView>
QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
prev_lc = auto_comp_pl = -1;
textCode = textLines = 0;
timer = 0;
_ignore_focus_out = _destructor = 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));
lbl_help = new IconedLabel();
lbl_help->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
lbl_help->setFocusPolicy(Qt::NoFocus);
lbl_help->setFrameShadow(QFrame::Sunken);
lbl_help->setFrameShape(QFrame::StyledPanel);
lbl_help->setDirection(IconedLabel::RightToLeft);
completer = new QTreeWidget();
completer->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
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);
//completer->setColumnWidth(0, 180);
completer->resize(500, 200);
textCode = new QPlainTextEdit();
textLines = new QPlainTextEdit();
textCode->setFrameShadow(QFrame::Plain);
textCode->setFrameShape(QFrame::NoFrame);
textCode->setLineWrapMode(QPlainTextEdit::NoWrap);
textCode->setTabChangesFocus(false);
textLines->setFrameShadow(QFrame::Plain);
textLines->setFrameShape(QFrame::NoFrame);
textLines->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
textLines->setFocusPolicy(Qt::NoFocus);
textLines->setTextInteractionFlags(Qt::NoTextInteraction);
textLines->setLineWrapMode(QPlainTextEdit::NoWrap);
textLines->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
textLines->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
textLines->viewport()->setAutoFillBackground(false);
textLines->viewport()->setCursor(Qt::ArrowCursor);
textLines->setFixedWidth(textLines->fontMetrics().width(" "));
setLayout(new QBoxLayout(QBoxLayout::BottomToTop));
layout()->setContentsMargins(0, 0, 0, 0);
QFrame * frame = new QFrame();
frame->setFrameShadow(QFrame::Sunken);
frame->setFrameShape(QFrame::StyledPanel);
frame->setLayout(new QBoxLayout(QBoxLayout::LeftToRight));
frame->layout()->setContentsMargins(0, 0, 0, 0);
frame->layout()->setSpacing(0);
frame->layout()->addWidget(textLines);
frame->layout()->addWidget(textCode);
layout()->addWidget(frame);
QAction * a = new QAction(this);
a->setShortcut(QKeySequence("Shift+Tab"));
a->setShortcutContext(Qt::WidgetShortcut);
connect(a, SIGNAL(triggered(bool)), this, SLOT(deindent()));
textCode->addAction(a);
a = new QAction(this);
a->setShortcut(QKeySequence("Ctrl+D"));
a->setShortcutContext(Qt::WidgetShortcut);
connect(a, SIGNAL(triggered(bool)), this, SLOT(deleteLine()));
textCode->addAction(a);
frame->setFocusProxy(textCode);
QTextOption to = textLines->document()->defaultTextOption();
to.setAlignment(Qt::AlignTop | Qt::AlignRight);
textLines->document()->setDefaultTextOption(to);
setShowSpaces(true);
connect(completer, SIGNAL(itemDoubleClicked(QTreeWidgetItem * ,int)), this, SLOT(commitCompletition()));
connect(textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), textLines->verticalScrollBar(), SLOT(setValue(int)));
connect(textCode, SIGNAL(textChanged()), this, SLOT(updateLines()));
connect(textCode, SIGNAL(textChanged()), this, SIGNAL(textChanged()));
connect(textCode, SIGNAL(cursorPositionChanged()), this, SLOT(textEdit_cursorPositionChanged()));
connect(textCode, SIGNAL(selectionChanged()), this, SLOT(textEdit_selectionChanged()));
updateLines();
registerAutoCompletitionClass(-1, QCodeEdit::Keyword, "Words", QIcon(":/icons/code-word.png"));
}
QCodeEdit::~QCodeEdit() {
_destructor = true;
delete textCode;
delete textLines;
delete completer;
delete lbl_help;
}
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);
textCode->removeEventFilter(this);
textCode->viewport()->removeEventFilter(this);
textLines->viewport()->removeEventFilter(this);
return QWidget::eventFilter(o, e);
}
if (textLines) {
if (o == 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<QPoint&>(((QMouseEvent*)e)->pos()) = QPoint(0, ((QMouseEvent*)e)->pos().y());
#endif
QApplication::sendEvent(textCode->viewport(), e);
return true;
}
if (e->type() == QEvent::Wheel) {
QApplication::sendEvent(textCode->viewport(), e);
return true;
}
}
}
if (o == completer) {
//qDebug() << o << e;
if (e->type() == QEvent::WindowActivate)
_ignore_focus_out = true;
//qDebug() << e;
return QWidget::eventFilter(o, e);
}
if (textCode) {
if (o == textCode->viewport()) {
if (e->type() == QEvent::MouseButtonPress) {
completer->hide();
hideHelp();
}
if (e->type() == QEvent::ToolTip) {
QTextCursor tc = textCode->cursorForPosition(((QHelpEvent*)e)->pos());
tc.select(QTextCursor::WordUnderCursor);
raiseHelp(tc);
}
return QWidget::eventFilter(o, e);
}
if (o == textCode) {
//qDebug() << o << e;
QMetaObject::invokeMethod(this, "syncScrolls", Qt::QueuedConnection);
QKeyEvent * ke;
QChar kc(0);
switch (e->type()) {
case QEvent::KeyPress:
ke = (QKeyEvent * )e;
switch (ke->key()) {
case Qt::Key_Space:
if (ke->modifiers().testFlag(Qt::ControlModifier)) {
invokeAutoCompletition(true);
return true;
}
break;
case Qt::Key_Escape:
completer->hide();
hideHelp();
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 (textCode->textCursor().selectedText().isEmpty())
QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection);
break;
case Qt::Key_Tab:
if (!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);
textCode->installEventFilter(this);
textCode->viewport()->installEventFilter(this);
textLines->viewport()->installEventFilter(this);
}
void QCodeEdit::timerEvent(QTimerEvent * ) {
parse();
emit parseRequest();
killTimer(timer);
timer = 0;
}
void QCodeEdit::applyExtraSelection() {
textCode->setExtraSelections(QList<QTextEdit::ExtraSelection>() << es_line << es_selected << es_custom << 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::syncScrolls() {
textLines->verticalScrollBar()->setValue(textCode->verticalScrollBar()->value());
}
void QCodeEdit::deleteLine() {
QTextCursor tc = 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);
textCode->setTextCursor(tc);
}
void QCodeEdit::copyLineUp() {
QTextCursor tc = 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();
textCode->setTextCursor(tc);
}
void QCodeEdit::copyLineDown() {
QTextCursor tc = 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();
textCode->setTextCursor(tc);
}
void QCodeEdit::moveLineUp() {
QTextCursor tc = 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();
textCode->setTextCursor(tc);
}
void QCodeEdit::moveLineDown() {
QTextCursor tc = 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();
textCode->setTextCursor(tc);
}
void QCodeEdit::indent() {
QTextCursor tc = 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);
textCode->setTextCursor(tc);
}
void QCodeEdit::deindent() {
QTextCursor tc = 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);
textCode->setTextCursor(tc);
}
void QCodeEdit::autoIndent() {
QTextCursor tc = 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<int>(0, line.count(QChar('{')) - line.count(QChar('}')));
tabs.append(QString("\t").repeated(nt));
if (tabs.isEmpty()) return;
stc.insertText(tabs);
textCode->setTextCursor(stc);
}
void QCodeEdit::scrollToTop() {
prev_lc = -1;
updateLines();
textCode->verticalScrollBar()->setValue(0);
textLines->verticalScrollBar()->setValue(0);
}
void QCodeEdit::updateLines() {
if (timer > 0) killTimer(timer);
timer = startTimer(500);
textCode->setTabStopWidth(textCode->fontMetrics().width(" "));
int lc = textCode->document()->lineCount();
if (prev_lc == lc) return;
prev_lc = lc;
textLines->setFixedWidth(textLines->fontMetrics().width(QString(" %1").arg(lc)));
textLines->clear();
for (int i = 1; i <= lc; ++i)
textLines->appendPlainText(QString("%1").arg(i));
textLines->verticalScrollBar()->setValue(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;
}
void QCodeEdit::raiseHelp(QTextCursor tc, int arg) {
bool ok;
QPair<QStringList, QString> scope = getScope(tc, &ok);
//qDebug() << "help" << scope;
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->setIcon(acc.icon);
lbl_help->setText(QString("<html><body>[%1] %2 %3</html></body>").arg(acc.name, s.first, selectArg(s.second, arg)));
ok = true;
break;
}
if (ok) break;
}
if (!ok) {
hideHelp();
return;
}
es_cursor.cursor = tc;
applyExtraSelection();
tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, st.size());
lbl_help->setFont(font());
lbl_help->resize(lbl_help->sizeHint());
lbl_help->move(textCode->mapToGlobal(textCode->cursorRect(tc).topLeft() - QPoint(0, lbl_help->height() + 8)));
lbl_help->show();
//qDebug() << "tooltip" << st;
}
void QCodeEdit::hideHelp() {
lbl_help->hide();
es_cursor.cursor = QTextCursor();
applyExtraSelection();
}
QTextCursor QCodeEdit::functionStart(QTextCursor tc, int * arg) {
QString doc = 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(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(textCode->document()->begin()), stc;
QStringList acwl;
tc = QTextCursor(textCode->document()->begin());
while (true) {
tc = 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<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 = 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(textCode->textCursor(), &arg);
if (!htc.isNull()) {
//qDebug() << "raise";
raiseHelp(htc, arg);
}
bool ok;
QPair<QStringList, QString> scope = getScope(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(textCode->mapToGlobal(textCode->cursorRect().bottomRight()));
completer->setVisible(completer->topLevelItemCount() > 0);
}
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 = 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()) {
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 = stc;
if (shifted)
tc.movePosition(QTextCursor::Right);
}
textCode->setTextCursor(tc);
textCode->textCursor().insertText(ins);
tc = textCode->textCursor();
if (ins_br) {
if (ret == "void") {
tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
if (tc.selectedText() != ";") {
textCode->textCursor().insertText(";");
tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
}
}
if (ins.endsWith(")") && !completer->currentItem()->text(1).endsWith("()")) {
tc.movePosition(QTextCursor::Left);
textCode->setTextCursor(tc);
}
} else {
if (completer->currentItem()->text(1).endsWith(")")) {
tc.movePosition(QTextCursor::Right);
textCode->setTextCursor(tc);
}
if (completer->currentItem()->text(1).endsWith("()")) {
tc.movePosition(QTextCursor::Right);
textCode->setTextCursor(tc);
}
}
completer->hide();
}
void QCodeEdit::textEdit_cursorPositionChanged() {
es_line.cursor = textCode->textCursor();
es_line.cursor.select(QTextCursor::LineUnderCursor);
es_line.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
applyExtraSelection();
}
void QCodeEdit::textEdit_textChanged() {
updateLines();
}
void QCodeEdit::textEdit_selectionChanged() {
es_selected.clear();
QString sf = textCode->textCursor().selectedText();
if (sf.trimmed().isEmpty() || sf.contains("\n")) {
applyExtraSelection();
return;
}
QTextCursor tc(textCode->document()->begin());
QTextEdit::ExtraSelection es;
es.format.setBackground(QColor(251, 250, 150));
while (true) {
tc = 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 = textCode->document()->defaultTextOption();
to.setFlags(yes ? QTextOption::ShowTabsAndSpaces : (QTextOption::Flags)0);
textCode->document()->setDefaultTextOption(to);
}