diff options
Diffstat (limited to 'tools/designer/src/lib/shared/actionrepository.cpp')
-rw-r--r-- | tools/designer/src/lib/shared/actionrepository.cpp | 659 |
1 files changed, 659 insertions, 0 deletions
diff --git a/tools/designer/src/lib/shared/actionrepository.cpp b/tools/designer/src/lib/shared/actionrepository.cpp new file mode 100644 index 0000000..941a9ba --- /dev/null +++ b/tools/designer/src/lib/shared/actionrepository.cpp @@ -0,0 +1,659 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt Designer of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "actionrepository_p.h" +#include "qtresourceview_p.h" +#include "iconloader_p.h" + +#include <QtDesigner/QDesignerFormEditorInterface> +#include <QtDesigner/QDesignerPropertySheetExtension> +#include <QtDesigner/QExtensionManager> + +#include <QtGui/QDrag> +#include <QtGui/QContextMenuEvent> +#include <QtGui/QStandardItemModel> +#include <QtGui/QToolButton> +#include <QtGui/QPixmap> +#include <QtGui/QAction> +#include <QtGui/QHeaderView> +#include <QtGui/QToolBar> +#include <QtGui/QMenu> +#include <QtGui/qevent.h> +#include <QtCore/QSet> +#include <QtCore/QDebug> + +Q_DECLARE_METATYPE(QAction*) + +QT_BEGIN_NAMESPACE + +namespace { + enum { listModeIconSize = 16, iconModeIconSize = 24 }; +} + +static const char *actionMimeType = "action-repository/actions"; +static const char *plainTextMimeType = "text/plain"; + +static inline QAction *actionOfItem(const QStandardItem* item) +{ + return qvariant_cast<QAction*>(item->data(qdesigner_internal::ActionModel::ActionRole)); +} + +static QIcon fixActionIcon(const QIcon &icon) +{ + if (icon.isNull()) + return qdesigner_internal::emptyIcon(); + return icon; +} + +namespace qdesigner_internal { + +// ----------- ActionModel +ActionModel::ActionModel(QWidget *parent ) : + QStandardItemModel(parent), + m_core(0) +{ + QStringList headers; + headers += tr("Name"); + headers += tr("Used"); + headers += tr("Text"); + headers += tr("Shortcut"); + headers += tr("Checkable"); + headers += tr("ToolTip"); + Q_ASSERT(NumColumns == headers.size()); + setHorizontalHeaderLabels(headers); +} + +void ActionModel::clearActions() +{ + removeRows(0, rowCount()); +} + +int ActionModel::findAction(QAction *action) const +{ + const int rows = rowCount(); + for (int i = 0; i < rows; i++) + if (action == actionOfItem(item(i))) + return i; + return -1; +} + +void ActionModel::update(int row) +{ + Q_ASSERT(m_core); + // need to create the row list ... grrr.. + if (row >= rowCount()) + return; + + QStandardItemList list; + for (int i = 0; i < NumColumns; i++) + list += item(row, i); + + setItems(m_core, actionOfItem(list.front()), list); +} + +void ActionModel::remove(int row) +{ + qDeleteAll(takeRow(row)); +} + +QModelIndex ActionModel::addAction(QAction *action) +{ + Q_ASSERT(m_core); + QStandardItemList items; + const Qt::ItemFlags flags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled; + + QVariant itemData; + qVariantSetValue(itemData, action); + + for (int i = 0; i < NumColumns; i++) { + QStandardItem *item = new QStandardItem; + item->setData(itemData, ActionRole); + item->setFlags(flags); + items.push_back(item); + } + setItems(m_core, action, items); + appendRow(items); + return indexFromItem(items.front()); +} + +// Find the associated menus and toolbars, ignore toolbuttons +QWidgetList ActionModel::associatedWidgets(const QAction *action) +{ + QWidgetList rc = action->associatedWidgets(); + for (QWidgetList::iterator it = rc.begin(); it != rc.end(); ) + if (qobject_cast<const QMenu *>(*it) || qobject_cast<const QToolBar *>(*it)) { + ++it; + } else { + it = rc.erase(it); + } + return rc; +} + +// shortcut is a fake property, need to retrieve it via property sheet. +static QString actionShortCut(QDesignerFormEditorInterface *core, QAction *action) +{ + QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), action); + if (!sheet) + return QString(); + const int index = sheet->indexOf(QLatin1String("shortcut")); + if (index == -1) + return QString(); + const QKeySequence keysequence = qvariant_cast<QKeySequence>(sheet->property(index)); + return keysequence.toString(); +} + +void ActionModel::setItems(QDesignerFormEditorInterface *core, QAction *action, QStandardItemList &sl) +{ + + // Tooltip, mostly for icon view mode + QString firstTooltip = action->objectName(); + const QString text = action->text(); + if (!text.isEmpty()) { + firstTooltip += QLatin1Char('\n'); + firstTooltip += text; + } + + Q_ASSERT(sl.size() == NumColumns); + + QStandardItem *item = sl[NameColumn]; + item->setText(action->objectName()); + item->setIcon(fixActionIcon(action->icon())); + item->setToolTip(firstTooltip); + item->setWhatsThis(firstTooltip); + // Used + const QWidgetList associatedDesignerWidgets = associatedWidgets(action); + const bool used = !associatedDesignerWidgets.empty(); + item = sl[UsedColumn]; + item->setCheckState(used ? Qt::Checked : Qt::Unchecked); + if (used) { + QString usedToolTip; + const QString separator = QLatin1String(", "); + const int count = associatedDesignerWidgets.size(); + for (int i = 0; i < count; i++) { + if (i) + usedToolTip += separator; + usedToolTip += associatedDesignerWidgets.at(i)->objectName(); + } + item->setToolTip(usedToolTip); + } else { + item->setToolTip(QString()); + } + // text + item = sl[TextColumn]; + item->setText(action->text()); + item->setToolTip(action->text()); + // shortcut + const QString shortcut = actionShortCut(core, action); + item = sl[ShortCutColumn]; + item->setText(shortcut); + item->setToolTip(shortcut); + // checkable + sl[CheckedColumn]->setCheckState(action->isCheckable() ? Qt::Checked : Qt::Unchecked); + // ToolTip. This might be multi-line, rich text + QString toolTip = action->toolTip(); + item = sl[ToolTipColumn]; + item->setToolTip(toolTip); + item->setText(toolTip.replace(QLatin1Char('\n'), QLatin1Char(' '))); +} + +QMimeData *ActionModel::mimeData(const QModelIndexList &indexes ) const +{ + ActionRepositoryMimeData::ActionList actionList; + + QSet<QAction*> actions; + foreach (const QModelIndex &index, indexes) + if (QStandardItem *item = itemFromIndex(index)) + if (QAction *action = actionOfItem(item)) + actions.insert(action); + return new ActionRepositoryMimeData(actions.toList(), Qt::CopyAction); +} + +// Resource images are plain text. The drag needs to be restricted, however. +QStringList ActionModel::mimeTypes() const +{ + return QStringList(QLatin1String(plainTextMimeType)); +} + +QString ActionModel::actionName(int row) const +{ + return item(row, NameColumn)->text(); +} + +bool ActionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &) +{ + if (action != Qt::CopyAction) + return false; + + QStandardItem *droppedItem = item(row, column); + if (!droppedItem) + return false; + + + QtResourceView::ResourceType type; + QString path; + if (!QtResourceView::decodeMimeData(data, &type, &path) || type != QtResourceView::ResourceImage) + return false; + + emit resourceImageDropped(path, actionOfItem(droppedItem)); + return true; +} + +QAction *ActionModel::actionAt(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + QStandardItem *i = itemFromIndex(index); + if (!i) + return 0; + return actionOfItem(i); +} + +// helpers + +static bool handleImageDragEnterMoveEvent(QDropEvent *event) +{ + QtResourceView::ResourceType type; + const bool rc = QtResourceView::decodeMimeData(event->mimeData(), &type) && type == QtResourceView::ResourceImage; + if (rc) + event->acceptProposedAction(); + else + event->ignore(); + return rc; +} + +static void handleImageDropEvent(const QAbstractItemView *iv, QDropEvent *event, ActionModel *am) +{ + const QModelIndex index = iv->indexAt(event->pos()); + if (!index.isValid()) { + event->ignore(); + return; + } + + if (!handleImageDragEnterMoveEvent(event)) + return; + + am->dropMimeData(event->mimeData(), event->proposedAction(), index.row(), 0, iv->rootIndex()); +} + +// Basically mimic QAbstractItemView's startDrag routine, except that +// another pixmap is used, we don't want the whole row. + +void startActionDrag(QWidget *dragParent, ActionModel *model, const QModelIndexList &indexes, Qt::DropActions supportedActions) +{ + if (indexes.empty()) + return; + + QDrag *drag = new QDrag(dragParent); + QMimeData *data = model->mimeData(indexes); + drag->setMimeData(data); + if (ActionRepositoryMimeData *actionMimeData = qobject_cast<ActionRepositoryMimeData *>(data)) + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(actionMimeData->actionList().front())); + + drag->start(supportedActions); +} + +// ---------------- ActionTreeView: +ActionTreeView::ActionTreeView(ActionModel *model, QWidget *parent) : + QTreeView(parent), + m_model(model) +{ + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setDragDropMode(DragDrop); + setModel(model); + setRootIsDecorated(false); + setTextElideMode(Qt::ElideMiddle); + + setModel(model); + connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex))); + connect(header(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(resizeColumnToContents(int))); + + setIconSize(QSize(listModeIconSize, listModeIconSize)); + +} + +QAction *ActionTreeView::currentAction() const +{ + return m_model->actionAt(currentIndex()); +} + +void ActionTreeView::filter(const QString &text) +{ + const int rowCount = m_model->rowCount(); + const bool empty = text.isEmpty(); + const QModelIndex parent = rootIndex(); + for (int i = 0; i < rowCount; i++) + setRowHidden(i, parent, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive)); +} + +void ActionTreeView::dragEnterEvent(QDragEnterEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionTreeView::dropEvent(QDropEvent *event) +{ + handleImageDropEvent(this, event, m_model); +} + +void ActionTreeView::focusInEvent(QFocusEvent *event) +{ + QTreeView::focusInEvent(event); + // Make property editor display current action + if (QAction *a = currentAction()) + emit currentChanged(a); +} + +void ActionTreeView::contextMenuEvent(QContextMenuEvent *event) +{ + emit contextMenuRequested(event, m_model->actionAt(indexAt(event->pos()))); +} + +void ActionTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &/*previous*/) +{ + emit currentChanged(m_model->actionAt(current)); +} + +void ActionTreeView::slotActivated(const QModelIndex &index) +{ + emit activated(m_model->actionAt(index)); +} + +void ActionTreeView::startDrag(Qt::DropActions supportedActions) +{ + startActionDrag(this, m_model, selectedIndexes(), supportedActions); +} + +// ---------------- ActionListView: +ActionListView::ActionListView(ActionModel *model, QWidget *parent) : + QListView(parent), + m_model(model) +{ + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setDragDropMode(DragDrop); + setModel(model); + setTextElideMode(Qt::ElideMiddle); + connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex))); + + // We actually want 'Static' as the user should be able to + // drag away actions only (not to rearrange icons). + // We emulate that by not accepting our own + // drag data. 'Static' causes the list view to disable drag and drop + // on the viewport. + setMovement(Snap); + setViewMode(IconMode); + setIconSize(QSize(iconModeIconSize, iconModeIconSize)); + setGridSize(QSize(4 * iconModeIconSize, 2 * iconModeIconSize)); + setSpacing(iconModeIconSize / 3); +} + +QAction *ActionListView::currentAction() const +{ + return m_model->actionAt(currentIndex()); +} + +void ActionListView::filter(const QString &text) +{ + const int rowCount = m_model->rowCount(); + const bool empty = text.isEmpty(); + for (int i = 0; i < rowCount; i++) + setRowHidden(i, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive)); +} + +void ActionListView::dragEnterEvent(QDragEnterEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionListView::dragMoveEvent(QDragMoveEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionListView::dropEvent(QDropEvent *event) +{ + handleImageDropEvent(this, event, m_model); +} + +void ActionListView::focusInEvent(QFocusEvent *event) +{ + QListView::focusInEvent(event); + // Make property editor display current action + if (QAction *a = currentAction()) + emit currentChanged(a); +} + +void ActionListView::contextMenuEvent(QContextMenuEvent *event) +{ + emit contextMenuRequested(event, m_model->actionAt(indexAt(event->pos()))); +} + +void ActionListView::currentChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) +{ + emit currentChanged(m_model->actionAt(current)); +} + +void ActionListView::slotActivated(const QModelIndex &index) +{ + emit activated(m_model->actionAt(index)); +} + +void ActionListView::startDrag(Qt::DropActions supportedActions) +{ + startActionDrag(this, m_model, selectedIndexes(), supportedActions); +} + +// ActionView +ActionView::ActionView(QWidget *parent) : + QStackedWidget(parent), + m_model(new ActionModel(this)), + m_actionTreeView(new ActionTreeView(m_model)), + m_actionListView(new ActionListView(m_model)) +{ + addWidget(m_actionListView); + addWidget(m_actionTreeView); + // Wire signals + connect(m_actionTreeView, SIGNAL(contextMenuRequested(QContextMenuEvent*, QAction*)), + this, SIGNAL(contextMenuRequested(QContextMenuEvent*, QAction*))); + connect(m_actionListView, SIGNAL(contextMenuRequested(QContextMenuEvent*, QAction*)), + this, SIGNAL(contextMenuRequested(QContextMenuEvent*, QAction*))); + + // make it possible for vs integration to reimplement edit action dialog + // [which it shouldn't do actually] + connect(m_actionListView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*))); + connect(m_actionTreeView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*))); + + connect(m_actionListView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*))); + connect(m_actionTreeView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*))); + + connect(m_model, SIGNAL(resourceImageDropped(QString,QAction*)), + this, SIGNAL(resourceImageDropped(QString,QAction*))); + + // sync selection models + QItemSelectionModel *selectionModel = m_actionTreeView->selectionModel(); + m_actionListView->setSelectionModel(selectionModel); + connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SIGNAL(selectionChanged(QItemSelection,QItemSelection))); +} + +int ActionView::viewMode() const +{ + return currentWidget() == m_actionListView ? IconView : DetailedView; +} + +void ActionView::setViewMode(int lm) +{ + if (viewMode() == lm) + return; + + switch (lm) { + case IconView: + setCurrentWidget(m_actionListView); + break; + case DetailedView: + setCurrentWidget(m_actionTreeView); + break; + default: + break; + } +} + +void ActionView::slotCurrentChanged(QAction *action) +{ + // emit only for currently visible + if (sender() == currentWidget()) + emit currentChanged(action); +} + +void ActionView::filter(const QString &text) +{ + m_actionTreeView->filter(text); + m_actionListView->filter(text); +} + +void ActionView::selectAll() +{ + m_actionTreeView->selectAll(); +} + +void ActionView::clearSelection() +{ + m_actionTreeView->selectionModel()->clearSelection(); +} + +void ActionView::setCurrentIndex(const QModelIndex &index) +{ + m_actionTreeView->setCurrentIndex(index); +} + +QAction *ActionView::currentAction() const +{ + return m_actionListView->currentAction(); +} + +void ActionView::setSelectionMode(QAbstractItemView::SelectionMode sm) +{ + m_actionTreeView->setSelectionMode(sm); + m_actionListView->setSelectionMode(sm); +} + +QAbstractItemView::SelectionMode ActionView::selectionMode() const +{ + return m_actionListView->selectionMode(); +} + +QItemSelection ActionView::selection() const +{ + return m_actionListView->selectionModel()->selection(); +} + +ActionView::ActionList ActionView::selectedActions() const +{ + ActionList rc; + foreach (const QModelIndex &index, selection().indexes()) + if (index.column() == 0) + rc += actionOfItem(m_model->itemFromIndex(index)); + return rc; +} +// ---------- ActionRepositoryMimeData +ActionRepositoryMimeData::ActionRepositoryMimeData(QAction *a, Qt::DropAction dropAction) : + m_dropAction(dropAction) +{ + m_actionList += a; +} + +ActionRepositoryMimeData::ActionRepositoryMimeData(const ActionList &al, Qt::DropAction dropAction) : + m_dropAction(dropAction), + m_actionList(al) +{ +} + +QStringList ActionRepositoryMimeData::formats() const +{ + return QStringList(QLatin1String(actionMimeType)); +} + +QPixmap ActionRepositoryMimeData::actionDragPixmap(const QAction *action) +{ + + // Try to find a suitable pixmap. Grab either widget or icon. + const QIcon icon = action->icon(); + if (!icon.isNull()) + return icon.pixmap(QSize(22, 22)); + + foreach (QWidget *w, action->associatedWidgets()) + if (QToolButton *tb = qobject_cast<QToolButton *>(w)) + return QPixmap::grabWidget(tb); + + // Create a QToolButton + QToolButton *tb = new QToolButton; + tb->setText(action->text()); + tb->setToolButtonStyle(Qt::ToolButtonTextOnly); +#ifdef Q_WS_WIN // Force alien off to make adjustSize() take the system minimumsize into account. + tb->createWinId(); +#endif + tb->adjustSize(); + const QPixmap rc = QPixmap::grabWidget(tb); + tb->deleteLater(); + return rc; +} + +void ActionRepositoryMimeData::accept(QDragMoveEvent *event) const +{ + if (event->proposedAction() == m_dropAction) { + event->acceptProposedAction(); + } else { + event->setDropAction(m_dropAction); + event->accept(); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE |