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