1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "qdesigner_toolbar_p.h"
30 #include "qdesigner_command_p.h"
31 #include "actionrepository_p.h"
32 #include "actionprovider_p.h"
33 #include "qdesigner_utils_p.h"
34 #include "qdesigner_objectinspector_p.h"
35 #include "promotiontaskmenu_p.h"
36 
37 #include <QtDesigner/abstractformwindow.h>
38 #include <QtDesigner/abstractpropertyeditor.h>
39 #include <QtDesigner/abstractformeditor.h>
40 #include <actionprovider_p.h>
41 #include <QtDesigner/qextensionmanager.h>
42 #include <QtDesigner/abstractwidgetfactory.h>
43 
44 #include <QtWidgets/qaction.h>
45 #include <QtWidgets/qapplication.h>
46 #include <QtWidgets/qtoolbutton.h>
47 #include <QtWidgets/qtoolbar.h>
48 #include <QtWidgets/qmenu.h>
49 #include <QtGui/qevent.h>
50 #include <QtGui/qdrag.h>
51 #include <QtWidgets/qapplication.h>
52 #include <QtCore/qdebug.h>
53 
54 Q_DECLARE_METATYPE(QAction*)
55 
56 QT_BEGIN_NAMESPACE
57 
58 using ActionList = QList<QAction *>;
59 
60 namespace qdesigner_internal {
61 // ------------------- ToolBarEventFilter
install(QToolBar * tb)62 void ToolBarEventFilter::install(QToolBar *tb)
63 {
64     ToolBarEventFilter *tf = new ToolBarEventFilter(tb);
65     tb->installEventFilter(tf);
66     tb->setAcceptDrops(true); // ### fake
67 }
68 
ToolBarEventFilter(QToolBar * tb)69 ToolBarEventFilter::ToolBarEventFilter(QToolBar *tb) :
70     QObject(tb),
71     m_toolBar(tb),
72     m_promotionTaskMenu(nullptr)
73 {
74 }
75 
eventFilterOf(const QToolBar * tb)76 ToolBarEventFilter *ToolBarEventFilter::eventFilterOf(const QToolBar *tb)
77 {
78     // Look for 1st order children only..otherwise, we might get filters of nested widgets
79     for (QObject *o : tb->children()) {
80         if (!o->isWidgetType())
81             if (ToolBarEventFilter *ef = qobject_cast<ToolBarEventFilter *>(o))
82                 return ef;
83     }
84     return nullptr;
85 }
86 
eventFilter(QObject * watched,QEvent * event)87 bool ToolBarEventFilter::eventFilter (QObject *watched, QEvent *event)
88 {
89     if (watched != m_toolBar)
90         return QObject::eventFilter (watched, event);
91 
92     switch (event->type()) {
93     case QEvent::ChildAdded: {
94         // Children should not interact with the mouse
95         const QChildEvent *ce = static_cast<const QChildEvent *>(event);
96         if (QWidget *w = qobject_cast<QWidget *>(ce->child())) {
97             w->setAttribute(Qt::WA_TransparentForMouseEvents, true);
98             w->setFocusPolicy(Qt::NoFocus);
99         }
100     }
101         break;
102     case QEvent::ContextMenu:
103         return handleContextMenuEvent(static_cast<QContextMenuEvent*>(event));
104     case QEvent::DragEnter:
105     case QEvent::DragMove:
106         return handleDragEnterMoveEvent(static_cast<QDragMoveEvent *>(event));
107     case QEvent::DragLeave:
108         return handleDragLeaveEvent(static_cast<QDragLeaveEvent *>(event));
109     case QEvent::Drop:
110         return handleDropEvent(static_cast<QDropEvent *>(event));
111     case QEvent::MouseButtonPress:
112         return handleMousePressEvent(static_cast<QMouseEvent*>(event));
113     case QEvent::MouseButtonRelease:
114         return handleMouseReleaseEvent(static_cast<QMouseEvent*>(event));
115     case QEvent::MouseMove:
116         return handleMouseMoveEvent(static_cast<QMouseEvent*>(event));
117     default:
118         break;
119     }
120     return QObject::eventFilter (watched, event);
121 }
122 
contextMenuActions(const QPoint & globalPos)123 ActionList ToolBarEventFilter::contextMenuActions(const QPoint &globalPos)
124 {
125     ActionList rc;
126     const int index = actionIndexAt(m_toolBar, m_toolBar->mapFromGlobal(globalPos), m_toolBar->orientation());
127     const auto actions = m_toolBar->actions();
128     QAction *action = index != -1 ?actions.at(index) : 0;
129     QVariant itemData;
130 
131     // Insert before
132     if (action && index != 0 && !action->isSeparator()) {
133         QAction *newSeperatorAct = new QAction(tr("Insert Separator before '%1'").arg(action->objectName()), nullptr);
134         itemData.setValue(action);
135         newSeperatorAct->setData(itemData);
136         connect(newSeperatorAct, &QAction::triggered, this, &ToolBarEventFilter::slotInsertSeparator);
137         rc.push_back(newSeperatorAct);
138     }
139 
140     // Append separator
141     if (actions.isEmpty() || !actions.constLast()->isSeparator()) {
142         QAction *newSeperatorAct = new QAction(tr("Append Separator"), nullptr);
143         itemData.setValue(static_cast<QAction*>(nullptr));
144         newSeperatorAct->setData(itemData);
145         connect(newSeperatorAct, &QAction::triggered, this, &ToolBarEventFilter::slotInsertSeparator);
146         rc.push_back(newSeperatorAct);
147     }
148     // Promotion
149     if (!m_promotionTaskMenu)
150         m_promotionTaskMenu = new PromotionTaskMenu(m_toolBar, PromotionTaskMenu::ModeSingleWidget, this);
151     m_promotionTaskMenu->addActions(formWindow(), PromotionTaskMenu::LeadingSeparator|PromotionTaskMenu::TrailingSeparator, rc);
152     // Remove
153     if (action) {
154         QAction *a = new QAction(tr("Remove action '%1'").arg(action->objectName()), nullptr);
155         itemData.setValue(action);
156         a->setData(itemData);
157         connect(a, &QAction::triggered, this, &ToolBarEventFilter::slotRemoveSelectedAction);
158         rc.push_back(a);
159     }
160 
161     QAction *remove_toolbar = new QAction(tr("Remove Toolbar '%1'").arg(m_toolBar->objectName()), nullptr);
162     connect(remove_toolbar, &QAction::triggered, this, &ToolBarEventFilter::slotRemoveToolBar);
163     rc.push_back(remove_toolbar);
164     return rc;
165 }
166 
handleContextMenuEvent(QContextMenuEvent * event)167 bool ToolBarEventFilter::handleContextMenuEvent(QContextMenuEvent * event )
168 {
169     event->accept();
170 
171     const QPoint globalPos = event->globalPos();
172     const ActionList al = contextMenuActions(event->globalPos());
173 
174     QMenu menu(nullptr);
175     const ActionList::const_iterator acend = al.constEnd();
176     for (ActionList::const_iterator it = al.constBegin(); it != acend; ++it)
177         menu.addAction(*it);
178     menu.exec(globalPos);
179     return true;
180 }
181 
slotRemoveSelectedAction()182 void ToolBarEventFilter::slotRemoveSelectedAction()
183 {
184     QAction *action = qobject_cast<QAction*>(sender());
185     if (!action)
186         return;
187 
188     QAction *a = qvariant_cast<QAction*>(action->data());
189     Q_ASSERT(a != nullptr);
190 
191     QDesignerFormWindowInterface *fw = formWindow();
192     Q_ASSERT(fw);
193 
194     const ActionList actions = m_toolBar->actions();
195     const int pos = actions.indexOf(a);
196     QAction *action_before = nullptr;
197     if (pos != -1 && actions.count() > pos + 1)
198         action_before = actions.at(pos + 1);
199 
200     RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
201     cmd->init(m_toolBar, a, action_before);
202     fw->commandHistory()->push(cmd);
203 }
204 
slotRemoveToolBar()205 void ToolBarEventFilter::slotRemoveToolBar()
206 {
207     QDesignerFormWindowInterface *fw = formWindow();
208     Q_ASSERT(fw);
209     DeleteToolBarCommand *cmd = new DeleteToolBarCommand(fw);
210     cmd->init(m_toolBar);
211     fw->commandHistory()->push(cmd);
212 }
213 
slotInsertSeparator()214 void ToolBarEventFilter::slotInsertSeparator()
215 {
216     QDesignerFormWindowInterface *fw = formWindow();
217     QAction *theSender = qobject_cast<QAction*>(sender());
218     QAction *previous = qvariant_cast<QAction *>(theSender->data());
219     fw->beginCommand(tr("Insert Separator"));
220     QAction *action = createAction(fw, QStringLiteral("separator"), true);
221     InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
222     cmd->init(m_toolBar, action, previous);
223     fw->commandHistory()->push(cmd);
224     fw->endCommand();
225 }
226 
formWindow() const227 QDesignerFormWindowInterface *ToolBarEventFilter::formWindow() const
228 {
229     return QDesignerFormWindowInterface::findFormWindow(m_toolBar);
230 }
231 
createAction(QDesignerFormWindowInterface * fw,const QString & objectName,bool separator)232 QAction *ToolBarEventFilter::createAction(QDesignerFormWindowInterface *fw, const QString &objectName, bool separator)
233 {
234     QAction *action = new QAction(fw);
235     fw->core()->widgetFactory()->initialize(action);
236     if (separator)
237         action->setSeparator(true);
238 
239     action->setObjectName(objectName);
240     fw->ensureUniqueObjectName(action);
241 
242     qdesigner_internal::AddActionCommand *cmd = new  qdesigner_internal::AddActionCommand(fw);
243     cmd->init(action);
244     fw->commandHistory()->push(cmd);
245 
246     return action;
247 }
248 
adjustDragIndicator(const QPoint & pos)249 void ToolBarEventFilter::adjustDragIndicator(const QPoint &pos)
250 {
251     if (QDesignerFormWindowInterface *fw = formWindow()) {
252         QDesignerFormEditorInterface *core = fw->core();
253         if (QDesignerActionProviderExtension *a = qt_extension<QDesignerActionProviderExtension*>(core->extensionManager(), m_toolBar))
254             a->adjustIndicator(pos);
255     }
256 }
257 
hideDragIndicator()258 void ToolBarEventFilter::hideDragIndicator()
259 {
260     adjustDragIndicator(QPoint(-1, -1));
261 }
262 
handleMousePressEvent(QMouseEvent * event)263 bool ToolBarEventFilter::handleMousePressEvent(QMouseEvent *event)
264 {
265     if (event->button() != Qt::LeftButton || withinHandleArea(m_toolBar, event->pos()))
266         return false;
267 
268     if (QDesignerFormWindowInterface *fw = formWindow()) {
269         QDesignerFormEditorInterface *core = fw->core();
270         // Keep selection in sync
271         fw->clearSelection(false);
272         if (QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(core->objectInspector())) {
273             oi->clearSelection();
274             oi->selectObject(m_toolBar);
275         }
276         core->propertyEditor()->setObject(m_toolBar);
277     }
278     m_startPosition = m_toolBar->mapFromGlobal(event->globalPos());
279     event->accept();
280     return true;
281 }
282 
handleMouseReleaseEvent(QMouseEvent * event)283 bool ToolBarEventFilter::handleMouseReleaseEvent(QMouseEvent *event)
284 {
285     if (event->button() != Qt::LeftButton || m_startPosition.isNull() || withinHandleArea(m_toolBar, event->pos()))
286         return false;
287 
288     // Accept the event, otherwise, form window selection will trigger
289     m_startPosition = QPoint();
290     event->accept();
291     return true;
292 }
293 
handleMouseMoveEvent(QMouseEvent * event)294 bool ToolBarEventFilter::handleMouseMoveEvent(QMouseEvent *event)
295 {
296     if (m_startPosition.isNull() || withinHandleArea(m_toolBar, event->pos()))
297         return false;
298 
299     const QPoint pos = m_toolBar->mapFromGlobal(event->globalPos());
300     if ((pos - m_startPosition).manhattanLength() > qApp->startDragDistance()) {
301         startDrag(m_startPosition, event->modifiers());
302         m_startPosition = QPoint();
303         event->accept();
304         return true;
305     }
306     return false;
307 }
308 
handleDragEnterMoveEvent(QDragMoveEvent * event)309 bool ToolBarEventFilter::handleDragEnterMoveEvent(QDragMoveEvent *event)
310 {
311     const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData());
312     if (!d)
313         return false;
314 
315     if (d->actionList().isEmpty()) {
316         event->ignore();
317         hideDragIndicator();
318         return true;
319     }
320 
321     QAction *action = d->actionList().first();
322     if (!action || action->menu() || m_toolBar->actions().contains(action) || !Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) {
323         event->ignore();
324         hideDragIndicator();
325         return true;
326     }
327 
328     d->accept(event);
329     adjustDragIndicator(event->pos());
330     return true;
331 }
332 
handleDragLeaveEvent(QDragLeaveEvent *)333 bool ToolBarEventFilter::handleDragLeaveEvent(QDragLeaveEvent *)
334 {
335     hideDragIndicator();
336     return false;
337 }
338 
handleDropEvent(QDropEvent * event)339 bool ToolBarEventFilter::handleDropEvent(QDropEvent *event)
340 {
341     const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData());
342     if (!d)
343         return false;
344 
345     if (d->actionList().isEmpty()) {
346         event->ignore();
347         hideDragIndicator();
348         return true;
349     }
350 
351     QAction *action = d->actionList().first();
352 
353     const ActionList actions = m_toolBar->actions();
354     if (!action || actions.contains(action)) {
355         event->ignore();
356         hideDragIndicator();
357         return true;
358     }
359 
360     // Try to find action to 'insert before'. Click on action or in free area, else ignore.
361     QAction *beforeAction = nullptr;
362     const QPoint pos = event->pos();
363     const int index = actionIndexAt(m_toolBar, pos, m_toolBar->orientation());
364     if (index != -1) {
365         beforeAction = actions.at(index);
366     } else {
367         if (!freeArea(m_toolBar).contains(pos)) {
368             event->ignore();
369             hideDragIndicator();
370             return true;
371         }
372     }
373 
374     event->acceptProposedAction();
375     QDesignerFormWindowInterface *fw = formWindow();
376     InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
377     cmd->init(m_toolBar, action, beforeAction);
378     fw->commandHistory()->push(cmd);
379     hideDragIndicator();
380     return true;
381 }
382 
startDrag(const QPoint & pos,Qt::KeyboardModifiers modifiers)383 void ToolBarEventFilter::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers)
384 {
385     const int index = actionIndexAt(m_toolBar, pos, m_toolBar->orientation());
386     if (index == - 1)
387         return;
388 
389     const ActionList actions = m_toolBar->actions();
390     QAction *action = actions.at(index);
391     QDesignerFormWindowInterface *fw = formWindow();
392 
393     const Qt::DropAction dropAction = (modifiers & Qt::ControlModifier) ? Qt::CopyAction : Qt::MoveAction;
394     if (dropAction == Qt::MoveAction) {
395         RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
396         const int nextIndex = index + 1;
397         QAction *nextAction = nextIndex < actions.size() ? actions.at(nextIndex) : 0;
398         cmd->init(m_toolBar, action, nextAction);
399         fw->commandHistory()->push(cmd);
400     }
401 
402     QDrag *drag = new QDrag(m_toolBar);
403     drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap( action));
404     drag->setMimeData(new ActionRepositoryMimeData(action, dropAction));
405 
406     if (drag->exec(dropAction) == Qt::IgnoreAction) {
407         hideDragIndicator();
408         if (dropAction == Qt::MoveAction) {
409             const ActionList currentActions = m_toolBar->actions();
410             QAction *previous = nullptr;
411             if (index >= 0 && index < currentActions.size())
412                 previous = currentActions.at(index);
413             InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
414             cmd->init(m_toolBar, action, previous);
415             fw->commandHistory()->push(cmd);
416         }
417     }
418 }
419 
actionAt(const QToolBar * tb,const QPoint & pos)420 QAction *ToolBarEventFilter::actionAt(const QToolBar *tb, const QPoint &pos)
421 {
422     const int index = actionIndexAt(tb, pos, tb->orientation());
423     if (index == -1)
424         return nullptr;
425     return tb->actions().at(index);
426 }
427 
428 //that's a trick to get access to the initStyleOption which is a protected member
429 class FriendlyToolBar : public QToolBar {
430 public:
431     friend class ToolBarEventFilter;
432 };
433 
handleArea(const QToolBar * tb)434 QRect ToolBarEventFilter::handleArea(const QToolBar *tb)
435 {
436     QStyleOptionToolBar opt;
437     static_cast<const FriendlyToolBar*>(tb)->initStyleOption(&opt);
438     return tb->style()->subElementRect(QStyle::SE_ToolBarHandle, &opt, tb);
439 }
440 
withinHandleArea(const QToolBar * tb,const QPoint & pos)441 bool ToolBarEventFilter::withinHandleArea(const QToolBar *tb, const QPoint &pos)
442 {
443     return handleArea(tb).contains(pos);
444 }
445 
446 // Determine the free area behind the last action.
freeArea(const QToolBar * tb)447 QRect ToolBarEventFilter::freeArea(const QToolBar *tb)
448 {
449     QRect rc = QRect(QPoint(0, 0), tb->size());
450     const ActionList actionList = tb->actions();
451     QRect exclusionRectangle = actionList.isEmpty()
452         ? handleArea(tb) : tb->actionGeometry(actionList.constLast());
453     switch (tb->orientation()) {
454     case Qt::Horizontal:
455         switch (tb->layoutDirection()) {
456         case Qt::LayoutDirectionAuto: // Should never happen
457         case Qt::LeftToRight:
458             rc.setX(exclusionRectangle.right() + 1);
459             break;
460         case Qt::RightToLeft:
461             rc.setRight(exclusionRectangle.x());
462             break;
463         }
464         break;
465     case Qt::Vertical:
466         rc.setY(exclusionRectangle.bottom() + 1);
467         break;
468     }
469     return rc;
470 }
471 
472 }
473 
474 QT_END_NAMESPACE
475