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 |
62 | void ToolBarEventFilter::install(QToolBar *tb) |
63 | { |
64 | ToolBarEventFilter *tf = new ToolBarEventFilter(tb); |
65 | tb->installEventFilter(filterObj: tf); |
66 | tb->setAcceptDrops(true); // ### fake |
67 | } |
68 | |
69 | ToolBarEventFilter::ToolBarEventFilter(QToolBar *tb) : |
70 | QObject(tb), |
71 | m_toolBar(tb), |
72 | m_promotionTaskMenu(nullptr) |
73 | { |
74 | } |
75 | |
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 *>(object: o)) |
82 | return ef; |
83 | } |
84 | return nullptr; |
85 | } |
86 | |
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 *>(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 | |
123 | ActionList ToolBarEventFilter::(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 | |
167 | bool 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 (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 | |
182 | void 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 | |
205 | void 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 | |
214 | void 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 | |
227 | QDesignerFormWindowInterface *ToolBarEventFilter::formWindow() const |
228 | { |
229 | return QDesignerFormWindowInterface::findFormWindow(w: m_toolBar); |
230 | } |
231 | |
232 | QAction *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 | |
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*>(manager: core->extensionManager(), object: m_toolBar)) |
254 | a->adjustIndicator(pos); |
255 | } |
256 | } |
257 | |
258 | void ToolBarEventFilter::hideDragIndicator() |
259 | { |
260 | adjustDragIndicator(pos: QPoint(-1, -1)); |
261 | } |
262 | |
263 | bool 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 | |
283 | bool 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 | |
294 | bool 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 | |
309 | bool 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 | |
333 | bool ToolBarEventFilter::handleDragLeaveEvent(QDragLeaveEvent *) |
334 | { |
335 | hideDragIndicator(); |
336 | return false; |
337 | } |
338 | |
339 | bool 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 | |
383 | void 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 | |
420 | QAction *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 |
429 | class FriendlyToolBar : public QToolBar { |
430 | public: |
431 | friend class ToolBarEventFilter; |
432 | }; |
433 | |
434 | QRect 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 | |
441 | bool 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. |
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(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 | |
474 | QT_END_NAMESPACE |
475 | |