/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Designer of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qdesigner_menubar_p.h" #include "qdesigner_menu_p.h" #include "qdesigner_command_p.h" #include "qdesigner_propertycommand_p.h" #include "actionrepository_p.h" #include "actionprovider_p.h" #include "actioneditor_p.h" #include "qdesigner_utils_p.h" #include "promotiontaskmenu_p.h" #include "qdesigner_objectinspector_p.h" #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QAction*) QT_BEGIN_NAMESPACE using ActionList = QList; using namespace qdesigner_internal; namespace qdesigner_internal { ///////////////////////////////////////////////////////////////////////////////////////////////////////// SpecialMenuAction::SpecialMenuAction(QObject *parent) : QAction(parent) { } SpecialMenuAction::~SpecialMenuAction() = default; } // namespace qdesigner_internal ///////////////////////////////////////////////////////////////////////////////////////////////////////// QDesignerMenuBar::QDesignerMenuBar(QWidget *parent) : QMenuBar(parent), m_addMenu(new SpecialMenuAction(this)), m_editor(new QLineEdit(this)), m_promotionTaskMenu(new PromotionTaskMenu(this, PromotionTaskMenu::ModeSingleWidget, this)) { setContextMenuPolicy(Qt::DefaultContextMenu); setAcceptDrops(true); // ### fake // Fake property: Keep the menu bar editable in the form even if a native menu bar is used. setNativeMenuBar(false); m_addMenu->setText(tr("Type Here")); addAction(m_addMenu); QFont italic; italic.setItalic(true); m_addMenu->setFont(italic); m_editor->setObjectName(QStringLiteral("__qt__passive_editor")); m_editor->hide(); m_editor->installEventFilter(this); installEventFilter(this); } QDesignerMenuBar::~QDesignerMenuBar() = default; void QDesignerMenuBar::paintEvent(QPaintEvent *event) { QMenuBar::paintEvent(event); QPainter p(this); const auto &actionList = actions(); for (QAction *a : actionList) { if (qobject_cast(a)) { const QRect g = actionGeometry(a); QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom()); lg.setColorAt(0.0, Qt::transparent); lg.setColorAt(0.7, QColor(0, 0, 0, 32)); lg.setColorAt(1.0, Qt::transparent); p.fillRect(g, lg); } } QAction *action = currentAction(); if (m_dragging || !action) return; if (hasFocus()) { const QRect g = actionGeometry(action); QDesignerMenu::drawSelection(&p, g.adjusted(1, 1, -1, -1)); } else if (action->menu() && action->menu()->isVisible()) { const QRect g = actionGeometry(action); p.drawRect(g.adjusted(1, 1, -1, -1)); } } bool QDesignerMenuBar::handleEvent(QWidget *widget, QEvent *event) { if (!formWindow()) return false; if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) update(); switch (event->type()) { default: break; case QEvent::MouseButtonDblClick: return handleMouseDoubleClickEvent(widget, static_cast(event)); case QEvent::MouseButtonPress: return handleMousePressEvent(widget, static_cast(event)); case QEvent::MouseButtonRelease: return handleMouseReleaseEvent(widget, static_cast(event)); case QEvent::MouseMove: return handleMouseMoveEvent(widget, static_cast(event)); case QEvent::ContextMenu: return handleContextMenuEvent(widget, static_cast(event)); case QEvent::KeyPress: return handleKeyPressEvent(widget, static_cast(event)); case QEvent::FocusIn: case QEvent::FocusOut: return widget != m_editor; } return true; } bool QDesignerMenuBar::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event) { if (!rect().contains(event->pos())) return true; if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) return true; event->accept(); m_startPosition = QPoint(); m_currentIndex = actionIndexAt(this, event->pos(), Qt::Horizontal); if (m_currentIndex != -1) { showLineEdit(); } return true; } bool QDesignerMenuBar::handleKeyPressEvent(QWidget *, QKeyEvent *e) { if (m_editor->isHidden()) { // In navigation mode switch (e->key()) { case Qt::Key_Delete: if (m_currentIndex == -1 || m_currentIndex >= realActionCount()) break; hideMenu(); deleteMenu(); break; case Qt::Key_Left: e->accept(); moveLeft(e->modifiers() & Qt::ControlModifier); return true; case Qt::Key_Right: e->accept(); moveRight(e->modifiers() & Qt::ControlModifier); return true; // no update case Qt::Key_Up: e->accept(); moveUp(); return true; case Qt::Key_Down: e->accept(); moveDown(); return true; case Qt::Key_PageUp: m_currentIndex = 0; break; case Qt::Key_PageDown: m_currentIndex = actions().count() - 1; break; case Qt::Key_Enter: case Qt::Key_Return: e->accept(); enterEditMode(); return true; // no update case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Control: case Qt::Key_Escape: e->ignore(); setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed return true; // no update default: if (!e->text().isEmpty() && e->text().at(0).toLatin1() >= 32) { showLineEdit(); QApplication::sendEvent(m_editor, e); e->accept(); } else { e->ignore(); } return true; } } else { // In edit mode switch (e->key()) { default: return false; case Qt::Key_Control: e->ignore(); return true; case Qt::Key_Enter: case Qt::Key_Return: if (!m_editor->text().isEmpty()) { leaveEditMode(ForceAccept); if (m_lastFocusWidget) m_lastFocusWidget->setFocus(); m_editor->hide(); showMenu(); break; } Q_FALLTHROUGH(); case Qt::Key_Escape: update(); setFocus(); break; } } e->accept(); update(); return true; } void QDesignerMenuBar::startDrag(const QPoint &pos) { const int index = findAction(pos); if (m_currentIndex == -1 || index >= realActionCount()) return; QAction *action = safeActionAt(index); QDesignerFormWindowInterface *fw = formWindow(); RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); cmd->init(this, action, actions().at(index + 1)); fw->commandHistory()->push(cmd); adjustSize(); hideMenu(index); QDrag *drag = new QDrag(this); drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action)); drag->setMimeData(new ActionRepositoryMimeData(action, Qt::MoveAction)); const int old_index = m_currentIndex; m_currentIndex = -1; if (drag->exec(Qt::MoveAction) == Qt::IgnoreAction) { InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); cmd->init(this, action, safeActionAt(index)); fw->commandHistory()->push(cmd); m_currentIndex = old_index; adjustSize(); } } bool QDesignerMenuBar::handleMousePressEvent(QWidget *, QMouseEvent *event) { m_startPosition = QPoint(); event->accept(); if (event->button() != Qt::LeftButton) return true; m_startPosition = event->pos(); const int newIndex = actionIndexAt(this, m_startPosition, Qt::Horizontal); const bool changed = newIndex != m_currentIndex; m_currentIndex = newIndex; updateCurrentAction(changed); return true; } bool QDesignerMenuBar::handleMouseReleaseEvent(QWidget *, QMouseEvent *event) { m_startPosition = QPoint(); if (event->button() != Qt::LeftButton) return true; event->accept(); m_currentIndex = actionIndexAt(this, event->pos(), Qt::Horizontal); if (!m_editor->isVisible() && m_currentIndex != -1 && m_currentIndex < realActionCount()) showMenu(); return true; } bool QDesignerMenuBar::handleMouseMoveEvent(QWidget *, QMouseEvent *event) { if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) return true; if (m_startPosition.isNull()) return true; const QPoint pos = mapFromGlobal(event->globalPos()); if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance()) return true; const int index = actionIndexAt(this, m_startPosition, Qt::Horizontal); if (index < actions().count()) { hideMenu(index); update(); } startDrag(m_startPosition); m_startPosition = QPoint(); return true; } ActionList QDesignerMenuBar::contextMenuActions() { ActionList rc; if (QAction *action = safeActionAt(m_currentIndex)) { if (!qobject_cast(action)) { QVariant itemData; itemData.setValue(action); QAction *remove_action = new QAction(tr("Remove Menu '%1'").arg(action->menu()->objectName()), nullptr); remove_action->setData(itemData); connect(remove_action, &QAction::triggered, this, &QDesignerMenuBar::deleteMenu); rc.push_back(remove_action); QAction *sep = new QAction(nullptr); sep->setSeparator(true); rc.push_back(sep); } } m_promotionTaskMenu->addActions(formWindow(), PromotionTaskMenu::TrailingSeparator, rc); QAction *remove_menubar = new QAction(tr("Remove Menu Bar"), nullptr); connect(remove_menubar, &QAction::triggered, this, &QDesignerMenuBar::slotRemoveMenuBar); rc.push_back(remove_menubar); return rc; } bool QDesignerMenuBar::handleContextMenuEvent(QWidget *, QContextMenuEvent *event) { event->accept(); m_currentIndex = actionIndexAt(this, mapFromGlobal(event->globalPos()), Qt::Horizontal); update(); QMenu menu; const ActionList al = contextMenuActions(); const ActionList::const_iterator acend = al.constEnd(); for (ActionList::const_iterator it = al.constBegin(); it != acend; ++it) menu.addAction(*it); menu.exec(event->globalPos()); return true; } void QDesignerMenuBar::slotRemoveMenuBar() { Q_ASSERT(formWindow() != nullptr); QDesignerFormWindowInterface *fw = formWindow(); DeleteMenuBarCommand *cmd = new DeleteMenuBarCommand(fw); cmd->init(this); fw->commandHistory()->push(cmd); } void QDesignerMenuBar::focusOutEvent(QFocusEvent *event) { QMenuBar::focusOutEvent(event); } void QDesignerMenuBar::enterEditMode() { if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) { showLineEdit(); } } void QDesignerMenuBar::leaveEditMode(LeaveEditMode mode) { m_editor->releaseKeyboard(); if (mode == Default) return; if (m_editor->text().isEmpty()) return; QAction *action = nullptr; QDesignerFormWindowInterface *fw = formWindow(); Q_ASSERT(fw); if (m_currentIndex >= 0 && m_currentIndex < realActionCount()) { action = safeActionAt(m_currentIndex); fw->beginCommand(QApplication::translate("Command", "Change Title")); } else { fw->beginCommand(QApplication::translate("Command", "Insert Menu")); const QString niceObjectName = ActionEditor::actionTextToName(m_editor->text(), QStringLiteral("menu")); QMenu *menu = qobject_cast(fw->core()->widgetFactory()->createWidget(QStringLiteral("QMenu"), this)); fw->core()->widgetFactory()->initialize(menu); menu->setObjectName(niceObjectName); menu->setTitle(tr("Menu")); fw->ensureUniqueObjectName(menu); action = menu->menuAction(); AddMenuActionCommand *cmd = new AddMenuActionCommand(fw); cmd->init(action, m_addMenu, this, this); fw->commandHistory()->push(cmd); } SetPropertyCommand *cmd = new SetPropertyCommand(fw); cmd->init(action, QStringLiteral("text"), m_editor->text()); fw->commandHistory()->push(cmd); fw->endCommand(); } void QDesignerMenuBar::showLineEdit() { QAction *action = nullptr; if (m_currentIndex >= 0 && m_currentIndex < realActionCount()) action = safeActionAt(m_currentIndex); else action = m_addMenu; if (action->isSeparator()) return; // hideMenu(); m_lastFocusWidget = qApp->focusWidget(); // open edit field for item name const QString text = action != m_addMenu ? action->text() : QString(); m_editor->setText(text); m_editor->selectAll(); m_editor->setGeometry(actionGeometry(action)); m_editor->show(); qApp->setActiveWindow(m_editor); m_editor->setFocus(); m_editor->grabKeyboard(); } bool QDesignerMenuBar::eventFilter(QObject *object, QEvent *event) { if (object != this && object != m_editor) return false; if (!m_editor->isHidden() && object == m_editor && event->type() == QEvent::FocusOut) { leaveEditMode(Default); m_editor->hide(); update(); return true; } bool dispatch = true; switch (event->type()) { default: break; case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::ContextMenu: case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: dispatch = (object != m_editor); Q_FALLTHROUGH(); // no break case QEvent::Enter: case QEvent::Leave: case QEvent::FocusIn: case QEvent::FocusOut: { QWidget *widget = qobject_cast(object); if (dispatch && widget && (widget == this || isAncestorOf(widget))) return handleEvent(widget, event); } break; case QEvent::Shortcut: event->accept(); return true; } return false; }; int QDesignerMenuBar::findAction(const QPoint &pos) const { const int index = actionIndexAt(this, pos, Qt::Horizontal); if (index == -1) return realActionCount(); return index; } void QDesignerMenuBar::adjustIndicator(const QPoint &pos) { const int index = findAction(pos); QAction *action = safeActionAt(index); Q_ASSERT(action != nullptr); if (pos != QPoint(-1, -1)) { QDesignerMenu *m = qobject_cast(action->menu()); if (!m || m->parentMenu()) { m_currentIndex = index; showMenu(index); } } if (QDesignerActionProviderExtension *a = actionProvider()) { a->adjustIndicator(pos); } } QDesignerMenuBar::ActionDragCheck QDesignerMenuBar::checkAction(QAction *action) const { // action belongs to another form if (!action || !Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) return NoActionDrag; if (!action->menu()) return ActionDragOnSubMenu; // simple action only on sub menus QDesignerMenu *m = qobject_cast(action->menu()); if (m && m->parentMenu()) return ActionDragOnSubMenu; // it looks like a submenu if (actions().contains(action)) return ActionDragOnSubMenu; // we already have the action in the menubar return AcceptActionDrag; } void QDesignerMenuBar::dragEnterEvent(QDragEnterEvent *event) { const ActionRepositoryMimeData *d = qobject_cast(event->mimeData()); if (!d || d->actionList().isEmpty()) { event->ignore(); return; } QAction *action = d->actionList().first(); switch (checkAction(action)) { case NoActionDrag: event->ignore(); break; case ActionDragOnSubMenu: m_dragging = true; d->accept(event); break; case AcceptActionDrag: m_dragging = true; d->accept(event); adjustIndicator(event->pos()); break; } } void QDesignerMenuBar::dragMoveEvent(QDragMoveEvent *event) { const ActionRepositoryMimeData *d = qobject_cast(event->mimeData()); if (!d || d->actionList().isEmpty()) { event->ignore(); return; } QAction *action = d->actionList().first(); switch (checkAction(action)) { case NoActionDrag: event->ignore(); break; case ActionDragOnSubMenu: event->ignore(); showMenu(findAction(event->pos())); break; case AcceptActionDrag: d->accept(event); adjustIndicator(event->pos()); break; } } void QDesignerMenuBar::dragLeaveEvent(QDragLeaveEvent *) { m_dragging = false; adjustIndicator(QPoint(-1, -1)); } void QDesignerMenuBar::dropEvent(QDropEvent *event) { m_dragging = false; if (const ActionRepositoryMimeData *d = qobject_cast(event->mimeData())) { QAction *action = d->actionList().first(); if (checkAction(action) == AcceptActionDrag) { event->acceptProposedAction(); int index = findAction(event->pos()); index = qMin(index, actions().count() - 1); QDesignerFormWindowInterface *fw = formWindow(); InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); cmd->init(this, action, safeActionAt(index)); fw->commandHistory()->push(cmd); m_currentIndex = index; update(); adjustIndicator(QPoint(-1, -1)); return; } } event->ignore(); } void QDesignerMenuBar::actionEvent(QActionEvent *event) { QMenuBar::actionEvent(event); } QDesignerFormWindowInterface *QDesignerMenuBar::formWindow() const { return QDesignerFormWindowInterface::findFormWindow(const_cast(this)); } QDesignerActionProviderExtension *QDesignerMenuBar::actionProvider() { if (QDesignerFormWindowInterface *fw = formWindow()) { QDesignerFormEditorInterface *core = fw->core(); return qt_extension(core->extensionManager(), this); } return nullptr; } QAction *QDesignerMenuBar::currentAction() const { if (m_currentIndex < 0 || m_currentIndex >= actions().count()) return nullptr; return safeActionAt(m_currentIndex); } int QDesignerMenuBar::realActionCount() const { return actions().count() - 1; // 1 fake actions } bool QDesignerMenuBar::dragging() const { return m_dragging; } void QDesignerMenuBar::moveLeft(bool ctrl) { if (layoutDirection() == Qt::LeftToRight) { movePrevious(ctrl); } else { moveNext(ctrl); } } void QDesignerMenuBar::moveRight(bool ctrl) { if (layoutDirection() == Qt::LeftToRight) { moveNext(ctrl); } else { movePrevious(ctrl); } } void QDesignerMenuBar::movePrevious(bool ctrl) { const bool swapped = ctrl && swapActions(m_currentIndex, m_currentIndex - 1); const int newIndex = qMax(0, m_currentIndex - 1); // Always re-select, swapping destroys order if (swapped || newIndex != m_currentIndex) { m_currentIndex = newIndex; updateCurrentAction(true); } } void QDesignerMenuBar::moveNext(bool ctrl) { const bool swapped = ctrl && swapActions(m_currentIndex + 1, m_currentIndex); const int newIndex = qMin(actions().count() - 1, m_currentIndex + 1); if (swapped || newIndex != m_currentIndex) { m_currentIndex = newIndex; updateCurrentAction(!ctrl); } } void QDesignerMenuBar::moveUp() { update(); } void QDesignerMenuBar::moveDown() { showMenu(); } void QDesignerMenuBar::adjustSpecialActions() { removeAction(m_addMenu); addAction(m_addMenu); } void QDesignerMenuBar::hideMenu(int index) { if (index < 0 && m_currentIndex >= 0) index = m_currentIndex; if (index < 0 || index >= realActionCount()) return; QAction *action = safeActionAt(index); if (action && action->menu()) { action->menu()->hide(); if (QDesignerMenu *menu = qobject_cast(action->menu())) { menu->closeMenuChain(); } } } void QDesignerMenuBar::deleteMenu() { deleteMenuAction(currentAction()); } void QDesignerMenuBar::deleteMenuAction(QAction *action) { if (action && !qobject_cast(action)) { const int pos = actions().indexOf(action); QAction *action_before = nullptr; if (pos != -1) action_before = safeActionAt(pos + 1); QDesignerFormWindowInterface *fw = formWindow(); RemoveMenuActionCommand *cmd = new RemoveMenuActionCommand(fw); cmd->init(action, action_before, this, this); fw->commandHistory()->push(cmd); } } void QDesignerMenuBar::showMenu(int index) { if (index < 0 && m_currentIndex >= 0) index = m_currentIndex; if (index < 0 || index >= realActionCount()) return; m_currentIndex = index; QAction *action = currentAction(); if (action && action->menu()) { if (m_lastMenuActionIndex != -1 && m_lastMenuActionIndex != index) { hideMenu(m_lastMenuActionIndex); } m_lastMenuActionIndex = index; QMenu *menu = action->menu(); const QRect g = actionGeometry(action); if (!menu->isVisible()) { if ((menu->windowFlags() & Qt::Popup) != Qt::Popup) menu->setWindowFlags(Qt::Popup); menu->adjustSize(); if (layoutDirection() == Qt::LeftToRight) { menu->move(mapToGlobal(g.bottomLeft())); } else { // The position is not initially correct due to the unknown width, // causing it to overlap a bit the first time it is invoked. QPoint point = g.bottomRight() - QPoint(menu->width(), 0); menu->move(mapToGlobal(point)); } menu->setFocus(Qt::MouseFocusReason); menu->raise(); menu->show(); } else { menu->raise(); } } } QAction *QDesignerMenuBar::safeActionAt(int index) const { if (index < 0 || index >= actions().count()) return nullptr; return actions().at(index); } bool QDesignerMenuBar::swapActions(int a, int b) { const int left = qMin(a, b); int right = qMax(a, b); QAction *action_a = safeActionAt(left); QAction *action_b = safeActionAt(right); if (action_a == action_b || !action_a || !action_b || qobject_cast(action_a) || qobject_cast(action_b)) return false; // nothing to do right = qMin(right, realActionCount()); if (right < 0) return false; // nothing to do formWindow()->beginCommand(QApplication::translate("Command", "Move action")); QAction *action_b_before = safeActionAt(right + 1); QDesignerFormWindowInterface *fw = formWindow(); RemoveActionFromCommand *cmd1 = new RemoveActionFromCommand(fw); cmd1->init(this, action_b, action_b_before, false); fw->commandHistory()->push(cmd1); QAction *action_a_before = safeActionAt(left + 1); InsertActionIntoCommand *cmd2 = new InsertActionIntoCommand(fw); cmd2->init(this, action_b, action_a_before, false); fw->commandHistory()->push(cmd2); RemoveActionFromCommand *cmd3 = new RemoveActionFromCommand(fw); cmd3->init(this, action_a, action_b, false); fw->commandHistory()->push(cmd3); InsertActionIntoCommand *cmd4 = new InsertActionIntoCommand(fw); cmd4->init(this, action_a, action_b_before, true); fw->commandHistory()->push(cmd4); fw->endCommand(); return true; } void QDesignerMenuBar::keyPressEvent(QKeyEvent *event) { event->ignore(); } void QDesignerMenuBar::keyReleaseEvent(QKeyEvent *event) { event->ignore(); } void QDesignerMenuBar::updateCurrentAction(bool selectAction) { update(); if (!selectAction) return; QAction *action = currentAction(); if (!action || action == m_addMenu) return; QMenu *menu = action->menu(); if (!menu) return; QDesignerObjectInspector *oi = nullptr; if (QDesignerFormWindowInterface *fw = formWindow()) oi = qobject_cast(fw->core()->objectInspector()); if (!oi) return; oi->clearSelection(); oi->selectObject(menu); } QT_END_NAMESPACE