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 "dragtool.h"
27 
28 #include "formeditorscene.h"
29 #include "formeditorview.h"
30 #include "itemlibrarywidget.h"
31 #include <metainfo.h>
32 #include <nodehints.h>
33 #include <rewritingexception.h>
34 
35 #include <QDebug>
36 #include <QGraphicsSceneMouseEvent>
37 #include <QLoggingCategory>
38 #include <QMimeData>
39 #include <QTimer>
40 #include <QWidget>
41 
42 static Q_LOGGING_CATEGORY(dragToolInfo, "qtc.qmldesigner.formeditor", QtWarningMsg);
43 
44 namespace QmlDesigner {
45 
DragTool(FormEditorView * editorView)46 DragTool::DragTool(FormEditorView *editorView)
47     : AbstractFormEditorTool(editorView),
48     m_moveManipulator(editorView->scene()->manipulatorLayerItem(), editorView),
49     m_selectionIndicator(editorView->scene()->manipulatorLayerItem())
50 {
51 }
52 
53 DragTool::~DragTool() = default;
54 
clear()55 void DragTool::clear()
56 {
57     m_moveManipulator.clear();
58     m_selectionIndicator.clear();
59     m_movingItems.clear();
60 }
61 
mousePressEvent(const QList<QGraphicsItem * > &,QGraphicsSceneMouseEvent *)62 void DragTool::mousePressEvent(const QList<QGraphicsItem *> &, QGraphicsSceneMouseEvent *) {}
mouseMoveEvent(const QList<QGraphicsItem * > &,QGraphicsSceneMouseEvent *)63 void DragTool::mouseMoveEvent(const QList<QGraphicsItem *> &, QGraphicsSceneMouseEvent *) {}
hoverMoveEvent(const QList<QGraphicsItem * > &,QGraphicsSceneMouseEvent *)64 void DragTool::hoverMoveEvent(const QList<QGraphicsItem *> &, QGraphicsSceneMouseEvent *) {}
65 
keyPressEvent(QKeyEvent * event)66 void DragTool::keyPressEvent(QKeyEvent *event)
67 {
68     if (event->key() == Qt::Key_Escape) {
69         abort();
70         event->accept();
71         commitTransaction();
72         view()->changeToSelectionTool();
73     }
74 }
75 
keyReleaseEvent(QKeyEvent *)76 void DragTool::keyReleaseEvent(QKeyEvent *) {}
mouseReleaseEvent(const QList<QGraphicsItem * > &,QGraphicsSceneMouseEvent *)77 void DragTool::mouseReleaseEvent(const QList<QGraphicsItem *> &, QGraphicsSceneMouseEvent *) {}
mouseDoubleClickEvent(const QList<QGraphicsItem * > &,QGraphicsSceneMouseEvent *)78 void DragTool::mouseDoubleClickEvent(const QList<QGraphicsItem *> &, QGraphicsSceneMouseEvent *) {}
itemsAboutToRemoved(const QList<FormEditorItem * > &)79 void DragTool::itemsAboutToRemoved(const QList<FormEditorItem *> &) {}
selectedItemsChanged(const QList<FormEditorItem * > &)80 void DragTool::selectedItemsChanged(const QList<FormEditorItem *> &) {}
updateMoveManipulator()81 void DragTool::updateMoveManipulator() {}
82 
beginWithPoint(const QPointF & beginPoint)83 void DragTool::beginWithPoint(const QPointF &beginPoint)
84 {
85     m_movingItems = scene()->itemsForQmlItemNodes(m_dragNodes);
86 
87     m_moveManipulator.setItems(m_movingItems);
88     m_moveManipulator.begin(beginPoint);
89 }
90 
createQmlItemNode(const ItemLibraryEntry & itemLibraryEntry,const QmlItemNode & parentNode,const QPointF & scenePosition)91 void DragTool::createQmlItemNode(const ItemLibraryEntry &itemLibraryEntry,
92                                  const QmlItemNode &parentNode,
93                                  const QPointF &scenePosition)
94 {
95     MetaInfo metaInfo = MetaInfo::global();
96 
97     FormEditorItem *parentItem = scene()->itemForQmlItemNode(parentNode);
98     const QPointF positonInItemSpace = parentItem->qmlItemNode().instanceSceneContentItemTransform().inverted().map(scenePosition);
99     QPointF itemPos = positonInItemSpace;
100 
101     const bool rootIsFlow = QmlItemNode(view()->rootModelNode()).isFlowView();
102 
103     QmlItemNode adjustedParentNode = parentNode;
104 
105     if (rootIsFlow) {
106         itemPos = QPointF();
107         adjustedParentNode = view()->rootModelNode();
108     }
109 
110     m_dragNodes.append(QmlItemNode::createQmlItemNode(view(), itemLibraryEntry, itemPos, adjustedParentNode));
111 
112     if (rootIsFlow) {
113         for (QmlItemNode &dragNode : m_dragNodes)
114             dragNode.setFlowItemPosition(positonInItemSpace);
115     }
116 
117     m_selectionIndicator.setItems(scene()->itemsForQmlItemNodes(m_dragNodes));
118 }
119 
createQmlItemNodeFromImage(const QString & imagePath,const QmlItemNode & parentNode,const QPointF & scenePosition)120 void DragTool::createQmlItemNodeFromImage(const QString &imagePath,
121                                           const QmlItemNode &parentNode,
122                                           const QPointF &scenePosition)
123 {
124     if (parentNode.isValid()) {
125         MetaInfo metaInfo = MetaInfo::global();
126 
127         FormEditorItem *parentItem = scene()->itemForQmlItemNode(parentNode);
128         QPointF positonInItemSpace = parentItem->qmlItemNode().instanceSceneContentItemTransform().inverted().map(scenePosition);
129 
130         m_dragNodes.append(QmlItemNode::createQmlItemNodeFromImage(view(), imagePath, positonInItemSpace, parentNode));
131     }
132 }
133 
createQmlItemNodeFromFont(const QString & fontPath,const QmlItemNode & parentNode,const QPointF & scenePos)134 void DragTool::createQmlItemNodeFromFont(const QString &fontPath,
135                                          const QmlItemNode &parentNode,
136                                          const QPointF &scenePos)
137 {
138     if (parentNode.isValid()) {
139         MetaInfo metaInfo = MetaInfo::global();
140 
141         FormEditorItem *parentItem = scene()->itemForQmlItemNode(parentNode);
142         QPointF positonInItemSpace = parentItem->qmlItemNode().instanceSceneContentItemTransform()
143                 .inverted().map(scenePos);
144 
145         const auto typeAndData = ItemLibraryWidget::getAssetTypeAndData(fontPath);
146         QString fontFamily = QString::fromUtf8(typeAndData.second);
147 
148         m_dragNodes.append(QmlItemNode::createQmlItemNodeFromFont(view(), fontFamily,
149                                                                   positonInItemSpace, parentNode));
150     }
151 }
152 
targetContainerOrRootItem(const QList<QGraphicsItem * > & itemList,const QList<FormEditorItem * > & currentItems)153 FormEditorItem *DragTool::targetContainerOrRootItem(const QList<QGraphicsItem *> &itemList,
154                                                     const QList<FormEditorItem *> &currentItems)
155 {
156     FormEditorItem *formEditorItem = containerFormEditorItem(itemList, currentItems);
157 
158     if (!formEditorItem)
159         formEditorItem = scene()->rootFormEditorItem();
160 
161     return formEditorItem;
162 }
163 
formEditorItemsChanged(const QList<FormEditorItem * > & itemList)164 void DragTool::formEditorItemsChanged(const QList<FormEditorItem *> &itemList)
165 {
166     if (!m_movingItems.isEmpty()) {
167         for (auto item : std::as_const(m_movingItems)) {
168             if (itemList.contains(item)) {
169                 m_selectionIndicator.updateItems(m_movingItems);
170                 break;
171             }
172         }
173     }
174 }
175 
instancesCompleted(const QList<FormEditorItem * > & itemList)176 void DragTool::instancesCompleted(const QList<FormEditorItem *> &itemList)
177 {
178     m_moveManipulator.synchronizeInstanceParent(itemList);
179     for (FormEditorItem *item : itemList) {
180         for (const QmlItemNode &dragNode : std::as_const(m_dragNodes)) {
181             if (item->qmlItemNode() == dragNode) {
182                 clearMoveDelay();
183                 break;
184             }
185         }
186     }
187 }
188 
instancesParentChanged(const QList<FormEditorItem * > & itemList)189 void DragTool::instancesParentChanged(const QList<FormEditorItem *> &itemList)
190 {
191     m_moveManipulator.synchronizeInstanceParent(itemList);
192 }
193 
instancePropertyChange(const QList<QPair<ModelNode,PropertyName>> &)194 void DragTool::instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &) {}
195 
clearMoveDelay()196 void DragTool::clearMoveDelay()
197 {
198     if (m_blockMove) {
199         m_blockMove = false;
200         if (!m_dragNodes.isEmpty())
201             beginWithPoint(m_startPoint);
202     }
203 }
204 
focusLost()205 void DragTool::focusLost() {}
206 
abort()207 void DragTool::abort()
208 {
209     if (!m_isAborted) {
210         m_isAborted = true;
211 
212         for (auto &node : m_dragNodes) {
213             if (node.isValid())
214                 node.destroy();
215         }
216         m_dragNodes.clear();
217     }
218 }
219 
itemLibraryEntryFromMimeData(const QMimeData * mimeData)220 static ItemLibraryEntry itemLibraryEntryFromMimeData(const QMimeData *mimeData)
221 {
222     QByteArray data = mimeData->data(QStringLiteral("application/vnd.bauhaus.itemlibraryinfo"));
223 
224     QDataStream stream(data);
225 
226     ItemLibraryEntry itemLibraryEntry;
227     stream >> itemLibraryEntry;
228 
229     return itemLibraryEntry;
230 }
231 
canBeDropped(const QMimeData * mimeData)232 static bool canBeDropped(const QMimeData *mimeData)
233 {
234     return NodeHints::fromItemLibraryEntry(itemLibraryEntryFromMimeData(mimeData)).canBeDroppedInFormEditor();
235 }
236 
hasItemLibraryInfo(const QMimeData * mimeData)237 static bool hasItemLibraryInfo(const QMimeData *mimeData)
238 {
239     return mimeData->hasFormat(QStringLiteral("application/vnd.bauhaus.itemlibraryinfo"));
240 }
241 
dropEvent(const QList<QGraphicsItem * > &,QGraphicsSceneDragDropEvent * event)242 void DragTool::dropEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSceneDragDropEvent *event)
243 {
244     if (canBeDropped(event->mimeData())) {
245         event->accept();
246         end(generateUseSnapping(event->modifiers()));
247 
248         bool resetPuppet = false;
249         for (auto &node : m_dragNodes) {
250             if (node.isValid()) {
251                 if ((node.instanceParentItem().isValid()
252                      && node.instanceParent().modelNode().metaInfo().isLayoutable())
253                     || node.isFlowItem()) {
254                     node.removeProperty("x");
255                     node.removeProperty("y");
256                     resetPuppet = true;
257                 }
258             }
259         }
260         if (resetPuppet)
261             view()->resetPuppet(); // Otherwise the layout might not reposition the items
262 
263         commitTransaction();
264 
265         if (!m_dragNodes.isEmpty()) {
266             QList<ModelNode> nodeList;
267             for (auto &node : std::as_const(m_dragNodes)) {
268                 if (node.isValid())
269                     nodeList.append(node);
270             }
271             view()->setSelectedModelNodes(nodeList);
272         }
273         m_dragNodes.clear();
274 
275         view()->changeToSelectionTool();
276     }
277 }
278 
dragEnterEvent(const QList<QGraphicsItem * > &,QGraphicsSceneDragDropEvent * event)279 void DragTool::dragEnterEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSceneDragDropEvent *event)
280 {
281     if (canBeDropped(event->mimeData())) {
282         m_blockMove = false;
283 
284         if (hasItemLibraryInfo(event->mimeData())) {
285             view()->widgetInfo().widget->setFocus();
286             m_isAborted = false;
287         }
288 
289         if (!m_rewriterTransaction.isValid()) {
290             view()->clearSelectedModelNodes();
291             m_rewriterTransaction = view()->beginRewriterTransaction(QByteArrayLiteral("DragTool::dragEnterEvent"));
292         }
293     }
294 }
295 
dragLeaveEvent(const QList<QGraphicsItem * > &,QGraphicsSceneDragDropEvent * event)296 void DragTool::dragLeaveEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSceneDragDropEvent *event)
297 {
298     if (canBeDropped(event->mimeData())) {
299         event->accept();
300 
301         m_moveManipulator.end();
302         clear();
303 
304         for (auto &node : m_dragNodes) {
305             if (node.isValid())
306                 node.destroy();
307         }
308         m_dragNodes.clear();
309 
310         commitTransaction();
311     }
312 
313     view()->changeToSelectionTool();
314 }
315 
createDragNodes(const QMimeData * mimeData,const QPointF & scenePosition,const QList<QGraphicsItem * > & itemList)316 void DragTool::createDragNodes(const QMimeData *mimeData, const QPointF &scenePosition,
317                                const QList<QGraphicsItem *> &itemList)
318 {
319     if (m_dragNodes.isEmpty()) {
320         FormEditorItem *targetContainerFormEditorItem = targetContainerOrRootItem(itemList);
321         if (targetContainerFormEditorItem) {
322             QmlItemNode targetContainerQmlItemNode = targetContainerFormEditorItem->qmlItemNode();
323 
324             if (hasItemLibraryInfo(mimeData)) {
325                 createQmlItemNode(itemLibraryEntryFromMimeData(mimeData), targetContainerQmlItemNode,
326                                   scenePosition);
327             } else {
328                 const QStringList assetPaths = QString::fromUtf8(mimeData
329                                     ->data("application/vnd.bauhaus.libraryresource")).split(",");
330                 for (const QString &assetPath : assetPaths) {
331                     QString assetType = ItemLibraryWidget::getAssetTypeAndData(assetPath).first;
332                     if (assetType == "application/vnd.bauhaus.libraryresource.image")
333                         createQmlItemNodeFromImage(assetPath, targetContainerQmlItemNode, scenePosition);
334                     else if (assetType == "application/vnd.bauhaus.libraryresource.font")
335                         createQmlItemNodeFromFont(assetPath, targetContainerQmlItemNode, scenePosition);
336                 }
337 
338                 if (!m_dragNodes.isEmpty())
339                     m_selectionIndicator.setItems(scene()->itemsForQmlItemNodes(m_dragNodes));
340             }
341 
342             m_blockMove = true;
343             m_startPoint = scenePosition;
344         }
345     }
346 }
347 
dragMoveEvent(const QList<QGraphicsItem * > & itemList,QGraphicsSceneDragDropEvent * event)348 void DragTool::dragMoveEvent(const QList<QGraphicsItem *> &itemList, QGraphicsSceneDragDropEvent *event)
349 {
350     if (!m_blockMove && !m_isAborted && canBeDropped(event->mimeData())) {
351         event->accept();
352         if (!m_dragNodes.isEmpty()) {
353             FormEditorItem *targetContainerItem = targetContainerOrRootItem(itemList);
354             if (targetContainerItem) {
355                 move(event->scenePos(), itemList);
356             } else {
357                 end();
358                 for (auto &node : m_dragNodes) {
359                     if (node.isValid())
360                         node.destroy();
361                 }
362                 m_dragNodes.clear();
363             }
364         } else {
365             createDragNodes(event->mimeData(), event->scenePos(), itemList);
366         }
367     } else {
368         event->ignore();
369     }
370 }
371 
end()372 void  DragTool::end()
373 {
374     m_moveManipulator.end();
375     clear();
376 }
377 
end(Snapper::Snapping useSnapping)378 void DragTool::end(Snapper::Snapping useSnapping)
379 {
380     m_moveManipulator.end(useSnapping);
381     clear();
382 }
383 
move(const QPointF & scenePosition,const QList<QGraphicsItem * > & itemList)384 void DragTool::move(const QPointF &scenePosition, const QList<QGraphicsItem *> &itemList)
385 {
386     if (!m_movingItems.isEmpty()) {
387         FormEditorItem *containerItem = targetContainerOrRootItem(itemList, m_movingItems);
388         for (auto &movingItem : std::as_const(m_movingItems)) {
389             if (containerItem && movingItem->parentItem() &&
390                 containerItem != movingItem->parentItem()) {
391                 const QmlItemNode movingNode = movingItem->qmlItemNode();
392                 const QmlItemNode containerNode = containerItem->qmlItemNode();
393 
394                 qCInfo(dragToolInfo()) << Q_FUNC_INFO << movingNode << containerNode << movingNode.canBereparentedTo(containerNode);
395 
396                 if (movingNode.canBereparentedTo(containerNode))
397                     m_moveManipulator.reparentTo(containerItem);
398             }
399         }
400 
401         Snapper::Snapping useSnapping = Snapper::UseSnapping;
402 
403         m_moveManipulator.update(scenePosition, useSnapping, MoveManipulator::UseBaseState);
404     }
405 }
406 
commitTransaction()407 void DragTool::commitTransaction()
408 {
409     try {
410         m_rewriterTransaction.commit();
411     } catch (const RewritingException &e) {
412         e.showException();
413     }
414 }
415 
416 } // namespace QmlDesigner
417