1
/***************************************************************************
2
* Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
26
# include <boost/algorithm/string/predicate.hpp>
27
# include <QAbstractItemView>
28
# include <QContextMenuEvent>
34
#include <App/Application.h>
35
#include <App/Document.h>
36
#include <App/DocumentObject.h>
37
#include <App/ExpressionParser.h>
38
#include <App/ObjectIdentifier.h>
39
#include <Base/Tools.h>
40
#include <CXX/Extensions.hxx>
42
#include "ExpressionCompleter.h"
45
FC_LOG_LEVEL_INIT("Completer", true, true)
47
Q_DECLARE_METATYPE(App::ObjectIdentifier)
52
class ExpressionCompleterModel: public QAbstractItemModel
55
ExpressionCompleterModel(QObject* parent, bool noProperty)
56
:QAbstractItemModel(parent), noProperty(noProperty)
60
void setNoProperty(bool enabled) {
64
void setDocumentObject(const App::DocumentObject* obj, bool checkInList)
68
currentDoc = obj->getDocument()->getName();
69
currentObj = obj->getNameInDocument();
70
if (!noProperty && checkInList) {
71
inList = obj->getInListEx(true);
82
// This ExpressionCompleter model works without any physical items.
83
// Everything item related is stored inside QModelIndex.InternalPointer/InternalId(),
84
// using the following Info structure.
86
// The Info contains two indices, one for document and the other for object.
87
// For 32-bit system, the index is 16bit which limits the size to 64K. For
88
// 64-bit system, the index is 32bit.
90
// The "virtual" items are organized as a tree. The root items are special,
91
// which consists of three types in the following order,
93
// * Document, even index contains item using document's name, while
94
// odd index with quoted document label.
95
// * Objects of the current document, even index with object's internal
96
// name, and odd index with quoted object label.
97
// * Properties of the current object.
99
// Document item contains object item as child, and object item contains
102
// The QModelIndex of a root item has both the doc field and obj field set
103
// to -1, and uses the row as the item index. We can figure out the type of
104
// the item solely based on this row index.
106
// QModelIndex of a non-root object item has doc field as the document
107
// index, and obj field set to -1.
109
// QModelIndex of a non-root property item has doc field as the document
110
// index, and obj field as the object index.
112
// An item is uniquely identified by the pair (row, father_link) in the QModelIndex
114
// The completion tree structure created takes into account the current document and object
116
// It is done as such, in order to have contextual completion (prop -> object -> files):
120
// |----- current documents' objects [externally set]
121
// |----- current objects' props [externally set]
123
// This complicates the decoding schema for the root, where the childcount will be
124
// doc.size() + current_doc.Objects.size() + current_obj.Props.size().
126
// This is reflected in the complexity of the DATA function.
128
// Example encoding of a QMODEL Index
130
// ROOT (row -1, [-1,-1,-1,0]), info represented as [-1,-1,-1,0]
131
// |-- doc 1 (non contextual) - (row 0, [-1,-1,-1,0]) = encode as parent => [0,-1,-1,0]
132
// |-- doc 2 (non contextual) - (row 1, [-1,-1,-1,0]) = encode as parent => [1,-1,-1,0]
133
// | |- doc 2.obj1 - (row 0, [1,-1,-1,0]) = encode as parent => [1, 0,-1,0]
134
// | |- doc 2.obj2 - (row 1, [1,-1,-1,0]) = encode as parent => [1, 1,-1,0]
135
// | |- doc 2.obj3 - (row 2, [1,-1,-1,0]) = encode as parent => [1, 2,-1,0]
136
// | |- doc 2.obj3.prop1 - (row 0, [1, 2,-1,0]) = encode as parent => [1, 2, 0,0]
137
// | |- doc 2.obj3.prop2 - (row 1, [1, 2,-1,0]) = encode as parent => [1, 2, 1,0]
138
// | |- doc 2.obj3.prop3 - (row 2, [1, 2,-1,0]) = encode as parent => [1, 2, 2,0]
139
// | |- doc 2.obj3.prop3.path0 - (row 0, [1, 2, 2,0]) = encode as parent => INVALID, LEAF ITEM
140
// | |- doc 2.obj3.prop3.path1 - (row 1, [1, 2, 2,0]) = encode as parent => INVALID, LEAF ITEM
143
// |-- doc 3 (non contextual) - (row 2, [-1,-1,-1,0]) = encode as parent => [2,-1,-1,0]
145
// |-- obj1 (current doc - contextual) - (row 3, [-1,-1,-1,0]) = encode as parent => [3,-1,-1,1]
146
// |-- obj2 (current doc - contextual) - (row 4, [-1,-1,-1,0]) = encode as parent => [4,-1,-1,1]
147
// | |- obj2.prop1 (contextual) - (row 0, [4,-1,-1,1]) = encode as parent => [4,-1,0,1]
148
// | |- obj2.prop2 (contextual) - (row 1, [4,-1,-1,1]) = encode as parent => [4,-1,1,1]
149
// | | - obj2.prop2.path1 (contextual) - (row 0, [4,-1,0 ,1]) = encode as parent => INVALID, LEAF ITEM
150
// | | - obj2.prop2.path2 (contextual) - (row 1, [4,-1,1 ,1]) = encode as parent => INVALID, LEAF ITEM
152
// |-- prop1 (current obj - contextual) - (row 5, [-1,-1,-1,0]) = encode as parent => [5,-1,-1,1]
153
// |-- prop2 (current obj - contextual) - (row 6, [-1,-1,-1,0]) = encode as parent => [6,-1,-1,1]
154
// |-- prop2.path1 (contextual) - (row 0, [ 6,-1,-1,0]) = encode as parent => INVALID, LEAF ITEM
155
// |-- prop2.path2 (contextual) - (row 1, [ 6,-1,-1,0]) = encode as parent => INVALID, LEAF ITEM
163
quint32 contextualHierarchy : 1;
165
static const Info root;
168
static const quint64 k_numBitsProp = 16ULL; // 0 .. 15
169
static const quint64 k_numBitsObj = 24ULL; // 16.. 39
170
static const quint64 k_numBitsContextualHierarchy = 1; // 40
171
static const quint64 k_numBitsDocuments = 23ULL; // 41.. 63
173
static const quint64 k_offsetProp = 0;
174
static const quint64 k_offsetObj = k_offsetProp + k_numBitsProp;
175
static const quint64 k_offsetContextualHierarchy = k_offsetObj + k_numBitsObj;
176
static const quint64 k_offsetDocuments = k_offsetContextualHierarchy + k_numBitsContextualHierarchy;
178
static const quint64 k_maskProp = ((1ULL << k_numBitsProp) - 1);
179
static const quint64 k_maskObj = ((1ULL << k_numBitsObj) - 1);
180
static const quint64 k_maskContextualHierarchy = ((1ULL << k_numBitsContextualHierarchy) - 1);
181
static const quint64 k_maskDocuments = ((1ULL << k_numBitsDocuments) - 1);
183
union InfoPtrEncoding
191
quint16 contextualHierarchy : 1;
195
InfoPtrEncoding(const Info& info)
198
if (sizeof(void*) < sizeof(InfoPtrEncoding)) {
199
d32.doc = (quint8)(info.doc + 1);
200
d32.obj = (quint16)(info.obj + 1);
201
d32.prop = (quint8)(info.prop + 1);
202
d32.contextualHierarchy = info.contextualHierarchy;
205
d_enc = ((quint64(info.doc + 1) & k_maskDocuments) << k_offsetDocuments)
206
| ((quint64(info.contextualHierarchy) & k_maskContextualHierarchy)
207
<< k_offsetContextualHierarchy)
208
| ((quint64(info.obj + 1) & k_maskObj) << k_offsetObj)
209
| ((quint64(info.prop + 1) & k_maskProp) << k_offsetProp);
212
InfoPtrEncoding(void* pointer)
221
if (sizeof(void*) < sizeof(InfoPtrEncoding)) {
222
info.doc = qint32(d32.doc) - 1;
223
info.obj = qint32(d32.obj) - 1;
224
info.prop = qint32(d32.prop) - 1;
225
info.contextualHierarchy = d32.contextualHierarchy;
228
info.doc = ((d_enc >> k_offsetDocuments) & k_maskDocuments) - 1;
229
info.contextualHierarchy =
230
((d_enc >> k_offsetContextualHierarchy) & k_maskContextualHierarchy);
231
info.obj = ((d_enc >> k_offsetObj) & k_maskObj) - 1;
232
info.prop = ((d_enc >> k_offsetProp) & k_maskProp) - 1;
238
static void* infoId(const Info& info)
240
InfoPtrEncoding ptrEnc(info);
244
static Info getInfo(const QModelIndex& index)
246
InfoPtrEncoding enc(index.internalPointer());
247
return enc.DecodeInfo();
250
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
252
if (role != Qt::EditRole && role != Qt::DisplayRole && role != Qt::UserRole)
255
Info info = getInfo(index);
256
_data(info, index.row(), &variant, nullptr, role == Qt::UserRole);
257
FC_TRACE(info.doc << "," << info.obj << "," << info.prop << "," << info.contextualHierarchy
258
<< "," << index.row() << ": " << variant.toString().toUtf8().constData());
262
static std::vector<App::ObjectIdentifier> retrieveSubPaths(const App::Property* prop)
264
std::vector<App::ObjectIdentifier> result;
266
prop->getPaths(result);
267
// need to filter out irrelevant paths (len 1, aka just this object identifier)
268
auto res = std::remove_if(
269
result.begin(), result.end(), [](const App::ObjectIdentifier& path) -> bool {
270
return path.getComponents().empty();
272
result.erase(res, result.end());
277
// The completion tree structure created takes into account the current document and object
279
// It is done as such:
283
// |----- current documents' objects [externally set]
284
// |----- current objects' props [externally set]
286
// This complicates the decoding schema for the root, where the childcount will be
287
// doc.size() + current_doc.Objects.size() + current_obj.Props.size().
289
// this function is called in two modes:
290
// - obtain the count of a node identified by Info,row => count != nullptr, v==nullptr
291
// - get the text of an item. This text will contain separators but NO full path
292
void _data(const Info& info, int row, QVariant* v, int* count, bool sep = false) const
295
// identify the document index. For any children of the root, it is given by traversing
296
// the flat list and identified by [row]
297
idx = info.doc < 0 ? row : info.doc;
298
const auto &docs = App::GetApplication().getDocuments();
299
int docSize = (int)docs.size() * 2;
302
std::vector<std::pair<const char*, App::Property*> > props;
303
App::Document* doc = nullptr;
304
App::DocumentObject* obj = nullptr;
305
const char* propName = nullptr;
306
App::Property* prop = nullptr;
307
// check if the document is uniquely identified: either the correct index in info.doc
308
// OR if, the node is a descendant of the root, its row lands within 0...docsize
309
if (idx >= 0 && idx < docSize) {
313
// if we're looking at the ROOT, or the row identifies one of the other ROOT elements
314
// |----- current documents' objects, rows: docs.size ... docs.size + objs.size
315
// |----- current objects' props, rows: docs.size + objs.size ... docs.size + objs.size+ props.size
317
// We need to process the ROOT so we get the correct count for its children
318
doc = App::GetApplication().getDocument(currentDoc.c_str());
319
if (!doc)// no current, there are no additional objects
322
// move to the current documents' objects' range
327
const auto &objs = doc->getObjects();
328
objSize = (int)objs.size() * 2;
329
// if this is a valid object, we found our object and break.
330
// if not, this may be the root or one of current object's properties
331
if (idx >= 0 && idx < objSize) {
333
// if they are in the ignore list skip
334
if (inList.count(obj))
337
else if (!noProperty) {
338
// need to check the current object's props range, or we're parsing the ROOT
339
auto cobj = doc->getObject(currentObj.c_str());
341
// move to the props range of the current object
345
// get the properties
346
cobj->getPropertyNamedList(props);
347
propSize = (int)props.size();
349
// if this is an invalid index, bail out
350
// if it's the ROOT break!
354
obj = cobj; // we only set the active object if we're not processing the root.
355
propName = props[idx].first;
356
prop = props[idx].second;
361
// the item is the ROOT or a CHILD of the root
363
// and we're asking for a count, compute it
365
// note that if we're dealing with a valid DOC node (row>0, ROOT_info)
366
// objSize and propSize will be zero because of the early exit above
367
*count = docSize + objSize + propSize;
370
// we're asking for this child's data, and IT's NOT THE ROOT
372
// we resolved the property
374
res = QString::fromLatin1(propName);
375
// resolve the property
376
if (sep && !noProperty && !retrieveSubPaths(prop).empty())
377
res += QLatin1Char('.');
380
// the object has been resolved, use the saved idx to figure out quotation or not.
382
res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str());
384
res = QString::fromLatin1(obj->getNameInDocument());
385
if (sep && !noProperty)
386
res += QLatin1Char('.');
389
// the document has been resolved, use the saved idx to figure out quotation or not.
391
res = QString::fromUtf8(quote(doc->Label.getStrValue()).c_str());
393
res = QString::fromLatin1(doc->getName());
395
res += QLatin1Char('#');
399
// done processing the ROOT or any child items
403
// object not resolved
405
// are we pointing to an object item, or our father (info) is an object
406
idx = info.obj < 0 ? row : info.obj;
407
const auto &objs = doc->getObjects();
408
objSize = (int)objs.size() * 2;
409
// if invalid index, or in the ignore list bail out
410
if (idx < 0 || idx >= objSize || inList.count(obj))
415
// if this is AN actual Object item and not a root
417
*count = objSize; // set the correct count if requested
422
res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str());
424
res = QString::fromLatin1(obj->getNameInDocument());
425
if (sep && !noProperty)
426
res += QLatin1Char('.');
436
idx = info.prop < 0 ? row : info.prop;
437
obj->getPropertyNamedList(props);
438
propSize = (int)props.size();
439
// return if the property is invalid
440
if (idx < 0 || idx >= propSize) {
443
propName = props[idx].first;
444
prop = props[idx].second;
445
// if this is a root object item
447
// set the property size count
452
QString res = QString::fromLatin1(propName);
454
// check to see if we have accessible paths from this prop name?
455
if (sep && !retrieveSubPaths(prop).empty())
456
res += QLatin1Char('.');
465
// idx identifies the path
467
std::vector<App::ObjectIdentifier> paths = retrieveSubPaths(prop);
470
*count = paths.size();
473
// check to see if this is a valid path
474
if (idx < 0 || idx >= static_cast<int>(paths.size())) {
479
auto str = paths[idx].getSubPathStr();
480
if (str.size() && (str[0] == '.' || str[0] == '#')) {
482
*v = QString::fromLatin1(str.c_str() + 1);
485
*v = QString::fromLatin1(str.c_str());
492
QModelIndex parent(const QModelIndex & index) const override {
493
if (!index.isValid())
496
Info parentInfo = getInfo(index);
497
Info grandParentInfo = parentInfo;
499
if (parentInfo.contextualHierarchy) {
500
// for contextual hierarchy we have this:
501
// ROOT -> Object in Current Doc -> Prop In Object -> PropPath
502
// ROOT -> prop in Current Object -> prop Path
504
if (parentInfo.prop >= 0) {
505
grandParentInfo.prop = -1;
506
return createIndex(parentInfo.prop, 0, infoId(grandParentInfo));
508
// if the parent is the object or a prop attached to the root, we just need the below line
509
return createIndex(parentInfo.doc, 0, infoId(Info::root));
512
if (parentInfo.prop >= 0) {
513
grandParentInfo.prop = -1;
514
return createIndex(parentInfo.prop, 0, infoId(grandParentInfo));
516
if (parentInfo.obj >= 0) {
517
grandParentInfo.obj = -1;
518
return createIndex(parentInfo.obj, 0, infoId(grandParentInfo));
520
if (parentInfo.doc >= 0) {
521
grandParentInfo.doc = -1;
522
return createIndex(parentInfo.doc, 0, infoId(grandParentInfo));
530
// returns true if successful, false if 'element' identifies a Leaf
531
bool modelIndexToParentInfo(QModelIndex element, Info & info) const
536
if (element.isValid()) {
537
parentInfo = getInfo(element);
540
// Our wonderful element is a child of the root
541
if (parentInfo.doc < 0) {
542
// need special casing to properly identify this model's object
543
const auto& docs = App::GetApplication().getDocuments();
544
auto docsSize = static_cast<int>(docs.size() * 2);
546
info.doc = element.row();
548
// if my element is a contextual descendant of root (current doc object list, current object prop list)
550
if (element.row() >= docsSize) {
551
info.contextualHierarchy = 1;
554
else if (parentInfo.contextualHierarchy) {
555
const auto& docs = App::GetApplication().getDocuments();
556
auto cdoc = App::GetApplication().getDocument(currentDoc.c_str());
559
int objsSize = static_cast<int>(cdoc->getObjects().size() * 2);
560
int idx = parentInfo.doc - static_cast<int>(docs.size());
561
if (idx < objsSize) {
562
// |-- Parent (OBJECT) - (row 4, [-1,-1,-1,0]) = encode as element => [parent.row,-1,-1,1]
563
// |- element (PROP) - (row 0, [parent.row,-1,-1,1]) = encode as element => [parent.row,-1,parent.row,1]
565
info.doc = parentInfo.doc;
566
info.obj = -1;// object information is determined by the DOC index actually
567
info.prop = element.row();
568
info.contextualHierarchy = 1;
571
// if my parent (parentInfo) is a prop, it means that our element is a prop path
572
// and that is a leaf item (we don't split prop paths further)
573
// we can't encode leaf items into an "Info"
578
// no contextual document
584
else if (parentInfo.obj <= 0) {
585
info.obj = element.row();
587
else if (parentInfo.prop <= 0) {
588
info.prop = element.row();
597
QModelIndex index(int row, int column,
598
const QModelIndex & parent = QModelIndex()) const override {
601
Info myParentInfoEncoded = Info::root;
603
// encode the parent's QModelIndex into an 'Info' structure
604
bool parentCanHaveChildren = modelIndexToParentInfo(parent, myParentInfoEncoded);
605
if (!parentCanHaveChildren) {
608
return createIndex(row, column, infoId(myParentInfoEncoded));
611
// function returns how many children the QModelIndex parent has
612
int rowCount(const QModelIndex& parent = QModelIndex()) const override
616
if (!parent.isValid()) {
617
// we're getting the row count for the root
618
// that is: document hierarchy _and_ contextual completion
623
// try to encode the parent's QModelIndex into an info structure
624
// if the paren't can't have any children, return 0
625
if (!modelIndexToParentInfo(parent, info)) {
630
_data(info, row, nullptr, &count);
631
FC_TRACE(info.doc << "," << info.obj << "," << info.prop << "," << info.contextualHierarchy
632
<< "," << row << " row count " << count);
636
int columnCount(const QModelIndex&) const override
642
std::set<App::DocumentObject*> inList;
643
std::string currentDoc;
644
std::string currentObj;
648
const ExpressionCompleterModel::Info ExpressionCompleterModel::Info::root = {-1, -1, -1, 0};
651
* @brief Construct an ExpressionCompleter object.
652
* @param currentDoc Current document to generate the model from.
653
* @param currentDocObj Current document object to generate model from.
654
* @param parent Parent object owning the completer.
657
ExpressionCompleter::ExpressionCompleter(const App::DocumentObject* currentDocObj,
658
QObject* parent, bool noProperty, bool checkInList)
659
: QCompleter(parent), currentObj(currentDocObj)
660
, noProperty(noProperty), checkInList(checkInList)
662
setCaseSensitivity(Qt::CaseInsensitive);
665
void ExpressionCompleter::init() {
669
auto m = new ExpressionCompleterModel(this,noProperty);
670
m->setDocumentObject(currentObj.getObject(),checkInList);
674
void ExpressionCompleter::setDocumentObject(const App::DocumentObject* obj, bool _checkInList)
676
if (!obj || !obj->isAttachedToDocument())
677
currentObj = App::DocumentObjectT();
680
setCompletionPrefix(QString());
681
checkInList = _checkInList;
684
static_cast<ExpressionCompleterModel*>(m)->setDocumentObject(obj, checkInList);
687
void ExpressionCompleter::setNoProperty(bool enabled) {
688
noProperty = enabled;
691
static_cast<ExpressionCompleterModel*>(m)->setNoProperty(enabled);
694
QString ExpressionCompleter::pathFromIndex(const QModelIndex& index) const
697
if (!m || !index.isValid())
703
res = m->data(parent, Qt::UserRole).toString() + res;
704
parent = parent.parent();
705
} while (parent.isValid());
707
auto info = ExpressionCompleterModel::getInfo(index);
708
FC_TRACE("join path " << info.doc << "," << info.obj << "," << info.prop << ","
709
<< info.contextualHierarchy << "," << index.row()
710
<< ": " << res.toUtf8().constData());
714
QStringList ExpressionCompleter::splitPath(const QString& input) const
716
QStringList resultList;
717
std::string path = input.toUtf8().constData();
722
std::string lastElem; // used to recover in case of parse failure after ".".
723
std::string trim; // used to delete ._self added for another recovery path
726
// this will not work for incomplete Tokens at the end
727
// "Sketch." will fail to parse and complete.
729
App::ObjectIdentifier ident = ObjectIdentifier::parse(
730
currentObj.getObject(), path);
732
std::vector<std::string> stringList = ident.getStringList();
733
auto stringListIter = stringList.begin();
734
if (retry > 1 && !stringList.empty())
735
stringList.pop_back();
737
if (!stringList.empty()) {
738
if (!trim.empty() && boost::ends_with(stringList.back(), trim))
739
stringList.back().resize(stringList.back().size() - trim.size());
740
while (stringListIter != stringList.end()) {
741
resultList << Base::Tools::fromStdString(*stringListIter);
745
if (lastElem.size()) {
746
// if we finish in a trailing separator
747
if (!lastElem.empty()) {
748
// erase the separator
749
lastElem.erase(lastElem.begin());
750
resultList << Base::Tools::fromStdString(lastElem);
752
// add empty string to allow completion after "." or "#"
753
resultList << QString();
756
FC_TRACE("split path " << path << " -> "
757
<< resultList.join(QLatin1String("/")).toUtf8().constData());
760
catch (const Base::Exception& except) {
761
FC_TRACE("split path " << path << " error: " << except.what());
763
size_t lastElemStart = path.rfind('.');
765
if (lastElemStart == std::string::npos) {
766
lastElemStart = path.rfind('#');
768
if (lastElemStart != std::string::npos) {
769
lastElem = path.substr(lastElemStart);
770
path = path.substr(0, lastElemStart);
775
else if (retry == 1) {
776
// restore path from retry 0
777
if (lastElem.size() > 1) {
778
path = path + lastElem;
781
// else... we don't reset lastElem if it's a '.' or '#' to allow chaining completions
783
char last = path[path.size() - 1];
784
if (last != '#' && last != '.' && path.find('#') != std::string::npos) {
791
else if (retry == 2) {
792
if (path.size() >= 6) {
793
path.resize(path.size() - 6);
796
char last = path[path.size() - 1];
797
if (last != '.' && last != '<' && path.find("#<<") != std::string::npos) {
805
return QStringList() << input;
810
// Code below inspired by blog entry:
811
// https://john.nachtimwald.com/2009/07/04/qcompleter-and-comma-separated-tags/
813
void ExpressionCompleter::slotUpdate(const QString & prefix, int pos)
815
FC_TRACE("SlotUpdate:" << prefix.toUtf8().constData());
819
QString completionPrefix = tokenizer.perform(prefix, pos);
820
if (completionPrefix.isEmpty()) {
821
if (auto itemView = popup())
822
itemView->setVisible(false);
826
FC_TRACE("Completion Prefix:" << completionPrefix.toUtf8().constData());
827
// Set completion prefix
828
setCompletionPrefix(completionPrefix);
830
if (widget()->hasFocus()) {
831
FC_TRACE("Complete on Prefix" << completionPrefix.toUtf8().constData());
833
FC_TRACE("Complete Done");
836
else if (auto itemView = popup()) {
837
itemView->setVisible(false);
841
ExpressionLineEdit::ExpressionLineEdit(QWidget* parent, bool noProperty,
842
char checkPrefix, bool checkInList)
846
, noProperty(noProperty)
848
, checkInList(checkInList)
849
, checkPrefix(checkPrefix)
851
connect(this, &QLineEdit::textEdited, this, &ExpressionLineEdit::slotTextChanged);
854
void ExpressionLineEdit::setPrefix(char prefix) {
855
checkPrefix = prefix;
858
void ExpressionLineEdit::setDocumentObject(const App::DocumentObject* currentDocObj,
861
checkInList = _checkInList;
863
completer->setDocumentObject(currentDocObj, checkInList);
867
completer = new ExpressionCompleter(currentDocObj, this, noProperty, checkInList);
868
completer->setWidget(this);
869
completer->setCaseSensitivity(Qt::CaseInsensitive);
871
completer->setFilterMode(Qt::MatchContains);
872
connect(completer, qOverload<const QString&>(&QCompleter::activated),
873
this, &ExpressionLineEdit::slotCompleteTextSelected);
875
qOverload<const QString&>(&QCompleter::highlighted),
876
this, &ExpressionLineEdit::slotCompleteTextHighlighted);
877
connect(this, &ExpressionLineEdit::textChanged2,
878
completer, &ExpressionCompleter::slotUpdate);
882
void ExpressionLineEdit::setNoProperty(bool enabled) {
883
noProperty = enabled;
885
completer->setNoProperty(enabled);
888
void ExpressionLineEdit::setExactMatch(bool enabled) {
889
exactMatch = enabled;
891
completer->setFilterMode(exactMatch ? Qt::MatchStartsWith : Qt::MatchContains);
895
bool ExpressionLineEdit::completerActive() const
897
return completer && completer->popup() && completer->popup()->isVisible();
900
void ExpressionLineEdit::hideCompleter()
902
if (completer && completer->popup())
903
completer->popup()->setVisible(false);
906
void ExpressionLineEdit::slotTextChanged(const QString & text)
909
if (!text.size() || (checkPrefix && text[0] != QLatin1Char(checkPrefix)))
911
Q_EMIT textChanged2(text,cursorPosition());
915
void ExpressionLineEdit::slotCompleteText(const QString & completionPrefix, bool isActivated)
918
completer->getPrefixRange(start,end);
919
QString before(text().left(start));
920
QString after(text().mid(end));
923
Base::FlagToggler<bool> flag(block, false);
924
before += completionPrefix;
925
setText(before + after);
926
setCursorPosition(before.length());
927
completer->updatePrefixEnd(before.length());
930
// chain completions if we select an entry from the completer drop down
931
// and that entry ends with '.' or '#'
933
std::string textToComplete = completionPrefix.toUtf8().constData();
934
if (textToComplete.size()
935
&& (*textToComplete.crbegin() == '.' || *textToComplete.crbegin() == '#')) {
936
Base::FlagToggler<bool> flag(block, true);
937
slotTextChanged(before + after);
942
void ExpressionLineEdit::slotCompleteTextHighlighted(const QString& completionPrefix)
944
slotCompleteText(completionPrefix, false);
947
void ExpressionLineEdit::slotCompleteTextSelected(const QString& completionPrefix)
949
slotCompleteText(completionPrefix, true);
953
void ExpressionLineEdit::keyPressEvent(QKeyEvent* e)
955
Base::FlagToggler<bool> flag(block,true);
956
QLineEdit::keyPressEvent(e);
959
void ExpressionLineEdit::contextMenuEvent(QContextMenuEvent* event)
961
QMenu* menu = createStandardContextMenu();
964
menu->addSeparator();
965
QAction *match = menu->addAction(tr("Exact match"));
966
match->setCheckable(true);
967
match->setChecked(completer->filterMode() == Qt::MatchStartsWith);
968
QObject::connect(match, &QAction::toggled,
969
this, &Gui::ExpressionLineEdit::setExactMatch);
971
menu->setAttribute(Qt::WA_DeleteOnClose);
973
menu->popup(event->globalPos());
977
///////////////////////////////////////////////////////////////////////
979
ExpressionTextEdit::ExpressionTextEdit(QWidget* parent)
980
: QPlainTextEdit(parent)
985
connect(this, &QPlainTextEdit::textChanged, this, &ExpressionTextEdit::slotTextChanged);
988
void ExpressionTextEdit::setExactMatch(bool enabled)
990
exactMatch = enabled;
992
completer->setFilterMode(exactMatch ? Qt::MatchStartsWith : Qt::MatchContains);
995
void ExpressionTextEdit::setDocumentObject(const App::DocumentObject* currentDocObj)
998
completer->setDocumentObject(currentDocObj);
1002
if (currentDocObj) {
1003
completer = new ExpressionCompleter(currentDocObj, this);
1005
completer->setFilterMode(Qt::MatchContains);
1006
completer->setWidget(this);
1007
completer->setCaseSensitivity(Qt::CaseInsensitive);
1008
connect(completer, qOverload<const QString&>(&QCompleter::activated), this,
1009
&ExpressionTextEdit::slotCompleteText);
1010
connect(completer, qOverload<const QString&>(&QCompleter::highlighted), this,
1011
&ExpressionTextEdit::slotCompleteText);
1012
connect(this, &ExpressionTextEdit::textChanged2, completer,
1013
&ExpressionCompleter::slotUpdate);
1017
bool ExpressionTextEdit::completerActive() const
1019
return completer && completer->popup() && completer->popup()->isVisible();
1022
void ExpressionTextEdit::hideCompleter()
1024
if (completer && completer->popup())
1025
completer->popup()->setVisible(false);
1028
void ExpressionTextEdit::slotTextChanged()
1031
QTextCursor cursor = textCursor();
1032
Q_EMIT textChanged2(cursor.block().text(),cursor.positionInBlock());
1036
void ExpressionTextEdit::slotCompleteText(const QString& completionPrefix)
1038
QTextCursor cursor = textCursor();
1040
completer->getPrefixRange(start,end);
1041
int pos = cursor.positionInBlock();
1043
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, end - pos);
1045
cursor.movePosition(QTextCursor::PreviousCharacter,QTextCursor::KeepAnchor,end-start);
1046
Base::FlagToggler<bool> flag(block,false);
1047
cursor.insertText(completionPrefix);
1048
completer->updatePrefixEnd(cursor.positionInBlock());
1051
void ExpressionTextEdit::keyPressEvent(QKeyEvent* e)
1053
Base::FlagToggler<bool> flag(block,true);
1054
QPlainTextEdit::keyPressEvent(e);
1057
void ExpressionTextEdit::contextMenuEvent(QContextMenuEvent* event)
1059
QMenu* menu = createStandardContextMenu();
1060
menu->addSeparator();
1061
QAction* match = menu->addAction(tr("Exact match"));
1064
match->setCheckable(true);
1065
match->setChecked(completer->filterMode() == Qt::MatchStartsWith);
1068
match->setVisible(false);
1071
QAction* action = menu->exec(event->globalPos());
1074
if (action == match)
1075
setExactMatch(match->isChecked());
1081
///////////////////////////////////////////////////////////////////////
1083
ExpressionParameter* ExpressionParameter::instance()
1085
static auto inst = new ExpressionParameter();
1089
bool ExpressionParameter::isCaseSensitive() const
1091
auto handle = GetApplication().GetParameterGroupByPath(
1092
"User parameter:BaseApp/Preferences/Expression");
1093
return handle->GetBool("CompleterCaseSensitive", false);
1096
bool ExpressionParameter::isExactMatch() const
1098
auto handle = GetApplication().GetParameterGroupByPath(
1099
"User parameter:BaseApp/Preferences/Expression");
1100
return handle->GetBool("CompleterMatchExact", false);
1103
#include "moc_ExpressionCompleter.cpp"