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