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_menubar_p.h"
30 #include "qdesigner_menu_p.h"
31 #include "qdesigner_command_p.h"
32 #include "qdesigner_propertycommand_p.h"
33 #include "actionrepository_p.h"
34 #include "actionprovider_p.h"
35 #include "actioneditor_p.h"
36 #include "qdesigner_utils_p.h"
37 #include "promotiontaskmenu_p.h"
38 #include "qdesigner_objectinspector_p.h"
39 
40 #include <QtDesigner/abstractformwindow.h>
41 #include <QtDesigner/abstractformeditor.h>
42 #include <QtDesigner/abstractwidgetfactory.h>
43 #include <QtDesigner/qextensionmanager.h>
44 
45 #include <QtCore/qmimedata.h>
46 
47 #include <QtCore/qdebug.h>
48 
49 #include <QtWidgets/qapplication.h>
50 #include <QtGui/qdrag.h>
51 #include <QtWidgets/qlineedit.h>
52 #include <QtGui/qpainter.h>
53 #include <QtGui/qevent.h>
54 
55 Q_DECLARE_METATYPE(QAction*)
56 
57 QT_BEGIN_NAMESPACE
58 
59 using ActionList = QList<QAction *>;
60 
61 using namespace qdesigner_internal;
62 
63 namespace qdesigner_internal
64 {
65 
66 /////////////////////////////////////////////////////////////////////////////////////////////////////////
SpecialMenuAction(QObject * parent)67 SpecialMenuAction::SpecialMenuAction(QObject *parent)
68     : QAction(parent)
69 {
70 }
71 
72 SpecialMenuAction::~SpecialMenuAction() = default;
73 
74 } // namespace qdesigner_internal
75 
76 
77 /////////////////////////////////////////////////////////////////////////////////////////////////////////
QDesignerMenuBar(QWidget * parent)78 QDesignerMenuBar::QDesignerMenuBar(QWidget *parent)  :
79     QMenuBar(parent),
80     m_addMenu(new SpecialMenuAction(this)),
81     m_editor(new QLineEdit(this)),
82     m_promotionTaskMenu(new PromotionTaskMenu(this, PromotionTaskMenu::ModeSingleWidget, this))
83 {
84     setContextMenuPolicy(Qt::DefaultContextMenu);
85 
86     setAcceptDrops(true); // ### fake
87     // Fake property: Keep the menu bar editable in the form even if a native menu bar is used.
88     setNativeMenuBar(false);
89 
90     m_addMenu->setText(tr("Type Here"));
91     addAction(m_addMenu);
92 
93     QFont italic;
94     italic.setItalic(true);
95     m_addMenu->setFont(italic);
96 
97     m_editor->setObjectName(QStringLiteral("__qt__passive_editor"));
98     m_editor->hide();
99     m_editor->installEventFilter(this);
100     installEventFilter(this);
101 }
102 
103 QDesignerMenuBar::~QDesignerMenuBar() = default;
104 
paintEvent(QPaintEvent * event)105 void QDesignerMenuBar::paintEvent(QPaintEvent *event)
106 {
107     QMenuBar::paintEvent(event);
108 
109     QPainter p(this);
110 
111     const auto &actionList = actions();
112     for (QAction *a : actionList) {
113         if (qobject_cast<SpecialMenuAction*>(a)) {
114             const QRect g = actionGeometry(a);
115             QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom());
116             lg.setColorAt(0.0, Qt::transparent);
117             lg.setColorAt(0.7, QColor(0, 0, 0, 32));
118             lg.setColorAt(1.0, Qt::transparent);
119 
120             p.fillRect(g, lg);
121         }
122     }
123 
124     QAction *action = currentAction();
125 
126     if (m_dragging || !action)
127         return;
128 
129     if (hasFocus()) {
130         const QRect g = actionGeometry(action);
131         QDesignerMenu::drawSelection(&p, g.adjusted(1, 1, -1, -1));
132     } else if (action->menu() && action->menu()->isVisible()) {
133         const QRect g = actionGeometry(action);
134         p.drawRect(g.adjusted(1, 1, -1, -1));
135     }
136 }
137 
handleEvent(QWidget * widget,QEvent * event)138 bool QDesignerMenuBar::handleEvent(QWidget *widget, QEvent *event)
139 {
140     if (!formWindow())
141         return false;
142 
143     if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut)
144         update();
145 
146     switch (event->type()) {
147         default: break;
148 
149         case QEvent::MouseButtonDblClick:
150             return handleMouseDoubleClickEvent(widget, static_cast<QMouseEvent*>(event));
151         case QEvent::MouseButtonPress:
152             return handleMousePressEvent(widget, static_cast<QMouseEvent*>(event));
153         case QEvent::MouseButtonRelease:
154             return handleMouseReleaseEvent(widget, static_cast<QMouseEvent*>(event));
155         case QEvent::MouseMove:
156             return handleMouseMoveEvent(widget, static_cast<QMouseEvent*>(event));
157         case QEvent::ContextMenu:
158             return handleContextMenuEvent(widget, static_cast<QContextMenuEvent*>(event));
159         case QEvent::KeyPress:
160             return handleKeyPressEvent(widget, static_cast<QKeyEvent*>(event));
161         case QEvent::FocusIn:
162         case QEvent::FocusOut:
163             return widget != m_editor;
164     }
165 
166     return true;
167 }
168 
handleMouseDoubleClickEvent(QWidget *,QMouseEvent * event)169 bool QDesignerMenuBar::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event)
170 {
171     if (!rect().contains(event->pos()))
172         return true;
173 
174     if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton)
175         return true;
176 
177     event->accept();
178 
179     m_startPosition = QPoint();
180 
181     m_currentIndex = actionIndexAt(this, event->pos(), Qt::Horizontal);
182     if (m_currentIndex != -1) {
183         showLineEdit();
184     }
185 
186     return true;
187 }
188 
handleKeyPressEvent(QWidget *,QKeyEvent * e)189 bool QDesignerMenuBar::handleKeyPressEvent(QWidget *, QKeyEvent *e)
190 {
191     if (m_editor->isHidden()) { // In navigation mode
192         switch (e->key()) {
193 
194         case Qt::Key_Delete:
195             if (m_currentIndex == -1 || m_currentIndex >= realActionCount())
196                 break;
197             hideMenu();
198             deleteMenu();
199             break;
200 
201         case Qt::Key_Left:
202             e->accept();
203             moveLeft(e->modifiers() & Qt::ControlModifier);
204             return true;
205 
206         case Qt::Key_Right:
207             e->accept();
208             moveRight(e->modifiers() & Qt::ControlModifier);
209             return true; // no update
210 
211         case Qt::Key_Up:
212             e->accept();
213             moveUp();
214             return true;
215 
216         case Qt::Key_Down:
217             e->accept();
218             moveDown();
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             e->accept();
232             enterEditMode();
233             return true; // no update
234 
235         case Qt::Key_Alt:
236         case Qt::Key_Shift:
237         case Qt::Key_Control:
238         case Qt::Key_Escape:
239             e->ignore();
240             setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed
241             return true; // no update
242 
243         default:
244             if (!e->text().isEmpty() && e->text().at(0).toLatin1() >= 32) {
245                 showLineEdit();
246                 QApplication::sendEvent(m_editor, e);
247                 e->accept();
248             } else {
249                 e->ignore();
250             }
251             return true;
252         }
253     } else { // In edit mode
254         switch (e->key()) {
255         default:
256             return false;
257 
258         case Qt::Key_Control:
259             e->ignore();
260             return true;
261 
262         case Qt::Key_Enter:
263         case Qt::Key_Return:
264             if (!m_editor->text().isEmpty()) {
265                 leaveEditMode(ForceAccept);
266                 if (m_lastFocusWidget)
267                     m_lastFocusWidget->setFocus();
268 
269                 m_editor->hide();
270                 showMenu();
271                 break;
272             }
273             Q_FALLTHROUGH();
274 
275         case Qt::Key_Escape:
276             update();
277             setFocus();
278             break;
279         }
280     }
281 
282     e->accept();
283     update();
284 
285     return true;
286 }
287 
startDrag(const QPoint & pos)288 void QDesignerMenuBar::startDrag(const QPoint &pos)
289 {
290     const int index = findAction(pos);
291     if (m_currentIndex == -1 || index >= realActionCount())
292         return;
293 
294     QAction *action = safeActionAt(index);
295 
296     QDesignerFormWindowInterface *fw = formWindow();
297     RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw);
298     cmd->init(this, action, actions().at(index + 1));
299     fw->commandHistory()->push(cmd);
300 
301     adjustSize();
302 
303     hideMenu(index);
304 
305     QDrag *drag = new QDrag(this);
306     drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action));
307     drag->setMimeData(new ActionRepositoryMimeData(action, Qt::MoveAction));
308 
309     const int old_index = m_currentIndex;
310     m_currentIndex = -1;
311 
312     if (drag->exec(Qt::MoveAction) == Qt::IgnoreAction) {
313         InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
314         cmd->init(this, action, safeActionAt(index));
315         fw->commandHistory()->push(cmd);
316 
317         m_currentIndex = old_index;
318         adjustSize();
319     }
320 }
321 
handleMousePressEvent(QWidget *,QMouseEvent * event)322 bool QDesignerMenuBar::handleMousePressEvent(QWidget *, QMouseEvent *event)
323 {
324     m_startPosition = QPoint();
325     event->accept();
326 
327     if (event->button() != Qt::LeftButton)
328         return true;
329 
330     m_startPosition = event->pos();
331     const int newIndex = actionIndexAt(this, m_startPosition, Qt::Horizontal);
332     const bool changed = newIndex != m_currentIndex;
333     m_currentIndex =  newIndex;
334     updateCurrentAction(changed);
335 
336     return true;
337 }
338 
handleMouseReleaseEvent(QWidget *,QMouseEvent * event)339 bool QDesignerMenuBar::handleMouseReleaseEvent(QWidget *, QMouseEvent *event)
340 {
341     m_startPosition = QPoint();
342 
343     if (event->button() != Qt::LeftButton)
344         return true;
345 
346     event->accept();
347     m_currentIndex = actionIndexAt(this, event->pos(), Qt::Horizontal);
348     if (!m_editor->isVisible() && m_currentIndex != -1 && m_currentIndex < realActionCount())
349         showMenu();
350 
351     return true;
352 }
353 
handleMouseMoveEvent(QWidget *,QMouseEvent * event)354 bool QDesignerMenuBar::handleMouseMoveEvent(QWidget *, QMouseEvent *event)
355 {
356     if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton)
357         return true;
358 
359     if (m_startPosition.isNull())
360         return true;
361 
362     const QPoint pos = mapFromGlobal(event->globalPos());
363 
364     if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance())
365         return true;
366 
367     const int index =  actionIndexAt(this, m_startPosition, Qt::Horizontal);
368     if (index < actions().count()) {
369         hideMenu(index);
370         update();
371     }
372 
373     startDrag(m_startPosition);
374     m_startPosition = QPoint();
375 
376     return true;
377 }
378 
contextMenuActions()379 ActionList QDesignerMenuBar::contextMenuActions()
380 {
381     ActionList rc;
382     if (QAction *action = safeActionAt(m_currentIndex)) {
383         if (!qobject_cast<SpecialMenuAction*>(action)) {
384             QVariant itemData;
385             itemData.setValue(action);
386 
387             QAction *remove_action = new QAction(tr("Remove Menu '%1'").arg(action->menu()->objectName()), nullptr);
388             remove_action->setData(itemData);
389             connect(remove_action, &QAction::triggered, this, &QDesignerMenuBar::deleteMenu);
390             rc.push_back(remove_action);
391             QAction *sep = new QAction(nullptr);
392             sep->setSeparator(true);
393             rc.push_back(sep);
394         }
395     }
396 
397     m_promotionTaskMenu->addActions(formWindow(), PromotionTaskMenu::TrailingSeparator, rc);
398 
399     QAction *remove_menubar = new QAction(tr("Remove Menu Bar"), nullptr);
400     connect(remove_menubar, &QAction::triggered, this, &QDesignerMenuBar::slotRemoveMenuBar);
401     rc.push_back(remove_menubar);
402     return rc;
403 }
404 
handleContextMenuEvent(QWidget *,QContextMenuEvent * event)405 bool QDesignerMenuBar::handleContextMenuEvent(QWidget *, QContextMenuEvent *event)
406 {
407     event->accept();
408 
409     m_currentIndex = actionIndexAt(this, mapFromGlobal(event->globalPos()), Qt::Horizontal);
410 
411     update();
412 
413     QMenu menu;
414     const ActionList al = contextMenuActions();
415     const ActionList::const_iterator acend = al.constEnd();
416     for (ActionList::const_iterator it =  al.constBegin(); it != acend; ++it)
417         menu.addAction(*it);
418     menu.exec(event->globalPos());
419     return true;
420 }
421 
slotRemoveMenuBar()422 void QDesignerMenuBar::slotRemoveMenuBar()
423 {
424     Q_ASSERT(formWindow() != nullptr);
425 
426     QDesignerFormWindowInterface *fw = formWindow();
427 
428     DeleteMenuBarCommand *cmd = new DeleteMenuBarCommand(fw);
429     cmd->init(this);
430     fw->commandHistory()->push(cmd);
431 }
432 
focusOutEvent(QFocusEvent * event)433 void QDesignerMenuBar::focusOutEvent(QFocusEvent *event)
434 {
435     QMenuBar::focusOutEvent(event);
436 }
437 
enterEditMode()438 void QDesignerMenuBar::enterEditMode()
439 {
440     if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) {
441         showLineEdit();
442     }
443 }
444 
leaveEditMode(LeaveEditMode mode)445 void QDesignerMenuBar::leaveEditMode(LeaveEditMode mode)
446 {
447     m_editor->releaseKeyboard();
448 
449     if (mode == Default)
450         return;
451 
452     if (m_editor->text().isEmpty())
453         return;
454 
455     QAction *action = nullptr;
456 
457     QDesignerFormWindowInterface *fw = formWindow();
458     Q_ASSERT(fw);
459 
460     if (m_currentIndex >= 0 && m_currentIndex < realActionCount()) {
461         action = safeActionAt(m_currentIndex);
462         fw->beginCommand(QApplication::translate("Command", "Change Title"));
463     } else {
464         fw->beginCommand(QApplication::translate("Command", "Insert Menu"));
465         const QString niceObjectName = ActionEditor::actionTextToName(m_editor->text(), QStringLiteral("menu"));
466         QMenu *menu = qobject_cast<QMenu*>(fw->core()->widgetFactory()->createWidget(QStringLiteral("QMenu"), this));
467         fw->core()->widgetFactory()->initialize(menu);
468         menu->setObjectName(niceObjectName);
469         menu->setTitle(tr("Menu"));
470         fw->ensureUniqueObjectName(menu);
471         action = menu->menuAction();
472         AddMenuActionCommand *cmd = new AddMenuActionCommand(fw);
473         cmd->init(action, m_addMenu, this, this);
474         fw->commandHistory()->push(cmd);
475     }
476 
477     SetPropertyCommand *cmd = new SetPropertyCommand(fw);
478     cmd->init(action, QStringLiteral("text"), m_editor->text());
479     fw->commandHistory()->push(cmd);
480     fw->endCommand();
481 }
482 
showLineEdit()483 void QDesignerMenuBar::showLineEdit()
484 {
485     QAction *action = nullptr;
486 
487     if (m_currentIndex >= 0 && m_currentIndex < realActionCount())
488         action = safeActionAt(m_currentIndex);
489     else
490         action = m_addMenu;
491 
492     if (action->isSeparator())
493         return;
494 
495     // hideMenu();
496 
497     m_lastFocusWidget = qApp->focusWidget();
498 
499     // open edit field for item name
500     const QString text = action != m_addMenu ? action->text() : QString();
501 
502     m_editor->setText(text);
503     m_editor->selectAll();
504     m_editor->setGeometry(actionGeometry(action));
505     m_editor->show();
506     qApp->setActiveWindow(m_editor);
507     m_editor->setFocus();
508     m_editor->grabKeyboard();
509 }
510 
eventFilter(QObject * object,QEvent * event)511 bool QDesignerMenuBar::eventFilter(QObject *object, QEvent *event)
512 {
513     if (object != this && object != m_editor)
514         return false;
515 
516     if (!m_editor->isHidden() && object == m_editor && event->type() == QEvent::FocusOut) {
517         leaveEditMode(Default);
518         m_editor->hide();
519         update();
520         return true;
521     }
522 
523     bool dispatch = true;
524 
525     switch (event->type()) {
526         default: break;
527 
528         case QEvent::KeyPress:
529         case QEvent::KeyRelease:
530         case QEvent::ContextMenu:
531         case QEvent::MouseMove:
532         case QEvent::MouseButtonPress:
533         case QEvent::MouseButtonRelease:
534         case QEvent::MouseButtonDblClick:
535             dispatch = (object != m_editor);
536             Q_FALLTHROUGH(); // no break
537 
538         case QEvent::Enter:
539         case QEvent::Leave:
540         case QEvent::FocusIn:
541         case QEvent::FocusOut:
542         {
543             QWidget *widget = qobject_cast<QWidget*>(object);
544 
545             if (dispatch && widget && (widget == this || isAncestorOf(widget)))
546                 return handleEvent(widget, event);
547         } break;
548 
549         case QEvent::Shortcut:
550             event->accept();
551             return true;
552     }
553 
554     return false;
555 };
556 
findAction(const QPoint & pos) const557 int QDesignerMenuBar::findAction(const QPoint &pos) const
558 {
559     const int index = actionIndexAt(this, pos, Qt::Horizontal);
560     if (index == -1)
561         return realActionCount();
562 
563     return index;
564 }
565 
adjustIndicator(const QPoint & pos)566 void QDesignerMenuBar::adjustIndicator(const QPoint &pos)
567 {
568     const int index = findAction(pos);
569     QAction *action = safeActionAt(index);
570     Q_ASSERT(action != nullptr);
571 
572     if (pos != QPoint(-1, -1)) {
573         QDesignerMenu *m = qobject_cast<QDesignerMenu*>(action->menu());
574         if (!m || m->parentMenu()) {
575             m_currentIndex = index;
576             showMenu(index);
577         }
578     }
579 
580     if (QDesignerActionProviderExtension *a = actionProvider()) {
581         a->adjustIndicator(pos);
582     }
583 }
584 
checkAction(QAction * action) const585 QDesignerMenuBar::ActionDragCheck QDesignerMenuBar::checkAction(QAction *action) const
586 {
587     // action belongs to another form
588     if (!action || !Utils::isObjectAncestorOf(formWindow()->mainContainer(), action))
589         return NoActionDrag;
590 
591     if (!action->menu())
592         return ActionDragOnSubMenu; // simple action only on sub menus
593 
594     QDesignerMenu *m = qobject_cast<QDesignerMenu*>(action->menu());
595     if (m && m->parentMenu())
596         return ActionDragOnSubMenu; // it looks like a submenu
597 
598     if (actions().contains(action))
599         return ActionDragOnSubMenu; // we already have the action in the menubar
600 
601     return AcceptActionDrag;
602 }
603 
dragEnterEvent(QDragEnterEvent * event)604 void QDesignerMenuBar::dragEnterEvent(QDragEnterEvent *event)
605 {
606     const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData());
607     if (!d || d->actionList().isEmpty()) {
608         event->ignore();
609         return;
610     }
611 
612     QAction *action = d->actionList().first();
613     switch (checkAction(action)) {
614     case NoActionDrag:
615         event->ignore();
616         break;
617     case ActionDragOnSubMenu:
618         m_dragging = true;
619         d->accept(event);
620         break;
621     case AcceptActionDrag:
622         m_dragging = true;
623         d->accept(event);
624         adjustIndicator(event->pos());
625         break;
626     }
627 }
628 
dragMoveEvent(QDragMoveEvent * event)629 void QDesignerMenuBar::dragMoveEvent(QDragMoveEvent *event)
630 {
631     const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData());
632     if (!d || d->actionList().isEmpty()) {
633         event->ignore();
634         return;
635     }
636     QAction *action = d->actionList().first();
637 
638     switch (checkAction(action)) {
639     case NoActionDrag:
640         event->ignore();
641         break;
642     case ActionDragOnSubMenu:
643         event->ignore();
644         showMenu(findAction(event->pos()));
645         break;
646     case AcceptActionDrag:
647         d->accept(event);
648         adjustIndicator(event->pos());
649         break;
650     }
651 }
652 
dragLeaveEvent(QDragLeaveEvent *)653 void QDesignerMenuBar::dragLeaveEvent(QDragLeaveEvent *)
654 {
655     m_dragging = false;
656 
657     adjustIndicator(QPoint(-1, -1));
658 }
659 
dropEvent(QDropEvent * event)660 void QDesignerMenuBar::dropEvent(QDropEvent *event)
661 {
662     m_dragging = false;
663 
664     if (const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData())) {
665 
666         QAction *action = d->actionList().first();
667         if (checkAction(action) == AcceptActionDrag) {
668             event->acceptProposedAction();
669             int index = findAction(event->pos());
670             index = qMin(index, actions().count() - 1);
671 
672             QDesignerFormWindowInterface *fw = formWindow();
673             InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw);
674             cmd->init(this, action, safeActionAt(index));
675             fw->commandHistory()->push(cmd);
676 
677             m_currentIndex = index;
678             update();
679             adjustIndicator(QPoint(-1, -1));
680             return;
681         }
682     }
683     event->ignore();
684 }
685 
actionEvent(QActionEvent * event)686 void QDesignerMenuBar::actionEvent(QActionEvent *event)
687 {
688     QMenuBar::actionEvent(event);
689 }
690 
formWindow() const691 QDesignerFormWindowInterface *QDesignerMenuBar::formWindow() const
692 {
693     return QDesignerFormWindowInterface::findFormWindow(const_cast<QDesignerMenuBar*>(this));
694 }
695 
actionProvider()696 QDesignerActionProviderExtension *QDesignerMenuBar::actionProvider()
697 {
698     if (QDesignerFormWindowInterface *fw = formWindow()) {
699         QDesignerFormEditorInterface *core = fw->core();
700         return qt_extension<QDesignerActionProviderExtension*>(core->extensionManager(), this);
701     }
702 
703     return nullptr;
704 }
705 
currentAction() const706 QAction *QDesignerMenuBar::currentAction() const
707 {
708     if (m_currentIndex < 0 || m_currentIndex >= actions().count())
709         return nullptr;
710 
711     return safeActionAt(m_currentIndex);
712 }
713 
realActionCount() const714 int QDesignerMenuBar::realActionCount() const
715 {
716     return actions().count() - 1; // 1 fake actions
717 }
718 
dragging() const719 bool QDesignerMenuBar::dragging() const
720 {
721     return m_dragging;
722 }
723 
moveLeft(bool ctrl)724 void QDesignerMenuBar::moveLeft(bool ctrl)
725 {
726     if (layoutDirection() == Qt::LeftToRight) {
727         movePrevious(ctrl);
728     } else {
729         moveNext(ctrl);
730     }
731 }
732 
moveRight(bool ctrl)733 void QDesignerMenuBar::moveRight(bool ctrl)
734 {
735     if (layoutDirection() == Qt::LeftToRight) {
736         moveNext(ctrl);
737     } else {
738         movePrevious(ctrl);
739     }
740 }
741 
movePrevious(bool ctrl)742 void QDesignerMenuBar::movePrevious(bool ctrl)
743 {
744     const bool swapped = ctrl && swapActions(m_currentIndex, m_currentIndex - 1);
745     const int newIndex = qMax(0, m_currentIndex - 1);
746     // Always re-select, swapping destroys order
747     if (swapped || newIndex != m_currentIndex) {
748         m_currentIndex = newIndex;
749         updateCurrentAction(true);
750     }
751 }
752 
moveNext(bool ctrl)753 void QDesignerMenuBar::moveNext(bool ctrl)
754 {
755     const bool swapped = ctrl && swapActions(m_currentIndex + 1, m_currentIndex);
756     const int newIndex = qMin(actions().count() - 1, m_currentIndex + 1);
757     if (swapped || newIndex != m_currentIndex) {
758         m_currentIndex = newIndex;
759         updateCurrentAction(!ctrl);
760     }
761 }
762 
moveUp()763 void QDesignerMenuBar::moveUp()
764 {
765     update();
766 }
767 
moveDown()768 void QDesignerMenuBar::moveDown()
769 {
770     showMenu();
771 }
772 
adjustSpecialActions()773 void QDesignerMenuBar::adjustSpecialActions()
774 {
775     removeAction(m_addMenu);
776     addAction(m_addMenu);
777 }
778 
hideMenu(int index)779 void QDesignerMenuBar::hideMenu(int index)
780 {
781     if (index < 0 && m_currentIndex >= 0)
782         index = m_currentIndex;
783 
784     if (index < 0 || index >= realActionCount())
785         return;
786 
787     QAction *action = safeActionAt(index);
788 
789     if (action && action->menu()) {
790         action->menu()->hide();
791 
792         if (QDesignerMenu *menu = qobject_cast<QDesignerMenu*>(action->menu())) {
793             menu->closeMenuChain();
794         }
795     }
796 }
797 
deleteMenu()798 void QDesignerMenuBar::deleteMenu()
799 {
800     deleteMenuAction(currentAction());
801 }
802 
deleteMenuAction(QAction * action)803 void QDesignerMenuBar::deleteMenuAction(QAction *action)
804 {
805     if (action && !qobject_cast<SpecialMenuAction*>(action)) {
806         const int pos = actions().indexOf(action);
807         QAction *action_before = nullptr;
808         if (pos != -1)
809             action_before = safeActionAt(pos + 1);
810 
811         QDesignerFormWindowInterface *fw = formWindow();
812         RemoveMenuActionCommand *cmd = new RemoveMenuActionCommand(fw);
813         cmd->init(action, action_before, this, this);
814         fw->commandHistory()->push(cmd);
815     }
816 }
817 
showMenu(int index)818 void QDesignerMenuBar::showMenu(int index)
819 {
820     if (index < 0 && m_currentIndex >= 0)
821         index = m_currentIndex;
822 
823     if (index < 0 || index >= realActionCount())
824         return;
825 
826     m_currentIndex = index;
827     QAction *action = currentAction();
828 
829     if (action && action->menu()) {
830         if (m_lastMenuActionIndex != -1 && m_lastMenuActionIndex != index) {
831             hideMenu(m_lastMenuActionIndex);
832         }
833 
834         m_lastMenuActionIndex = index;
835         QMenu *menu = action->menu();
836         const QRect g = actionGeometry(action);
837 
838         if (!menu->isVisible()) {
839             if ((menu->windowFlags() & Qt::Popup) != Qt::Popup)
840                 menu->setWindowFlags(Qt::Popup);
841             menu->adjustSize();
842             if (layoutDirection() == Qt::LeftToRight) {
843                 menu->move(mapToGlobal(g.bottomLeft()));
844             } else {
845                 // The position is not initially correct due to the unknown width,
846                 // causing it to overlap a bit the first time it is invoked.
847                 QPoint point = g.bottomRight() - QPoint(menu->width(), 0);
848                 menu->move(mapToGlobal(point));
849             }
850             menu->setFocus(Qt::MouseFocusReason);
851             menu->raise();
852             menu->show();
853         } else {
854             menu->raise();
855         }
856     }
857 }
858 
safeActionAt(int index) const859 QAction *QDesignerMenuBar::safeActionAt(int index) const
860 {
861     if (index < 0 || index >= actions().count())
862         return nullptr;
863 
864     return actions().at(index);
865 }
866 
swapActions(int a,int b)867 bool QDesignerMenuBar::swapActions(int a, int b)
868 {
869     const int left = qMin(a, b);
870     int right = qMax(a, b);
871 
872     QAction *action_a = safeActionAt(left);
873     QAction *action_b = safeActionAt(right);
874 
875     if (action_a == action_b
876             || !action_a
877             || !action_b
878             || qobject_cast<SpecialMenuAction*>(action_a)
879             || qobject_cast<SpecialMenuAction*>(action_b))
880         return false; // nothing to do
881 
882     right = qMin(right, realActionCount());
883     if (right < 0)
884         return false; // nothing to do
885 
886     formWindow()->beginCommand(QApplication::translate("Command", "Move action"));
887 
888     QAction *action_b_before = safeActionAt(right + 1);
889 
890     QDesignerFormWindowInterface *fw = formWindow();
891     RemoveActionFromCommand *cmd1 = new RemoveActionFromCommand(fw);
892     cmd1->init(this, action_b, action_b_before, false);
893     fw->commandHistory()->push(cmd1);
894 
895     QAction *action_a_before = safeActionAt(left + 1);
896 
897     InsertActionIntoCommand *cmd2 = new InsertActionIntoCommand(fw);
898     cmd2->init(this, action_b, action_a_before, false);
899     fw->commandHistory()->push(cmd2);
900 
901     RemoveActionFromCommand *cmd3 = new RemoveActionFromCommand(fw);
902     cmd3->init(this, action_a, action_b, false);
903     fw->commandHistory()->push(cmd3);
904 
905     InsertActionIntoCommand *cmd4 = new InsertActionIntoCommand(fw);
906     cmd4->init(this, action_a, action_b_before, true);
907     fw->commandHistory()->push(cmd4);
908 
909     fw->endCommand();
910 
911     return true;
912 }
913 
keyPressEvent(QKeyEvent * event)914 void QDesignerMenuBar::keyPressEvent(QKeyEvent *event)
915 {
916     event->ignore();
917 }
918 
keyReleaseEvent(QKeyEvent * event)919 void QDesignerMenuBar::keyReleaseEvent(QKeyEvent *event)
920 {
921     event->ignore();
922 }
923 
updateCurrentAction(bool selectAction)924 void QDesignerMenuBar::updateCurrentAction(bool selectAction)
925 {
926     update();
927 
928     if (!selectAction)
929         return;
930 
931     QAction *action = currentAction();
932     if (!action || action == m_addMenu)
933         return;
934 
935     QMenu *menu = action->menu();
936     if (!menu)
937         return;
938 
939     QDesignerObjectInspector *oi = nullptr;
940     if (QDesignerFormWindowInterface *fw = formWindow())
941         oi = qobject_cast<QDesignerObjectInspector *>(fw->core()->objectInspector());
942 
943     if (!oi)
944         return;
945 
946     oi->clearSelection();
947     oi->selectObject(menu);
948 }
949 
950 QT_END_NAMESPACE
951