/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the Qt Designer of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qdesigner_menu_p.h" | |
#include "qdesigner_menubar_p.h" | |
#include "qdesigner_toolbar_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 "qdesigner_objectinspector_p.h" | |
#include <QtCore/QTimer> | |
#include <QtCore/qdebug.h> | |
#include <QtDesigner/QDesignerFormEditorInterface> | |
#include <QtDesigner/QDesignerWidgetFactoryInterface> | |
#include <QtDesigner/QDesignerMetaDataBaseInterface> | |
#include <QtDesigner/QExtensionManager> | |
#include <QtGui/QAction> | |
#include <QtGui/QApplication> | |
#include <QtGui/QLineEdit> | |
#include <QtGui/QPainter> | |
#include <QtGui/QRubberBand> | |
#include <QtGui/QToolTip> | |
#include <QtGui/QToolBar> | |
#include <QtGui/qevent.h> | |
Q_DECLARE_METATYPE(QAction*) | |
QT_BEGIN_NAMESPACE | |
using namespace qdesigner_internal; | |
// give the user a little more space to click on the sub menu rectangle | |
static inline void extendClickableArea(QRect *subMenuRect, Qt::LayoutDirection dir) | |
{ | |
switch (dir) { | |
case Qt::LayoutDirectionAuto: // Should never happen | |
case Qt::LeftToRight: | |
subMenuRect->setLeft(subMenuRect->left() - 20); | |
break; | |
case Qt::RightToLeft: | |
subMenuRect->setRight(subMenuRect->right() + 20); | |
break; | |
} | |
} | |
QDesignerMenu::QDesignerMenu(QWidget *parent) : | |
QMenu(parent), | |
m_subMenuPixmap(QPixmap(QLatin1String(":/trolltech/formeditor/images/submenu.png"))), | |
m_currentIndex(0), | |
m_addItem(new SpecialMenuAction(this)), | |
m_addSeparator(new SpecialMenuAction(this)), | |
m_showSubMenuTimer(new QTimer(this)), | |
m_deactivateWindowTimer(new QTimer(this)), | |
m_adjustSizeTimer(new QTimer(this)), | |
m_editor(new QLineEdit(this)), | |
m_dragging(false), | |
m_lastSubMenuIndex(-1) | |
{ | |
setContextMenuPolicy(Qt::DefaultContextMenu); | |
setAcceptDrops(true); // ### fake | |
setSeparatorsCollapsible(false); | |
connect(m_adjustSizeTimer, SIGNAL(timeout()), this, SLOT(slotAdjustSizeNow())); | |
m_addItem->setText(tr("Type Here")); | |
addAction(m_addItem); | |
m_addSeparator->setText(tr("Add Separator")); | |
addAction(m_addSeparator); | |
connect(m_showSubMenuTimer, SIGNAL(timeout()), this, SLOT(slotShowSubMenuNow())); | |
connect(m_deactivateWindowTimer, SIGNAL(timeout()), this, SLOT(slotDeactivateNow())); | |
m_editor->setObjectName(QLatin1String("__qt__passive_editor")); | |
m_editor->hide(); | |
m_editor->installEventFilter(this); | |
installEventFilter(this); | |
} | |
QDesignerMenu::~QDesignerMenu() | |
{ | |
} | |
void QDesignerMenu::slotAdjustSizeNow() | |
{ | |
// Not using a single-shot, since we want to compress the timers if many items are being | |
// adjusted | |
m_adjustSizeTimer->stop(); | |
adjustSize(); | |
} | |
bool QDesignerMenu::handleEvent(QWidget *widget, QEvent *event) | |
{ | |
if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) { | |
update(); | |
if (widget == m_editor) | |
return false; | |
} | |
switch (event->type()) { | |
default: break; | |
case QEvent::MouseButtonPress: | |
return handleMousePressEvent(widget, static_cast<QMouseEvent*>(event)); | |
case QEvent::MouseButtonRelease: | |
return handleMouseReleaseEvent(widget, static_cast<QMouseEvent*>(event)); | |
case QEvent::MouseButtonDblClick: | |
return handleMouseDoubleClickEvent(widget, static_cast<QMouseEvent*>(event)); | |
case QEvent::MouseMove: | |
return handleMouseMoveEvent(widget, static_cast<QMouseEvent*>(event)); | |
case QEvent::ContextMenu: | |
return handleContextMenuEvent(widget, static_cast<QContextMenuEvent*>(event)); | |
case QEvent::KeyPress: | |
return handleKeyPressEvent(widget, static_cast<QKeyEvent*>(event)); | |
} | |
return true; | |
} | |
void QDesignerMenu::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers) | |
{ | |
const int index = findAction(pos); | |
if (index >= realActionCount()) | |
return; | |
QAction *action = safeActionAt(index); | |
QDesignerFormWindowInterface *fw = formWindow(); | |
const Qt::DropAction dropAction = (modifiers & Qt::ControlModifier) ? Qt::CopyAction : Qt::MoveAction; | |
if (dropAction == Qt::MoveAction) { | |
RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); | |
cmd->init(this, action, actions().at(index + 1)); | |
fw->commandHistory()->push(cmd); | |
} | |
QDrag *drag = new QDrag(this); | |
drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action)); | |
drag->setMimeData(new ActionRepositoryMimeData(action, dropAction)); | |
const int old_index = m_currentIndex; | |
m_currentIndex = -1; | |
if (drag->start(dropAction) == Qt::IgnoreAction) { | |
if (dropAction == Qt::MoveAction) { | |
QAction *previous = safeActionAt(index); | |
InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); | |
cmd->init(this, action, previous); | |
fw->commandHistory()->push(cmd); | |
} | |
m_currentIndex = old_index; | |
} | |
} | |
bool QDesignerMenu::handleKeyPressEvent(QWidget * /*widget*/, QKeyEvent *e) | |
{ | |
m_showSubMenuTimer->stop(); | |
if (m_editor->isHidden() && hasFocus()) { // In navigation mode | |
switch (e->key()) { | |
case Qt::Key_Delete: | |
if (m_currentIndex == -1 || m_currentIndex >= realActionCount()) | |
break; | |
hideSubMenu(); | |
deleteAction(); | |
break; | |
case Qt::Key_Left: | |
e->accept(); | |
moveLeft(); | |
return true; | |
case Qt::Key_Right: | |
e->accept(); | |
moveRight(); | |
return true; // no update | |
case Qt::Key_Up: | |
e->accept(); | |
moveUp(e->modifiers() & Qt::ControlModifier); | |
return true; | |
case Qt::Key_Down: | |
e->accept(); | |
moveDown(e->modifiers() & Qt::ControlModifier); | |
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: | |
case Qt::Key_F2: | |
e->accept(); | |
enterEditMode(); | |
return true; // no update | |
case Qt::Key_Escape: | |
e->ignore(); | |
setFocus(); | |
hide(); | |
closeMenuChain(); | |
return true; | |
case Qt::Key_Alt: | |
case Qt::Key_Shift: | |
case Qt::Key_Control: | |
e->ignore(); | |
setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed | |
return true; // no update | |
default: { | |
QAction *action = currentAction(); | |
if (!action || action->isSeparator() || action == m_addSeparator) { | |
e->ignore(); | |
return true; | |
} else if (!e->text().isEmpty() && e->text().at(0).toLatin1() >= 32) { | |
showLineEdit(); | |
QApplication::sendEvent(m_editor, e); | |
e->accept(); | |
} else { | |
e->ignore(); | |
} | |
} | |
return true; | |
} | |
} else if (m_editor->hasFocus()) { // In edit mode | |
switch (e->key()) { | |
default: | |
e->ignore(); | |
return false; | |
case Qt::Key_Enter: | |
case Qt::Key_Return: | |
if (!m_editor->text().isEmpty()) { | |
leaveEditMode(ForceAccept); | |
m_editor->hide(); | |
setFocus(); | |
moveDown(false); | |
break; | |
} | |
// fall through | |
case Qt::Key_Escape: | |
m_editor->hide(); | |
setFocus(); | |
break; | |
} | |
} | |
e->accept(); | |
update(); | |
return true; | |
} | |
static void sendMouseEventTo(QWidget *target, const QPoint &targetPoint, const QMouseEvent *event) | |
{ | |
QMouseEvent e(event->type(), targetPoint, event->globalPos(), event->button(), event->buttons(), event->modifiers()); | |
QApplication::sendEvent(target, &e); | |
} | |
bool QDesignerMenu::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event) | |
{ | |
event->accept(); | |
m_startPosition = QPoint(); | |
if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) | |
return true; | |
if (!rect().contains(event->pos())) { | |
// special case for menubar | |
QWidget *target = QApplication::widgetAt(event->globalPos()); | |
QMenuBar *mb = qobject_cast<QMenuBar*>(target); | |
QDesignerMenu *menu = qobject_cast<QDesignerMenu*>(target); | |
if (mb != 0 || menu != 0) { | |
const QPoint pt = target->mapFromGlobal(event->globalPos()); | |
QAction *action = mb == 0 ? menu->actionAt(pt) : mb->actionAt(pt); | |
if (action) | |
sendMouseEventTo(target, pt, event); | |
} | |
return true; | |
} | |
m_currentIndex = findAction(event->pos()); | |
QAction *action = safeActionAt(m_currentIndex); | |
QRect pm_rect; | |
if (action->menu() || hasSubMenuPixmap(action)) { | |
pm_rect = subMenuPixmapRect(action); | |
extendClickableArea(&pm_rect, layoutDirection()); | |
} | |
if (!pm_rect.contains(event->pos()) && m_currentIndex != -1) | |
enterEditMode(); | |
return true; | |
} | |
bool QDesignerMenu::handleMousePressEvent(QWidget * /*widget*/, QMouseEvent *event) | |
{ | |
if (!rect().contains(event->pos())) { | |
QWidget *clickedWidget = QApplication::widgetAt(event->globalPos()); | |
if (QMenuBar *mb = qobject_cast<QMenuBar*>(clickedWidget)) { | |
const QPoint pt = mb->mapFromGlobal(event->globalPos()); | |
if (QAction *action = mb->actionAt(pt)) { | |
QMenu * menu = action->menu(); | |
if (menu == findRootMenu()) { | |
// propagate the mouse press event (but don't close the popup) | |
sendMouseEventTo(mb, pt, event); | |
return true; | |
} | |
} | |
} | |
if (QDesignerMenu *m = qobject_cast<QDesignerMenu *>(clickedWidget)) { | |
m->hideSubMenu(); | |
sendMouseEventTo(m, m->mapFromGlobal(event->globalPos()), event); | |
} else { | |
QDesignerMenu *root = findRootMenu(); | |
root->hide(); | |
root->hideSubMenu(); | |
} | |
if (clickedWidget) { | |
if (QWidget *focusProxy = clickedWidget->focusProxy()) | |
clickedWidget = focusProxy; | |
if (clickedWidget->focusPolicy() != Qt::NoFocus) | |
clickedWidget->setFocus(Qt::OtherFocusReason); | |
} | |
return true; | |
} | |
m_showSubMenuTimer->stop(); | |
m_startPosition = QPoint(); | |
event->accept(); | |
if (event->button() != Qt::LeftButton) | |
return true; | |
m_startPosition = mapFromGlobal(event->globalPos()); | |
const int index = findAction(m_startPosition); | |
QAction *action = safeActionAt(index); | |
QRect pm_rect = subMenuPixmapRect(action); | |
extendClickableArea(&pm_rect, layoutDirection()); | |
const int old_index = m_currentIndex; | |
m_currentIndex = index; | |
if ((hasSubMenuPixmap(action) || action->menu() != 0) | |
&& pm_rect.contains(m_startPosition)) { | |
if (m_currentIndex == m_lastSubMenuIndex) { | |
hideSubMenu(); | |
} else | |
slotShowSubMenuNow(); | |
} else { | |
if (index == old_index) { | |
if (m_currentIndex == m_lastSubMenuIndex) | |
hideSubMenu(); | |
} else { | |
hideSubMenu(); | |
} | |
} | |
update(); | |
if (index != old_index) | |
selectCurrentAction(); | |
return true; | |
} | |
bool QDesignerMenu::handleMouseReleaseEvent(QWidget *, QMouseEvent *event) | |
{ | |
event->accept(); | |
m_startPosition = QPoint(); | |
return true; | |
} | |
bool QDesignerMenu::handleMouseMoveEvent(QWidget *, QMouseEvent *event) | |
{ | |
if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) | |
return true; | |
if (!rect().contains(event->pos())) { | |
if (QMenuBar *mb = qobject_cast<QMenuBar*>(QApplication::widgetAt(event->globalPos()))) { | |
const QPoint pt = mb->mapFromGlobal(event->globalPos()); | |
QAction *action = mb->actionAt(pt); | |
if (action && action->menu() == findRootMenu()) { | |
// propagate the mouse press event (but don't close the popup) | |
sendMouseEventTo(mb, pt, event); | |
return true; | |
} | |
// hide the popup Qt will replay the event | |
slotDeactivateNow(); | |
} | |
return true; | |
} | |
if (m_startPosition.isNull()) | |
return true; | |
event->accept(); | |
const QPoint pos = mapFromGlobal(event->globalPos()); | |
if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance()) | |
return true; | |
startDrag(m_startPosition, event->modifiers()); | |
m_startPosition = QPoint(); | |
return true; | |
} | |
bool QDesignerMenu::handleContextMenuEvent(QWidget *, QContextMenuEvent *event) | |
{ | |
event->accept(); | |
const int index = findAction(mapFromGlobal(event->globalPos())); | |
QAction *action = safeActionAt(index); | |
if (qobject_cast<SpecialMenuAction*>(action)) | |
return true; | |
QMenu menu; | |
QVariant itemData; | |
qVariantSetValue(itemData, action); | |
QAction *addSeparatorAction = menu.addAction(tr("Insert separator")); | |
addSeparatorAction->setData(itemData); | |
QAction *removeAction = 0; | |
if (action->isSeparator()) | |
removeAction = menu.addAction(tr("Remove separator")); | |
else | |
removeAction = menu.addAction(tr("Remove action '%1'").arg(action->objectName())); | |
removeAction->setData(itemData); | |
connect(addSeparatorAction, SIGNAL(triggered(bool)), this, SLOT(slotAddSeparator())); | |
connect(removeAction, SIGNAL(triggered(bool)), this, SLOT(slotRemoveSelectedAction())); | |
menu.exec(event->globalPos()); | |
return true; | |
} | |
void QDesignerMenu::slotAddSeparator() | |
{ | |
QAction *action = qobject_cast<QAction *>(sender()); | |
if (!action) | |
return; | |
QAction *a = qvariant_cast<QAction*>(action->data()); | |
Q_ASSERT(a != 0); | |
const int pos = actions().indexOf(a); | |
QAction *action_before = 0; | |
if (pos != -1) | |
action_before = safeActionAt(pos); | |
QDesignerFormWindowInterface *fw = formWindow(); | |
fw->beginCommand(tr("Add separator")); | |
QAction *sep = createAction(QString(), true); | |
InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); | |
cmd->init(this, sep, action_before); | |
fw->commandHistory()->push(cmd); | |
if (parentMenu()) { | |
QAction *parent_action = parentMenu()->currentAction(); | |
if (parent_action->menu() == 0) { | |
CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw); | |
cmd->init(parentMenu(), parentMenu()->currentAction()); | |
fw->commandHistory()->push(cmd); | |
} | |
} | |
fw->endCommand(); | |
} | |
void QDesignerMenu::slotRemoveSelectedAction() | |
{ | |
if (QAction *action = qobject_cast<QAction *>(sender())) | |
if (QAction *a = qvariant_cast<QAction*>(action->data())) | |
deleteAction(a); | |
} | |
void QDesignerMenu::deleteAction(QAction *a) | |
{ | |
const int pos = actions().indexOf(a); | |
QAction *action_before = 0; | |
if (pos != -1) | |
action_before = safeActionAt(pos + 1); | |
QDesignerFormWindowInterface *fw = formWindow(); | |
RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); | |
cmd->init(this, a, action_before); | |
fw->commandHistory()->push(cmd); | |
} | |
QRect QDesignerMenu::subMenuPixmapRect(QAction *action) const | |
{ | |
const QRect g = actionGeometry(action); | |
const int x = layoutDirection() == Qt::LeftToRight ? (g.right() - m_subMenuPixmap.width() - 2) : 2; | |
const int y = g.top() + (g.height() - m_subMenuPixmap.height())/2 + 1; | |
return QRect(x, y, m_subMenuPixmap.width(), m_subMenuPixmap.height()); | |
} | |
bool QDesignerMenu::hasSubMenuPixmap(QAction *action) const | |
{ | |
return action != 0 | |
&& qobject_cast<SpecialMenuAction*>(action) == 0 | |
&& !action->isSeparator() | |
&& !action->menu() | |
&& canCreateSubMenu(action); | |
} | |
void QDesignerMenu::showEvent ( QShowEvent * event ) | |
{ | |
selectCurrentAction(); | |
QMenu::showEvent (event); | |
} | |
void QDesignerMenu::paintEvent(QPaintEvent *event) | |
{ | |
QMenu::paintEvent(event); | |
QPainter p(this); | |
QAction *current = currentAction(); | |
foreach (QAction *a, actions()) { | |
const QRect g = actionGeometry(a); | |
if (qobject_cast<SpecialMenuAction*>(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); | |
} else if (hasSubMenuPixmap(a)) { | |
p.drawPixmap(subMenuPixmapRect(a).topLeft(), m_subMenuPixmap); | |
} | |
} | |
if (!hasFocus() || !current || m_dragging) | |
return; | |
if (QDesignerMenu *menu = parentMenu()) { | |
if (menu->dragging()) | |
return; | |
} | |
if (QDesignerMenuBar *menubar = qobject_cast<QDesignerMenuBar*>(parentWidget())) { | |
if (menubar->dragging()) | |
return; | |
} | |
const QRect g = actionGeometry(current); | |
drawSelection(&p, g.adjusted(1, 1, -3, -3)); | |
} | |
bool QDesignerMenu::dragging() const | |
{ | |
return m_dragging; | |
} | |
QDesignerMenu *QDesignerMenu::findRootMenu() const | |
{ | |
if (parentMenu()) | |
return parentMenu()->findRootMenu(); | |
return const_cast<QDesignerMenu*>(this); | |
} | |
QDesignerMenu *QDesignerMenu::findActivatedMenu() const | |
{ | |
QList<QDesignerMenu*> candidates; | |
candidates.append(const_cast<QDesignerMenu*>(this)); | |
candidates += qFindChildren<QDesignerMenu*>(this); | |
foreach (QDesignerMenu *m, candidates) { | |
if (m == qApp->activeWindow()) | |
return m; | |
} | |
return 0; | |
} | |
bool QDesignerMenu::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 false; | |
} | |
bool dispatch = true; | |
switch (event->type()) { | |
default: break; | |
case QEvent::WindowDeactivate: | |
deactivateMenu(); | |
break; | |
case QEvent::ContextMenu: | |
case QEvent::MouseButtonPress: | |
case QEvent::MouseButtonRelease: | |
case QEvent::MouseButtonDblClick: | |
while (QApplication::activePopupWidget() && !qobject_cast<QDesignerMenu*>(QApplication::activePopupWidget())) { | |
QApplication::activePopupWidget()->close(); | |
} | |
// fall through | |
case QEvent::KeyPress: | |
case QEvent::KeyRelease: | |
case QEvent::MouseMove: | |
dispatch = (object != m_editor); | |
// no break | |
case QEvent::Enter: | |
case QEvent::Leave: | |
case QEvent::FocusIn: | |
case QEvent::FocusOut: | |
if (dispatch) | |
if (QWidget *widget = qobject_cast<QWidget*>(object)) | |
if (widget == this || isAncestorOf(widget)) | |
return handleEvent(widget, event); | |
break; | |
} | |
return false; | |
}; | |
int QDesignerMenu::findAction(const QPoint &pos) const | |
{ | |
const int index = actionIndexAt(this, pos, Qt::Vertical); | |
if (index == -1) | |
return realActionCount(); | |
return index; | |
} | |
void QDesignerMenu::adjustIndicator(const QPoint &pos) | |
{ | |
if (QDesignerActionProviderExtension *a = actionProvider()) { | |
a->adjustIndicator(pos); | |
} | |
} | |
QDesignerMenu::ActionDragCheck QDesignerMenu::checkAction(QAction *action) const | |
{ | |
if (!action || (action->menu() && action->menu()->parentWidget() != const_cast<QDesignerMenu*>(this))) | |
return NoActionDrag; // menu action!! nothing to do | |
if (!Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) | |
return NoActionDrag; // the action belongs to another form window | |
if (actions().contains(action)) | |
return ActionDragOnSubMenu; // we already have the action in the menu | |
return AcceptActionDrag; | |
} | |
void QDesignerMenu::dragEnterEvent(QDragEnterEvent *event) | |
{ | |
const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData()); | |
if (!d || d->actionList().empty()) { | |
event->ignore(); | |
return; | |
} | |
QAction *action = d->actionList().first(); | |
switch (checkAction(action)) { | |
case NoActionDrag: | |
event->ignore(); | |
break; | |
case ActionDragOnSubMenu: | |
d->accept(event); | |
m_dragging = true; | |
break; | |
case AcceptActionDrag: | |
d->accept(event); | |
m_dragging = true; | |
adjustIndicator(event->pos()); | |
break; | |
} | |
} | |
void QDesignerMenu::dragMoveEvent(QDragMoveEvent *event) | |
{ | |
if (actionGeometry(m_addSeparator).contains(event->pos())) { | |
event->ignore(); | |
adjustIndicator(QPoint(-1, -1)); | |
return; | |
} | |
const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData()); | |
if (!d || d->actionList().empty()) { | |
event->ignore(); | |
return; | |
} | |
QAction *action = d->actionList().first(); | |
const ActionDragCheck dc = checkAction(action); | |
switch (dc) { | |
case NoActionDrag: | |
event->ignore(); | |
break; | |
case ActionDragOnSubMenu: | |
case AcceptActionDrag: { // Do not pop up submenu of action being dragged | |
const int newIndex = findAction(event->pos()); | |
if (safeActionAt(newIndex) != action) { | |
m_currentIndex = newIndex; | |
if (m_lastSubMenuIndex != m_currentIndex) | |
m_showSubMenuTimer->start(300); | |
} | |
if (dc == AcceptActionDrag) { | |
adjustIndicator(event->pos()); | |
d->accept(event); | |
} else { | |
event->ignore(); | |
} | |
} | |
break; | |
} | |
} | |
void QDesignerMenu::dragLeaveEvent(QDragLeaveEvent *) | |
{ | |
m_dragging = false; | |
adjustIndicator(QPoint(-1, -1)); | |
m_showSubMenuTimer->stop(); | |
} | |
void QDesignerMenu::dropEvent(QDropEvent *event) | |
{ | |
m_showSubMenuTimer->stop(); | |
hideSubMenu(); | |
m_dragging = false; | |
QDesignerFormWindowInterface *fw = formWindow(); | |
const ActionRepositoryMimeData *d = qobject_cast<const ActionRepositoryMimeData*>(event->mimeData()); | |
if (!d || d->actionList().empty()) { | |
event->ignore(); | |
return; | |
} | |
QAction *action = d->actionList().first(); | |
if (action && checkAction(action) == AcceptActionDrag) { | |
event->acceptProposedAction(); | |
int index = findAction(event->pos()); | |
index = qMin(index, actions().count() - 1); | |
fw->beginCommand(tr("Insert action")); | |
InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); | |
cmd->init(this, action, safeActionAt(index)); | |
fw->commandHistory()->push(cmd); | |
m_currentIndex = index; | |
if (parentMenu()) { | |
QAction *parent_action = parentMenu()->currentAction(); | |
if (parent_action->menu() == 0) { | |
CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw); | |
cmd->init(parentMenu(), parentMenu()->currentAction(), action); | |
fw->commandHistory()->push(cmd); | |
} | |
} | |
update(); | |
fw->endCommand(); | |
} else { | |
event->ignore(); | |
} | |
adjustIndicator(QPoint(-1, -1)); | |
} | |
void QDesignerMenu::actionEvent(QActionEvent *event) | |
{ | |
QMenu::actionEvent(event); | |
m_adjustSizeTimer->start(0); | |
} | |
QDesignerFormWindowInterface *QDesignerMenu::formWindow() const | |
{ | |
if (parentMenu()) | |
return parentMenu()->formWindow(); | |
return QDesignerFormWindowInterface::findFormWindow(parentWidget()); | |
} | |
QDesignerActionProviderExtension *QDesignerMenu::actionProvider() | |
{ | |
if (QDesignerFormWindowInterface *fw = formWindow()) { | |
QDesignerFormEditorInterface *core = fw->core(); | |
return qt_extension<QDesignerActionProviderExtension*>(core->extensionManager(), this); | |
} | |
return 0; | |
} | |
void QDesignerMenu::closeMenuChain() | |
{ | |
m_showSubMenuTimer->stop(); | |
QWidget *w = this; | |
while (w && qobject_cast<QMenu*>(w)) | |
w = w->parentWidget(); | |
if (w) { | |
foreach (QMenu *subMenu, qFindChildren<QMenu*>(w)) { | |
subMenu->hide(); | |
} | |
} | |
m_lastSubMenuIndex = -1; | |
} | |
// Close submenu using the left/right keys according to layoutDirection(). | |
// Return false to indicate the event must be propagated to the menu bar. | |
bool QDesignerMenu::hideSubMenuOnCursorKey() | |
{ | |
if (parentMenu()) { | |
hide(); | |
return true; | |
} | |
closeMenuChain(); | |
update(); | |
if (parentMenuBar()) | |
return false; | |
return true; | |
} | |
// Open a submenu using the left/right keys according to layoutDirection(). | |
// Return false to indicate the event must be propagated to the menu bar. | |
bool QDesignerMenu::showSubMenuOnCursorKey() | |
{ | |
const QAction *action = currentAction(); | |
if (qobject_cast<const SpecialMenuAction*>(action) || action->isSeparator()) { | |
closeMenuChain(); | |
if (parentMenuBar()) | |
return false; | |
return true; | |
} | |
m_lastSubMenuIndex = -1; // force a refresh | |
slotShowSubMenuNow(); | |
return true; | |
} | |
void QDesignerMenu::moveLeft() | |
{ | |
const bool handled = layoutDirection() == Qt::LeftToRight ? | |
hideSubMenuOnCursorKey() : showSubMenuOnCursorKey(); | |
if (!handled) | |
parentMenuBar()->moveLeft(); | |
} | |
void QDesignerMenu::moveRight() | |
{ | |
const bool handled = layoutDirection() == Qt::LeftToRight ? | |
showSubMenuOnCursorKey() : hideSubMenuOnCursorKey(); | |
if (!handled) | |
parentMenuBar()->moveRight(); | |
} | |
void QDesignerMenu::moveUp(bool ctrl) | |
{ | |
if (m_currentIndex == 0) { | |
hide(); | |
return; | |
} | |
if (ctrl) | |
(void) swap(m_currentIndex, m_currentIndex - 1); | |
--m_currentIndex; | |
m_currentIndex = qMax(0, m_currentIndex); | |
// Always re-select, swapping destroys order | |
update(); | |
selectCurrentAction(); | |
} | |
void QDesignerMenu::moveDown(bool ctrl) | |
{ | |
if (m_currentIndex == actions().count() - 1) { | |
return; | |
} | |
if (ctrl) | |
(void) swap(m_currentIndex + 1, m_currentIndex); | |
++m_currentIndex; | |
m_currentIndex = qMin(actions().count() - 1, m_currentIndex); | |
update(); | |
if (!ctrl) | |
selectCurrentAction(); | |
} | |
QAction *QDesignerMenu::currentAction() const | |
{ | |
if (m_currentIndex < 0 || m_currentIndex >= actions().count()) | |
return 0; | |
return safeActionAt(m_currentIndex); | |
} | |
int QDesignerMenu::realActionCount() const | |
{ | |
return actions().count() - 2; // 2 fake actions | |
} | |
void QDesignerMenu::selectCurrentAction() | |
{ | |
QAction *action = currentAction(); | |
if (!action || action == m_addSeparator || action == m_addItem) | |
return; | |
QDesignerObjectInspector *oi = 0; | |
if (QDesignerFormWindowInterface *fw = formWindow()) | |
oi = qobject_cast<QDesignerObjectInspector *>(fw->core()->objectInspector()); | |
if (!oi) | |
return; | |
oi->clearSelection(); | |
if (QMenu *menu = action->menu()) | |
oi->selectObject(menu); | |
else | |
oi->selectObject(action); | |
} | |
void QDesignerMenu::createRealMenuAction(QAction *action) | |
{ | |
if (action->menu()) | |
return; // nothing to do | |
QDesignerFormWindowInterface *fw = formWindow(); | |
QDesignerFormEditorInterface *core = formWindow()->core(); | |
QDesignerMenu *menu = findOrCreateSubMenu(action); | |
m_subMenus.remove(action); | |
action->setMenu(menu); | |
menu->setTitle(action->text()); | |
Q_ASSERT(fw); | |
core->widgetFactory()->initialize(menu); | |
const QString niceObjectName = ActionEditor::actionTextToName(menu->title(), QLatin1String("menu")); | |
menu->setObjectName(niceObjectName); | |
core->metaDataBase()->add(menu); | |
fw->ensureUniqueObjectName(menu); | |
QAction *menuAction = menu->menuAction(); | |
core->metaDataBase()->add(menuAction); | |
} | |
void QDesignerMenu::removeRealMenu(QAction *action) | |
{ | |
QDesignerMenu *menu = qobject_cast<QDesignerMenu*>(action->menu()); | |
if (menu == 0) | |
return; | |
action->setMenu(0); | |
m_subMenus.insert(action, menu); | |
QDesignerFormEditorInterface *core = formWindow()->core(); | |
core->metaDataBase()->remove(menu); | |
} | |
QDesignerMenu *QDesignerMenu::findOrCreateSubMenu(QAction *action) | |
{ | |
if (action->menu()) | |
return qobject_cast<QDesignerMenu*>(action->menu()); | |
QDesignerMenu *menu = m_subMenus.value(action); | |
if (!menu) { | |
menu = new QDesignerMenu(this); | |
m_subMenus.insert(action, menu); | |
} | |
return menu; | |
} | |
bool QDesignerMenu::canCreateSubMenu(QAction *action) const // ### improve it's a bit too slow | |
{ | |
foreach (const QWidget *aw, action->associatedWidgets()) | |
if (aw != this) { | |
if (const QMenu *m = qobject_cast<const QMenu *>(aw)) { | |
if (m->actions().contains(action)) | |
return false; // sorry | |
} else { | |
if (const QToolBar *tb = qobject_cast<const QToolBar *>(aw)) | |
if (tb->actions().contains(action)) | |
return false; // sorry | |
} | |
} | |
return true; | |
} | |
void QDesignerMenu::slotShowSubMenuNow() | |
{ | |
m_showSubMenuTimer->stop(); | |
if (m_lastSubMenuIndex == m_currentIndex) | |
return; | |
if (m_lastSubMenuIndex != -1) | |
hideSubMenu(); | |
if (m_currentIndex >= realActionCount()) | |
return; | |
QAction *action = currentAction(); | |
if (action->isSeparator() || !canCreateSubMenu(action)) | |
return; | |
if (QMenu *menu = findOrCreateSubMenu(action)) { | |
if (!menu->isVisible()) { | |
if ((menu->windowFlags() & Qt::Popup) != Qt::Popup) | |
menu->setWindowFlags(Qt::Popup); | |
const QRect g = actionGeometry(action); | |
if (layoutDirection() == Qt::LeftToRight) { | |
menu->move(mapToGlobal(g.topRight())); | |
} else { | |
// The position is not initially correct due to the unknown width, | |
// causing it to overlap a bit the first time it is invoked. | |
const QSize menuSize = menu->size(); | |
QPoint point = g.topLeft() - QPoint(menu->width() + 10, 0); | |
menu->move(mapToGlobal(point)); | |
} | |
menu->show(); | |
menu->setFocus(); | |
} else { | |
menu->raise(); | |
} | |
menu->setFocus(); | |
m_lastSubMenuIndex = m_currentIndex; | |
} | |
} | |
void QDesignerMenu::showSubMenu(QAction *action) | |
{ | |
m_showSubMenuTimer->stop(); | |
if (m_editor->isVisible() || !action || qobject_cast<SpecialMenuAction*>(action) | |
|| action->isSeparator() || !isVisible()) | |
return; | |
m_showSubMenuTimer->start(300); | |
} | |
QDesignerMenu *QDesignerMenu::parentMenu() const | |
{ | |
return qobject_cast<QDesignerMenu*>(parentWidget()); | |
} | |
QDesignerMenuBar *QDesignerMenu::parentMenuBar() const | |
{ | |
if (QDesignerMenuBar *mb = qobject_cast<QDesignerMenuBar*>(parentWidget())) { | |
return mb; | |
} else if (QDesignerMenu *m = parentMenu()) { | |
return m->parentMenuBar(); | |
} | |
return 0; | |
} | |
void QDesignerMenu::setVisible(bool visible) | |
{ | |
if (visible) | |
m_currentIndex = 0; | |
else | |
m_lastSubMenuIndex = -1; | |
QMenu::setVisible(visible); | |
} | |
void QDesignerMenu::adjustSpecialActions() | |
{ | |
removeAction(m_addItem); | |
removeAction(m_addSeparator); | |
addAction(m_addItem); | |
addAction(m_addSeparator); | |
} | |
bool QDesignerMenu::interactive(bool i) | |
{ | |
const bool old = m_interactive; | |
m_interactive = i; | |
return old; | |
} | |
void QDesignerMenu::enterEditMode() | |
{ | |
if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) { | |
showLineEdit(); | |
} else { | |
hideSubMenu(); | |
QDesignerFormWindowInterface *fw = formWindow(); | |
fw->beginCommand(tr("Add separator")); | |
QAction *sep = createAction(QString(), true); | |
InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); | |
cmd->init(this, sep, safeActionAt(realActionCount())); | |
fw->commandHistory()->push(cmd); | |
if (parentMenu()) { | |
QAction *parent_action = parentMenu()->currentAction(); | |
if (parent_action->menu() == 0) { | |
CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw); | |
cmd->init(parentMenu(), parentMenu()->currentAction()); | |
fw->commandHistory()->push(cmd); | |
} | |
} | |
fw->endCommand(); | |
m_currentIndex = actions().indexOf(m_addItem); | |
update(); | |
} | |
} | |
void QDesignerMenu::leaveEditMode(LeaveEditMode mode) | |
{ | |
if (mode == Default) | |
return; | |
QAction *action = 0; | |
QDesignerFormWindowInterface *fw = formWindow(); | |
if (m_currentIndex < realActionCount()) { | |
action = safeActionAt(m_currentIndex); | |
fw->beginCommand(QApplication::translate("Command", "Set action text")); | |
} else { | |
Q_ASSERT(fw != 0); | |
fw->beginCommand(QApplication::translate("Command", "Insert action")); | |
action = createAction(ActionEditor::actionTextToName(m_editor->text())); | |
InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); | |
cmd->init(this, action, currentAction()); | |
fw->commandHistory()->push(cmd); | |
} | |
SetPropertyCommand *cmd = new SetPropertyCommand(fw); | |
cmd->init(action, QLatin1String("text"), m_editor->text()); | |
fw->commandHistory()->push(cmd); | |
if (parentMenu()) { | |
QAction *parent_action = parentMenu()->currentAction(); | |
if (parent_action->menu() == 0) { | |
CreateSubmenuCommand *cmd = new CreateSubmenuCommand(fw); | |
cmd->init(parentMenu(), parentMenu()->currentAction(), action); | |
fw->commandHistory()->push(cmd); | |
} | |
} | |
update(); | |
fw->endCommand(); | |
} | |
QAction *QDesignerMenu::safeMenuAction(QDesignerMenu *menu) const | |
{ | |
QAction *action = menu->menuAction(); | |
if (!action) | |
action = m_subMenus.key(menu); | |
return action; | |
} | |
void QDesignerMenu::showLineEdit() | |
{ | |
m_showSubMenuTimer->stop(); | |
QAction *action = 0; | |
if (m_currentIndex < realActionCount()) | |
action = safeActionAt(m_currentIndex); | |
else | |
action = m_addItem; | |
if (action->isSeparator()) | |
return; | |
hideSubMenu(); | |
// open edit field for item name | |
setFocus(); | |
const QString text = action != m_addItem ? action->text() : QString(); | |
m_editor->setText(text); | |
m_editor->selectAll(); | |
m_editor->setGeometry(actionGeometry(action).adjusted(1, 1, -2, -2)); | |
m_editor->show(); | |
m_editor->setFocus(); | |
} | |
QAction *QDesignerMenu::createAction(const QString &objectName, bool separator) | |
{ | |
QDesignerFormWindowInterface *fw = formWindow(); | |
Q_ASSERT(fw); | |
return ToolBarEventFilter::createAction(fw, objectName, separator); | |
} | |
// ### share with QDesignerMenu::swap | |
bool QDesignerMenu::swap(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<SpecialMenuAction*>(action_a) | |
|| qobject_cast<SpecialMenuAction*>(action_b)) | |
return false; // nothing to do | |
right = qMin(right, realActionCount()); | |
if (right < 0) | |
return false; // nothing to do | |
QDesignerFormWindowInterface *fw = formWindow(); | |
fw->beginCommand(QApplication::translate("Command", "Move action")); | |
QAction *action_b_before = safeActionAt(right + 1); | |
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; | |
} | |
QAction *QDesignerMenu::safeActionAt(int index) const | |
{ | |
if (index < 0 || index >= actions().count()) | |
return 0; | |
return actions().at(index); | |
} | |
void QDesignerMenu::hideSubMenu() | |
{ | |
m_lastSubMenuIndex = -1; | |
foreach (QMenu *subMenu, qFindChildren<QMenu*>(this)) { | |
subMenu->hide(); | |
} | |
} | |
void QDesignerMenu::deleteAction() | |
{ | |
QAction *action = currentAction(); | |
const int pos = actions().indexOf(action); | |
QAction *action_before = 0; | |
if (pos != -1) | |
action_before = safeActionAt(pos + 1); | |
QDesignerFormWindowInterface *fw = formWindow(); | |
RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); | |
cmd->init(this, action, action_before); | |
fw->commandHistory()->push(cmd); | |
update(); | |
} | |
void QDesignerMenu::deactivateMenu() | |
{ | |
m_deactivateWindowTimer->start(10); | |
} | |
void QDesignerMenu::slotDeactivateNow() | |
{ | |
m_deactivateWindowTimer->stop(); | |
if (m_dragging) | |
return; | |
QDesignerMenu *root = findRootMenu(); | |
if (! root->findActivatedMenu()) { | |
root->hide(); | |
root->hideSubMenu(); | |
} | |
} | |
void QDesignerMenu::drawSelection(QPainter *p, const QRect &r) | |
{ | |
p->save(); | |
QColor c = Qt::blue; | |
p->setPen(QPen(c, 1)); | |
c.setAlpha(32); | |
p->setBrush(c); | |
p->drawRect(r); | |
p->restore(); | |
} | |
void QDesignerMenu::keyPressEvent(QKeyEvent *event) | |
{ | |
event->ignore(); | |
} | |
void QDesignerMenu::keyReleaseEvent(QKeyEvent *event) | |
{ | |
event->ignore(); | |
} | |
QT_END_NAMESPACE |