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