1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "modelnodepositionrecalculator.h"
34 #include "modeltotextmerger.h"
35 #include "qmltextgenerator.h"
36 #include "rewriteactioncompressor.h"
37 #include "rewriterview.h"
39 #include <qmljs/qmljsdocument.h>
40 #include <variantproperty.h>
41 #include <nodelistproperty.h>
42 #include <nodeproperty.h>
43 #include <textmodifier.h>
49 DebugRewriteActions = 0
53 using namespace QmlJS;
54 using namespace QmlDesigner;
55 using namespace QmlDesigner::Internal;
57 ModelToTextMerger::ModelToTextMerger(RewriterView *reWriterView):
58 m_rewriterView(reWriterView)
62 void ModelToTextMerger::nodeCreated(const ModelNode &/*createdNode*/)
64 //the rewriter ignores model nodes outside of the hierachy
67 void ModelToTextMerger::nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange)
69 if (!isInHierarchy(parentProperty))
72 if (parentProperty.isDefaultProperty()) {
73 schedule(new RemoveNodeRewriteAction(removedNode));
74 } else if (AbstractView::EmptyPropertiesRemoved == propertyChange) {
75 schedule(new RemovePropertyRewriteAction(parentProperty));
76 } else if (parentProperty.isNodeListProperty()) {
77 schedule(new RemoveNodeRewriteAction(removedNode));
81 void ModelToTextMerger::propertiesRemoved(const QList<AbstractProperty>& propertyList)
83 foreach (const AbstractProperty &property, propertyList) {
84 if (isInHierarchy(property) && !property.isDefaultProperty())
85 schedule(new RemovePropertyRewriteAction(property));
89 void ModelToTextMerger::propertiesChanged(const QList<AbstractProperty>& propertyList, PropertyChangeFlags propertyChange)
91 foreach (const AbstractProperty &property, propertyList) {
92 if (!isInHierarchy(property))
95 ModelNode containedModelNode;
96 const int indentDepth = m_rewriterView->textModifier()->indentDepth();
97 const QString propertyTextValue = QmlTextGenerator(getPropertyOrder(),
98 indentDepth)(property);
100 switch (propertyChange) {
101 case AbstractView::PropertiesAdded:
102 if (property.isNodeProperty())
103 containedModelNode = property.toNodeProperty().modelNode();
105 schedule(new AddPropertyRewriteAction(property,
107 propertyType(property, propertyTextValue),
108 containedModelNode));
111 case AbstractView::NoAdditionalChanges:
112 if (property.isNodeProperty())
113 containedModelNode = property.toNodeProperty().modelNode();
115 schedule(new ChangePropertyRewriteAction(property,
117 propertyType(property, propertyTextValue),
118 containedModelNode));
121 case AbstractView::EmptyPropertiesRemoved:
125 Q_ASSERT(!"Unknown PropertyChangeFlags");
130 void ModelToTextMerger::nodeTypeChanged(const ModelNode &node,const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/)
132 if (!node.isInHierarchy())
135 // TODO: handle the majorVersion and the minorVersion
137 schedule(new ChangeTypeRewriteAction(node));
140 void ModelToTextMerger::addImport(const Import &import)
142 if (!import.isEmpty())
143 schedule(new AddImportRewriteAction(import));
146 void ModelToTextMerger::removeImport(const Import &import)
148 if (!import.isEmpty())
149 schedule(new RemoveImportRewriteAction(import));
152 void ModelToTextMerger::nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange)
154 if (isInHierarchy(oldPropertyParent) && isInHierarchy(newPropertyParent)) { // the node is moved
155 schedule(new ReparentNodeRewriteAction(node,
158 propertyType(newPropertyParent)));
159 } else if (isInHierarchy(oldPropertyParent) && !isInHierarchy(newPropertyParent)) { // the node is removed from hierarchy
160 if (oldPropertyParent.isNodeProperty()) {
161 // ignore, the subsequent remove property will take care of all
162 } else if (oldPropertyParent.isNodeListProperty()) {
163 if (!oldPropertyParent.isDefaultProperty() && oldPropertyParent.toNodeListProperty().toModelNodeList().size() == 0) {
164 schedule(new RemovePropertyRewriteAction(oldPropertyParent));
166 schedule(new RemoveNodeRewriteAction(node));
169 schedule(new RemoveNodeRewriteAction(node));
171 } else if (!isInHierarchy(oldPropertyParent) && isInHierarchy(newPropertyParent)) { // the node is inserted into to hierarchy
172 switch (propertyChange) {
173 case AbstractView::PropertiesAdded:
174 schedule(new AddPropertyRewriteAction(newPropertyParent,
175 QmlTextGenerator(getPropertyOrder())(node),
176 propertyType(newPropertyParent),
180 case AbstractView::NoAdditionalChanges:
181 schedule(new ChangePropertyRewriteAction(newPropertyParent,
182 QmlTextGenerator(getPropertyOrder())(node),
183 propertyType(newPropertyParent),
187 case AbstractView::EmptyPropertiesRemoved:
191 Q_ASSERT(!"Unknown PropertyChange value");
194 // old is outside of hierarchy, new is outside of hierarchy, so who cares?
198 void ModelToTextMerger::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
200 if (!node.isInHierarchy())
203 schedule(new ChangeIdRewriteAction(node, oldId, newId));
206 void ModelToTextMerger::nodeSlidAround(const ModelNode &movingNode, const ModelNode &inFrontOfNode)
208 if (!inFrontOfNode.isValid() || movingNode.parentProperty() == inFrontOfNode.parentProperty())
209 schedule(new MoveNodeRewriteAction(movingNode, inFrontOfNode));
211 Q_ASSERT(!"Nodes do not belong to the same containing property");
214 RewriterView *ModelToTextMerger::view()
216 return m_rewriterView;
219 void ModelToTextMerger::applyChanges()
221 if (m_rewriteActions.isEmpty())
224 dumpRewriteActions(QLatin1String("Before compression"));
225 RewriteActionCompressor compress(getPropertyOrder());
226 compress(m_rewriteActions);
227 dumpRewriteActions(QLatin1String("After compression"));
229 if (m_rewriteActions.isEmpty())
232 Document::Ptr tmpDocument(Document::create(QLatin1String("<ModelToTextMerger>")));
233 tmpDocument->setSource(m_rewriterView->textModifier()->text());
234 if (!tmpDocument->parseQml()) {
235 qDebug() << "*** Possible problem: QML file wasn't parsed correctly.";
236 qDebug() << "*** QML text:" << m_rewriterView->textModifier()->text();
238 QString errorMessage = QLatin1String("Error while rewriting");
239 if (!tmpDocument->diagnosticMessages().isEmpty())
240 errorMessage = tmpDocument->diagnosticMessages().first().message;
242 m_rewriterView->enterErrorState(errorMessage);
246 TextModifier *textModifier = m_rewriterView->textModifier();
249 ModelNodePositionRecalculator positionRecalculator(m_rewriterView->positionStorage(), m_rewriterView->positionStorage()->modelNodes());
250 positionRecalculator.connectTo(textModifier);
252 QmlDesigner::QmlRefactoring refactoring(tmpDocument, *textModifier, getPropertyOrder());
254 textModifier->deactivateChangeSignals();
255 textModifier->startGroup();
257 for (int i = 0; i < m_rewriteActions.size(); ++i) {
258 RewriteAction* action = m_rewriteActions.at(i);
259 if (DebugRewriteActions) {
260 qDebug() << "Next rewrite action:" << qPrintable(action->info());
263 ModelNodePositionStorage *positionStore = m_rewriterView->positionStorage();
264 bool success = action->execute(refactoring, *positionStore);
267 textModifier->flushGroup();
268 success = refactoring.reparseDocument();
270 // don't merge these two if statements, because the previous then-part changes the value
273 m_rewriterView->enterErrorState(QLatin1String("Error rewriting document"));
275 if (true || DebugRewriteActions) {
276 qDebug() << "*** QML source code: ***";
277 qDebug() << qPrintable(textModifier->text());
278 qDebug() << "*** End of QML source code. ***";
285 qDeleteAll(m_rewriteActions);
286 m_rewriteActions.clear();
288 reindent(positionRecalculator.dirtyAreas());
290 textModifier->commitGroup();
292 textModifier->reactivateChangeSignals();
293 } catch (Exception &e) {
294 m_rewriterView->enterErrorState(e.description());
296 qDeleteAll(m_rewriteActions);
297 m_rewriteActions.clear();
298 textModifier->commitGroup();
299 textModifier->reactivateChangeSignals();
303 void ModelToTextMerger::reindent(const QMap<int, int> &dirtyAreas) const
305 QList<int> offsets = dirtyAreas.keys();
307 TextModifier *textModifier = m_rewriterView->textModifier();
309 foreach (const int offset, offsets) {
310 const int length = dirtyAreas[offset];
311 textModifier->indent(offset, length);
315 void ModelToTextMerger::schedule(RewriteAction *action)
319 m_rewriteActions.append(action);
322 QmlDesigner::QmlRefactoring::PropertyType ModelToTextMerger::propertyType(const AbstractProperty &property, const QString &textValue)
324 if (property.isBindingProperty()) {
325 QString val = textValue.trimmed();
327 return QmlDesigner::QmlRefactoring::ObjectBinding;
328 const QChar lastChar = val.at(val.size() - 1);
329 if (lastChar == '}' || lastChar == ';')
330 return QmlDesigner::QmlRefactoring::ObjectBinding;
332 return QmlDesigner::QmlRefactoring::ScriptBinding;
333 } else if (property.isNodeListProperty())
334 return QmlDesigner::QmlRefactoring::ArrayBinding;
335 else if (property.isNodeProperty())
336 return QmlDesigner::QmlRefactoring::ObjectBinding;
337 else if (property.isVariantProperty())
338 return QmlDesigner::QmlRefactoring::ScriptBinding;
340 Q_ASSERT(!"cannot convert property type");
341 return (QmlDesigner::QmlRefactoring::PropertyType) -1;
344 QStringList ModelToTextMerger::m_propertyOrder;
346 QStringList ModelToTextMerger::getPropertyOrder()
348 if (m_propertyOrder.isEmpty()) {
350 << QLatin1String("id")
351 << QLatin1String("name")
352 << QLatin1String("target")
353 << QLatin1String("property")
354 << QLatin1String("x")
355 << QLatin1String("y")
356 << QLatin1String("width")
357 << QLatin1String("height")
358 << QLatin1String("position")
359 << QLatin1String("color")
360 << QLatin1String("radius")
361 << QLatin1String("text")
363 << QLatin1String("states")
364 << QLatin1String("transitions")
368 return m_propertyOrder;
371 bool ModelToTextMerger::isInHierarchy(const AbstractProperty &property) {
372 return property.isValid() && property.parentModelNode().isInHierarchy();
375 void ModelToTextMerger::dumpRewriteActions(const QString &msg)
377 if (DebugRewriteActions) {
378 qDebug() << "---->" << qPrintable(msg);
380 foreach (RewriteAction *action, m_rewriteActions) {
381 qDebug() << "-----" << qPrintable(action->info());
384 qDebug() << "<----" << qPrintable(msg);