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