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
54Q_DECLARE_METATYPE(QAction*)
55
56QT_BEGIN_NAMESPACE
57
58using ActionList = QList<QAction *>;
59
60namespace qdesigner_internal {
61// ------------------- ToolBarEventFilter
62void ToolBarEventFilter::install(QToolBar *tb)
63{
64 ToolBarEventFilter *tf = new ToolBarEventFilter(tb);
65 tb->installEventFilter(filterObj: tf);
66 tb->setAcceptDrops(true); // ### fake
67}
68
69ToolBarEventFilter::ToolBarEventFilter(QToolBar *tb) :
70 QObject(tb),
71 m_toolBar(tb),
72 m_promotionTaskMenu(nullptr)
73{
74}
75
76ToolBarEventFilter *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 *>(object: o))
82 return ef;
83 }
84 return nullptr;
85}
86
87bool 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 *>(o: ce->child())) {
97 w->setAttribute(Qt::WA_TransparentForMouseEvents, on: true);
98 w->setFocusPolicy(Qt::NoFocus);
99 }
100 }
101 break;
102 case QEvent::ContextMenu:
103 return handleContextMenuEvent(event: static_cast<QContextMenuEvent*>(event));
104 case QEvent::DragEnter:
105 case QEvent::DragMove:
106 return handleDragEnterMoveEvent(event: static_cast<QDragMoveEvent *>(event));
107 case QEvent::DragLeave:
108 return handleDragLeaveEvent(static_cast<QDragLeaveEvent *>(event));
109 case QEvent::Drop:
110 return handleDropEvent(event: static_cast<QDropEvent *>(event));
111 case QEvent::MouseButtonPress:
112 return handleMousePressEvent(event: static_cast<QMouseEvent*>(event));
113 case QEvent::MouseButtonRelease:
114 return handleMouseReleaseEvent(event: static_cast<QMouseEvent*>(event));
115 case QEvent::MouseMove:
116 return handleMouseMoveEvent(event: static_cast<QMouseEvent*>(event));
117 default:
118 break;
119 }
120 return QObject::eventFilter (watched, event);
121}
122
123ActionList ToolBarEventFilter::contextMenuActions(const QPoint &globalPos)
124{
125 ActionList rc;
126 const int index = actionIndexAt(w: m_toolBar, pos: m_toolBar->mapFromGlobal(globalPos), orientation: m_toolBar->orientation());
127 const auto actions = m_toolBar->actions();
128 QAction *action = index != -1 ?actions.at(i: index) : 0;
129 QVariant itemData;
130
131 // Insert before
132 if (action && index != 0 && !action->isSeparator()) {
133 QAction *newSeperatorAct = new QAction(tr(s: "Insert Separator before '%1'").arg(a: action->objectName()), nullptr);
134 itemData.setValue(action);
135 newSeperatorAct->setData(itemData);
136 connect(sender: newSeperatorAct, signal: &QAction::triggered, receiver: this, slot: &ToolBarEventFilter::slotInsertSeparator);
137 rc.push_back(t: newSeperatorAct);
138 }
139
140 // Append separator
141 if (actions.isEmpty() || !actions.constLast()->isSeparator()) {
142 QAction *newSeperatorAct = new QAction(tr(s: "Append Separator"), nullptr);
143 itemData.setValue(static_cast<QAction*>(nullptr));
144 newSeperatorAct->setData(itemData);
145 connect(sender: newSeperatorAct, signal: &QAction::triggered, receiver: this, slot: &ToolBarEventFilter::slotInsertSeparator);
146 rc.push_back(t: newSeperatorAct);
147 }
148 // Promotion
149 if (!m_promotionTaskMenu)
150 m_promotionTaskMenu = new PromotionTaskMenu(m_toolBar, PromotionTaskMenu::ModeSingleWidget, this);
151 m_promotionTaskMenu->addActions(fw: formWindow(), flags: PromotionTaskMenu::LeadingSeparator|PromotionTaskMenu::TrailingSeparator, actionList&: rc);
152 // Remove
153 if (action) {
154 QAction *a = new QAction(tr(s: "Remove action '%1'").arg(a: action->objectName()), nullptr);
155 itemData.setValue(action);
156 a->setData(itemData);
157 connect(sender: a, signal: &QAction::triggered, receiver: this, slot: &ToolBarEventFilter::slotRemoveSelectedAction);
158 rc.push_back(t: a);
159 }
160
161 QAction *remove_toolbar = new QAction(tr(s: "Remove Toolbar '%1'").arg(a: m_toolBar->objectName()), nullptr);
162 connect(sender: remove_toolbar, signal: &QAction::triggered, receiver: this, slot: &ToolBarEventFilter::slotRemoveToolBar);
163 rc.push_back(t: remove_toolbar);
164 return rc;
165}
166
167bool ToolBarEventFilter::handleContextMenuEvent(QContextMenuEvent * event )
168{
169 event->accept();
170
171 const QPoint globalPos = event->globalPos();
172 const ActionList al = contextMenuActions(globalPos: 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(action: *it);
178 menu.exec(pos: globalPos);
179 return true;
180}
181
182void ToolBarEventFilter::slotRemoveSelectedAction()
183{
184 QAction *action = qobject_cast<QAction*>(object: sender());
185 if (!action)
186 return;
187
188 QAction *a = qvariant_cast<QAction*>(v: 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(t: a);
196 QAction *action_before = nullptr;
197 if (pos != -1 && actions.count() > pos + 1)
198 action_before = actions.at(i: pos + 1);
199
200 RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
201 cmd->init(parentWidget: m_toolBar, action: a, beforeAction: action_before);
202 fw->commandHistory()->push(cmd);
203}
204
205void ToolBarEventFilter::slotRemoveToolBar()
206{
207 QDesignerFormWindowInterface *fw = formWindow();
208 Q_ASSERT(fw);
209 DeleteToolBarCommand *cmd = new DeleteToolBarCommand(fw);
210 cmd->init(toolBar: m_toolBar);
211 fw->commandHistory()->push(cmd);
212}
213
214void ToolBarEventFilter::slotInsertSeparator()
215{
216 QDesignerFormWindowInterface *fw = formWindow();
217 QAction *theSender = qobject_cast<QAction*>(object: sender());
218 QAction *previous = qvariant_cast<QAction *>(v: theSender->data());
219 fw->beginCommand(description: tr(s: "Insert Separator"));
220 QAction *action = createAction(fw, QStringLiteral("separator"), separator: true);
221 InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
222 cmd->init(parentWidget: m_toolBar, action, beforeAction: previous);
223 fw->commandHistory()->push(cmd);
224 fw->endCommand();
225}
226
227QDesignerFormWindowInterface *ToolBarEventFilter::formWindow() const
228{
229 return QDesignerFormWindowInterface::findFormWindow(w: m_toolBar);
230}
231
232QAction *ToolBarEventFilter::createAction(QDesignerFormWindowInterface *fw, const QString &objectName, bool separator)
233{
234 QAction *action = new QAction(fw);
235 fw->core()->widgetFactory()->initialize(object: action);
236 if (separator)
237 action->setSeparator(true);
238
239 action->setObjectName(objectName);
240 fw->ensureUniqueObjectName(object: 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
249void ToolBarEventFilter::adjustDragIndicator(const QPoint &pos)
250{
251 if (QDesignerFormWindowInterface *fw = formWindow()) {
252 QDesignerFormEditorInterface *core = fw->core();
253 if (QDesignerActionProviderExtension *a = qt_extension<QDesignerActionProviderExtension*>(manager: core->extensionManager(), object: m_toolBar))
254 a->adjustIndicator(pos);
255 }
256}
257
258void ToolBarEventFilter::hideDragIndicator()
259{
260 adjustDragIndicator(pos: QPoint(-1, -1));
261}
262
263bool ToolBarEventFilter::handleMousePressEvent(QMouseEvent *event)
264{
265 if (event->button() != Qt::LeftButton || withinHandleArea(tb: m_toolBar, pos: event->pos()))
266 return false;
267
268 if (QDesignerFormWindowInterface *fw = formWindow()) {
269 QDesignerFormEditorInterface *core = fw->core();
270 // Keep selection in sync
271 fw->clearSelection(changePropertyDisplay: false);
272 if (QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(object: core->objectInspector())) {
273 oi->clearSelection();
274 oi->selectObject(o: 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
283bool ToolBarEventFilter::handleMouseReleaseEvent(QMouseEvent *event)
284{
285 if (event->button() != Qt::LeftButton || m_startPosition.isNull() || withinHandleArea(tb: m_toolBar, pos: 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
294bool ToolBarEventFilter::handleMouseMoveEvent(QMouseEvent *event)
295{
296 if (m_startPosition.isNull() || withinHandleArea(tb: m_toolBar, pos: 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(pos: m_startPosition, modifiers: event->modifiers());
302 m_startPosition = QPoint();
303 event->accept();
304 return true;
305 }
306 return false;
307}
308
309bool ToolBarEventFilter::handleDragEnterMoveEvent(QDragMoveEvent *event)
310{
311 const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(object: 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(t: action) || !Utils::isObjectAncestorOf(ancestor: formWindow()->mainContainer(), child: action)) {
323 event->ignore();
324 hideDragIndicator();
325 return true;
326 }
327
328 d->accept(event);
329 adjustDragIndicator(pos: event->pos());
330 return true;
331}
332
333bool ToolBarEventFilter::handleDragLeaveEvent(QDragLeaveEvent *)
334{
335 hideDragIndicator();
336 return false;
337}
338
339bool ToolBarEventFilter::handleDropEvent(QDropEvent *event)
340{
341 const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(object: 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(t: 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(w: m_toolBar, pos, orientation: m_toolBar->orientation());
364 if (index != -1) {
365 beforeAction = actions.at(i: index);
366 } else {
367 if (!freeArea(tb: m_toolBar).contains(p: 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(parentWidget: m_toolBar, action, beforeAction);
378 fw->commandHistory()->push(cmd);
379 hideDragIndicator();
380 return true;
381}
382
383void ToolBarEventFilter::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers)
384{
385 const int index = actionIndexAt(w: m_toolBar, pos, orientation: m_toolBar->orientation());
386 if (index == - 1)
387 return;
388
389 const ActionList actions = m_toolBar->actions();
390 QAction *action = actions.at(i: 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(i: nextIndex) : 0;
398 cmd->init(parentWidget: m_toolBar, action, beforeAction: 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(supportedActions: 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(i: index);
413 InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
414 cmd->init(parentWidget: m_toolBar, action, beforeAction: previous);
415 fw->commandHistory()->push(cmd);
416 }
417 }
418}
419
420QAction *ToolBarEventFilter::actionAt(const QToolBar *tb, const QPoint &pos)
421{
422 const int index = actionIndexAt(w: tb, pos, orientation: tb->orientation());
423 if (index == -1)
424 return nullptr;
425 return tb->actions().at(i: index);
426}
427
428//that's a trick to get access to the initStyleOption which is a protected member
429class FriendlyToolBar : public QToolBar {
430public:
431 friend class ToolBarEventFilter;
432};
433
434QRect ToolBarEventFilter::handleArea(const QToolBar *tb)
435{
436 QStyleOptionToolBar opt;
437 static_cast<const FriendlyToolBar*>(tb)->initStyleOption(option: &opt);
438 return tb->style()->subElementRect(subElement: QStyle::SE_ToolBarHandle, option: &opt, widget: tb);
439}
440
441bool ToolBarEventFilter::withinHandleArea(const QToolBar *tb, const QPoint &pos)
442{
443 return handleArea(tb).contains(p: pos);
444}
445
446// Determine the free area behind the last action.
447QRect 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(action: 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
474QT_END_NAMESPACE
475

source code of qttools/src/designer/src/lib/shared/qdesigner_toolbar.cpp