diff --git a/src/Gui/ExpressionCompleter.cpp b/src/Gui/ExpressionCompleter.cpp index 26b3d82772..5f16907b1e 100644 --- a/src/Gui/ExpressionCompleter.cpp +++ b/src/Gui/ExpressionCompleter.cpp @@ -30,14 +30,26 @@ using namespace Gui; class ExpressionCompleterModel: public QAbstractItemModel { public: - ExpressionCompleterModel(QObject *parent, const App::DocumentObject *obj) - :QAbstractItemModel(parent) + ExpressionCompleterModel(QObject *parent, const App::DocumentObject *obj, bool noProperty) + :QAbstractItemModel(parent), noProperty(noProperty) { + setDocumentObject(obj); + } + + void setDocumentObject(const App::DocumentObject *obj) { + beginResetModel(); if(obj) { currentDoc = obj->getDocument()->getName(); currentObj = obj->getNameInDocument(); - inList = obj->getInListEx(true); + if(!noProperty) + inList = obj->getInListEx(true); + } else { + currentDoc.clear(); + currentObj.clear(); + inList.clear(); } + endResetModel(); + } // This ExpressionCompleter model works without any pysical items. @@ -139,7 +151,7 @@ public: obj = objs[idx/2]; if(inList.count(obj)) return; - } else { + } else if (!noProperty) { auto cobj = doc->getObject(currentObj.c_str()); if(cobj) { idx -= objSize; @@ -168,9 +180,9 @@ public: res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str()); else res = QString::fromLatin1(obj->getNameInDocument()); - if(sep) + if(sep && !noProperty) res += QLatin1Char('.'); - }else { + } else { if(idx & 1) res = QString::fromUtf8(quote(doc->Label.getStrValue()).c_str()); else @@ -199,7 +211,7 @@ public: res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str()); else res = QString::fromLatin1(obj->getNameInDocument()); - if(sep) + if(sep && !noProperty) res += QLatin1Char('.'); v->setValue(res); } @@ -207,6 +219,8 @@ public: } } + if(noProperty) + return; if(!prop) { idx = row; obj->getPropertyList(props); @@ -217,8 +231,13 @@ public: if(count) *count = propSize; } - if(v) - *v = QString::fromLatin1(prop->getName()); + if(v) { + QString res; + if(sep) + res = QLatin1String("."); + res += QString::fromLatin1(prop->getName()); + *v = res; + } return; } @@ -288,6 +307,7 @@ private: std::set inList; std::string currentDoc; std::string currentObj; + bool noProperty; }; /** @@ -297,8 +317,9 @@ private: * @param parent Parent object owning the completer. */ -ExpressionCompleter::ExpressionCompleter(const App::DocumentObject * currentDocObj, QObject *parent) - : QCompleter(parent), prefixStart(0), currentObj(currentDocObj) +ExpressionCompleter::ExpressionCompleter(const App::DocumentObject * currentDocObj, + QObject *parent, bool noProperty) + : QCompleter(parent), currentObj(currentDocObj), noProperty(noProperty) { setCaseSensitivity(Qt::CaseInsensitive); } @@ -307,9 +328,19 @@ void ExpressionCompleter::init() { if(model()) return; - setModel(new ExpressionCompleterModel(this,currentObj.getObject())); + setModel(new ExpressionCompleterModel(this,currentObj.getObject(),noProperty)); } +void ExpressionCompleter::setDocumentObject(const App::DocumentObject *obj) { + if(!obj || !obj->getNameInDocument()) + currentObj = App::DocumentObjectT(); + else + currentObj = obj; + setCompletionPrefix(QString()); + auto m = model(); + if(m) + static_cast(m)->setDocumentObject(obj); +} QString ExpressionCompleter::pathFromIndex ( const QModelIndex & index ) const { @@ -385,7 +416,7 @@ QStringList ExpressionCompleter::splitPath ( const QString & input ) const // Code below inspired by blog entry: // https://john.nachtimwald.com/2009/07/04/qcompleter-and-comma-separated-tags/ -void ExpressionCompleter::slotUpdate(const QString & prefix) +void ExpressionCompleter::slotUpdate(const QString & prefix, int pos) { init(); @@ -395,34 +426,84 @@ void ExpressionCompleter::slotUpdate(const QString & prefix) // Compute start; if prefix starts with =, start parsing from offset 1. int start = (prefix.size() > 0 && prefix.at(0) == QChar::fromLatin1('=')) ? 1 : 0; - // Tokenize prefix - std::vector > tokens = ExpressionParser::tokenize(Base::Tools::toStdString(prefix.mid(start))); + std::string expression = Base::Tools::toStdString(prefix.mid(start)); - // No tokens, or last char is a space? - if (tokens.size() == 0 || (prefix.size() > 0 && prefix[prefix.size() - 1] == QChar(32))) { + // Tokenize prefix + std::vector > tokens = ExpressionParser::tokenize(expression); + + // No tokens + if (tokens.size() == 0) { if (popup()) popup()->setVisible(false); return; } - // Extract last tokens that can be rebuilt to a variable - ssize_t i = static_cast(tokens.size()) - 1; - while (i >= 0) { - if (get<0>(tokens[i]) != ExpressionParser::IDENTIFIER && - get<0>(tokens[i]) != ExpressionParser::STRING && - get<0>(tokens[i]) != ExpressionParser::UNIT && - get<0>(tokens[i]) != '.') + prefixEnd = prefix.size(); + + // Pop those trailing tokens depending on the given position, which may be + // in the middle of a token, and we shall include that token. + for(auto it=tokens.begin();it!=tokens.end();++it) { + if(get<1>(*it) >= pos) { + // Include the immediatly followed '.' or '#', because we'll be + // inserting these separater too, in ExpressionCompleteModel::pathFromIndex() + if(it!=tokens.begin() && get<0>(*it)!='.' && get<0>(*it)!='#') + it = it-1; + tokens.resize(it-tokens.begin()+1); + prefixEnd = start + get<1>(*it) + (int)get<2>(*it).size(); break; - --i; + } } - ++i; + int trim = 0; + if(prefixEnd > pos) + trim = prefixEnd - pos; + + // Extract last tokens that can be rebuilt to a variable + ssize_t i = static_cast(tokens.size()) - 1; + + // First, check if we have unclosing string starting from the end + bool stringing = false; + for(; i>=0; --i) { + int token = get<0>(tokens[i]); + if(token == ExpressionParser::STRING) { + stringing = false; + break; + } + if(token==ExpressionParser::LT + && i && get<0>(tokens[i-1])==ExpressionParser::LT) + { + --i; + stringing = true; + break; + } + } + + // Not an unclosed string and the last character is a space + if(!stringing && prefix.size() && prefix[prefixEnd-1] == QChar(32)) { + if (popup()) + popup()->setVisible(false); + return; + } + + if(!stringing) { + i = static_cast(tokens.size()) - 1; + for(;i>=0;--i) { + int token = get<0>(tokens[i]); + if (token != '.' && token != '#' && + token != ExpressionParser::IDENTIFIER && + token != ExpressionParser::STRING && + token != ExpressionParser::UNIT) + break; + } + ++i; + + } // Set prefix start for use when replacing later if (i == static_cast(tokens.size())) - prefixStart = prefix.size(); + prefixStart = prefixEnd; else - prefixStart = (prefix.at(0) == QChar::fromLatin1('=') ? 1 : 0) + get<1>(tokens[i]); + prefixStart = start + get<1>(tokens[i]); // Build prefix from tokens while (i < static_cast(tokens.size())) { @@ -430,6 +511,9 @@ void ExpressionCompleter::slotUpdate(const QString & prefix) ++i; } + if(trim && trim<(int)completionPrefix.size() ) + completionPrefix.resize(completionPrefix.size()-trim); + // Set completion prefix setCompletionPrefix(Base::Tools::fromStdString(completionPrefix)); @@ -441,10 +525,11 @@ void ExpressionCompleter::slotUpdate(const QString & prefix) } } -ExpressionLineEdit::ExpressionLineEdit(QWidget *parent) +ExpressionLineEdit::ExpressionLineEdit(QWidget *parent, bool noProperty) : QLineEdit(parent) , completer(0) , block(true) + , noProperty(noProperty) { connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(slotTextChanged(const QString&))); } @@ -452,17 +537,16 @@ ExpressionLineEdit::ExpressionLineEdit(QWidget *parent) void ExpressionLineEdit::setDocumentObject(const App::DocumentObject * currentDocObj) { if (completer) { - delete completer; - completer = 0; + completer->setDocumentObject(currentDocObj); + return; } - if (currentDocObj != 0) { - completer = new ExpressionCompleter(currentDocObj, this); + completer = new ExpressionCompleter(currentDocObj, this, noProperty); completer->setWidget(this); completer->setCaseSensitivity(Qt::CaseInsensitive); connect(completer, SIGNAL(activated(QString)), this, SLOT(slotCompleteText(QString))); connect(completer, SIGNAL(highlighted(QString)), this, SLOT(slotCompleteText(QString))); - connect(this, SIGNAL(textChanged2(QString)), completer, SLOT(slotUpdate(QString))); + connect(this, SIGNAL(textChanged2(QString,int)), completer, SLOT(slotUpdate(QString,int))); } } @@ -480,19 +564,22 @@ void ExpressionLineEdit::hideCompleter() void ExpressionLineEdit::slotTextChanged(const QString & text) { if (!block) { - Q_EMIT textChanged2(text.left(cursorPosition())); + Q_EMIT textChanged2(text,cursorPosition()); } } void ExpressionLineEdit::slotCompleteText(const QString & completionPrefix) { - int start = completer->getPrefixStart(); + int start,end; + completer->getPrefixRange(start,end); QString before(text().left(start)); - QString after(text().mid(cursorPosition())); + QString after(text().mid(end)); Base::FlagToggler flag(block,false); - setText(before + completionPrefix + after); - setCursorPosition(QString(before + completionPrefix).length()); + before += completionPrefix; + setText(before + after); + setCursorPosition(before.length()); + completer->updatePrefixEnd(before.length()); } void ExpressionLineEdit::keyPressEvent(QKeyEvent *e) { @@ -514,8 +601,8 @@ ExpressionTextEdit::ExpressionTextEdit(QWidget *parent) void ExpressionTextEdit::setDocumentObject(const App::DocumentObject * currentDocObj) { if (completer) { - delete completer; - completer = 0; + completer->setDocumentObject(currentDocObj); + return; } if (currentDocObj != 0) { @@ -524,7 +611,7 @@ void ExpressionTextEdit::setDocumentObject(const App::DocumentObject * currentDo completer->setCaseSensitivity(Qt::CaseInsensitive); connect(completer, SIGNAL(activated(QString)), this, SLOT(slotCompleteText(QString))); connect(completer, SIGNAL(highlighted(QString)), this, SLOT(slotCompleteText(QString))); - connect(this, SIGNAL(textChanged2(QString)), completer, SLOT(slotUpdate(QString))); + connect(this, SIGNAL(textChanged2(QString,int)), completer, SLOT(slotUpdate(QString,int))); } } @@ -543,21 +630,22 @@ void ExpressionTextEdit::slotTextChanged() { if (!block) { QTextCursor cursor = textCursor(); - Q_EMIT textChanged2(cursor.block().text().left(cursor.positionInBlock())); + Q_EMIT textChanged2(cursor.block().text(),cursor.positionInBlock()); } } void ExpressionTextEdit::slotCompleteText(const QString & completionPrefix) { QTextCursor cursor = textCursor(); - int start = completer->getPrefixStart(); + int start,end; + completer->getPrefixRange(start,end); int pos = cursor.positionInBlock(); - if(pos>=start) { - Base::FlagToggler flag(block,false); - if(pos>start) - cursor.movePosition(QTextCursor::PreviousCharacter,QTextCursor::KeepAnchor,pos-start); - cursor.insertText(completionPrefix); - } + if(pos flag(block,false); + cursor.insertText(completionPrefix); + completer->updatePrefixEnd(cursor.positionInBlock()); } void ExpressionTextEdit::keyPressEvent(QKeyEvent *e) { diff --git a/src/Gui/ExpressionCompleter.h b/src/Gui/ExpressionCompleter.h index 77ed9ecbe2..7370f16d7b 100644 --- a/src/Gui/ExpressionCompleter.h +++ b/src/Gui/ExpressionCompleter.h @@ -28,32 +28,45 @@ class GuiExport ExpressionCompleter : public QCompleter { Q_OBJECT public: - ExpressionCompleter(const App::DocumentObject * currentDocObj, QObject *parent = 0); + ExpressionCompleter(const App::DocumentObject * currentDocObj, + QObject *parent = 0, bool noProperty = false); - int getPrefixStart() const { return prefixStart; } + void getPrefixRange(int &start, int &end) const { + start = prefixStart; + end = prefixEnd; + } + + void updatePrefixEnd(int end) { + prefixEnd = end; + } + + void setDocumentObject(const App::DocumentObject*); public Q_SLOTS: - void slotUpdate(const QString &prefix); + void slotUpdate(const QString &prefix, int pos); private: void init(); virtual QString pathFromIndex ( const QModelIndex & index ) const; virtual QStringList splitPath ( const QString & path ) const; - int prefixStart; + int prefixStart = 0; + int prefixEnd = 0; + App::DocumentObjectT currentObj; + bool noProperty; }; class GuiExport ExpressionLineEdit : public QLineEdit { Q_OBJECT public: - ExpressionLineEdit(QWidget *parent = 0); + ExpressionLineEdit(QWidget *parent = 0, bool noProperty=false); void setDocumentObject(const App::DocumentObject *currentDocObj); bool completerActive() const; void hideCompleter(); Q_SIGNALS: - void textChanged2(QString text); + void textChanged2(QString text, int pos); public Q_SLOTS: void slotTextChanged(const QString & text); void slotCompleteText(const QString & completionPrefix); @@ -62,6 +75,7 @@ protected: private: ExpressionCompleter * completer; bool block; + bool noProperty; }; class GuiExport ExpressionTextEdit : public QPlainTextEdit { @@ -74,7 +88,7 @@ public: protected: void keyPressEvent(QKeyEvent * event); Q_SIGNALS: - void textChanged2(QString text); + void textChanged2(QString text, int pos); public Q_SLOTS: void slotTextChanged(); void slotCompleteText(const QString & completionPrefix);