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