source: trunk/tools/designer/src/lib/shared/actionrepository.cpp@ 769

Last change on this file since 769 was 769, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.3 sources from branches/vendor/nokia/qt.

File size: 20.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the Qt Designer of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "actionrepository_p.h"
43#include "qtresourceview_p.h"
44#include "iconloader_p.h"
45#include "qdesigner_utils_p.h"
46
47#include <QtDesigner/QDesignerFormEditorInterface>
48#include <QtDesigner/QDesignerPropertySheetExtension>
49#include <QtDesigner/QExtensionManager>
50
51#include <QtGui/QDrag>
52#include <QtGui/QContextMenuEvent>
53#include <QtGui/QStandardItemModel>
54#include <QtGui/QToolButton>
55#include <QtGui/QPixmap>
56#include <QtGui/QAction>
57#include <QtGui/QHeaderView>
58#include <QtGui/QToolBar>
59#include <QtGui/QMenu>
60#include <QtGui/qevent.h>
61#include <QtCore/QSet>
62#include <QtCore/QDebug>
63
64Q_DECLARE_METATYPE(QAction*)
65
66QT_BEGIN_NAMESPACE
67
68namespace {
69 enum { listModeIconSize = 16, iconModeIconSize = 24 };
70}
71
72static const char *actionMimeType = "action-repository/actions";
73static const char *plainTextMimeType = "text/plain";
74
75static inline QAction *actionOfItem(const QStandardItem* item)
76{
77 return qvariant_cast<QAction*>(item->data(qdesigner_internal::ActionModel::ActionRole));
78}
79
80namespace qdesigner_internal {
81
82// ----------- ActionModel
83ActionModel::ActionModel(QWidget *parent ) :
84 QStandardItemModel(parent),
85 m_emptyIcon(emptyIcon()),
86 m_core(0)
87{
88 QStringList headers;
89 headers += tr("Name");
90 headers += tr("Used");
91 headers += tr("Text");
92 headers += tr("Shortcut");
93 headers += tr("Checkable");
94 headers += tr("ToolTip");
95 Q_ASSERT(NumColumns == headers.size());
96 setHorizontalHeaderLabels(headers);
97}
98
99void ActionModel::clearActions()
100{
101 removeRows(0, rowCount());
102}
103
104int ActionModel::findAction(QAction *action) const
105{
106 const int rows = rowCount();
107 for (int i = 0; i < rows; i++)
108 if (action == actionOfItem(item(i)))
109 return i;
110 return -1;
111}
112
113void ActionModel::update(int row)
114{
115 Q_ASSERT(m_core);
116 // need to create the row list ... grrr..
117 if (row >= rowCount())
118 return;
119
120 QStandardItemList list;
121 for (int i = 0; i < NumColumns; i++)
122 list += item(row, i);
123
124 setItems(m_core, actionOfItem(list.front()), m_emptyIcon, list);
125}
126
127void ActionModel::remove(int row)
128{
129 qDeleteAll(takeRow(row));
130}
131
132QModelIndex ActionModel::addAction(QAction *action)
133{
134 Q_ASSERT(m_core);
135 QStandardItemList items;
136 const Qt::ItemFlags flags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled;
137
138 QVariant itemData;
139 qVariantSetValue(itemData, action);
140
141 for (int i = 0; i < NumColumns; i++) {
142 QStandardItem *item = new QStandardItem;
143 item->setData(itemData, ActionRole);
144 item->setFlags(flags);
145 items.push_back(item);
146 }
147 setItems(m_core, action, m_emptyIcon, items);
148 appendRow(items);
149 return indexFromItem(items.front());
150}
151
152// Find the associated menus and toolbars, ignore toolbuttons
153QWidgetList ActionModel::associatedWidgets(const QAction *action)
154{
155 QWidgetList rc = action->associatedWidgets();
156 for (QWidgetList::iterator it = rc.begin(); it != rc.end(); )
157 if (qobject_cast<const QMenu *>(*it) || qobject_cast<const QToolBar *>(*it)) {
158 ++it;
159 } else {
160 it = rc.erase(it);
161 }
162 return rc;
163}
164
165// shortcut is a fake property, need to retrieve it via property sheet.
166PropertySheetKeySequenceValue ActionModel::actionShortCut(QDesignerFormEditorInterface *core, QAction *action)
167{
168 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), action);
169 if (!sheet)
170 return PropertySheetKeySequenceValue();
171 return actionShortCut(sheet);
172}
173
174PropertySheetKeySequenceValue ActionModel::actionShortCut(const QDesignerPropertySheetExtension *sheet)
175{
176 const int index = sheet->indexOf(QLatin1String("shortcut"));
177 if (index == -1)
178 return PropertySheetKeySequenceValue();
179 return qvariant_cast<PropertySheetKeySequenceValue>(sheet->property(index));
180}
181
182void ActionModel::setItems(QDesignerFormEditorInterface *core, QAction *action,
183 const QIcon &defaultIcon,
184 QStandardItemList &sl)
185{
186
187 // Tooltip, mostly for icon view mode
188 QString firstTooltip = action->objectName();
189 const QString text = action->text();
190 if (!text.isEmpty()) {
191 firstTooltip += QLatin1Char('\n');
192 firstTooltip += text;
193 }
194
195 Q_ASSERT(sl.size() == NumColumns);
196
197 QStandardItem *item = sl[NameColumn];
198 item->setText(action->objectName());
199 QIcon icon = action->icon();
200 if (icon.isNull())
201 icon = defaultIcon;
202 item->setIcon(icon);
203 item->setToolTip(firstTooltip);
204 item->setWhatsThis(firstTooltip);
205 // Used
206 const QWidgetList associatedDesignerWidgets = associatedWidgets(action);
207 const bool used = !associatedDesignerWidgets.empty();
208 item = sl[UsedColumn];
209 item->setCheckState(used ? Qt::Checked : Qt::Unchecked);
210 if (used) {
211 QString usedToolTip;
212 const QString separator = QLatin1String(", ");
213 const int count = associatedDesignerWidgets.size();
214 for (int i = 0; i < count; i++) {
215 if (i)
216 usedToolTip += separator;
217 usedToolTip += associatedDesignerWidgets.at(i)->objectName();
218 }
219 item->setToolTip(usedToolTip);
220 } else {
221 item->setToolTip(QString());
222 }
223 // text
224 item = sl[TextColumn];
225 item->setText(action->text());
226 item->setToolTip(action->text());
227 // shortcut
228 const QString shortcut = actionShortCut(core, action).value().toString(QKeySequence::NativeText);
229 item = sl[ShortCutColumn];
230 item->setText(shortcut);
231 item->setToolTip(shortcut);
232 // checkable
233 sl[CheckedColumn]->setCheckState(action->isCheckable() ? Qt::Checked : Qt::Unchecked);
234 // ToolTip. This might be multi-line, rich text
235 QString toolTip = action->toolTip();
236 item = sl[ToolTipColumn];
237 item->setToolTip(toolTip);
238 item->setText(toolTip.replace(QLatin1Char('\n'), QLatin1Char(' ')));
239}
240
241QMimeData *ActionModel::mimeData(const QModelIndexList &indexes ) const
242{
243 ActionRepositoryMimeData::ActionList actionList;
244
245 QSet<QAction*> actions;
246 foreach (const QModelIndex &index, indexes)
247 if (QStandardItem *item = itemFromIndex(index))
248 if (QAction *action = actionOfItem(item))
249 actions.insert(action);
250 return new ActionRepositoryMimeData(actions.toList(), Qt::CopyAction);
251}
252
253// Resource images are plain text. The drag needs to be restricted, however.
254QStringList ActionModel::mimeTypes() const
255{
256 return QStringList(QLatin1String(plainTextMimeType));
257}
258
259QString ActionModel::actionName(int row) const
260{
261 return item(row, NameColumn)->text();
262}
263
264bool ActionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &)
265{
266 if (action != Qt::CopyAction)
267 return false;
268
269 QStandardItem *droppedItem = item(row, column);
270 if (!droppedItem)
271 return false;
272
273
274 QtResourceView::ResourceType type;
275 QString path;
276 if (!QtResourceView::decodeMimeData(data, &type, &path) || type != QtResourceView::ResourceImage)
277 return false;
278
279 emit resourceImageDropped(path, actionOfItem(droppedItem));
280 return true;
281}
282
283QAction *ActionModel::actionAt(const QModelIndex &index) const
284{
285 if (!index.isValid())
286 return 0;
287 QStandardItem *i = itemFromIndex(index);
288 if (!i)
289 return 0;
290 return actionOfItem(i);
291}
292
293// helpers
294
295static bool handleImageDragEnterMoveEvent(QDropEvent *event)
296{
297#ifndef QT_NO_DRAGANDDROP
298 QtResourceView::ResourceType type;
299 const bool rc = QtResourceView::decodeMimeData(event->mimeData(), &type) && type == QtResourceView::ResourceImage;
300 if (rc)
301 event->acceptProposedAction();
302 else
303 event->ignore();
304 return rc;
305#else
306 return 0;
307#endif
308}
309
310static void handleImageDropEvent(const QAbstractItemView *iv, QDropEvent *event, ActionModel *am)
311{
312#ifndef QT_NO_DRAGANDDROP
313 const QModelIndex index = iv->indexAt(event->pos());
314 if (!index.isValid()) {
315 event->ignore();
316 return;
317 }
318#endif
319
320 if (!handleImageDragEnterMoveEvent(event))
321 return;
322
323#ifndef QT_NO_DRAGANDDROP
324 am->dropMimeData(event->mimeData(), event->proposedAction(), index.row(), 0, iv->rootIndex());
325#endif
326}
327
328// Basically mimic QAbstractItemView's startDrag routine, except that
329// another pixmap is used, we don't want the whole row.
330
331void startActionDrag(QWidget *dragParent, ActionModel *model, const QModelIndexList &indexes, Qt::DropActions supportedActions)
332{
333 if (indexes.empty())
334 return;
335
336#ifndef QT_NO_DRAGANDDROP
337 QDrag *drag = new QDrag(dragParent);
338 QMimeData *data = model->mimeData(indexes);
339 drag->setMimeData(data);
340 if (ActionRepositoryMimeData *actionMimeData = qobject_cast<ActionRepositoryMimeData *>(data))
341 drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(actionMimeData->actionList().front()));
342
343 drag->start(supportedActions);
344#endif
345}
346
347// ---------------- ActionTreeView:
348ActionTreeView::ActionTreeView(ActionModel *model, QWidget *parent) :
349 QTreeView(parent),
350 m_model(model)
351{
352#ifndef QT_NO_DRAGANDDROP
353 setDragEnabled(true);
354 setAcceptDrops(true);
355 setDropIndicatorShown(true);
356 setDragDropMode(DragDrop);
357#endif
358 setModel(model);
359 setRootIsDecorated(false);
360 setTextElideMode(Qt::ElideMiddle);
361
362 setModel(model);
363 connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex)));
364 connect(header(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(resizeColumnToContents(int)));
365
366 setIconSize(QSize(listModeIconSize, listModeIconSize));
367
368}
369
370QAction *ActionTreeView::currentAction() const
371{
372 return m_model->actionAt(currentIndex());
373}
374
375void ActionTreeView::filter(const QString &text)
376{
377 const int rowCount = m_model->rowCount();
378 const bool empty = text.isEmpty();
379 const QModelIndex parent = rootIndex();
380 for (int i = 0; i < rowCount; i++)
381 setRowHidden(i, parent, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive));
382}
383
384void ActionTreeView::dragEnterEvent(QDragEnterEvent *event)
385{
386#ifndef QT_NO_DRAGANDDROP
387 handleImageDragEnterMoveEvent(event);
388#endif
389}
390
391void ActionTreeView::dragMoveEvent(QDragMoveEvent *event)
392{
393#ifndef QT_NO_DRAGANDDROP
394 handleImageDragEnterMoveEvent(event);
395#endif
396}
397
398void ActionTreeView::dropEvent(QDropEvent *event)
399{
400#ifndef QT_NO_DRAGANDDROP
401 handleImageDropEvent(this, event, m_model);
402#endif
403}
404
405void ActionTreeView::focusInEvent(QFocusEvent *event)
406{
407 QTreeView::focusInEvent(event);
408 // Make property editor display current action
409 if (QAction *a = currentAction())
410 emit currentChanged(a);
411}
412
413void ActionTreeView::contextMenuEvent(QContextMenuEvent *event)
414{
415 emit contextMenuRequested(event, m_model->actionAt(indexAt(event->pos())));
416}
417
418void ActionTreeView::currentChanged(const QModelIndex &current, const QModelIndex &/*previous*/)
419{
420 emit currentChanged(m_model->actionAt(current));
421}
422
423void ActionTreeView::slotActivated(const QModelIndex &index)
424{
425 emit activated(m_model->actionAt(index));
426}
427
428void ActionTreeView::startDrag(Qt::DropActions supportedActions)
429{
430 startActionDrag(this, m_model, selectedIndexes(), supportedActions);
431}
432
433// ---------------- ActionListView:
434ActionListView::ActionListView(ActionModel *model, QWidget *parent) :
435 QListView(parent),
436 m_model(model)
437{
438#ifndef QT_NO_DRAGANDDROP
439 setDragEnabled(true);
440 setAcceptDrops(true);
441 setDropIndicatorShown(true);
442 setDragDropMode(DragDrop);
443#endif
444 setModel(model);
445 setTextElideMode(Qt::ElideMiddle);
446 connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex)));
447
448 // We actually want 'Static' as the user should be able to
449 // drag away actions only (not to rearrange icons).
450 // We emulate that by not accepting our own
451 // drag data. 'Static' causes the list view to disable drag and drop
452 // on the viewport.
453 setMovement(Snap);
454 setViewMode(IconMode);
455 setIconSize(QSize(iconModeIconSize, iconModeIconSize));
456 setGridSize(QSize(4 * iconModeIconSize, 2 * iconModeIconSize));
457 setSpacing(iconModeIconSize / 3);
458}
459
460QAction *ActionListView::currentAction() const
461{
462 return m_model->actionAt(currentIndex());
463}
464
465void ActionListView::filter(const QString &text)
466{
467 const int rowCount = m_model->rowCount();
468 const bool empty = text.isEmpty();
469 for (int i = 0; i < rowCount; i++)
470 setRowHidden(i, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive));
471}
472
473void ActionListView::dragEnterEvent(QDragEnterEvent *event)
474{
475#ifndef QT_NO_DRAGANDDROP
476 handleImageDragEnterMoveEvent(event);
477#endif
478}
479
480void ActionListView::dragMoveEvent(QDragMoveEvent *event)
481{
482#ifndef QT_NO_DRAGANDDROP
483 handleImageDragEnterMoveEvent(event);
484#endif
485}
486
487void ActionListView::dropEvent(QDropEvent *event)
488{
489#ifndef QT_NO_DRAGANDDROP
490 handleImageDropEvent(this, event, m_model);
491#endif
492}
493
494void ActionListView::focusInEvent(QFocusEvent *event)
495{
496 QListView::focusInEvent(event);
497 // Make property editor display current action
498 if (QAction *a = currentAction())
499 emit currentChanged(a);
500}
501
502void ActionListView::contextMenuEvent(QContextMenuEvent *event)
503{
504 emit contextMenuRequested(event, m_model->actionAt(indexAt(event->pos())));
505}
506
507void ActionListView::currentChanged(const QModelIndex &current, const QModelIndex & /*previous*/)
508{
509 emit currentChanged(m_model->actionAt(current));
510}
511
512void ActionListView::slotActivated(const QModelIndex &index)
513{
514 emit activated(m_model->actionAt(index));
515}
516
517void ActionListView::startDrag(Qt::DropActions supportedActions)
518{
519 startActionDrag(this, m_model, selectedIndexes(), supportedActions);
520}
521
522// ActionView
523ActionView::ActionView(QWidget *parent) :
524 QStackedWidget(parent),
525 m_model(new ActionModel(this)),
526 m_actionTreeView(new ActionTreeView(m_model)),
527 m_actionListView(new ActionListView(m_model))
528{
529 addWidget(m_actionListView);
530 addWidget(m_actionTreeView);
531 // Wire signals
532 connect(m_actionTreeView, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)),
533 this, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)));
534 connect(m_actionListView, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)),
535 this, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)));
536
537 // make it possible for vs integration to reimplement edit action dialog
538 // [which it shouldn't do actually]
539 connect(m_actionListView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*)));
540 connect(m_actionTreeView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*)));
541
542 connect(m_actionListView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*)));
543 connect(m_actionTreeView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*)));
544
545 connect(m_model, SIGNAL(resourceImageDropped(QString,QAction*)),
546 this, SIGNAL(resourceImageDropped(QString,QAction*)));
547
548 // sync selection models
549 QItemSelectionModel *selectionModel = m_actionTreeView->selectionModel();
550 m_actionListView->setSelectionModel(selectionModel);
551 connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
552 this, SIGNAL(selectionChanged(QItemSelection,QItemSelection)));
553}
554
555int ActionView::viewMode() const
556{
557 return currentWidget() == m_actionListView ? IconView : DetailedView;
558}
559
560void ActionView::setViewMode(int lm)
561{
562 if (viewMode() == lm)
563 return;
564
565 switch (lm) {
566 case IconView:
567 setCurrentWidget(m_actionListView);
568 break;
569 case DetailedView:
570 setCurrentWidget(m_actionTreeView);
571 break;
572 default:
573 break;
574 }
575}
576
577void ActionView::slotCurrentChanged(QAction *action)
578{
579 // emit only for currently visible
580 if (sender() == currentWidget())
581 emit currentChanged(action);
582}
583
584void ActionView::filter(const QString &text)
585{
586 m_actionTreeView->filter(text);
587 m_actionListView->filter(text);
588}
589
590void ActionView::selectAll()
591{
592 m_actionTreeView->selectAll();
593}
594
595void ActionView::clearSelection()
596{
597 m_actionTreeView->selectionModel()->clearSelection();
598}
599
600void ActionView::setCurrentIndex(const QModelIndex &index)
601{
602 m_actionTreeView->setCurrentIndex(index);
603}
604
605QAction *ActionView::currentAction() const
606{
607 return m_actionListView->currentAction();
608}
609
610void ActionView::setSelectionMode(QAbstractItemView::SelectionMode sm)
611{
612 m_actionTreeView->setSelectionMode(sm);
613 m_actionListView->setSelectionMode(sm);
614}
615
616QAbstractItemView::SelectionMode ActionView::selectionMode() const
617{
618 return m_actionListView->selectionMode();
619}
620
621QItemSelection ActionView::selection() const
622{
623 return m_actionListView->selectionModel()->selection();
624}
625
626ActionView::ActionList ActionView::selectedActions() const
627{
628 ActionList rc;
629 foreach (const QModelIndex &index, selection().indexes())
630 if (index.column() == 0)
631 rc += actionOfItem(m_model->itemFromIndex(index));
632 return rc;
633}
634// ---------- ActionRepositoryMimeData
635ActionRepositoryMimeData::ActionRepositoryMimeData(QAction *a, Qt::DropAction dropAction) :
636 m_dropAction(dropAction)
637{
638 m_actionList += a;
639}
640
641ActionRepositoryMimeData::ActionRepositoryMimeData(const ActionList &al, Qt::DropAction dropAction) :
642 m_dropAction(dropAction),
643 m_actionList(al)
644{
645}
646
647QStringList ActionRepositoryMimeData::formats() const
648{
649 return QStringList(QLatin1String(actionMimeType));
650}
651
652QPixmap ActionRepositoryMimeData::actionDragPixmap(const QAction *action)
653{
654
655 // Try to find a suitable pixmap. Grab either widget or icon.
656 const QIcon icon = action->icon();
657 if (!icon.isNull())
658 return icon.pixmap(QSize(22, 22));
659
660 foreach (QWidget *w, action->associatedWidgets())
661 if (QToolButton *tb = qobject_cast<QToolButton *>(w))
662 return QPixmap::grabWidget(tb);
663
664 // Create a QToolButton
665 QToolButton *tb = new QToolButton;
666 tb->setText(action->text());
667 tb->setToolButtonStyle(Qt::ToolButtonTextOnly);
668#ifdef Q_WS_WIN // Force alien off to make adjustSize() take the system minimumsize into account.
669 tb->createWinId();
670#endif
671 tb->adjustSize();
672 const QPixmap rc = QPixmap::grabWidget(tb);
673 tb->deleteLater();
674 return rc;
675}
676
677void ActionRepositoryMimeData::accept(QDragMoveEvent *event) const
678{
679#ifndef QT_NO_DRAGANDDROP
680 if (event->proposedAction() == m_dropAction) {
681 event->acceptProposedAction();
682 } else {
683 event->setDropAction(m_dropAction);
684 event->accept();
685 }
686#endif
687}
688
689} // namespace qdesigner_internal
690
691QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.