1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "bindingmodel.h"
27 
28 #include "connectionview.h"
29 
30 #include <nodemetainfo.h>
31 #include <nodeproperty.h>
32 #include <bindingproperty.h>
33 #include <variantproperty.h>
34 #include <rewritingexception.h>
35 #include <rewritertransaction.h>
36 #include <rewriterview.h>
37 
38 #include <QMessageBox>
39 #include <QTimer>
40 
41 namespace QmlDesigner {
42 
43 namespace Internal {
44 
BindingModel(ConnectionView * parent)45 BindingModel::BindingModel(ConnectionView *parent)
46     : QStandardItemModel(parent)
47     , m_connectionView(parent)
48 {
49     connect(this, &QStandardItemModel::dataChanged, this, &BindingModel::handleDataChanged);
50 }
51 
resetModel()52 void BindingModel::resetModel()
53 {
54     beginResetModel();
55     clear();
56     setHorizontalHeaderLabels(
57         QStringList({tr("Item"), tr("Property"), tr("Source Item"), tr("Source Property")}));
58 
59     if (connectionView()->isAttached()) {
60         for (const ModelNode &modelNode : connectionView()->selectedModelNodes())
61             addModelNode(modelNode);
62     }
63 
64     endResetModel();
65 }
66 
bindingChanged(const BindingProperty & bindingProperty)67 void BindingModel::bindingChanged(const BindingProperty &bindingProperty)
68 {
69     m_handleDataChanged = false;
70 
71     QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
72     if (!selectedNodes.contains(bindingProperty.parentModelNode()))
73         return;
74     if (!m_lock) {
75         int rowNumber = findRowForBinding(bindingProperty);
76 
77         if (rowNumber == -1) {
78             addBindingProperty(bindingProperty);
79         } else {
80             updateBindingProperty(rowNumber);
81         }
82     }
83 
84     m_handleDataChanged = true;
85 }
86 
bindingRemoved(const BindingProperty & bindingProperty)87 void BindingModel::bindingRemoved(const BindingProperty &bindingProperty)
88 {
89     m_handleDataChanged = false;
90 
91     QList<ModelNode> selectedNodes = connectionView()->selectedModelNodes();
92     if (!selectedNodes.contains(bindingProperty.parentModelNode()))
93         return;
94     if (!m_lock) {
95         int rowNumber = findRowForBinding(bindingProperty);
96         removeRow(rowNumber);
97     }
98 
99     m_handleDataChanged = true;
100 }
101 
selectionChanged(const QList<ModelNode> & selectedNodes)102 void BindingModel::selectionChanged(const QList<ModelNode> &selectedNodes)
103 {
104     Q_UNUSED(selectedNodes)
105     m_handleDataChanged = false;
106     resetModel();
107     m_handleDataChanged = true;
108 }
109 
connectionView() const110 ConnectionView *BindingModel::connectionView() const
111 {
112     return m_connectionView;
113 }
114 
bindingPropertyForRow(int rowNumber) const115 BindingProperty BindingModel::bindingPropertyForRow(int rowNumber) const
116 {
117 
118     const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt();
119     const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString();
120 
121     ModelNode  modelNode = connectionView()->modelNodeForInternalId(internalId);
122 
123     if (modelNode.isValid())
124         return modelNode.bindingProperty(targetPropertyName.toLatin1());
125 
126     return BindingProperty();
127 }
128 
possibleTargetProperties(const BindingProperty & bindingProperty) const129 QStringList BindingModel::possibleTargetProperties(const BindingProperty &bindingProperty) const
130 {
131     const ModelNode modelNode = bindingProperty.parentModelNode();
132 
133     if (!modelNode.isValid()) {
134         qWarning() << " BindingModel::possibleTargetPropertiesForRow invalid model node";
135         return QStringList();
136     }
137 
138     NodeMetaInfo metaInfo = modelNode.metaInfo();
139 
140     if (metaInfo.isValid()) {
141         QStringList possibleProperties;
142         foreach (const PropertyName &propertyName, metaInfo.propertyNames()) {
143             if (metaInfo.propertyIsWritable(propertyName))
144                 possibleProperties << QString::fromUtf8(propertyName);
145         }
146 
147         return possibleProperties;
148     }
149 
150     return QStringList();
151 }
152 
possibleSourceProperties(const BindingProperty & bindingProperty) const153 QStringList BindingModel::possibleSourceProperties(const BindingProperty &bindingProperty) const
154 {
155     const QString expression = bindingProperty.expression();
156     const QStringList stringlist = expression.split(QLatin1String("."));
157     QStringList possibleProperties;
158 
159     TypeName typeName;
160 
161     if (bindingProperty.parentModelNode().metaInfo().isValid()) {
162         typeName = bindingProperty.parentModelNode().metaInfo().propertyTypeName(bindingProperty.name());
163     } else {
164         qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for target node";
165     }
166 
167     const QString &id = stringlist.constFirst();
168 
169     ModelNode modelNode = getNodeByIdOrParent(id, bindingProperty.parentModelNode());
170 
171     if (!modelNode.isValid()) {
172         //if it's not a valid model node, maybe it's a singleton
173         if (RewriterView* rv = connectionView()->rewriterView()) {
174             for (const QmlTypeData &data : rv->getQMLTypes()) {
175                 if (!data.typeName.isEmpty()) {
176                     if (data.typeName == id) {
177                         NodeMetaInfo metaInfo = connectionView()->model()->metaInfo(data.typeName.toUtf8());
178 
179                         if (metaInfo.isValid()) {
180                             for (const PropertyName &propertyName : metaInfo.propertyNames()) {
181                                 //without check for now
182                                 possibleProperties << QString::fromUtf8(propertyName);
183                             }
184 
185                             return possibleProperties;
186                         }
187                     }
188                 }
189             }
190         }
191 
192         qWarning() << " BindingModel::possibleSourcePropertiesForRow invalid model node";
193         return QStringList();
194     }
195 
196     NodeMetaInfo metaInfo = modelNode.metaInfo();
197 
198     for (const VariantProperty &variantProperty : modelNode.variantProperties()) {
199         if (variantProperty.isDynamic())
200             possibleProperties << QString::fromUtf8(variantProperty.name());
201     }
202 
203     for (const BindingProperty &bindingProperty : modelNode.bindingProperties()) {
204         if (bindingProperty.isDynamic())
205             possibleProperties << QString::fromUtf8((bindingProperty.name()));
206     }
207 
208     if (metaInfo.isValid())  {
209         for (const PropertyName &propertyName : metaInfo.propertyNames()) {
210             if (metaInfo.propertyTypeName(propertyName) == typeName) //### todo proper check
211                 possibleProperties << QString::fromUtf8(propertyName);
212         }
213     } else {
214         qWarning() << " BindingModel::possibleSourcePropertiesForRow no meta info for source node";
215     }
216 
217     return possibleProperties;
218 }
219 
deleteBindindByRow(int rowNumber)220 void BindingModel::deleteBindindByRow(int rowNumber)
221 {
222       BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
223 
224       if (bindingProperty.isValid()) {
225         bindingProperty.parentModelNode().removeProperty(bindingProperty.name());
226       }
227 
228       resetModel();
229 }
230 
unusedProperty(const ModelNode & modelNode)231 static PropertyName unusedProperty(const ModelNode &modelNode)
232 {
233     PropertyName propertyName = "none";
234     if (modelNode.metaInfo().isValid()) {
235         foreach (const PropertyName &propertyName, modelNode.metaInfo().propertyNames()) {
236             if (modelNode.metaInfo().propertyIsWritable(propertyName) && !modelNode.hasProperty(propertyName))
237                 return propertyName;
238         }
239     }
240 
241     return propertyName;
242 }
243 
addBindingForCurrentNode()244 void BindingModel::addBindingForCurrentNode()
245 {
246     if (connectionView()->selectedModelNodes().count() == 1) {
247         const ModelNode modelNode = connectionView()->selectedModelNodes().constFirst();
248         if (modelNode.isValid()) {
249             try {
250                 modelNode.bindingProperty(unusedProperty(modelNode)).setExpression(QLatin1String("none.none"));
251             } catch (RewritingException &e) {
252                 m_exceptionError = e.description();
253                 QTimer::singleShot(200, this, &BindingModel::handleException);
254             }
255         }
256     } else {
257         qWarning() << " BindingModel::addBindingForCurrentNode not one node selected";
258     }
259 }
260 
addBindingProperty(const BindingProperty & property)261 void BindingModel::addBindingProperty(const BindingProperty &property)
262 {
263     QStandardItem *idItem;
264     QStandardItem *targetPropertyNameItem;
265     QStandardItem *sourceIdItem;
266     QStandardItem *sourcePropertyNameItem;
267 
268     QString idLabel = property.parentModelNode().id();
269     if (idLabel.isEmpty())
270         idLabel = property.parentModelNode().simplifiedTypeName();
271     idItem = new QStandardItem(idLabel);
272     updateCustomData(idItem, property);
273     targetPropertyNameItem = new QStandardItem(QString::fromUtf8(property.name()));
274     QList<QStandardItem*> items;
275 
276     items.append(idItem);
277     items.append(targetPropertyNameItem);
278 
279     QString sourceNodeName;
280     QString sourcePropertyName;
281     getExpressionStrings(property, &sourceNodeName, &sourcePropertyName);
282 
283     sourceIdItem = new QStandardItem(sourceNodeName);
284     sourcePropertyNameItem = new QStandardItem(sourcePropertyName);
285 
286     items.append(sourceIdItem);
287     items.append(sourcePropertyNameItem);
288     appendRow(items);
289 }
290 
updateBindingProperty(int rowNumber)291 void BindingModel::updateBindingProperty(int rowNumber)
292 {
293     BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
294 
295     if (bindingProperty.isValid()) {
296         QString targetPropertyName = QString::fromUtf8(bindingProperty.name());
297         updateDisplayRole(rowNumber, TargetPropertyNameRow, targetPropertyName);
298         QString sourceNodeName;
299         QString sourcePropertyName;
300         getExpressionStrings(bindingProperty, &sourceNodeName, &sourcePropertyName);
301         updateDisplayRole(rowNumber, SourceModelNodeRow, sourceNodeName);
302         updateDisplayRole(rowNumber, SourcePropertyNameRow, sourcePropertyName);
303     }
304 }
305 
addModelNode(const ModelNode & modelNode)306 void BindingModel::addModelNode(const ModelNode &modelNode)
307 {
308     foreach (const BindingProperty &bindingProperty, modelNode.bindingProperties()) {
309         addBindingProperty(bindingProperty);
310     }
311 }
312 
updateExpression(int row)313 void BindingModel::updateExpression(int row)
314 {
315     const QString sourceNode = data(index(row, SourceModelNodeRow)).toString().trimmed();
316     const QString sourceProperty = data(index(row, SourcePropertyNameRow)).toString().trimmed();
317 
318     QString expression;
319     if (sourceProperty.isEmpty()) {
320         expression = sourceNode;
321     } else {
322         expression = sourceNode + QLatin1String(".") + sourceProperty;
323     }
324 
325     connectionView()->executeInTransaction("BindingModel::updateExpression", [this, row, expression](){
326         BindingProperty bindingProperty = bindingPropertyForRow(row);
327         bindingProperty.setExpression(expression.trimmed());
328     });
329 }
330 
updatePropertyName(int rowNumber)331 void BindingModel::updatePropertyName(int rowNumber)
332 {
333     BindingProperty bindingProperty = bindingPropertyForRow(rowNumber);
334 
335     const PropertyName newName = data(index(rowNumber, TargetPropertyNameRow)).toString().toUtf8();
336     const QString expression = bindingProperty.expression();
337     const PropertyName dynamicPropertyType = bindingProperty.dynamicTypeName();
338     ModelNode targetNode = bindingProperty.parentModelNode();
339 
340     if (!newName.isEmpty()) {
341         RewriterTransaction transaction =
342             connectionView()->beginRewriterTransaction(QByteArrayLiteral("BindingModel::updatePropertyName"));
343         try {
344             if (bindingProperty.isDynamic()) {
345                 targetNode.bindingProperty(newName).setDynamicTypeNameAndExpression(dynamicPropertyType, expression);
346             } else {
347                 targetNode.bindingProperty(newName).setExpression(expression);
348             }
349             targetNode.removeProperty(bindingProperty.name());
350             transaction.commit(); //committing in the try block
351         } catch (Exception &e) { //better save then sorry
352             m_exceptionError = e.description();
353             QTimer::singleShot(200, this, &BindingModel::handleException);
354         }
355 
356         QStandardItem* idItem = item(rowNumber, 0);
357         BindingProperty newBindingProperty = targetNode.bindingProperty(newName);
358         updateCustomData(idItem, newBindingProperty);
359 
360     } else {
361         qWarning() << "BindingModel::updatePropertyName invalid property name";
362     }
363 }
364 
getNodeByIdOrParent(const QString & id,const ModelNode & targetNode) const365 ModelNode BindingModel::getNodeByIdOrParent(const QString &id, const ModelNode &targetNode) const
366 {
367     ModelNode modelNode;
368 
369     if (id != QLatin1String("parent")) {
370         modelNode = connectionView()->modelNodeForId(id);
371     } else {
372         if (targetNode.hasParentProperty()) {
373             modelNode = targetNode.parentProperty().parentModelNode();
374         }
375     }
376     return modelNode;
377 }
378 
updateCustomData(QStandardItem * item,const BindingProperty & bindingProperty)379 void BindingModel::updateCustomData(QStandardItem *item, const BindingProperty &bindingProperty)
380 {
381     item->setData(bindingProperty.parentModelNode().internalId(), Qt::UserRole + 1);
382     item->setData(bindingProperty.name(), Qt::UserRole + 2);
383 }
384 
findRowForBinding(const BindingProperty & bindingProperty)385 int BindingModel::findRowForBinding(const BindingProperty &bindingProperty)
386 {
387     for (int i=0; i < rowCount(); i++) {
388         if (compareBindingProperties(bindingPropertyForRow(i), bindingProperty))
389             return i;
390     }
391     //not found
392     return -1;
393 }
394 
getExpressionStrings(const BindingProperty & bindingProperty,QString * sourceNode,QString * sourceProperty)395 bool BindingModel::getExpressionStrings(const BindingProperty &bindingProperty, QString *sourceNode, QString *sourceProperty)
396 {
397     //### todo we assume no expressions yet
398 
399     const QString expression = bindingProperty.expression();
400 
401     if (true) {
402         const QStringList stringList = expression.split(QLatin1String("."));
403 
404         *sourceNode = stringList.constFirst();
405 
406         QString propertyName;
407 
408         for (int i=1; i < stringList.count(); i++) {
409             propertyName += stringList.at(i);
410             if (i != stringList.count() - 1)
411                 propertyName += QLatin1String(".");
412         }
413         *sourceProperty = propertyName;
414     }
415     return true;
416 }
417 
updateDisplayRole(int row,int columns,const QString & string)418 void BindingModel::updateDisplayRole(int row, int columns, const QString &string)
419 {
420     QModelIndex modelIndex = index(row, columns);
421     if (data(modelIndex).toString() != string)
422         setData(modelIndex, string);
423 }
424 
handleDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)425 void BindingModel::handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
426 {
427     if (!m_handleDataChanged)
428         return;
429 
430     if (topLeft != bottomRight) {
431         qWarning() << "BindingModel::handleDataChanged multi edit?";
432         return;
433     }
434 
435     m_lock = true;
436 
437     int currentColumn = topLeft.column();
438     int currentRow = topLeft.row();
439 
440     switch (currentColumn) {
441     case TargetModelNodeRow: {
442         //updating user data
443     } break;
444     case TargetPropertyNameRow: {
445         updatePropertyName(currentRow);
446     } break;
447     case SourceModelNodeRow: {
448         updateExpression(currentRow);
449     } break;
450     case SourcePropertyNameRow: {
451         updateExpression(currentRow);
452     } break;
453 
454     default: qWarning() << "BindingModel::handleDataChanged column" << currentColumn;
455     }
456 
457     m_lock = false;
458 }
459 
handleException()460 void BindingModel::handleException()
461 {
462     QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
463     resetModel();
464 }
465 
466 } // namespace Internal
467 
468 } // namespace QmlDesigner
469