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_menu_p.h"
30#include "qdesigner_menubar_p.h"
31#include "qdesigner_toolbar_p.h"
32#include "qdesigner_command_p.h"
33#include "qdesigner_propertycommand_p.h"
34#include "actionrepository_p.h"
35#include "actionprovider_p.h"
36#include "actioneditor_p.h"
37#include "qdesigner_utils_p.h"
38#include "qdesigner_objectinspector_p.h"
39
40#include <QtCore/qtimer.h>
41#include <QtCore/qdebug.h>
42
43#include <QtDesigner/abstractformeditor.h>
44#include <QtDesigner/abstractwidgetfactory.h>
45#include <QtDesigner/abstractmetadatabase.h>
46#include <QtDesigner/qextensionmanager.h>
47
48#include <QtWidgets/qaction.h>
49#include <QtWidgets/qapplication.h>
50#include <QtWidgets/qlineedit.h>
51#include <QtGui/qpainter.h>
52#include <QtGui/qdrag.h>
53#include <QtWidgets/qrubberband.h>
54#include <QtWidgets/qtooltip.h>
55#include <QtWidgets/qtoolbar.h>
56#include <QtGui/qevent.h>
57
58Q_DECLARE_METATYPE(QAction*)
59
60QT_BEGIN_NAMESPACE
61
62using namespace qdesigner_internal;
63
64// give the user a little more space to click on the sub menu rectangle
65static inline void extendClickableArea(QRect *subMenuRect, Qt::LayoutDirection dir)
66{
67 switch (dir) {
68 case Qt::LayoutDirectionAuto: // Should never happen
69 case Qt::LeftToRight:
70 subMenuRect->setLeft(subMenuRect->left() - 20);
71 break;
72 case Qt::RightToLeft:
73 subMenuRect->setRight(subMenuRect->right() + 20);
74 break;
75 }
76}
77
78QDesignerMenu::QDesignerMenu(QWidget *parent) :
79 QMenu(parent),
80 m_subMenuPixmap(QPixmap(QStringLiteral(":/qt-project.org/formeditor/images/submenu.png"))),
81 m_currentIndex(0),
82 m_addItem(new SpecialMenuAction(this)),
83 m_addSeparator(new SpecialMenuAction(this)),
84 m_showSubMenuTimer(new QTimer(this)),
85 m_deactivateWindowTimer(new QTimer(this)),
86 m_adjustSizeTimer(new QTimer(this)),
87 m_editor(new QLineEdit(this)),
88 m_dragging(false),
89 m_lastSubMenuIndex(-1)
90{
91 setContextMenuPolicy(Qt::DefaultContextMenu);
92 setAcceptDrops(true); // ### fake
93 setSeparatorsCollapsible(false);
94
95 connect(sender: m_adjustSizeTimer, signal: &QTimer::timeout, receiver: this, slot: &QDesignerMenu::slotAdjustSizeNow);
96 m_addItem->setText(tr(s: "Type Here"));
97 addAction(action: m_addItem);
98
99 m_addSeparator->setText(tr(s: "Add Separator"));
100 addAction(action: m_addSeparator);
101
102 connect(sender: m_showSubMenuTimer, signal: &QTimer::timeout, receiver: this, slot: &QDesignerMenu::slotShowSubMenuNow);
103
104 connect(sender: m_deactivateWindowTimer, signal: &QTimer::timeout, receiver: this, slot: &QDesignerMenu::slotDeactivateNow);
105
106 m_editor->setObjectName(QStringLiteral("__qt__passive_editor"));
107 m_editor->hide();
108
109 m_editor->installEventFilter(filterObj: this);
110 installEventFilter(filterObj: this);
111}
112
113QDesignerMenu::~QDesignerMenu() = default;
114
115void QDesignerMenu::slotAdjustSizeNow()
116{
117 // Not using a single-shot, since we want to compress the timers if many items are being
118 // adjusted
119 m_adjustSizeTimer->stop();
120 adjustSize();
121}
122
123bool QDesignerMenu::handleEvent(QWidget *widget, QEvent *event)
124{
125 if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) {
126 update();
127
128 if (widget == m_editor)
129 return false;
130 }
131
132 switch (event->type()) {
133 default: break;
134
135 case QEvent::MouseButtonPress:
136 return handleMousePressEvent(widget, event: static_cast<QMouseEvent*>(event));
137 case QEvent::MouseButtonRelease:
138 return handleMouseReleaseEvent(widget, event: static_cast<QMouseEvent*>(event));
139 case QEvent::MouseButtonDblClick:
140 return handleMouseDoubleClickEvent(widget, event: static_cast<QMouseEvent*>(event));
141 case QEvent::MouseMove:
142 return handleMouseMoveEvent(widget, event: static_cast<QMouseEvent*>(event));
143 case QEvent::ContextMenu:
144 return handleContextMenuEvent(widget, event: static_cast<QContextMenuEvent*>(event));
145 case QEvent::KeyPress:
146 return handleKeyPressEvent(widget, event: static_cast<QKeyEvent*>(event));
147 }
148
149 return true;
150}
151
152void QDesignerMenu::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers)
153{
154 const int index = findAction(pos);
155 if (index >= realActionCount())
156 return;
157
158 QAction *action = safeActionAt(index);
159
160 QDesignerFormWindowInterface *fw = formWindow();
161 const Qt::DropAction dropAction = (modifiers & Qt::ControlModifier) ? Qt::CopyAction : Qt::MoveAction;
162 if (dropAction == Qt::MoveAction) {
163 RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
164 cmd->init(parentWidget: this, action, beforeAction: actions().at(i: index + 1));
165 fw->commandHistory()->push(cmd);
166 }
167
168 QDrag *drag = new QDrag(this);
169 drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action));
170 drag->setMimeData(new ActionRepositoryMimeData(action, dropAction));
171
172 const int old_index = m_currentIndex;
173 m_currentIndex = -1;
174
175 if (drag->exec(supportedActions: dropAction) == Qt::IgnoreAction) {
176 if (dropAction == Qt::MoveAction) {
177 QAction *previous = safeActionAt(index);
178 InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
179 cmd->init(parentWidget: this, action, beforeAction: previous);
180 fw->commandHistory()->push(cmd);
181 }
182
183 m_currentIndex = old_index;
184 }
185}
186
187bool QDesignerMenu::handleKeyPressEvent(QWidget * /*widget*/, QKeyEvent *e)
188{
189 m_showSubMenuTimer->stop();
190
191 if (m_editor->isHidden() && hasFocus()) { // In navigation mode
192 switch (e->key()) {
193
194 case Qt::Key_Delete:
195 if (m_currentIndex == -1 || m_currentIndex >= realActionCount())
196 break;
197 hideSubMenu();
198 deleteAction();
199 break;
200
201 case Qt::Key_Left:
202 e->accept();
203 moveLeft();
204 return true;
205
206 case Qt::Key_Right:
207 e->accept();
208 moveRight();
209 return true; // no update
210
211 case Qt::Key_Up:
212 e->accept();
213 moveUp(ctrl: e->modifiers() & Qt::ControlModifier);
214 return true;
215
216 case Qt::Key_Down:
217 e->accept();
218 moveDown(ctrl: e->modifiers() & Qt::ControlModifier);
219 return true;
220
221 case Qt::Key_PageUp:
222 m_currentIndex = 0;
223 break;
224
225 case Qt::Key_PageDown:
226 m_currentIndex = actions().count() - 1;
227 break;
228
229 case Qt::Key_Enter:
230 case Qt::Key_Return:
231 case Qt::Key_F2:
232 e->accept();
233 enterEditMode();
234 return true; // no update
235
236 case Qt::Key_Escape:
237 e->ignore();
238 setFocus();
239 hide();
240 closeMenuChain();
241 return true;
242
243 case Qt::Key_Alt:
244 case Qt::Key_Shift:
245 case Qt::Key_Control:
246 e->ignore();
247 setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed
248 return true; // no update
249
250 default: {
251 QAction *action = currentAction();
252 if (!action || action->isSeparator() || action == m_addSeparator) {
253 e->ignore();
254 return true;
255 }
256 if (!e->text().isEmpty() && e->text().at(i: 0).toLatin1() >= 32) {
257 showLineEdit();
258 QApplication::sendEvent(receiver: m_editor, event: e);
259 e->accept();
260 } else {
261 e->ignore();
262 }
263 }
264 return true;
265 }
266 } else if (m_editor->hasFocus()) { // In edit mode
267 switch (e->key()) {
268 default:
269 e->ignore();
270 return false;
271
272 case Qt::Key_Enter:
273 case Qt::Key_Return:
274 if (!m_editor->text().isEmpty()) {
275 leaveEditMode(mode: ForceAccept);
276 m_editor->hide();
277 setFocus();
278 moveDown(ctrl: false);
279 break;
280 }
281 Q_FALLTHROUGH();
282
283 case Qt::Key_Escape:
284 m_editor->hide();
285 setFocus();
286 break;
287 }
288 }
289
290 e->accept();
291 update();
292
293 return true;
294}
295
296static void sendMouseEventTo(QWidget *target, const QPoint &targetPoint, const QMouseEvent *event)
297{
298 QMouseEvent e(event->type(), targetPoint, event->globalPos(), event->button(), event->buttons(), event->modifiers());
299 QApplication::sendEvent(receiver: target, event: &e);
300}
301
302bool QDesignerMenu::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event)
303{
304 event->accept();
305 m_startPosition = QPoint();
306
307 if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton)
308 return true;
309
310 if (!rect().contains(p: event->pos())) {
311 // special case for menubar
312 QWidget *target = QApplication::widgetAt(p: event->globalPos());
313 QMenuBar *mb = qobject_cast<QMenuBar*>(object: target);
314 QDesignerMenu *menu = qobject_cast<QDesignerMenu*>(object: target);
315 if (mb != nullptr || menu != nullptr) {
316 const QPoint pt = target->mapFromGlobal(event->globalPos());
317 QAction *action = mb == nullptr ? menu->actionAt(pt) : mb->actionAt(pt);
318 if (action)
319 sendMouseEventTo(target, targetPoint: pt, event);
320 }
321 return true;
322 }
323
324 m_currentIndex = findAction(pos: event->pos());
325 QAction *action = safeActionAt(index: m_currentIndex);
326
327 QRect pm_rect;
328 if (action->menu() || hasSubMenuPixmap(action)) {
329 pm_rect = subMenuPixmapRect(action);
330 extendClickableArea(subMenuRect: &pm_rect, dir: layoutDirection());
331 }
332
333 if (!pm_rect.contains(p: event->pos()) && m_currentIndex != -1)
334 enterEditMode();
335
336 return true;
337}
338
339bool QDesignerMenu::handleMousePressEvent(QWidget * /*widget*/, QMouseEvent *event)
340{
341 if (!rect().contains(p: event->pos())) {
342 QWidget *clickedWidget = QApplication::widgetAt(p: event->globalPos());
343 if (QMenuBar *mb = qobject_cast<QMenuBar*>(object: clickedWidget)) {
344 const QPoint pt = mb->mapFromGlobal(event->globalPos());
345 if (QAction *action = mb->actionAt(pt)) {
346 QMenu * menu = action->menu();
347 if (menu == findRootMenu()) {
348 // propagate the mouse press event (but don't close the popup)
349 sendMouseEventTo(target: mb, targetPoint: pt, event);
350 return true;
351 }
352 }
353 }
354
355 if (QDesignerMenu *m = qobject_cast<QDesignerMenu *>(object: clickedWidget)) {
356 m->hideSubMenu();
357 sendMouseEventTo(target: m, targetPoint: m->mapFromGlobal(event->globalPos()), event);
358 } else {
359 QDesignerMenu *root = findRootMenu();
360 root->hide();
361 root->hideSubMenu();
362 }
363 if (clickedWidget) {
364 if (QWidget *focusProxy = clickedWidget->focusProxy())
365 clickedWidget = focusProxy;
366 if (clickedWidget->focusPolicy() != Qt::NoFocus)
367 clickedWidget->setFocus(Qt::OtherFocusReason);
368 }
369 return true;
370 }
371
372 m_showSubMenuTimer->stop();
373 m_startPosition = QPoint();
374 event->accept();
375
376 if (event->button() != Qt::LeftButton)
377 return true;
378
379 m_startPosition = mapFromGlobal(event->globalPos());
380
381 const int index = findAction(pos: m_startPosition);
382
383 QAction *action = safeActionAt(index);
384 QRect pm_rect = subMenuPixmapRect(action);
385 extendClickableArea(subMenuRect: &pm_rect, dir: layoutDirection());
386
387 const int old_index = m_currentIndex;
388 m_currentIndex = index;
389 if ((hasSubMenuPixmap(action) || action->menu() != nullptr)
390 && pm_rect.contains(p: m_startPosition)) {
391 if (m_currentIndex == m_lastSubMenuIndex) {
392 hideSubMenu();
393 } else
394 slotShowSubMenuNow();
395 } else {
396 if (index == old_index) {
397 if (m_currentIndex == m_lastSubMenuIndex)
398 hideSubMenu();
399 } else {
400 hideSubMenu();
401 }
402 }
403
404 update();
405 if (index != old_index)
406 selectCurrentAction();
407
408 return true;
409}
410
411bool QDesignerMenu::handleMouseReleaseEvent(QWidget *, QMouseEvent *event)
412{
413 event->accept();
414 m_startPosition = QPoint();
415
416 return true;
417}
418
419bool QDesignerMenu::handleMouseMoveEvent(QWidget *, QMouseEvent *event)
420{
421 if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton)
422 return true;
423
424 if (!rect().contains(p: event->pos())) {
425
426 if (QMenuBar *mb = qobject_cast<QMenuBar*>(object: QApplication::widgetAt(p: event->globalPos()))) {
427 const QPoint pt = mb->mapFromGlobal(event->globalPos());
428 QAction *action = mb->actionAt(pt);
429 if (action && action->menu() == findRootMenu()) {
430 // propagate the mouse press event (but don't close the popup)
431 sendMouseEventTo(target: mb, targetPoint: pt, event);
432 return true;
433 }
434 // hide the popup Qt will replay the event
435 slotDeactivateNow();
436 }
437 return true;
438 }
439
440 if (m_startPosition.isNull())
441 return true;
442
443 event->accept();
444
445 const QPoint pos = mapFromGlobal(event->globalPos());
446
447 if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance())
448 return true;
449
450 startDrag(pos: m_startPosition, modifiers: event->modifiers());
451 m_startPosition = QPoint();
452
453 return true;
454}
455
456bool QDesignerMenu::handleContextMenuEvent(QWidget *, QContextMenuEvent *event)
457{
458 event->accept();
459
460 const int index = findAction(pos: mapFromGlobal(event->globalPos()));
461 QAction *action = safeActionAt(index);
462 if (qobject_cast<SpecialMenuAction*>(object: action))
463 return true;
464
465 QMenu menu;
466 QVariant itemData;
467 itemData.setValue(action);
468
469 QAction *addSeparatorAction = menu.addAction(text: tr(s: "Insert separator"));
470 addSeparatorAction->setData(itemData);
471
472 QAction *removeAction = nullptr;
473 if (action->isSeparator())
474 removeAction = menu.addAction(text: tr(s: "Remove separator"));
475 else
476 removeAction = menu.addAction(text: tr(s: "Remove action '%1'").arg(a: action->objectName()));
477 removeAction->setData(itemData);
478
479 connect(sender: addSeparatorAction, signal: &QAction::triggered, receiver: this, slot: &QDesignerMenu::slotAddSeparator);
480 connect(sender: removeAction, signal: &QAction::triggered, receiver: this, slot: &QDesignerMenu::slotRemoveSelectedAction);
481 menu.exec(pos: event->globalPos());
482
483 return true;
484}
485
486void QDesignerMenu::slotAddSeparator()
487{
488 QAction *action = qobject_cast<QAction *>(object: sender());
489 if (!action)
490 return;
491
492 QAction *a = qvariant_cast<QAction*>(v: action->data());
493 Q_ASSERT(a != nullptr);
494
495 const int pos = actions().indexOf(t: a);
496 QAction *action_before = nullptr;
497 if (pos != -1)
498 action_before = safeActionAt(index: pos);
499
500 QDesignerFormWindowInterface *fw = formWindow();
501 fw->beginCommand(description: tr(s: "Add separator"));
502 QAction *sep = createAction(text: QString(), separator: true);
503
504 InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
505 cmd->init(parentWidget: this, action: sep, beforeAction: action_before);
506 fw->commandHistory()->push(cmd);
507
508 if (parentMenu()) {
509 QAction *parent_action = parentMenu()->currentAction();
510 if (parent_action->menu() == nullptr) {
511 CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw);
512 cmd->init(menu: parentMenu(), action: parentMenu()->currentAction());
513 fw->commandHistory()->push(cmd);
514 }
515 }
516
517 fw->endCommand();
518}
519
520void QDesignerMenu::slotRemoveSelectedAction()
521{
522 if (QAction *action = qobject_cast<QAction *>(object: sender()))
523 if (QAction *a = qvariant_cast<QAction*>(v: action->data()))
524 deleteAction(a);
525}
526
527void QDesignerMenu::deleteAction(QAction *a)
528{
529 const int pos = actions().indexOf(t: a);
530 QAction *action_before = nullptr;
531 if (pos != -1)
532 action_before = safeActionAt(index: pos + 1);
533
534 QDesignerFormWindowInterface *fw = formWindow();
535 RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
536 cmd->init(parentWidget: this, action: a, beforeAction: action_before);
537 fw->commandHistory()->push(cmd);
538}
539
540QRect QDesignerMenu::subMenuPixmapRect(QAction *action) const
541{
542 const QRect g = actionGeometry(action);
543 const int x = layoutDirection() == Qt::LeftToRight ? (g.right() - m_subMenuPixmap.width() - 2) : 2;
544 const int y = g.top() + (g.height() - m_subMenuPixmap.height())/2 + 1;
545 return QRect(x, y, m_subMenuPixmap.width(), m_subMenuPixmap.height());
546}
547
548bool QDesignerMenu::hasSubMenuPixmap(QAction *action) const
549{
550 return action != nullptr
551 && qobject_cast<SpecialMenuAction*>(object: action) == 0
552 && !action->isSeparator()
553 && !action->menu()
554 && canCreateSubMenu(action);
555}
556
557void QDesignerMenu::showEvent ( QShowEvent * event )
558{
559 selectCurrentAction();
560 QMenu::showEvent (event);
561}
562
563void QDesignerMenu::paintEvent(QPaintEvent *event)
564{
565 QMenu::paintEvent(event);
566
567 QPainter p(this);
568
569 QAction *current = currentAction();
570
571 const auto &actionList = actions();
572 for (QAction *a : actionList) {
573 const QRect g = actionGeometry(a);
574
575 if (qobject_cast<SpecialMenuAction*>(object: a)) {
576 QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom());
577 lg.setColorAt(pos: 0.0, color: Qt::transparent);
578 lg.setColorAt(pos: 0.7, color: QColor(0, 0, 0, 32));
579 lg.setColorAt(pos: 1.0, color: Qt::transparent);
580
581 p.fillRect(g, lg);
582 } else if (hasSubMenuPixmap(action: a)) {
583 p.drawPixmap(p: subMenuPixmapRect(action: a).topLeft(), pm: m_subMenuPixmap);
584 }
585 }
586
587 if (!hasFocus() || !current || m_dragging)
588 return;
589
590 if (QDesignerMenu *menu = parentMenu()) {
591 if (menu->dragging())
592 return;
593 }
594
595 if (QDesignerMenuBar *menubar = qobject_cast<QDesignerMenuBar*>(object: parentWidget())) {
596 if (menubar->dragging())
597 return;
598 }
599
600 const QRect g = actionGeometry(current);
601 drawSelection(p: &p, r: g.adjusted(xp1: 1, yp1: 1, xp2: -3, yp2: -3));
602}
603
604bool QDesignerMenu::dragging() const
605{
606 return m_dragging;
607}
608
609QDesignerMenu *QDesignerMenu::findRootMenu() const
610{
611 if (parentMenu())
612 return parentMenu()->findRootMenu();
613
614 return const_cast<QDesignerMenu*>(this);
615}
616
617QDesignerMenu *QDesignerMenu::findActivatedMenu() const
618{
619 if (QDesignerMenu *activeDesignerMenu = qobject_cast<QDesignerMenu *>(object: QApplication::activeWindow())) {
620 if (activeDesignerMenu == this || findChildren<QDesignerMenu *>().contains(t: activeDesignerMenu))
621 return activeDesignerMenu;
622 }
623
624 return nullptr;
625}
626
627bool QDesignerMenu::eventFilter(QObject *object, QEvent *event)
628{
629 if (object != this && object != m_editor) {
630 return false;
631 }
632
633 if (!m_editor->isHidden() && object == m_editor && event->type() == QEvent::FocusOut) {
634 leaveEditMode(mode: Default);
635 m_editor->hide();
636 update();
637 return false;
638 }
639
640 bool dispatch = true;
641
642 switch (event->type()) {
643 default: break;
644
645 case QEvent::WindowDeactivate:
646 deactivateMenu();
647 break;
648 case QEvent::ContextMenu:
649 case QEvent::MouseButtonPress:
650 case QEvent::MouseButtonRelease:
651 case QEvent::MouseButtonDblClick:
652
653 while (QApplication::activePopupWidget() && !qobject_cast<QDesignerMenu*>(object: QApplication::activePopupWidget())) {
654 QApplication::activePopupWidget()->close();
655 }
656
657 Q_FALLTHROUGH(); // fall through
658 case QEvent::KeyPress:
659 case QEvent::KeyRelease:
660 case QEvent::MouseMove:
661 dispatch = (object != m_editor);
662 Q_FALLTHROUGH(); // no break
663
664 case QEvent::Enter:
665 case QEvent::Leave:
666 case QEvent::FocusIn:
667 case QEvent::FocusOut:
668 if (dispatch)
669 if (QWidget *widget = qobject_cast<QWidget*>(o: object))
670 if (widget == this || isAncestorOf(child: widget))
671 return handleEvent(widget, event);
672 break;
673 }
674
675 return false;
676};
677
678int QDesignerMenu::findAction(const QPoint &pos) const
679{
680 const int index = actionIndexAt(w: this, pos, orientation: Qt::Vertical);
681 if (index == -1)
682 return realActionCount();
683
684 return index;
685}
686
687void QDesignerMenu::adjustIndicator(const QPoint &pos)
688{
689 if (QDesignerActionProviderExtension *a = actionProvider()) {
690 a->adjustIndicator(pos);
691 }
692}
693
694QDesignerMenu::ActionDragCheck QDesignerMenu::checkAction(QAction *action) const
695{
696 if (!action || (action->menu() && action->menu()->parentWidget() != const_cast<QDesignerMenu*>(this)))
697 return NoActionDrag; // menu action!! nothing to do
698
699 if (!Utils::isObjectAncestorOf(ancestor: formWindow()->mainContainer(), child: action))
700 return NoActionDrag; // the action belongs to another form window
701
702 if (actions().contains(t: action))
703 return ActionDragOnSubMenu; // we already have the action in the menu
704
705 return AcceptActionDrag;
706}
707
708void QDesignerMenu::dragEnterEvent(QDragEnterEvent *event)
709{
710 const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(object: event->mimeData());
711 if (!d || d->actionList().isEmpty()) {
712 event->ignore();
713 return;
714 }
715
716 QAction *action = d->actionList().first();
717
718 switch (checkAction(action)) {
719 case NoActionDrag:
720 event->ignore();
721 break;
722 case ActionDragOnSubMenu:
723 d->accept(event);
724 m_dragging = true;
725 break;
726 case AcceptActionDrag:
727 d->accept(event);
728 m_dragging = true;
729 adjustIndicator(pos: event->pos());
730 break;
731 }
732}
733
734void QDesignerMenu::dragMoveEvent(QDragMoveEvent *event)
735{
736 if (actionGeometry(m_addSeparator).contains(p: event->pos())) {
737 event->ignore();
738 adjustIndicator(pos: QPoint(-1, -1));
739 return;
740 }
741
742 const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(object: event->mimeData());
743 if (!d || d->actionList().isEmpty()) {
744 event->ignore();
745 return;
746 }
747
748 QAction *action = d->actionList().first();
749 const ActionDragCheck dc = checkAction(action);
750 switch (dc) {
751 case NoActionDrag:
752 event->ignore();
753 break;
754 case ActionDragOnSubMenu:
755 case AcceptActionDrag: { // Do not pop up submenu of action being dragged
756 const int newIndex = findAction(pos: event->pos());
757 if (safeActionAt(index: newIndex) != action) {
758 m_currentIndex = newIndex;
759 if (m_lastSubMenuIndex != m_currentIndex)
760 m_showSubMenuTimer->start(msec: 300);
761 }
762 if (dc == AcceptActionDrag) {
763 adjustIndicator(pos: event->pos());
764 d->accept(event);
765 } else {
766 event->ignore();
767 }
768 }
769 break;
770 }
771}
772
773void QDesignerMenu::dragLeaveEvent(QDragLeaveEvent *)
774{
775 m_dragging = false;
776 adjustIndicator(pos: QPoint(-1, -1));
777 m_showSubMenuTimer->stop();
778}
779
780void QDesignerMenu::dropEvent(QDropEvent *event)
781{
782 m_showSubMenuTimer->stop();
783 hideSubMenu();
784 m_dragging = false;
785
786 QDesignerFormWindowInterface *fw = formWindow();
787 const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(object: event->mimeData());
788 if (!d || d->actionList().isEmpty()) {
789 event->ignore();
790 return;
791 }
792 QAction *action = d->actionList().first();
793 if (action && checkAction(action) == AcceptActionDrag) {
794 event->acceptProposedAction();
795 int index = findAction(pos: event->pos());
796 index = qMin(a: index, b: actions().count() - 1);
797
798 fw->beginCommand(description: tr(s: "Insert action"));
799 InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
800 cmd->init(parentWidget: this, action, beforeAction: safeActionAt(index));
801 fw->commandHistory()->push(cmd);
802
803 m_currentIndex = index;
804
805 if (parentMenu()) {
806 QAction *parent_action = parentMenu()->currentAction();
807 if (parent_action->menu() == nullptr) {
808 CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw);
809 cmd->init(menu: parentMenu(), action: parentMenu()->currentAction(), m_objectToSelect: action);
810 fw->commandHistory()->push(cmd);
811 }
812 }
813 update();
814 fw->endCommand();
815 } else {
816 event->ignore();
817 }
818 adjustIndicator(pos: QPoint(-1, -1));
819}
820
821void QDesignerMenu::actionEvent(QActionEvent *event)
822{
823 QMenu::actionEvent(event);
824 m_adjustSizeTimer->start(msec: 0);
825}
826
827QDesignerFormWindowInterface *QDesignerMenu::formWindow() const
828{
829 if (parentMenu())
830 return parentMenu()->formWindow();
831
832 return QDesignerFormWindowInterface::findFormWindow(w: parentWidget());
833}
834
835QDesignerActionProviderExtension *QDesignerMenu::actionProvider()
836{
837 if (QDesignerFormWindowInterface *fw = formWindow()) {
838 QDesignerFormEditorInterface *core = fw->core();
839 return qt_extension<QDesignerActionProviderExtension*>(manager: core->extensionManager(), object: this);
840 }
841
842 return nullptr;
843}
844
845void QDesignerMenu::closeMenuChain()
846{
847 m_showSubMenuTimer->stop();
848
849 QWidget *w = this;
850 while (w && qobject_cast<QMenu*>(object: w))
851 w = w->parentWidget();
852
853 if (w) {
854 const auto &menus = w->findChildren<QMenu *>();
855 for (QMenu *subMenu : menus)
856 subMenu->hide();
857 }
858
859 m_lastSubMenuIndex = -1;
860}
861
862// Close submenu using the left/right keys according to layoutDirection().
863// Return false to indicate the event must be propagated to the menu bar.
864bool QDesignerMenu::hideSubMenuOnCursorKey()
865{
866 if (parentMenu()) {
867 hide();
868 return true;
869 }
870 closeMenuChain();
871 update();
872 return parentMenuBar() == nullptr;
873}
874
875// Open a submenu using the left/right keys according to layoutDirection().
876// Return false to indicate the event must be propagated to the menu bar.
877bool QDesignerMenu::showSubMenuOnCursorKey()
878{
879 const QAction *action = currentAction();
880
881 if (qobject_cast<const SpecialMenuAction*>(object: action) || action->isSeparator()) {
882 closeMenuChain();
883 if (parentMenuBar())
884 return false;
885 return true;
886 }
887 m_lastSubMenuIndex = -1; // force a refresh
888 slotShowSubMenuNow();
889 return true;
890}
891
892void QDesignerMenu::moveLeft()
893{
894 const bool handled = layoutDirection() == Qt::LeftToRight ?
895 hideSubMenuOnCursorKey() : showSubMenuOnCursorKey();
896 if (!handled)
897 parentMenuBar()->moveLeft();
898}
899
900void QDesignerMenu::moveRight()
901{
902 const bool handled = layoutDirection() == Qt::LeftToRight ?
903 showSubMenuOnCursorKey() : hideSubMenuOnCursorKey();
904 if (!handled)
905 parentMenuBar()->moveRight();
906}
907
908void QDesignerMenu::moveUp(bool ctrl)
909{
910 if (m_currentIndex == 0) {
911 hide();
912 return;
913 }
914
915 if (ctrl)
916 (void) swap(a: m_currentIndex, b: m_currentIndex - 1);
917 --m_currentIndex;
918 m_currentIndex = qMax(a: 0, b: m_currentIndex);
919 // Always re-select, swapping destroys order
920 update();
921 selectCurrentAction();
922}
923
924void QDesignerMenu::moveDown(bool ctrl)
925{
926 if (m_currentIndex == actions().count() - 1) {
927 return;
928 }
929
930 if (ctrl)
931 (void) swap(a: m_currentIndex + 1, b: m_currentIndex);
932
933 ++m_currentIndex;
934 m_currentIndex = qMin(a: actions().count() - 1, b: m_currentIndex);
935 update();
936 if (!ctrl)
937 selectCurrentAction();
938}
939
940QAction *QDesignerMenu::currentAction() const
941{
942 if (m_currentIndex < 0 || m_currentIndex >= actions().count())
943 return nullptr;
944
945 return safeActionAt(index: m_currentIndex);
946}
947
948int QDesignerMenu::realActionCount() const
949{
950 return actions().count() - 2; // 2 fake actions
951}
952
953void QDesignerMenu::selectCurrentAction()
954{
955 QAction *action = currentAction();
956 if (!action || action == m_addSeparator || action == m_addItem)
957 return;
958
959 QDesignerObjectInspector *oi = nullptr;
960 if (QDesignerFormWindowInterface *fw = formWindow())
961 oi = qobject_cast<QDesignerObjectInspector *>(object: fw->core()->objectInspector());
962
963 if (!oi)
964 return;
965
966 oi->clearSelection();
967 if (QMenu *menu = action->menu())
968 oi->selectObject(o: menu);
969 else
970 oi->selectObject(o: action);
971}
972
973void QDesignerMenu::createRealMenuAction(QAction *action)
974{
975 if (action->menu())
976 return; // nothing to do
977
978 QDesignerFormWindowInterface *fw = formWindow();
979 QDesignerFormEditorInterface *core = formWindow()->core();
980
981 QDesignerMenu *menu = findOrCreateSubMenu(action);
982 m_subMenus.remove(akey: action);
983
984 action->setMenu(menu);
985 menu->setTitle(action->text());
986
987 Q_ASSERT(fw);
988
989 core->widgetFactory()->initialize(object: menu);
990
991 const QString niceObjectName = ActionEditor::actionTextToName(text: menu->title(), QStringLiteral("menu"));
992 menu->setObjectName(niceObjectName);
993
994 core->metaDataBase()->add(object: menu);
995 fw->ensureUniqueObjectName(object: menu);
996
997 QAction *menuAction = menu->menuAction();
998 core->metaDataBase()->add(object: menuAction);
999}
1000
1001void QDesignerMenu::removeRealMenu(QAction *action)
1002{
1003 QDesignerMenu *menu = qobject_cast<QDesignerMenu*>(object: action->menu());
1004 if (menu == nullptr)
1005 return;
1006 action->setMenu(nullptr);
1007 m_subMenus.insert(akey: action, avalue: menu);
1008 QDesignerFormEditorInterface *core = formWindow()->core();
1009 core->metaDataBase()->remove(object: menu);
1010}
1011
1012QDesignerMenu *QDesignerMenu::findOrCreateSubMenu(QAction *action)
1013{
1014 if (action->menu())
1015 return qobject_cast<QDesignerMenu*>(object: action->menu());
1016
1017 QDesignerMenu *menu = m_subMenus.value(akey: action);
1018 if (!menu) {
1019 menu = new QDesignerMenu(this);
1020 m_subMenus.insert(akey: action, avalue: menu);
1021 }
1022
1023 return menu;
1024}
1025
1026bool QDesignerMenu::canCreateSubMenu(QAction *action) const // ### improve it's a bit too slow
1027{
1028 const QWidgetList &associatedWidgets = action->associatedWidgets();
1029 for (const QWidget *aw : associatedWidgets) {
1030 if (aw != this) {
1031 if (const QMenu *m = qobject_cast<const QMenu *>(object: aw)) {
1032 if (m->actions().contains(t: action))
1033 return false; // sorry
1034 } else {
1035 if (const QToolBar *tb = qobject_cast<const QToolBar *>(object: aw))
1036 if (tb->actions().contains(t: action))
1037 return false; // sorry
1038 }
1039 }
1040 }
1041 return true;
1042}
1043
1044void QDesignerMenu::slotShowSubMenuNow()
1045{
1046 m_showSubMenuTimer->stop();
1047
1048 if (m_lastSubMenuIndex == m_currentIndex)
1049 return;
1050
1051 if (m_lastSubMenuIndex != -1)
1052 hideSubMenu();
1053
1054 if (m_currentIndex >= realActionCount())
1055 return;
1056
1057 QAction *action = currentAction();
1058
1059 if (action->isSeparator() || !canCreateSubMenu(action))
1060 return;
1061
1062 if (QMenu *menu = findOrCreateSubMenu(action)) {
1063 if (!menu->isVisible()) {
1064 if ((menu->windowFlags() & Qt::Popup) != Qt::Popup)
1065 menu->setWindowFlags(Qt::Popup);
1066 const QRect g = actionGeometry(action);
1067 if (layoutDirection() == Qt::LeftToRight) {
1068 menu->move(mapToGlobal(g.topRight()));
1069 } else {
1070 // The position is not initially correct due to the unknown width,
1071 // causing it to overlap a bit the first time it is invoked.
1072 QPoint point = g.topLeft() - QPoint(menu->width() + 10, 0);
1073 menu->move(mapToGlobal(point));
1074 }
1075 menu->show();
1076 menu->setFocus();
1077 } else {
1078 menu->raise();
1079 }
1080 menu->setFocus();
1081
1082 m_lastSubMenuIndex = m_currentIndex;
1083 }
1084}
1085
1086void QDesignerMenu::showSubMenu(QAction *action)
1087{
1088 m_showSubMenuTimer->stop();
1089
1090 if (m_editor->isVisible() || !action || qobject_cast<SpecialMenuAction*>(object: action)
1091 || action->isSeparator() || !isVisible())
1092 return;
1093
1094 m_showSubMenuTimer->start(msec: 300);
1095}
1096
1097QDesignerMenu *QDesignerMenu::parentMenu() const
1098{
1099 return qobject_cast<QDesignerMenu*>(object: parentWidget());
1100}
1101
1102QDesignerMenuBar *QDesignerMenu::parentMenuBar() const
1103{
1104 if (QDesignerMenuBar *mb = qobject_cast<QDesignerMenuBar*>(object: parentWidget()))
1105 return mb;
1106 if (QDesignerMenu *m = parentMenu())
1107 return m->parentMenuBar();
1108
1109 return nullptr;
1110}
1111
1112void QDesignerMenu::setVisible(bool visible)
1113{
1114 if (visible)
1115 m_currentIndex = 0;
1116 else
1117 m_lastSubMenuIndex = -1;
1118
1119 QMenu::setVisible(visible);
1120
1121}
1122
1123void QDesignerMenu::adjustSpecialActions()
1124{
1125 removeAction(action: m_addItem);
1126 removeAction(action: m_addSeparator);
1127 addAction(action: m_addItem);
1128 addAction(action: m_addSeparator);
1129}
1130
1131void QDesignerMenu::enterEditMode()
1132{
1133 if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) {
1134 showLineEdit();
1135 } else {
1136 hideSubMenu();
1137 QDesignerFormWindowInterface *fw = formWindow();
1138 fw->beginCommand(description: tr(s: "Add separator"));
1139 QAction *sep = createAction(text: QString(), separator: true);
1140
1141 InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
1142 cmd->init(parentWidget: this, action: sep, beforeAction: safeActionAt(index: realActionCount()));
1143 fw->commandHistory()->push(cmd);
1144
1145 if (parentMenu()) {
1146 QAction *parent_action = parentMenu()->currentAction();
1147 if (parent_action->menu() == nullptr) {
1148 CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw);
1149 cmd->init(menu: parentMenu(), action: parentMenu()->currentAction());
1150 fw->commandHistory()->push(cmd);
1151 }
1152 }
1153
1154 fw->endCommand();
1155
1156 m_currentIndex = actions().indexOf(t: m_addItem);
1157 update();
1158 }
1159}
1160
1161void QDesignerMenu::leaveEditMode(LeaveEditMode mode)
1162{
1163 if (mode == Default)
1164 return;
1165
1166 QAction *action = nullptr;
1167
1168 QDesignerFormWindowInterface *fw = formWindow();
1169 if (m_currentIndex < realActionCount()) {
1170 action = safeActionAt(index: m_currentIndex);
1171 fw->beginCommand(description: QApplication::translate(context: "Command", key: "Set action text"));
1172 } else {
1173 Q_ASSERT(fw != nullptr);
1174 fw->beginCommand(description: QApplication::translate(context: "Command", key: "Insert action"));
1175 action = createAction(text: ActionEditor::actionTextToName(text: m_editor->text()));
1176 InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
1177 cmd->init(parentWidget: this, action, beforeAction: currentAction());
1178 fw->commandHistory()->push(cmd);
1179 }
1180
1181 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
1182 cmd->init(object: action, QStringLiteral("text"), newValue: m_editor->text());
1183 fw->commandHistory()->push(cmd);
1184
1185 if (parentMenu()) {
1186 QAction *parent_action = parentMenu()->currentAction();
1187 if (parent_action->menu() == nullptr) {
1188 CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw);
1189 cmd->init(menu: parentMenu(), action: parentMenu()->currentAction(), m_objectToSelect: action);
1190 fw->commandHistory()->push(cmd);
1191 }
1192 }
1193
1194 update();
1195 fw->endCommand();
1196}
1197
1198QAction *QDesignerMenu::safeMenuAction(QDesignerMenu *menu) const
1199{
1200 QAction *action = menu->menuAction();
1201
1202 if (!action)
1203 action = m_subMenus.key(avalue: menu);
1204
1205 return action;
1206}
1207
1208void QDesignerMenu::showLineEdit()
1209{
1210 m_showSubMenuTimer->stop();
1211
1212 QAction *action = nullptr;
1213
1214 if (m_currentIndex < realActionCount())
1215 action = safeActionAt(index: m_currentIndex);
1216 else
1217 action = m_addItem;
1218
1219 if (action->isSeparator())
1220 return;
1221
1222 hideSubMenu();
1223
1224 // open edit field for item name
1225 setFocus();
1226
1227 const QString text = action != m_addItem ? action->text() : QString();
1228 m_editor->setText(text);
1229 m_editor->selectAll();
1230 m_editor->setGeometry(actionGeometry(action).adjusted(xp1: 1, yp1: 1, xp2: -2, yp2: -2));
1231 m_editor->show();
1232 m_editor->setFocus();
1233}
1234
1235QAction *QDesignerMenu::createAction(const QString &objectName, bool separator)
1236{
1237 QDesignerFormWindowInterface *fw = formWindow();
1238 Q_ASSERT(fw);
1239 return ToolBarEventFilter::createAction(fw, objectName, separator);
1240}
1241
1242// ### share with QDesignerMenu::swap
1243bool QDesignerMenu::swap(int a, int b)
1244{
1245 const int left = qMin(a, b);
1246 int right = qMax(a, b);
1247
1248 QAction *action_a = safeActionAt(index: left);
1249 QAction *action_b = safeActionAt(index: right);
1250
1251 if (action_a == action_b
1252 || !action_a
1253 || !action_b
1254 || qobject_cast<SpecialMenuAction*>(object: action_a)
1255 || qobject_cast<SpecialMenuAction*>(object: action_b))
1256 return false; // nothing to do
1257
1258 right = qMin(a: right, b: realActionCount());
1259 if (right < 0)
1260 return false; // nothing to do
1261
1262 QDesignerFormWindowInterface *fw = formWindow();
1263 fw->beginCommand(description: QApplication::translate(context: "Command", key: "Move action"));
1264
1265 QAction *action_b_before = safeActionAt(index: right + 1);
1266
1267 RemoveActionFromCommand *cmd1 = new RemoveActionFromCommand(fw);
1268 cmd1->init(parentWidget: this, action: action_b, beforeAction: action_b_before, update: false);
1269 fw->commandHistory()->push(cmd: cmd1);
1270
1271 QAction *action_a_before = safeActionAt(index: left + 1);
1272
1273 InsertActionIntoCommand *cmd2 = new InsertActionIntoCommand(fw);
1274 cmd2->init(parentWidget: this, action: action_b, beforeAction: action_a_before, update: false);
1275 fw->commandHistory()->push(cmd: cmd2);
1276
1277 RemoveActionFromCommand *cmd3 = new RemoveActionFromCommand(fw);
1278 cmd3->init(parentWidget: this, action: action_a, beforeAction: action_b, update: false);
1279 fw->commandHistory()->push(cmd: cmd3);
1280
1281 InsertActionIntoCommand *cmd4 = new InsertActionIntoCommand(fw);
1282 cmd4->init(parentWidget: this, action: action_a, beforeAction: action_b_before, update: true);
1283 fw->commandHistory()->push(cmd: cmd4);
1284
1285 fw->endCommand();
1286
1287 return true;
1288}
1289
1290QAction *QDesignerMenu::safeActionAt(int index) const
1291{
1292 if (index < 0 || index >= actions().count())
1293 return nullptr;
1294
1295 return actions().at(i: index);
1296}
1297
1298void QDesignerMenu::hideSubMenu()
1299{
1300 m_lastSubMenuIndex = -1;
1301 const auto &menus = findChildren<QMenu *>();
1302 for (QMenu *subMenu : menus)
1303 subMenu->hide();
1304}
1305
1306void QDesignerMenu::deleteAction()
1307{
1308 QAction *action = currentAction();
1309 const int pos = actions().indexOf(t: action);
1310 QAction *action_before = nullptr;
1311 if (pos != -1)
1312 action_before = safeActionAt(index: pos + 1);
1313
1314 QDesignerFormWindowInterface *fw = formWindow();
1315 RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
1316 cmd->init(parentWidget: this, action, beforeAction: action_before);
1317 fw->commandHistory()->push(cmd);
1318
1319 update();
1320}
1321
1322void QDesignerMenu::deactivateMenu()
1323{
1324 m_deactivateWindowTimer->start(msec: 10);
1325}
1326
1327void QDesignerMenu::slotDeactivateNow()
1328{
1329 m_deactivateWindowTimer->stop();
1330
1331 if (m_dragging)
1332 return;
1333
1334 QDesignerMenu *root = findRootMenu();
1335
1336 if (! root->findActivatedMenu()) {
1337 root->hide();
1338 root->hideSubMenu();
1339 }
1340}
1341
1342void QDesignerMenu::drawSelection(QPainter *p, const QRect &r)
1343{
1344 p->save();
1345
1346 QColor c = Qt::blue;
1347 p->setPen(QPen(c, 1));
1348 c.setAlpha(32);
1349 p->setBrush(c);
1350 p->drawRect(r);
1351
1352 p->restore();
1353}
1354
1355void QDesignerMenu::keyPressEvent(QKeyEvent *event)
1356{
1357 event->ignore();
1358}
1359
1360void QDesignerMenu::keyReleaseEvent(QKeyEvent *event)
1361{
1362 event->ignore();
1363}
1364
1365QT_END_NAMESPACE
1366

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