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 
58 Q_DECLARE_METATYPE(QAction*)
59 
60 QT_BEGIN_NAMESPACE
61 
62 using namespace qdesigner_internal;
63 
64 // give the user a little more space to click on the sub menu rectangle
extendClickableArea(QRect * subMenuRect,Qt::LayoutDirection dir)65 static 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 
QDesignerMenu(QWidget * parent)78 QDesignerMenu::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(m_adjustSizeTimer, &QTimer::timeout, this, &QDesignerMenu::slotAdjustSizeNow);
96     m_addItem->setText(tr("Type Here"));
97     addAction(m_addItem);
98 
99     m_addSeparator->setText(tr("Add Separator"));
100     addAction(m_addSeparator);
101 
102     connect(m_showSubMenuTimer, &QTimer::timeout, this, &QDesignerMenu::slotShowSubMenuNow);
103 
104     connect(m_deactivateWindowTimer, &QTimer::timeout, this, &QDesignerMenu::slotDeactivateNow);
105 
106     m_editor->setObjectName(QStringLiteral("__qt__passive_editor"));
107     m_editor->hide();
108 
109     m_editor->installEventFilter(this);
110     installEventFilter(this);
111 }
112 
113 QDesignerMenu::~QDesignerMenu() = default;
114 
slotAdjustSizeNow()115 void 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 
handleEvent(QWidget * widget,QEvent * event)123 bool 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, static_cast<QMouseEvent*>(event));
137         case QEvent::MouseButtonRelease:
138             return handleMouseReleaseEvent(widget, static_cast<QMouseEvent*>(event));
139         case QEvent::MouseButtonDblClick:
140             return handleMouseDoubleClickEvent(widget, static_cast<QMouseEvent*>(event));
141         case QEvent::MouseMove:
142             return handleMouseMoveEvent(widget, static_cast<QMouseEvent*>(event));
143         case QEvent::ContextMenu:
144             return handleContextMenuEvent(widget, static_cast<QContextMenuEvent*>(event));
145         case QEvent::KeyPress:
146             return handleKeyPressEvent(widget, static_cast<QKeyEvent*>(event));
147     }
148 
149     return true;
150 }
151 
startDrag(const QPoint & pos,Qt::KeyboardModifiers modifiers)152 void 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(this, action, actions().at(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(dropAction) == Qt::IgnoreAction) {
176         if (dropAction == Qt::MoveAction) {
177             QAction *previous = safeActionAt(index);
178             InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
179             cmd->init(this, action, previous);
180             fw->commandHistory()->push(cmd);
181         }
182 
183         m_currentIndex = old_index;
184     }
185 }
186 
handleKeyPressEvent(QWidget *,QKeyEvent * e)187 bool 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(e->modifiers() & Qt::ControlModifier);
214             return true;
215 
216         case Qt::Key_Down:
217             e->accept();
218             moveDown(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(0).toLatin1() >= 32) {
257                 showLineEdit();
258                 QApplication::sendEvent(m_editor, 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(ForceAccept);
276                 m_editor->hide();
277                 setFocus();
278                 moveDown(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 
sendMouseEventTo(QWidget * target,const QPoint & targetPoint,const QMouseEvent * event)296 static 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(target, &e);
300 }
301 
handleMouseDoubleClickEvent(QWidget *,QMouseEvent * event)302 bool 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(event->pos())) {
311         // special case for menubar
312         QWidget *target = QApplication::widgetAt(event->globalPos());
313         QMenuBar *mb = qobject_cast<QMenuBar*>(target);
314         QDesignerMenu *menu = qobject_cast<QDesignerMenu*>(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, pt, event);
320         }
321         return true;
322     }
323 
324     m_currentIndex = findAction(event->pos());
325     QAction *action = safeActionAt(m_currentIndex);
326 
327     QRect pm_rect;
328     if (action->menu() || hasSubMenuPixmap(action)) {
329         pm_rect = subMenuPixmapRect(action);
330         extendClickableArea(&pm_rect, layoutDirection());
331     }
332 
333     if (!pm_rect.contains(event->pos()) && m_currentIndex != -1)
334         enterEditMode();
335 
336     return true;
337 }
338 
handleMousePressEvent(QWidget *,QMouseEvent * event)339 bool QDesignerMenu::handleMousePressEvent(QWidget * /*widget*/, QMouseEvent *event)
340 {
341     if (!rect().contains(event->pos())) {
342         QWidget *clickedWidget = QApplication::widgetAt(event->globalPos());
343         if (QMenuBar *mb = qobject_cast<QMenuBar*>(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(mb, pt, event);
350                     return true;
351                 }
352             }
353         }
354 
355         if (QDesignerMenu *m = qobject_cast<QDesignerMenu *>(clickedWidget)) {
356             m->hideSubMenu();
357             sendMouseEventTo(m, 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(m_startPosition);
382 
383     QAction *action = safeActionAt(index);
384     QRect pm_rect = subMenuPixmapRect(action);
385     extendClickableArea(&pm_rect, layoutDirection());
386 
387     const int old_index = m_currentIndex;
388     m_currentIndex = index;
389     if ((hasSubMenuPixmap(action) || action->menu() != nullptr)
390         && pm_rect.contains(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 
handleMouseReleaseEvent(QWidget *,QMouseEvent * event)411 bool QDesignerMenu::handleMouseReleaseEvent(QWidget *, QMouseEvent *event)
412 {
413     event->accept();
414     m_startPosition = QPoint();
415 
416     return true;
417 }
418 
handleMouseMoveEvent(QWidget *,QMouseEvent * event)419 bool QDesignerMenu::handleMouseMoveEvent(QWidget *, QMouseEvent *event)
420 {
421     if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton)
422         return true;
423 
424     if (!rect().contains(event->pos())) {
425 
426         if (QMenuBar *mb = qobject_cast<QMenuBar*>(QApplication::widgetAt(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(mb, 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(m_startPosition, event->modifiers());
451     m_startPosition = QPoint();
452 
453     return true;
454 }
455 
handleContextMenuEvent(QWidget *,QContextMenuEvent * event)456 bool QDesignerMenu::handleContextMenuEvent(QWidget *, QContextMenuEvent *event)
457 {
458     event->accept();
459 
460     const int index = findAction(mapFromGlobal(event->globalPos()));
461     QAction *action = safeActionAt(index);
462     if (qobject_cast<SpecialMenuAction*>(action))
463         return true;
464 
465     QMenu menu;
466     QVariant itemData;
467     itemData.setValue(action);
468 
469     QAction *addSeparatorAction = menu.addAction(tr("Insert separator"));
470     addSeparatorAction->setData(itemData);
471 
472     QAction *removeAction = nullptr;
473     if (action->isSeparator())
474         removeAction = menu.addAction(tr("Remove separator"));
475     else
476         removeAction = menu.addAction(tr("Remove action '%1'").arg(action->objectName()));
477     removeAction->setData(itemData);
478 
479     connect(addSeparatorAction, &QAction::triggered, this, &QDesignerMenu::slotAddSeparator);
480     connect(removeAction, &QAction::triggered, this, &QDesignerMenu::slotRemoveSelectedAction);
481     menu.exec(event->globalPos());
482 
483     return true;
484 }
485 
slotAddSeparator()486 void QDesignerMenu::slotAddSeparator()
487 {
488     QAction *action = qobject_cast<QAction *>(sender());
489     if (!action)
490         return;
491 
492     QAction *a = qvariant_cast<QAction*>(action->data());
493     Q_ASSERT(a != nullptr);
494 
495     const int pos = actions().indexOf(a);
496     QAction *action_before = nullptr;
497     if (pos != -1)
498         action_before = safeActionAt(pos);
499 
500     QDesignerFormWindowInterface *fw = formWindow();
501     fw->beginCommand(tr("Add separator"));
502     QAction *sep = createAction(QString(), true);
503 
504     InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
505     cmd->init(this, sep, 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(parentMenu(), parentMenu()->currentAction());
513             fw->commandHistory()->push(cmd);
514         }
515     }
516 
517     fw->endCommand();
518 }
519 
slotRemoveSelectedAction()520 void QDesignerMenu::slotRemoveSelectedAction()
521 {
522     if (QAction *action = qobject_cast<QAction *>(sender()))
523         if (QAction *a = qvariant_cast<QAction*>(action->data()))
524             deleteAction(a);
525 }
526 
deleteAction(QAction * a)527 void QDesignerMenu::deleteAction(QAction *a)
528 {
529     const int pos = actions().indexOf(a);
530     QAction *action_before = nullptr;
531     if (pos != -1)
532         action_before = safeActionAt(pos + 1);
533 
534     QDesignerFormWindowInterface *fw = formWindow();
535     RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
536     cmd->init(this, a, action_before);
537     fw->commandHistory()->push(cmd);
538 }
539 
subMenuPixmapRect(QAction * action) const540 QRect 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 
hasSubMenuPixmap(QAction * action) const548 bool QDesignerMenu::hasSubMenuPixmap(QAction *action) const
549 {
550     return action != nullptr
551             && qobject_cast<SpecialMenuAction*>(action) == 0
552             && !action->isSeparator()
553             && !action->menu()
554             && canCreateSubMenu(action);
555 }
556 
showEvent(QShowEvent * event)557 void QDesignerMenu::showEvent ( QShowEvent * event )
558 {
559     selectCurrentAction();
560     QMenu::showEvent (event);
561 }
562 
paintEvent(QPaintEvent * event)563 void 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*>(a)) {
576             QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom());
577             lg.setColorAt(0.0, Qt::transparent);
578             lg.setColorAt(0.7, QColor(0, 0, 0, 32));
579             lg.setColorAt(1.0, Qt::transparent);
580 
581             p.fillRect(g, lg);
582         } else if (hasSubMenuPixmap(a)) {
583             p.drawPixmap(subMenuPixmapRect(a).topLeft(), 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*>(parentWidget())) {
596         if (menubar->dragging())
597             return;
598     }
599 
600     const QRect g = actionGeometry(current);
601     drawSelection(&p, g.adjusted(1, 1, -3, -3));
602 }
603 
dragging() const604 bool QDesignerMenu::dragging() const
605 {
606     return m_dragging;
607 }
608 
findRootMenu() const609 QDesignerMenu *QDesignerMenu::findRootMenu() const
610 {
611     if (parentMenu())
612         return parentMenu()->findRootMenu();
613 
614     return const_cast<QDesignerMenu*>(this);
615 }
616 
findActivatedMenu() const617 QDesignerMenu *QDesignerMenu::findActivatedMenu() const
618 {
619     if (QDesignerMenu *activeDesignerMenu = qobject_cast<QDesignerMenu *>(QApplication::activeWindow())) {
620         if (activeDesignerMenu == this || findChildren<QDesignerMenu *>().contains(activeDesignerMenu))
621             return activeDesignerMenu;
622     }
623 
624     return nullptr;
625 }
626 
eventFilter(QObject * object,QEvent * event)627 bool 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(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*>(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*>(object))
670                 if (widget == this || isAncestorOf(widget))
671                     return handleEvent(widget, event);
672         break;
673     }
674 
675     return false;
676 };
677 
findAction(const QPoint & pos) const678 int QDesignerMenu::findAction(const QPoint &pos) const
679 {
680     const int index = actionIndexAt(this, pos, Qt::Vertical);
681     if (index == -1)
682         return realActionCount();
683 
684     return index;
685 }
686 
adjustIndicator(const QPoint & pos)687 void QDesignerMenu::adjustIndicator(const QPoint &pos)
688 {
689     if (QDesignerActionProviderExtension *a = actionProvider()) {
690         a->adjustIndicator(pos);
691     }
692 }
693 
checkAction(QAction * action) const694 QDesignerMenu::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(formWindow()->mainContainer(), action))
700         return NoActionDrag; // the action belongs to another form window
701 
702     if (actions().contains(action))
703         return ActionDragOnSubMenu; // we already have the action in the menu
704 
705     return AcceptActionDrag;
706 }
707 
dragEnterEvent(QDragEnterEvent * event)708 void QDesignerMenu::dragEnterEvent(QDragEnterEvent *event)
709 {
710     const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(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(event->pos());
730         break;
731     }
732 }
733 
dragMoveEvent(QDragMoveEvent * event)734 void QDesignerMenu::dragMoveEvent(QDragMoveEvent *event)
735 {
736     if (actionGeometry(m_addSeparator).contains(event->pos())) {
737         event->ignore();
738         adjustIndicator(QPoint(-1, -1));
739         return;
740     }
741 
742     const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(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(event->pos());
757         if (safeActionAt(newIndex) != action) {
758             m_currentIndex = newIndex;
759             if (m_lastSubMenuIndex != m_currentIndex)
760                 m_showSubMenuTimer->start(300);
761         }
762         if (dc == AcceptActionDrag) {
763             adjustIndicator(event->pos());
764             d->accept(event);
765         } else {
766             event->ignore();
767         }
768     }
769         break;
770     }
771 }
772 
dragLeaveEvent(QDragLeaveEvent *)773 void QDesignerMenu::dragLeaveEvent(QDragLeaveEvent *)
774 {
775     m_dragging = false;
776     adjustIndicator(QPoint(-1, -1));
777     m_showSubMenuTimer->stop();
778 }
779 
dropEvent(QDropEvent * event)780 void 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*>(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(event->pos());
796         index = qMin(index, actions().count() - 1);
797 
798         fw->beginCommand(tr("Insert action"));
799         InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
800         cmd->init(this, action, 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(parentMenu(), parentMenu()->currentAction(), action);
810                 fw->commandHistory()->push(cmd);
811             }
812         }
813         update();
814         fw->endCommand();
815     } else {
816         event->ignore();
817     }
818     adjustIndicator(QPoint(-1, -1));
819 }
820 
actionEvent(QActionEvent * event)821 void QDesignerMenu::actionEvent(QActionEvent *event)
822 {
823     QMenu::actionEvent(event);
824     m_adjustSizeTimer->start(0);
825 }
826 
formWindow() const827 QDesignerFormWindowInterface *QDesignerMenu::formWindow() const
828 {
829     if (parentMenu())
830         return parentMenu()->formWindow();
831 
832     return QDesignerFormWindowInterface::findFormWindow(parentWidget());
833 }
834 
actionProvider()835 QDesignerActionProviderExtension *QDesignerMenu::actionProvider()
836 {
837     if (QDesignerFormWindowInterface *fw = formWindow()) {
838         QDesignerFormEditorInterface *core = fw->core();
839         return qt_extension<QDesignerActionProviderExtension*>(core->extensionManager(), this);
840     }
841 
842     return nullptr;
843 }
844 
closeMenuChain()845 void QDesignerMenu::closeMenuChain()
846 {
847     m_showSubMenuTimer->stop();
848 
849     QWidget *w = this;
850     while (w && qobject_cast<QMenu*>(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.
hideSubMenuOnCursorKey()864 bool 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.
showSubMenuOnCursorKey()877 bool QDesignerMenu::showSubMenuOnCursorKey()
878 {
879     const QAction *action = currentAction();
880 
881     if (qobject_cast<const SpecialMenuAction*>(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 
moveLeft()892 void QDesignerMenu::moveLeft()
893 {
894     const bool handled = layoutDirection() == Qt::LeftToRight ?
895                          hideSubMenuOnCursorKey() : showSubMenuOnCursorKey();
896     if (!handled)
897         parentMenuBar()->moveLeft();
898 }
899 
moveRight()900 void QDesignerMenu::moveRight()
901 {
902     const bool handled = layoutDirection() == Qt::LeftToRight ?
903                          showSubMenuOnCursorKey() : hideSubMenuOnCursorKey();
904     if (!handled)
905         parentMenuBar()->moveRight();
906 }
907 
moveUp(bool ctrl)908 void QDesignerMenu::moveUp(bool ctrl)
909 {
910     if (m_currentIndex == 0) {
911         hide();
912         return;
913     }
914 
915     if (ctrl)
916         (void) swap(m_currentIndex, m_currentIndex - 1);
917     --m_currentIndex;
918     m_currentIndex = qMax(0, m_currentIndex);
919     // Always re-select, swapping destroys order
920     update();
921     selectCurrentAction();
922 }
923 
moveDown(bool ctrl)924 void QDesignerMenu::moveDown(bool ctrl)
925 {
926     if (m_currentIndex == actions().count() - 1) {
927         return;
928     }
929 
930     if (ctrl)
931         (void) swap(m_currentIndex + 1, m_currentIndex);
932 
933     ++m_currentIndex;
934     m_currentIndex = qMin(actions().count() - 1, m_currentIndex);
935     update();
936     if (!ctrl)
937         selectCurrentAction();
938 }
939 
currentAction() const940 QAction *QDesignerMenu::currentAction() const
941 {
942     if (m_currentIndex < 0 || m_currentIndex >= actions().count())
943         return nullptr;
944 
945     return safeActionAt(m_currentIndex);
946 }
947 
realActionCount() const948 int QDesignerMenu::realActionCount() const
949 {
950     return actions().count() - 2; // 2 fake actions
951 }
952 
selectCurrentAction()953 void 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 *>(fw->core()->objectInspector());
962 
963     if (!oi)
964         return;
965 
966     oi->clearSelection();
967     if (QMenu *menu = action->menu())
968         oi->selectObject(menu);
969     else
970         oi->selectObject(action);
971 }
972 
createRealMenuAction(QAction * action)973 void 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(action);
983 
984     action->setMenu(menu);
985     menu->setTitle(action->text());
986 
987     Q_ASSERT(fw);
988 
989     core->widgetFactory()->initialize(menu);
990 
991     const QString niceObjectName = ActionEditor::actionTextToName(menu->title(), QStringLiteral("menu"));
992     menu->setObjectName(niceObjectName);
993 
994     core->metaDataBase()->add(menu);
995     fw->ensureUniqueObjectName(menu);
996 
997     QAction *menuAction = menu->menuAction();
998     core->metaDataBase()->add(menuAction);
999 }
1000 
removeRealMenu(QAction * action)1001 void QDesignerMenu::removeRealMenu(QAction *action)
1002 {
1003     QDesignerMenu *menu = qobject_cast<QDesignerMenu*>(action->menu());
1004     if (menu == nullptr)
1005         return;
1006     action->setMenu(nullptr);
1007     m_subMenus.insert(action, menu);
1008     QDesignerFormEditorInterface *core = formWindow()->core();
1009     core->metaDataBase()->remove(menu);
1010 }
1011 
findOrCreateSubMenu(QAction * action)1012 QDesignerMenu *QDesignerMenu::findOrCreateSubMenu(QAction *action)
1013 {
1014     if (action->menu())
1015         return qobject_cast<QDesignerMenu*>(action->menu());
1016 
1017     QDesignerMenu *menu = m_subMenus.value(action);
1018     if (!menu) {
1019         menu = new QDesignerMenu(this);
1020         m_subMenus.insert(action, menu);
1021     }
1022 
1023     return menu;
1024 }
1025 
canCreateSubMenu(QAction * action) const1026 bool 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 *>(aw)) {
1032                 if (m->actions().contains(action))
1033                     return false; // sorry
1034             } else {
1035                 if (const QToolBar *tb = qobject_cast<const QToolBar *>(aw))
1036                     if (tb->actions().contains(action))
1037                         return false; // sorry
1038             }
1039         }
1040     }
1041     return true;
1042 }
1043 
slotShowSubMenuNow()1044 void 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 
showSubMenu(QAction * action)1086 void QDesignerMenu::showSubMenu(QAction *action)
1087 {
1088     m_showSubMenuTimer->stop();
1089 
1090     if (m_editor->isVisible() || !action || qobject_cast<SpecialMenuAction*>(action)
1091             || action->isSeparator() || !isVisible())
1092         return;
1093 
1094     m_showSubMenuTimer->start(300);
1095 }
1096 
parentMenu() const1097 QDesignerMenu *QDesignerMenu::parentMenu() const
1098 {
1099     return qobject_cast<QDesignerMenu*>(parentWidget());
1100 }
1101 
parentMenuBar() const1102 QDesignerMenuBar *QDesignerMenu::parentMenuBar() const
1103 {
1104     if (QDesignerMenuBar *mb = qobject_cast<QDesignerMenuBar*>(parentWidget()))
1105         return mb;
1106     if (QDesignerMenu *m = parentMenu())
1107         return m->parentMenuBar();
1108 
1109     return nullptr;
1110 }
1111 
setVisible(bool visible)1112 void 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 
adjustSpecialActions()1123 void QDesignerMenu::adjustSpecialActions()
1124 {
1125     removeAction(m_addItem);
1126     removeAction(m_addSeparator);
1127     addAction(m_addItem);
1128     addAction(m_addSeparator);
1129 }
1130 
enterEditMode()1131 void QDesignerMenu::enterEditMode()
1132 {
1133     if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) {
1134         showLineEdit();
1135     } else {
1136         hideSubMenu();
1137         QDesignerFormWindowInterface *fw = formWindow();
1138         fw->beginCommand(tr("Add separator"));
1139         QAction *sep = createAction(QString(), true);
1140 
1141         InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
1142         cmd->init(this, sep, safeActionAt(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(parentMenu(), parentMenu()->currentAction());
1150                 fw->commandHistory()->push(cmd);
1151             }
1152         }
1153 
1154         fw->endCommand();
1155 
1156         m_currentIndex = actions().indexOf(m_addItem);
1157         update();
1158     }
1159 }
1160 
leaveEditMode(LeaveEditMode mode)1161 void 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(m_currentIndex);
1171         fw->beginCommand(QApplication::translate("Command", "Set action text"));
1172     } else {
1173         Q_ASSERT(fw != nullptr);
1174         fw->beginCommand(QApplication::translate("Command", "Insert action"));
1175         action = createAction(ActionEditor::actionTextToName(m_editor->text()));
1176         InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
1177         cmd->init(this, action, currentAction());
1178         fw->commandHistory()->push(cmd);
1179     }
1180 
1181     SetPropertyCommand *cmd = new SetPropertyCommand(fw);
1182     cmd->init(action, QStringLiteral("text"), 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(parentMenu(), parentMenu()->currentAction(), action);
1190             fw->commandHistory()->push(cmd);
1191         }
1192     }
1193 
1194     update();
1195     fw->endCommand();
1196 }
1197 
safeMenuAction(QDesignerMenu * menu) const1198 QAction *QDesignerMenu::safeMenuAction(QDesignerMenu *menu) const
1199 {
1200     QAction *action = menu->menuAction();
1201 
1202     if (!action)
1203         action = m_subMenus.key(menu);
1204 
1205     return action;
1206 }
1207 
showLineEdit()1208 void QDesignerMenu::showLineEdit()
1209 {
1210     m_showSubMenuTimer->stop();
1211 
1212     QAction *action = nullptr;
1213 
1214     if (m_currentIndex < realActionCount())
1215         action = safeActionAt(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(1, 1, -2, -2));
1231     m_editor->show();
1232     m_editor->setFocus();
1233 }
1234 
createAction(const QString & objectName,bool separator)1235 QAction *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
swap(int a,int b)1243 bool 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(left);
1249     QAction *action_b = safeActionAt(right);
1250 
1251     if (action_a == action_b
1252             || !action_a
1253             || !action_b
1254             || qobject_cast<SpecialMenuAction*>(action_a)
1255             || qobject_cast<SpecialMenuAction*>(action_b))
1256         return false; // nothing to do
1257 
1258     right = qMin(right, realActionCount());
1259     if (right < 0)
1260         return false; // nothing to do
1261 
1262     QDesignerFormWindowInterface *fw = formWindow();
1263     fw->beginCommand(QApplication::translate("Command", "Move action"));
1264 
1265     QAction *action_b_before = safeActionAt(right + 1);
1266 
1267     RemoveActionFromCommand *cmd1 = new RemoveActionFromCommand(fw);
1268     cmd1->init(this, action_b, action_b_before, false);
1269     fw->commandHistory()->push(cmd1);
1270 
1271     QAction *action_a_before = safeActionAt(left + 1);
1272 
1273     InsertActionIntoCommand *cmd2 = new InsertActionIntoCommand(fw);
1274     cmd2->init(this, action_b, action_a_before, false);
1275     fw->commandHistory()->push(cmd2);
1276 
1277     RemoveActionFromCommand *cmd3 = new RemoveActionFromCommand(fw);
1278     cmd3->init(this, action_a, action_b, false);
1279     fw->commandHistory()->push(cmd3);
1280 
1281     InsertActionIntoCommand *cmd4 = new InsertActionIntoCommand(fw);
1282     cmd4->init(this, action_a, action_b_before, true);
1283     fw->commandHistory()->push(cmd4);
1284 
1285     fw->endCommand();
1286 
1287     return true;
1288 }
1289 
safeActionAt(int index) const1290 QAction *QDesignerMenu::safeActionAt(int index) const
1291 {
1292     if (index < 0 || index >= actions().count())
1293         return nullptr;
1294 
1295     return actions().at(index);
1296 }
1297 
hideSubMenu()1298 void QDesignerMenu::hideSubMenu()
1299 {
1300     m_lastSubMenuIndex = -1;
1301     const auto &menus = findChildren<QMenu *>();
1302     for (QMenu *subMenu : menus)
1303         subMenu->hide();
1304 }
1305 
deleteAction()1306 void QDesignerMenu::deleteAction()
1307 {
1308     QAction *action = currentAction();
1309     const int pos = actions().indexOf(action);
1310     QAction *action_before = nullptr;
1311     if (pos != -1)
1312         action_before = safeActionAt(pos + 1);
1313 
1314     QDesignerFormWindowInterface *fw = formWindow();
1315     RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
1316     cmd->init(this, action, action_before);
1317     fw->commandHistory()->push(cmd);
1318 
1319     update();
1320 }
1321 
deactivateMenu()1322 void QDesignerMenu::deactivateMenu()
1323 {
1324     m_deactivateWindowTimer->start(10);
1325 }
1326 
slotDeactivateNow()1327 void 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 
drawSelection(QPainter * p,const QRect & r)1342 void 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 
keyPressEvent(QKeyEvent * event)1355 void QDesignerMenu::keyPressEvent(QKeyEvent *event)
1356 {
1357     event->ignore();
1358 }
1359 
keyReleaseEvent(QKeyEvent * event)1360 void QDesignerMenu::keyReleaseEvent(QKeyEvent *event)
1361 {
1362     event->ignore();
1363 }
1364 
1365 QT_END_NAMESPACE
1366