Added ExpressionCompleter class.
This commit is contained in:
parent
d7cbdf6a31
commit
31a6fece52
|
@ -204,6 +204,7 @@ set(Gui_MOC_HDRS
|
|||
DockWindowManager.h
|
||||
DocumentRecovery.h
|
||||
EditorView.h
|
||||
ExpressionCompleter.h
|
||||
FileDialog.h
|
||||
Flag.h
|
||||
GraphicsViewZoom.h
|
||||
|
@ -967,6 +968,7 @@ SET(FreeCADGui_CPP_SRCS
|
|||
DocumentModel.cpp
|
||||
DocumentPyImp.cpp
|
||||
GraphicsViewZoom.cpp
|
||||
ExpressionCompleter.cpp
|
||||
GuiApplicationNativeEventAware.cpp
|
||||
GuiConsole.cpp
|
||||
Macro.cpp
|
||||
|
@ -986,6 +988,7 @@ SET(FreeCADGui_SRCS
|
|||
BitmapFactory.h
|
||||
Document.h
|
||||
DocumentModel.h
|
||||
ExpressionCompleter.h
|
||||
FreeCADGuiInit.py
|
||||
GraphicsViewZoom.h
|
||||
GuiApplicationNativeEventAware.h
|
||||
|
|
320
src/Gui/ExpressionCompleter.cpp
Normal file
320
src/Gui/ExpressionCompleter.cpp
Normal file
|
@ -0,0 +1,320 @@
|
|||
#include "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QStandardItem>
|
||||
#include <QStandardItemModel>
|
||||
#include <QLineEdit>
|
||||
#include <QAbstractItemView>
|
||||
#endif
|
||||
|
||||
#include <Base/Tools.h>
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/ObjectIdentifier.h>
|
||||
#include "ExpressionCompleter.h"
|
||||
|
||||
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)
|
||||
{
|
||||
QStandardItemModel* model = new QStandardItemModel(this);
|
||||
|
||||
std::vector<App::Document*> docs = App::GetApplication().getDocuments();
|
||||
std::vector<App::Document*>::const_iterator di = docs.begin();
|
||||
|
||||
std::vector<DocumentObject*> deps = currentDocObj->getInList();
|
||||
std::set<const DocumentObject*> forbidden;
|
||||
|
||||
for (std::vector<DocumentObject*>::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::fromAscii((*di)->getName()));
|
||||
|
||||
docItem->setData(QString::fromAscii((*di)->getName()) + QString::fromAscii("#"), 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<const DocumentObject*> & forbidden) {
|
||||
std::vector<App::DocumentObject*> docObjs = doc->getObjects();
|
||||
std::vector<App::DocumentObject*>::const_iterator doi = docObjs.begin();
|
||||
|
||||
while (doi != docObjs.end()) {
|
||||
std::set<const DocumentObject*>::const_iterator it = forbidden.find(*doi);
|
||||
|
||||
// Skip?
|
||||
if (it != forbidden.end()) {
|
||||
++doi;
|
||||
continue;
|
||||
}
|
||||
|
||||
QStandardItem* docObjItem = new QStandardItem(QString::fromAscii((*doi)->getNameInDocument()));
|
||||
|
||||
docObjItem->setData(QString::fromAscii((*doi)->getNameInDocument()) + QString::fromAscii("."), 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::fromAscii("."), 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<App::Property*> props;
|
||||
docObj->getPropertyList(props);
|
||||
|
||||
std::vector<App::Property*>::const_iterator pi = props.begin();
|
||||
while (pi != props.end()) {
|
||||
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<ObjectIdentifier> paths;
|
||||
std::vector<ObjectIdentifier>::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<QStandardItemModel*>(model());
|
||||
|
||||
if (m->data(index, Qt::UserRole).canConvert<App::ObjectIdentifier>()) {
|
||||
App::ObjectIdentifier p = m->data(index, Qt::UserRole).value<App::ObjectIdentifier>();
|
||||
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<QString>()) {
|
||||
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<std::string> sl = p.getStringList();
|
||||
std::vector<std::string>::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::vector<boost::tuple<int, int, std::string> > tokens = ExpressionParser::tokenize(Base::Tools::toStdString(prefix));
|
||||
std::string completionPrefix;
|
||||
|
||||
if (tokens.size() == 0 || (prefix.size() > 0 && prefix[prefix.size() - 1] == QChar(32))) {
|
||||
if (popup())
|
||||
popup()->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t i = tokens.size();
|
||||
|
||||
do {
|
||||
--i;
|
||||
if (!(get<0>(tokens[i]) == ExpressionParser::IDENTIFIER ||
|
||||
get<0>(tokens[i]) == ExpressionParser::STRING ||
|
||||
get<0>(tokens[i]) == '.'))
|
||||
break;
|
||||
} while (i > 0);
|
||||
|
||||
prefixStart = get<1>(tokens[i]);
|
||||
while (i < tokens.size()) {
|
||||
completionPrefix += get<2>(tokens[i]);
|
||||
++i;
|
||||
}
|
||||
|
||||
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"
|
66
src/Gui/ExpressionCompleter.h
Normal file
66
src/Gui/ExpressionCompleter.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef EXPRESSIONCOMPLETER_H
|
||||
#define EXPRESSIONCOMPLETER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QCompleter>
|
||||
#include <QLineEdit>
|
||||
#include <set>
|
||||
|
||||
class QStandardItem;
|
||||
|
||||
namespace App {
|
||||
class Document;
|
||||
class DocumentObject;
|
||||
class Property;
|
||||
class ObjectIdentifier;
|
||||
}
|
||||
|
||||
namespace Gui {
|
||||
|
||||
/**
|
||||
* @brief The ExpressionCompleter class extends the QCompleter class to provide a completer model of documentobject names and properties.
|
||||
*/
|
||||
|
||||
class GuiExport ExpressionCompleter : public QCompleter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExpressionCompleter(const App::Document * currentDoc, const App::DocumentObject * currentDocObj, QObject *parent = 0);
|
||||
|
||||
int getPrefixStart() const { return prefixStart; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void slotUpdate(const QString &prefix);
|
||||
|
||||
private:
|
||||
void createModelForDocument(const App::Document * doc, QStandardItem * parent, const std::set<const App::DocumentObject *> &forbidden);
|
||||
void createModelForDocumentObject(const App::DocumentObject * docObj, QStandardItem * parent);
|
||||
void createModelForPaths(const App::Property * prop, QStandardItem *docObjItem);
|
||||
|
||||
virtual QString pathFromIndex ( const QModelIndex & index ) const;
|
||||
virtual QStringList splitPath ( const QString & path ) const;
|
||||
|
||||
int prefixStart;
|
||||
|
||||
};
|
||||
|
||||
class GuiExport ExpressionLineEdit : public QLineEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExpressionLineEdit(QWidget *parent = 0);
|
||||
void setDocumentObject(const App::DocumentObject *currentDocObj);
|
||||
bool completerActive() const;
|
||||
void hideCompleter();
|
||||
Q_SIGNALS:
|
||||
void textChanged2(QString text);
|
||||
public Q_SLOTS:
|
||||
void slotTextChanged(const QString & text);
|
||||
void slotCompleteText(const QString & completionPrefix);
|
||||
private:
|
||||
ExpressionCompleter * completer;
|
||||
bool block;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXPRESSIONCOMPLETER_H
|
Loading…
Reference in New Issue
Block a user