OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmldesigner / designercore / model / modeltotextmerger.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "modelnodepositionrecalculator.h"
34 #include "modeltotextmerger.h"
35 #include "qmltextgenerator.h"
36 #include "rewriteactioncompressor.h"
37 #include "rewriterview.h"
38
39 #include <qmljs/qmljsdocument.h>
40 #include <variantproperty.h>
41 #include <nodelistproperty.h>
42 #include <nodeproperty.h>
43 #include <textmodifier.h>
44
45 #include <QDebug>
46
47 namespace {
48     enum {
49         DebugRewriteActions = 0
50     };
51 }
52
53 using namespace QmlJS;
54 using namespace QmlDesigner;
55 using namespace QmlDesigner::Internal;
56
57 ModelToTextMerger::ModelToTextMerger(RewriterView *reWriterView):
58         m_rewriterView(reWriterView)
59 {
60 }
61
62 void ModelToTextMerger::nodeCreated(const ModelNode &/*createdNode*/)
63 {
64     //the rewriter ignores model nodes outside of the hierachy
65 }
66
67 void ModelToTextMerger::nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange)
68 {
69     if (!isInHierarchy(parentProperty))
70         return;
71
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));
78     }
79 }
80
81 void ModelToTextMerger::propertiesRemoved(const QList<AbstractProperty>& propertyList)
82 {
83     foreach (const AbstractProperty &property, propertyList) {
84         if (isInHierarchy(property) && !property.isDefaultProperty())
85             schedule(new RemovePropertyRewriteAction(property));
86     }
87 }
88
89 void ModelToTextMerger::propertiesChanged(const QList<AbstractProperty>& propertyList, PropertyChangeFlags propertyChange)
90 {
91     foreach (const AbstractProperty &property, propertyList) {
92         if (!isInHierarchy(property))
93             continue;
94
95         ModelNode containedModelNode;
96         const int indentDepth = m_rewriterView->textModifier()->indentDepth();
97         const QString propertyTextValue = QmlTextGenerator(getPropertyOrder(),
98                                                            indentDepth)(property);
99
100         switch (propertyChange) {
101         case AbstractView::PropertiesAdded:
102             if (property.isNodeProperty())
103                 containedModelNode = property.toNodeProperty().modelNode();
104
105             schedule(new AddPropertyRewriteAction(property,
106                                                   propertyTextValue,
107                                                   propertyType(property, propertyTextValue),
108                                                   containedModelNode));
109             break;
110
111         case AbstractView::NoAdditionalChanges:
112             if (property.isNodeProperty())
113                 containedModelNode = property.toNodeProperty().modelNode();
114
115             schedule(new ChangePropertyRewriteAction(property,
116                                                      propertyTextValue,
117                                                      propertyType(property, propertyTextValue),
118                                                      containedModelNode));
119             break;
120
121         case AbstractView::EmptyPropertiesRemoved:
122             break;
123
124         default:
125             Q_ASSERT(!"Unknown PropertyChangeFlags");
126         }
127     }
128 }
129
130 void ModelToTextMerger::nodeTypeChanged(const ModelNode &node,const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/)
131 {
132     if (!node.isInHierarchy())
133         return;
134
135     // TODO: handle the majorVersion and the minorVersion
136
137     schedule(new ChangeTypeRewriteAction(node));
138 }
139
140 void ModelToTextMerger::addImport(const Import &import)
141 {
142     if (!import.isEmpty())
143         schedule(new AddImportRewriteAction(import));
144 }
145
146 void ModelToTextMerger::removeImport(const Import &import)
147 {
148     if (!import.isEmpty())
149         schedule(new RemoveImportRewriteAction(import));
150 }
151
152 void ModelToTextMerger::nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange)
153 {
154     if (isInHierarchy(oldPropertyParent) && isInHierarchy(newPropertyParent)) { // the node is moved
155         schedule(new ReparentNodeRewriteAction(node,
156                                                oldPropertyParent,
157                                                newPropertyParent,
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));
165             } else {
166                 schedule(new RemoveNodeRewriteAction(node));
167             }
168         } else {
169             schedule(new RemoveNodeRewriteAction(node));
170         }
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),
177                                                   node));
178             break;
179
180         case AbstractView::NoAdditionalChanges:
181             schedule(new ChangePropertyRewriteAction(newPropertyParent,
182                                                      QmlTextGenerator(getPropertyOrder())(node),
183                                                      propertyType(newPropertyParent),
184                                                      node));
185             break;
186
187         case AbstractView::EmptyPropertiesRemoved:
188             break;
189
190         default:
191             Q_ASSERT(!"Unknown PropertyChange value");
192         }
193     } else {
194         // old is outside of hierarchy, new is outside of hierarchy, so who cares?
195     }
196 }
197
198 void ModelToTextMerger::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
199 {
200     if (!node.isInHierarchy())
201         return;
202
203     schedule(new ChangeIdRewriteAction(node, oldId, newId));
204 }
205
206 void ModelToTextMerger::nodeSlidAround(const ModelNode &movingNode, const ModelNode &inFrontOfNode)
207 {
208     if (!inFrontOfNode.isValid() || movingNode.parentProperty() == inFrontOfNode.parentProperty())
209         schedule(new MoveNodeRewriteAction(movingNode, inFrontOfNode));
210     else
211         Q_ASSERT(!"Nodes do not belong to the same containing property");
212 }
213
214 RewriterView *ModelToTextMerger::view()
215 {
216     return m_rewriterView;
217 }
218
219 void ModelToTextMerger::applyChanges()
220 {
221     if (m_rewriteActions.isEmpty())
222         return;
223
224     dumpRewriteActions(QLatin1String("Before compression"));
225     RewriteActionCompressor compress(getPropertyOrder());
226     compress(m_rewriteActions);
227     dumpRewriteActions(QLatin1String("After compression"));
228
229     if (m_rewriteActions.isEmpty())
230         return;
231
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();
237
238         QString errorMessage = QLatin1String("Error while rewriting");
239         if (!tmpDocument->diagnosticMessages().isEmpty())
240             errorMessage = tmpDocument->diagnosticMessages().first().message;
241
242         m_rewriterView->enterErrorState(errorMessage);
243         return;
244     }
245
246     TextModifier *textModifier = m_rewriterView->textModifier();
247
248     try {
249         ModelNodePositionRecalculator positionRecalculator(m_rewriterView->positionStorage(), m_rewriterView->positionStorage()->modelNodes());
250         positionRecalculator.connectTo(textModifier);
251
252         QmlDesigner::QmlRefactoring refactoring(tmpDocument, *textModifier, getPropertyOrder());
253
254         textModifier->deactivateChangeSignals();
255         textModifier->startGroup();
256
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());
261             }
262
263             ModelNodePositionStorage *positionStore = m_rewriterView->positionStorage();
264             bool success = action->execute(refactoring, *positionStore);
265
266             if (success) {
267                 textModifier->flushGroup();
268                 success = refactoring.reparseDocument();
269             }
270             // don't merge these two if statements, because the previous then-part changes the value
271             // of "success" !
272             if (!success) {
273                 m_rewriterView->enterErrorState(QLatin1String("Error rewriting document"));
274
275                 if (true || DebugRewriteActions) {
276                     qDebug() << "*** QML source code: ***";
277                     qDebug() << qPrintable(textModifier->text());
278                     qDebug() << "*** End of QML source code. ***";
279                 }
280
281                 break;
282             }
283         }
284
285         qDeleteAll(m_rewriteActions);
286         m_rewriteActions.clear();
287
288         reindent(positionRecalculator.dirtyAreas());
289
290         textModifier->commitGroup();
291
292         textModifier->reactivateChangeSignals();
293     } catch (Exception &e) {
294         m_rewriterView->enterErrorState(e.description());
295
296         qDeleteAll(m_rewriteActions);
297         m_rewriteActions.clear();
298         textModifier->commitGroup();
299         textModifier->reactivateChangeSignals();
300     }
301 }
302
303 void ModelToTextMerger::reindent(const QMap<int, int> &dirtyAreas) const
304 {
305     QList<int> offsets = dirtyAreas.keys();
306     qSort(offsets);
307     TextModifier *textModifier = m_rewriterView->textModifier();
308
309     foreach (const int offset, offsets) {
310         const int length = dirtyAreas[offset];
311         textModifier->indent(offset, length);
312     }
313 }
314
315 void ModelToTextMerger::schedule(RewriteAction *action)
316 {
317     Q_ASSERT(action);
318
319     m_rewriteActions.append(action);
320 }
321
322 QmlDesigner::QmlRefactoring::PropertyType ModelToTextMerger::propertyType(const AbstractProperty &property, const QString &textValue)
323 {
324     if (property.isBindingProperty()) {
325         QString val = textValue.trimmed();
326         if (val.isEmpty())
327             return QmlDesigner::QmlRefactoring::ObjectBinding;
328         const QChar lastChar = val.at(val.size() - 1);
329         if (lastChar == '}' || lastChar == ';')
330             return QmlDesigner::QmlRefactoring::ObjectBinding;
331         else
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;
339
340     Q_ASSERT(!"cannot convert property type");
341     return (QmlDesigner::QmlRefactoring::PropertyType) -1;
342 }
343
344 QStringList ModelToTextMerger::m_propertyOrder;
345
346 QStringList ModelToTextMerger::getPropertyOrder()
347 {
348     if (m_propertyOrder.isEmpty()) {
349         m_propertyOrder
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")
362                 << QString::null
363                 << QLatin1String("states")
364                 << QLatin1String("transitions")
365                 ;
366     }
367
368     return m_propertyOrder;
369 }
370
371 bool ModelToTextMerger::isInHierarchy(const AbstractProperty &property) {
372     return property.isValid() && property.parentModelNode().isInHierarchy();
373 }
374
375 void ModelToTextMerger::dumpRewriteActions(const QString &msg)
376 {
377     if (DebugRewriteActions) {
378         qDebug() << "---->" << qPrintable(msg);
379
380         foreach (RewriteAction *action, m_rewriteActions) {
381             qDebug() << "-----" << qPrintable(action->info());
382         }
383
384         qDebug() << "<----" << qPrintable(msg);
385     }
386 }