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 "abstractformeditortool.h"
27 #include "formeditorview.h"
28 #include "formeditorwidget.h"
29 #include "formeditorscene.h"
30 #include "itemlibrarywidget.h"
31 
32 #include <modelnodecontextmenu.h>
33 
34 #include <QDebug>
35 #include <QGraphicsSceneDragDropEvent>
36 #include <QMimeData>
37 #include <nodemetainfo.h>
38 #include <QAction>
39 
40 namespace QmlDesigner {
41 
AbstractFormEditorTool(FormEditorView * editorView)42 AbstractFormEditorTool::AbstractFormEditorTool(FormEditorView *editorView) : m_view(editorView)
43 {
44 }
45 
46 AbstractFormEditorTool::~AbstractFormEditorTool() = default;
47 
view() const48 FormEditorView* AbstractFormEditorTool::view() const
49 {
50     return m_view;
51 }
52 
setView(FormEditorView * view)53 void AbstractFormEditorTool::setView(FormEditorView *view)
54 {
55     m_view = view;
56 }
57 
scene() const58 FormEditorScene* AbstractFormEditorTool::scene() const
59 {
60     return view()->scene();
61 }
62 
setItems(const QList<FormEditorItem * > & itemList)63 void AbstractFormEditorTool::setItems(const QList<FormEditorItem*> &itemList)
64 {
65     m_itemList = itemList;
66     selectedItemsChanged(m_itemList);
67 }
68 
items() const69 QList<FormEditorItem*> AbstractFormEditorTool::items() const
70 {
71     return m_itemList;
72 }
73 
toFormEditorItemList(const QList<QGraphicsItem * > & itemList)74 QList<FormEditorItem *> AbstractFormEditorTool::toFormEditorItemList(const QList<QGraphicsItem *> &itemList)
75 {
76     QList<FormEditorItem *> formEditorItemList;
77 
78     foreach (QGraphicsItem *graphicsItem, itemList) {
79         auto formEditorItem = qgraphicsitem_cast<FormEditorItem*>(graphicsItem);
80         if (formEditorItem)
81             formEditorItemList.append(formEditorItem);
82     }
83 
84     return formEditorItemList;
85 }
86 
topItemIsMovable(const QList<QGraphicsItem * > & itemList)87 bool AbstractFormEditorTool::topItemIsMovable(const QList<QGraphicsItem*> & itemList)
88 {
89     QGraphicsItem *firstSelectableItem = topMovableGraphicsItem(itemList);
90     if (firstSelectableItem == nullptr)
91         return false;
92 
93     FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(firstSelectableItem);
94     QList<ModelNode> selectedNodes = view()->selectedModelNodes();
95 
96     if (formEditorItem != nullptr
97        && selectedNodes.contains(formEditorItem->qmlItemNode()))
98         return true;
99 
100     return false;
101 
102 }
103 
topSelectedItemIsMovable(const QList<QGraphicsItem * > & itemList)104 bool AbstractFormEditorTool::topSelectedItemIsMovable(const QList<QGraphicsItem*> &itemList)
105 {
106     QList<ModelNode> selectedNodes = view()->selectedModelNodes();
107 
108     foreach (QGraphicsItem *item, itemList) {
109         FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item);
110         if (formEditorItem
111             && selectedNodes.contains(formEditorItem->qmlItemNode())
112             && formEditorItem->qmlItemNode().instanceIsMovable()
113             && formEditorItem->qmlItemNode().modelIsMovable()
114             && !formEditorItem->qmlItemNode().instanceIsInLayoutable()
115             && (formEditorItem->qmlItemNode().instanceHasShowContent()))
116             return true;
117     }
118 
119     foreach (QGraphicsItem *item, itemList) {
120         FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item);
121         if (formEditorItem
122             && formEditorItem->qmlItemNode().isValid()
123             && formEditorItem->qmlItemNode().instanceIsMovable()
124             && formEditorItem->qmlItemNode().modelIsMovable()
125             && !formEditorItem->qmlItemNode().instanceIsInLayoutable()
126             && selectedNodes.contains(formEditorItem->qmlItemNode()))
127             return true;
128     }
129 
130     return false;
131 }
132 
selectedItemCursorInMovableArea(const QPointF & pos)133 bool AbstractFormEditorTool::selectedItemCursorInMovableArea(const QPointF &pos)
134 {
135     if (!view()->hasSingleSelectedModelNode())
136         return false;
137 
138     const ModelNode selectedNode = view()->singleSelectedModelNode();
139 
140     FormEditorItem *item = scene()->itemForQmlItemNode(selectedNode);
141 
142     if (!item)
143         return false;
144 
145      if (!topSelectedItemIsMovable({item}))
146          return false;
147 
148     const QPolygonF boundingRectInSceneSpace(item->mapToScene(item->qmlItemNode().instanceBoundingRect()));
149     QRectF boundingRect = boundingRectInSceneSpace.boundingRect();
150     QRectF innerRect = boundingRect;
151 
152     innerRect.adjust(2, 2, -2, -2);
153     const int heightOffset = -20 / scene()->formLayerItem()->viewportTransform().m11();
154     boundingRect.adjust(-2, heightOffset, 2, 2);
155 
156     return !innerRect.contains(pos) && boundingRect.contains(pos);
157 }
158 
topItemIsResizeHandle(const QList<QGraphicsItem * > &)159 bool AbstractFormEditorTool::topItemIsResizeHandle(const QList<QGraphicsItem*> &/*itemList*/)
160 {
161     return false;
162 }
163 
topMovableGraphicsItem(const QList<QGraphicsItem * > & itemList)164 QGraphicsItem *AbstractFormEditorTool::topMovableGraphicsItem(const QList<QGraphicsItem*> &itemList)
165 {
166     foreach (QGraphicsItem *item, itemList) {
167         if (item->flags().testFlag(QGraphicsItem::ItemIsMovable))
168             return item;
169     }
170 
171     return nullptr;
172 }
173 
topMovableFormEditorItem(const QList<QGraphicsItem * > & itemList,bool selectOnlyContentItems)174 FormEditorItem *AbstractFormEditorTool::topMovableFormEditorItem(const QList<QGraphicsItem*> &itemList, bool selectOnlyContentItems)
175 {
176     foreach (QGraphicsItem *item, itemList) {
177         FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item);
178         if (formEditorItem
179                 && formEditorItem->qmlItemNode().isValid()
180                 && !formEditorItem->qmlItemNode().instanceIsInLayoutable()
181                 && formEditorItem->qmlItemNode().instanceIsMovable()
182                 && formEditorItem->qmlItemNode().modelIsMovable()
183                 && (formEditorItem->qmlItemNode().instanceHasShowContent() || !selectOnlyContentItems))
184             return formEditorItem;
185     }
186 
187     return nullptr;
188 }
189 
nearestFormEditorItem(const QPointF & point,const QList<QGraphicsItem * > & itemList)190 FormEditorItem* AbstractFormEditorTool::nearestFormEditorItem(const QPointF &point, const QList<QGraphicsItem*> &itemList)
191 {
192     FormEditorItem* nearestItem = nullptr;
193     for (QGraphicsItem *item : itemList) {
194         FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item);
195 
196         if (formEditorItem && formEditorItem->flowHitTest(point))
197             return formEditorItem;
198 
199         if (!formEditorItem || !formEditorItem->qmlItemNode().isValid())
200             continue;
201 
202         if (formEditorItem->parentItem() && !formEditorItem->parentItem()->isContentVisible())
203             continue;
204 
205         if (formEditorItem && ModelNode::isThisOrAncestorLocked(formEditorItem->qmlItemNode().modelNode()))
206             continue;
207 
208         if (!nearestItem)
209             nearestItem = formEditorItem;
210         else if (formEditorItem->selectionWeigth(point, 1) < nearestItem->selectionWeigth(point, 0))
211             nearestItem = formEditorItem;
212     }
213 
214     if (nearestItem && nearestItem->qmlItemNode().isInStackedContainer())
215         return nearestItem->parentItem();
216 
217     return nearestItem;
218 }
219 
filterSelectedModelNodes(const QList<FormEditorItem * > & itemList) const220 QList<FormEditorItem *> AbstractFormEditorTool::filterSelectedModelNodes(const QList<FormEditorItem *> &itemList) const
221 {
222     QList<FormEditorItem *> filteredItemList;
223 
224     foreach (FormEditorItem *item, itemList) {
225         if (view()->isSelectedModelNode(item->qmlItemNode()))
226             filteredItemList.append(item);
227     }
228 
229     return filteredItemList;
230 }
231 
dropEvent(const QList<QGraphicsItem * > &,QGraphicsSceneDragDropEvent *)232 void AbstractFormEditorTool::dropEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /* event */)
233 {
234 }
235 
dragEnterEvent(const QList<QGraphicsItem * > & itemList,QGraphicsSceneDragDropEvent * event)236 void AbstractFormEditorTool::dragEnterEvent(const QList<QGraphicsItem*> &itemList, QGraphicsSceneDragDropEvent *event)
237 {
238     bool hasValidAssets = false;
239     if (event->mimeData()->hasFormat("application/vnd.bauhaus.libraryresource")) {
240         const QStringList assetPaths = QString::fromUtf8(event->mimeData()
241                                 ->data("application/vnd.bauhaus.libraryresource")).split(",");
242         for (const QString &assetPath : assetPaths) {
243             QString assetType = ItemLibraryWidget::getAssetTypeAndData(assetPath).first;
244             if (assetType == "application/vnd.bauhaus.libraryresource.image"
245                 || assetType == "application/vnd.bauhaus.libraryresource.font") {
246                 hasValidAssets = true;
247                 break;
248             }
249         }
250     }
251 
252     if (event->mimeData()->hasFormat(QLatin1String("application/vnd.bauhaus.itemlibraryinfo")) || hasValidAssets) {
253         event->accept();
254         view()->changeToDragTool();
255         view()->currentTool()->dragEnterEvent(itemList, event);
256     } else {
257         event->ignore();
258     }
259 }
260 
mousePressEvent(const QList<QGraphicsItem * > &,QGraphicsSceneMouseEvent * event)261 void AbstractFormEditorTool::mousePressEvent(const QList<QGraphicsItem*> & /*itemList*/, QGraphicsSceneMouseEvent *event)
262 {
263     if (event->button() == Qt::RightButton)
264         event->accept();
265 }
266 
containsItemNode(const QList<QGraphicsItem * > & itemList,const QmlItemNode & itemNode)267 static bool containsItemNode(const QList<QGraphicsItem*> & itemList, const QmlItemNode &itemNode)
268 {
269     foreach (QGraphicsItem *item, itemList) {
270         FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item);
271         if (formEditorItem && formEditorItem->qmlItemNode() == itemNode)
272             return true;
273     }
274 
275     return false;
276 }
277 
mouseReleaseEvent(const QList<QGraphicsItem * > & itemList,QGraphicsSceneMouseEvent * event)278 void AbstractFormEditorTool::mouseReleaseEvent(const QList<QGraphicsItem*> & itemList, QGraphicsSceneMouseEvent *event)
279 {
280     if (event->button() == Qt::RightButton) {
281 
282         QmlItemNode currentSelectedNode;
283 
284         if (view()->selectedModelNodes().count() == 1) {
285             currentSelectedNode = view()->selectedModelNodes().constFirst();
286 
287             if (!containsItemNode(itemList, currentSelectedNode)) {
288                 QmlItemNode selectedNode;
289 
290                 FormEditorItem *formEditorItem = nearestFormEditorItem(event->scenePos(), itemList);
291 
292                 if (formEditorItem && formEditorItem->qmlItemNode().isValid())
293                     selectedNode = formEditorItem->qmlItemNode();
294 
295                 if (selectedNode.isValid()) {
296                     QList<ModelNode> nodeList;
297                     nodeList.append(selectedNode);
298 
299                     view()->setSelectedModelNodes(nodeList);
300                 }
301             }
302         }
303 
304         showContextMenu(event);
305         event->accept();
306     }
307 }
308 
mouseDoubleClickEvent(const QList<QGraphicsItem * > & itemList,QGraphicsSceneMouseEvent * event)309 void AbstractFormEditorTool::mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList, QGraphicsSceneMouseEvent *event)
310 {
311     if (event->button() == Qt::LeftButton) {
312         FormEditorItem *formEditorItem = nearestFormEditorItem(event->pos(), itemList);
313         if (formEditorItem) {
314             view()->setSelectedModelNode(formEditorItem->qmlItemNode().modelNode());
315             view()->changeToCustomTool();
316         }
317     }
318 }
319 
showContextMenu(QGraphicsSceneMouseEvent * event)320 void AbstractFormEditorTool::showContextMenu(QGraphicsSceneMouseEvent *event)
321 {
322     ModelNodeContextMenu::showContextMenu(view(), event->screenPos(), event->scenePos().toPoint(), true);
323 }
324 
generateUseSnapping(Qt::KeyboardModifiers keyboardModifier) const325 Snapper::Snapping AbstractFormEditorTool::generateUseSnapping(Qt::KeyboardModifiers keyboardModifier) const
326 {
327     bool shouldSnapping = view()->formEditorWidget()->snappingAction()->isChecked();
328     bool shouldSnappingAndAnchoring = view()->formEditorWidget()->snappingAndAnchoringAction()->isChecked();
329 
330     Snapper::Snapping useSnapping = Snapper::NoSnapping;
331     if (keyboardModifier.testFlag(Qt::ControlModifier) != (shouldSnapping || shouldSnappingAndAnchoring)) {
332         if (shouldSnappingAndAnchoring)
333             useSnapping = Snapper::UseSnappingAndAnchoring;
334         else
335             useSnapping = Snapper::UseSnapping;
336     }
337 
338     return useSnapping;
339 }
340 
isNotAncestorOfItemInList(FormEditorItem * formEditorItem,const QList<FormEditorItem * > & itemList)341 static bool isNotAncestorOfItemInList(FormEditorItem *formEditorItem, const QList<FormEditorItem*> &itemList)
342 {
343     foreach (FormEditorItem *item, itemList) {
344         if (item
345             && item->qmlItemNode().isValid()
346             && item->qmlItemNode().isAncestorOf(formEditorItem->qmlItemNode()))
347             return false;
348     }
349 
350     return true;
351 }
352 
containerFormEditorItem(const QList<QGraphicsItem * > & itemUnderMouseList,const QList<FormEditorItem * > & selectedItemList) const353 FormEditorItem *AbstractFormEditorTool::containerFormEditorItem(const QList<QGraphicsItem *> &itemUnderMouseList, const QList<FormEditorItem *> &selectedItemList) const
354 {
355     foreach (QGraphicsItem* item, itemUnderMouseList) {
356         FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item);
357         if (formEditorItem
358                 && !selectedItemList.contains(formEditorItem)
359                 && isNotAncestorOfItemInList(formEditorItem, selectedItemList)
360                 && formEditorItem->isContainer()
361                 && formEditorItem->isContentVisible())
362             return formEditorItem;
363     }
364 
365     return nullptr;
366 }
367 
clear()368 void AbstractFormEditorTool::clear()
369 {
370     m_itemList.clear();
371 }
372 
start()373 void AbstractFormEditorTool::start()
374 {
375 
376 }
377 }
378