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

Last change on this file since 1168 was 846, checked in by Dmitry A. Kuminov, 14 years ago

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

File size: 20.3 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 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 QTreeView::currentChanged(current, previous);
422}
423
424void ActionTreeView::slotActivated(const QModelIndex &index)
425{
426 emit activated(m_model->actionAt(index));
427}
428
429void ActionTreeView::startDrag(Qt::DropActions supportedActions)
430{
431 startActionDrag(this, m_model, selectedIndexes(), supportedActions);
432}
433
434// ---------------- ActionListView:
435ActionListView::ActionListView(ActionModel *model, QWidget *parent) :
436 QListView(parent),
437 m_model(model)
438{
439#ifndef QT_NO_DRAGANDDROP
440 setDragEnabled(true);
441 setAcceptDrops(true);
442 setDropIndicatorShown(true);
443 setDragDropMode(DragDrop);
444#endif
445 setModel(model);
446 setTextElideMode(Qt::ElideMiddle);
447 connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex)));
448
449 // We actually want 'Static' as the user should be able to
450 // drag away actions only (not to rearrange icons).
451 // We emulate that by not accepting our own
452 // drag data. 'Static' causes the list view to disable drag and drop
453 // on the viewport.
454 setMovement(Snap);
455 setViewMode(IconMode);
456 setIconSize(QSize(iconModeIconSize, iconModeIconSize));
457 setGridSize(QSize(4 * iconModeIconSize, 2 * iconModeIconSize));
458 setSpacing(iconModeIconSize / 3);
459}
460
461QAction *ActionListView::currentAction() const
462{
463 return m_model->actionAt(currentIndex());
464}
465
466void ActionListView::filter(const QString &text)
467{
468 const int rowCount = m_model->rowCount();
469 const bool empty = text.isEmpty();
470 for (int i = 0; i < rowCount; i++)
471 setRowHidden(i, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive));
472}
473
474void ActionListView::dragEnterEvent(QDragEnterEvent *event)
475{
476#ifndef QT_NO_DRAGANDDROP
477 handleImageDragEnterMoveEvent(event);
478#endif
479}
480
481void ActionListView::dragMoveEvent(QDragMoveEvent *event)
482{
483#ifndef QT_NO_DRAGANDDROP
484 handleImageDragEnterMoveEvent(event);
485#endif
486}
487
488void ActionListView::dropEvent(QDropEvent *event)
489{
490#ifndef QT_NO_DRAGANDDROP
491 handleImageDropEvent(this, event, m_model);
492#endif
493}
494
495void ActionListView::focusInEvent(QFocusEvent *event)
496{
497 QListView::focusInEvent(event);
498 // Make property editor display current action
499 if (QAction *a = currentAction())
500 emit currentChanged(a);
501}
502
503void ActionListView::contextMenuEvent(QContextMenuEvent *event)
504{
505 emit contextMenuRequested(event, m_model->actionAt(indexAt(event->pos())));
506}
507
508void ActionListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
509{
510 emit currentChanged(m_model->actionAt(current));
511 QListView::currentChanged(current, previous);
512}
513
514void ActionListView::slotActivated(const QModelIndex &index)
515{
516 emit activated(m_model->actionAt(index));
517}
518
519void ActionListView::startDrag(Qt::DropActions supportedActions)
520{
521 startActionDrag(this, m_model, selectedIndexes(), supportedActions);
522}
523
524// ActionView
525ActionView::ActionView(QWidget *parent) :
526 QStackedWidget(parent),
527 m_model(new ActionModel(this)),
528 m_actionTreeView(new ActionTreeView(m_model)),
529 m_actionListView(new ActionListView(m_model))
530{
531 addWidget(m_actionListView);
532 addWidget(m_actionTreeView);
533 // Wire signals
534 connect(m_actionTreeView, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)),
535 this, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)));
536 connect(m_actionListView, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)),
537 this, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)));
538
539 // make it possible for vs integration to reimplement edit action dialog
540 // [which it shouldn't do actually]
541 connect(m_actionListView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*)));
542 connect(m_actionTreeView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*)));
543
544 connect(m_actionListView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*)));
545 connect(m_actionTreeView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*)));
546
547 connect(m_model, SIGNAL(resourceImageDropped(QString,QAction*)),
548 this, SIGNAL(resourceImageDropped(QString,QAction*)));
549
550 // sync selection models
551 QItemSelectionModel *selectionModel = m_actionTreeView->selectionModel();
552 m_actionListView->setSelectionModel(selectionModel);
553 connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
554 this, SIGNAL(selectionChanged(QItemSelection,QItemSelection)));
555}
556
557int ActionView::viewMode() const
558{
559 return currentWidget() == m_actionListView ? IconView : DetailedView;
560}
561
562void ActionView::setViewMode(int lm)
563{
564 if (viewMode() == lm)
565 return;
566
567 switch (lm) {
568 case IconView:
569 setCurrentWidget(m_actionListView);
570 break;
571 case DetailedView:
572 setCurrentWidget(m_actionTreeView);
573 break;
574 default:
575 break;
576 }
577}
578
579void ActionView::slotCurrentChanged(QAction *action)
580{
581 // emit only for currently visible
582 if (sender() == currentWidget())
583 emit currentChanged(action);
584}
585
586void ActionView::filter(const QString &text)
587{
588 m_actionTreeView->filter(text);
589 m_actionListView->filter(text);
590}
591
592void ActionView::selectAll()
593{
594 m_actionTreeView->selectAll();
595}
596
597void ActionView::clearSelection()
598{
599 m_actionTreeView->selectionModel()->clearSelection();
600}
601
602void ActionView::setCurrentIndex(const QModelIndex &index)
603{
604 m_actionTreeView->setCurrentIndex(index);
605}
606
607QAction *ActionView::currentAction() const
608{
609 return m_actionListView->currentAction();
610}
611
612void ActionView::setSelectionMode(QAbstractItemView::SelectionMode sm)
613{
614 m_actionTreeView->setSelectionMode(sm);
615 m_actionListView->setSelectionMode(sm);
616}
617
618QAbstractItemView::SelectionMode ActionView::selectionMode() const
619{
620 return m_actionListView->selectionMode();
621}
622
623QItemSelection ActionView::selection() const
624{
625 return m_actionListView->selectionModel()->selection();
626}
627
628ActionView::ActionList ActionView::selectedActions() const
629{
630 ActionList rc;
631 foreach (const QModelIndex &index, selection().indexes())
632 if (index.column() == 0)
633 rc += actionOfItem(m_model->itemFromIndex(index));
634 return rc;
635}
636// ---------- ActionRepositoryMimeData
637ActionRepositoryMimeData::ActionRepositoryMimeData(QAction *a, Qt::DropAction dropAction) :
638 m_dropAction(dropAction)
639{
640 m_actionList += a;
641}
642
643ActionRepositoryMimeData::ActionRepositoryMimeData(const ActionList &al, Qt::DropAction dropAction) :
644 m_dropAction(dropAction),
645 m_actionList(al)
646{
647}
648
649QStringList ActionRepositoryMimeData::formats() const
650{
651 return QStringList(QLatin1String(actionMimeType));
652}
653
654QPixmap ActionRepositoryMimeData::actionDragPixmap(const QAction *action)
655{
656
657 // Try to find a suitable pixmap. Grab either widget or icon.
658 const QIcon icon = action->icon();
659 if (!icon.isNull())
660 return icon.pixmap(QSize(22, 22));
661
662 foreach (QWidget *w, action->associatedWidgets())
663 if (QToolButton *tb = qobject_cast<QToolButton *>(w))
664 return QPixmap::grabWidget(tb);
665
666 // Create a QToolButton
667 QToolButton *tb = new QToolButton;
668 tb->setText(action->text());
669 tb->setToolButtonStyle(Qt::ToolButtonTextOnly);
670#ifdef Q_WS_WIN // Force alien off to make adjustSize() take the system minimumsize into account.
671 tb->createWinId();
672#endif
673 tb->adjustSize();
674 const QPixmap rc = QPixmap::grabWidget(tb);
675 tb->deleteLater();
676 return rc;
677}
678
679void ActionRepositoryMimeData::accept(QDragMoveEvent *event) const
680{
681#ifndef QT_NO_DRAGANDDROP
682 if (event->proposedAction() == m_dropAction) {
683 event->acceptProposedAction();
684 } else {
685 event->setDropAction(m_dropAction);
686 event->accept();
687 }
688#endif
689}
690
691} // namespace qdesigner_internal
692
693QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.