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