#include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #endif #include #include #include #include #include #include "ExpressionCompleter.h" #include Q_DECLARE_METATYPE(App::ObjectIdentifier); using namespace App; using namespace Gui; /** * @brief Construct an ExpressionCompleter object. * @param currentDoc Current document to generate the model from. * @param currentDocObj Current document object to generate model from. * @param parent Parent object owning the completer. */ ExpressionCompleter::ExpressionCompleter(const App::Document * currentDoc, const App::DocumentObject * currentDocObj, QObject *parent) : QCompleter(parent), prefixStart(0) { QStandardItemModel* model = new QStandardItemModel(this); std::vector docs = App::GetApplication().getDocuments(); std::vector::const_iterator di = docs.begin(); std::vector deps; if (currentDocObj) deps = currentDocObj->getInList(); std::set forbidden; for (std::vector::const_iterator it = deps.begin(); it != deps.end(); ++it) forbidden.insert(*it); /* Create tree with full path to all objects */ while (di != docs.end()) { QStandardItem* docItem = new QStandardItem(QString::fromLatin1((*di)->getName())); docItem->setData(QString::fromLatin1((*di)->getName()) + QString::fromLatin1("#"), Qt::UserRole); createModelForDocument(*di, docItem, forbidden); model->appendRow(docItem); ++di; } /* Create branch with current document object */ if (currentDocObj) { createModelForDocument(currentDocObj->getDocument(), model->invisibleRootItem(), forbidden); createModelForDocumentObject(currentDocObj, model->invisibleRootItem()); } else { if (currentDoc) createModelForDocument(currentDoc, model->invisibleRootItem(), forbidden); } setModel(model); setCaseSensitivity(Qt::CaseInsensitive); } /** * @brief Create model node given document, the parent and forbidden node. * @param doc Document * @param parent Parent item * @param forbidden Forbidden document objects; typically those that will create a loop in the DAG if used. */ void ExpressionCompleter::createModelForDocument(const App::Document * doc, QStandardItem * parent, const std::set & forbidden) { std::vector docObjs = doc->getObjects(); std::vector::const_iterator doi = docObjs.begin(); while (doi != docObjs.end()) { std::set::const_iterator it = forbidden.find(*doi); // Skip? if (it != forbidden.end()) { ++doi; continue; } QStandardItem* docObjItem = new QStandardItem(QString::fromLatin1((*doi)->getNameInDocument())); docObjItem->setData(QString::fromLatin1((*doi)->getNameInDocument()) + QString::fromLatin1("."), Qt::UserRole); createModelForDocumentObject(*doi, docObjItem); parent->appendRow(docObjItem); if (strcmp((*doi)->getNameInDocument(), (*doi)->Label.getValue()) != 0) { std::string label = (*doi)->Label.getValue(); if (!ExpressionParser::isTokenAnIndentifier(label)) label = quote(label); docObjItem = new QStandardItem(QString::fromUtf8(label.c_str())); docObjItem->setData( QString::fromUtf8(label.c_str()) + QString::fromLatin1("."), Qt::UserRole); createModelForDocumentObject(*doi, docObjItem); parent->appendRow(docObjItem); } ++doi; } } /** * @brief Create model nodes for document object * @param docObj Document object * @param parent Parent item */ void ExpressionCompleter::createModelForDocumentObject(const DocumentObject * docObj, QStandardItem * parent) { std::vector props; docObj->getPropertyList(props); std::vector::const_iterator pi = props.begin(); while (pi != props.end()) { // Skip all types of links if ((*pi)->isDerivedFrom(App::PropertyLink::getClassTypeId()) || (*pi)->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) { ++pi; continue; } createModelForPaths(*pi, parent); ++pi; } } /** * @brief Create nodes for a property. * @param prop * @param docObjItem */ void ExpressionCompleter::createModelForPaths(const App::Property * prop, QStandardItem *docObjItem) { std::vector paths; std::vector::const_iterator ppi; prop->getPaths(paths); for (ppi = paths.begin(); ppi != paths.end(); ++ppi) { QStandardItem* pathItem = new QStandardItem(Base::Tools::fromStdString(ppi->toString())); QVariant value; value.setValue(*ppi); pathItem->setData(value, Qt::UserRole); docObjItem->appendRow(pathItem); } } QString ExpressionCompleter::pathFromIndex ( const QModelIndex & index ) const { QStandardItemModel * m = static_cast(model()); if (m->data(index, Qt::UserRole).canConvert()) { App::ObjectIdentifier p = m->data(index, Qt::UserRole).value(); QString pStr = Base::Tools::fromStdString(p.toString()); QString parentStr; QModelIndex parent = index.parent(); while (parent.isValid()) { QString thisParentStr = m->data(parent, Qt::UserRole).toString(); parentStr = thisParentStr + parentStr; parent = parent.parent(); } return parentStr + pStr; } else if (m->data(index, Qt::UserRole).canConvert()) { QModelIndex parent = index; QString parentStr; while (parent.isValid()) { QString thisParentStr = m->data(parent, Qt::UserRole).toString(); parentStr = thisParentStr + parentStr; parent = parent.parent(); } return parentStr; } else return QString(); } QStringList ExpressionCompleter::splitPath ( const QString & path ) const { try { App::ObjectIdentifier p = ObjectIdentifier::parse(0, path.toUtf8().constData()); QStringList l; if (p.getProperty()) { for (int i = 0; i < p.numComponents(); ++i) l << Base::Tools::fromStdString(p.getPropertyComponent(i).toString()); return l; } else { std::vector sl = p.getStringList(); std::vector::const_iterator sli = sl.begin(); while (sli != sl.end()) { l << Base::Tools::fromStdString(*sli); ++sli; } return l; } } catch (const Base::Exception &) { return QStringList() << path; } } // Code below inspired by blog entry: // https://john.nachtimwald.com/2009/07/04/qcompleter-and-comma-separated-tags/ void ExpressionCompleter::slotUpdate(const QString & prefix) { using namespace boost::tuples; std::string completionPrefix; // 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))); // No tokens, or last char is a space? if (tokens.size() == 0 || (prefix.size() > 0 && prefix[prefix.size() - 1] == QChar(32))) { 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]) != '.') break; --i; } ++i; // Set prefix start for use when replacing later if (i == static_cast(tokens.size())) prefixStart = prefix.size(); else prefixStart = (prefix.at(0) == QChar::fromLatin1('=') ? 1 : 0) + get<1>(tokens[i]); // Build prefix from tokens while (i < static_cast(tokens.size())) { completionPrefix += get<2>(tokens[i]); ++i; } // Set completion prefix setCompletionPrefix(Base::Tools::fromStdString(completionPrefix)); if (!completionPrefix.empty() && widget()->hasFocus()) complete(); else { if (popup()) popup()->setVisible(false); } } ExpressionLineEdit::ExpressionLineEdit(QWidget *parent) : QLineEdit(parent) , completer(0) , block(false) { connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(slotTextChanged(const QString&))); } void ExpressionLineEdit::setDocumentObject(const App::DocumentObject * currentDocObj) { if (completer) { delete completer; completer = 0; } if (currentDocObj != 0) { completer = new ExpressionCompleter(currentDocObj->getDocument(), currentDocObj, this); 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))); } } bool ExpressionLineEdit::completerActive() const { return completer && completer->popup() && completer->popup()->isVisible(); } void ExpressionLineEdit::hideCompleter() { if (completer && completer->popup()) completer->popup()->setVisible(false); } void ExpressionLineEdit::slotTextChanged(const QString & text) { if (!block) { Q_EMIT textChanged2(text.left(cursorPosition())); } } void ExpressionLineEdit::slotCompleteText(const QString & completionPrefix) { int start = completer->getPrefixStart(); QString before(text().left(start)); QString after(text().mid(cursorPosition())); block = true; setText(before + completionPrefix + after); setCursorPosition(QString(before + completionPrefix).length()); block = false; } #include "moc_ExpressionCompleter.cpp"