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

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

trunk: Merged in qt 4.6.2 sources.

File size: 20.1 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
80static QIcon fixActionIcon(const QIcon &icon)
81{
82 if (icon.isNull())
83 return qdesigner_internal::emptyIcon();
84 return icon;
85}
86
87namespace qdesigner_internal {
88
89// ----------- ActionModel
90ActionModel::ActionModel(QWidget *parent ) :
91 QStandardItemModel(parent),
92 m_core(0)
93{
94 QStringList headers;
95 headers += tr("Name");
96 headers += tr("Used");
97 headers += tr("Text");
98 headers += tr("Shortcut");
99 headers += tr("Checkable");
100 headers += tr("ToolTip");
101 Q_ASSERT(NumColumns == headers.size());
102 setHorizontalHeaderLabels(headers);
103}
104
105void ActionModel::clearActions()
106{
107 removeRows(0, rowCount());
108}
109
110int ActionModel::findAction(QAction *action) const
111{
112 const int rows = rowCount();
113 for (int i = 0; i < rows; i++)
114 if (action == actionOfItem(item(i)))
115 return i;
116 return -1;
117}
118
119void ActionModel::update(int row)
120{
121 Q_ASSERT(m_core);
122 // need to create the row list ... grrr..
123 if (row >= rowCount())
124 return;
125
126 QStandardItemList list;
127 for (int i = 0; i < NumColumns; i++)
128 list += item(row, i);
129
130 setItems(m_core, actionOfItem(list.front()), list);
131}
132
133void ActionModel::remove(int row)
134{
135 qDeleteAll(takeRow(row));
136}
137
138QModelIndex ActionModel::addAction(QAction *action)
139{
140 Q_ASSERT(m_core);
141 QStandardItemList items;
142 const Qt::ItemFlags flags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled;
143
144 QVariant itemData;
145 qVariantSetValue(itemData, action);
146
147 for (int i = 0; i < NumColumns; i++) {
148 QStandardItem *item = new QStandardItem;
149 item->setData(itemData, ActionRole);
150 item->setFlags(flags);
151 items.push_back(item);
152 }
153 setItems(m_core, action, items);
154 appendRow(items);
155 return indexFromItem(items.front());
156}
157
158// Find the associated menus and toolbars, ignore toolbuttons
159QWidgetList ActionModel::associatedWidgets(const QAction *action)
160{
161 QWidgetList rc = action->associatedWidgets();
162 for (QWidgetList::iterator it = rc.begin(); it != rc.end(); )
163 if (qobject_cast<const QMenu *>(*it) || qobject_cast<const QToolBar *>(*it)) {
164 ++it;
165 } else {
166 it = rc.erase(it);
167 }
168 return rc;
169}
170
171// shortcut is a fake property, need to retrieve it via property sheet.
172PropertySheetKeySequenceValue ActionModel::actionShortCut(QDesignerFormEditorInterface *core, QAction *action)
173{
174 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), action);
175 if (!sheet)
176 return PropertySheetKeySequenceValue();
177 return actionShortCut(sheet);
178}
179
180PropertySheetKeySequenceValue ActionModel::actionShortCut(const QDesignerPropertySheetExtension *sheet)
181{
182 const int index = sheet->indexOf(QLatin1String("shortcut"));
183 if (index == -1)
184 return PropertySheetKeySequenceValue();
185 return qvariant_cast<PropertySheetKeySequenceValue>(sheet->property(index));
186}
187
188void ActionModel::setItems(QDesignerFormEditorInterface *core, QAction *action, QStandardItemList &sl)
189{
190
191 // Tooltip, mostly for icon view mode
192 QString firstTooltip = action->objectName();
193 const QString text = action->text();
194 if (!text.isEmpty()) {
195 firstTooltip += QLatin1Char('\n');
196 firstTooltip += text;
197 }
198
199 Q_ASSERT(sl.size() == NumColumns);
200
201 QStandardItem *item = sl[NameColumn];
202 item->setText(action->objectName());
203 item->setIcon(fixActionIcon(action->icon()));
204 item->setToolTip(firstTooltip);
205 item->setWhatsThis(firstTooltip);
206 // Used
207 const QWidgetList associatedDesignerWidgets = associatedWidgets(action);
208 const bool used = !associatedDesignerWidgets.empty();
209 item = sl[UsedColumn];
210 item->setCheckState(used ? Qt::Checked : Qt::Unchecked);
211 if (used) {
212 QString usedToolTip;
213 const QString separator = QLatin1String(", ");
214 const int count = associatedDesignerWidgets.size();
215 for (int i = 0; i < count; i++) {
216 if (i)
217 usedToolTip += separator;
218 usedToolTip += associatedDesignerWidgets.at(i)->objectName();
219 }
220 item->setToolTip(usedToolTip);
221 } else {
222 item->setToolTip(QString());
223 }
224 // text
225 item = sl[TextColumn];
226 item->setText(action->text());
227 item->setToolTip(action->text());
228 // shortcut
229 const QString shortcut = actionShortCut(core, action).value().toString(QKeySequence::NativeText);
230 item = sl[ShortCutColumn];
231 item->setText(shortcut);
232 item->setToolTip(shortcut);
233 // checkable
234 sl[CheckedColumn]->setCheckState(action->isCheckable() ? Qt::Checked : Qt::Unchecked);
235 // ToolTip. This might be multi-line, rich text
236 QString toolTip = action->toolTip();
237 item = sl[ToolTipColumn];
238 item->setToolTip(toolTip);
239 item->setText(toolTip.replace(QLatin1Char('\n'), QLatin1Char(' ')));
240}
241
242QMimeData *ActionModel::mimeData(const QModelIndexList &indexes ) const
243{
244 ActionRepositoryMimeData::ActionList actionList;
245
246 QSet<QAction*> actions;
247 foreach (const QModelIndex &index, indexes)
248 if (QStandardItem *item = itemFromIndex(index))
249 if (QAction *action = actionOfItem(item))
250 actions.insert(action);
251 return new ActionRepositoryMimeData(actions.toList(), Qt::CopyAction);
252}
253
254// Resource images are plain text. The drag needs to be restricted, however.
255QStringList ActionModel::mimeTypes() const
256{
257 return QStringList(QLatin1String(plainTextMimeType));
258}
259
260QString ActionModel::actionName(int row) const
261{
262 return item(row, NameColumn)->text();
263}
264
265bool ActionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &)
266{
267 if (action != Qt::CopyAction)
268 return false;
269
270 QStandardItem *droppedItem = item(row, column);
271 if (!droppedItem)
272 return false;
273
274
275 QtResourceView::ResourceType type;
276 QString path;
277 if (!QtResourceView::decodeMimeData(data, &type, &path) || type != QtResourceView::ResourceImage)
278 return false;
279
280 emit resourceImageDropped(path, actionOfItem(droppedItem));
281 return true;
282}
283
284QAction *ActionModel::actionAt(const QModelIndex &index) const
285{
286 if (!index.isValid())
287 return 0;
288 QStandardItem *i = itemFromIndex(index);
289 if (!i)
290 return 0;
291 return actionOfItem(i);
292}
293
294// helpers
295
296static bool handleImageDragEnterMoveEvent(QDropEvent *event)
297{
298#ifndef QT_NO_DRAGANDDROP
299 QtResourceView::ResourceType type;
300 const bool rc = QtResourceView::decodeMimeData(event->mimeData(), &type) && type == QtResourceView::ResourceImage;
301 if (rc)
302 event->acceptProposedAction();
303 else
304 event->ignore();
305 return rc;
306#else
307 return 0;
308#endif
309}
310
311static void handleImageDropEvent(const QAbstractItemView *iv, QDropEvent *event, ActionModel *am)
312{
313#ifndef QT_NO_DRAGANDDROP
314 const QModelIndex index = iv->indexAt(event->pos());
315 if (!index.isValid()) {
316 event->ignore();
317 return;
318 }
319#endif
320
321 if (!handleImageDragEnterMoveEvent(event))
322 return;
323
324#ifndef QT_NO_DRAGANDDROP
325 am->dropMimeData(event->mimeData(), event->proposedAction(), index.row(), 0, iv->rootIndex());
326#endif
327}
328
329// Basically mimic QAbstractItemView's startDrag routine, except that
330// another pixmap is used, we don't want the whole row.
331
332void startActionDrag(QWidget *dragParent, ActionModel *model, const QModelIndexList &indexes, Qt::DropActions supportedActions)
333{
334 if (indexes.empty())
335 return;
336
337#ifndef QT_NO_DRAGANDDROP
338 QDrag *drag = new QDrag(dragParent);
339 QMimeData *data = model->mimeData(indexes);
340 drag->setMimeData(data);
341 if (ActionRepositoryMimeData *actionMimeData = qobject_cast<ActionRepositoryMimeData *>(data))
342 drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(actionMimeData->actionList().front()));
343
344 drag->start(supportedActions);
345#endif
346}
347
348// ---------------- ActionTreeView:
349ActionTreeView::ActionTreeView(ActionModel *model, QWidget *parent) :
350 QTreeView(parent),
351 m_model(model)
352{
353#ifndef QT_NO_DRAGANDDROP
354 setDragEnabled(true);
355 setAcceptDrops(true);
356 setDropIndicatorShown(true);
357 setDragDropMode(DragDrop);
358#endif
359 setModel(model);
360 setRootIsDecorated(false);
361 setTextElideMode(Qt::ElideMiddle);
362
363 setModel(model);
364 connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(slotActivated(QModelIndex)));
365 connect(header(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(resizeColumnToContents(int)));
366
367 setIconSize(QSize(listModeIconSize, listModeIconSize));
368
369}
370
371QAction *ActionTreeView::currentAction() const
372{
373 return m_model->actionAt(currentIndex());
374}
375
376void ActionTreeView::filter(const QString &text)
377{
378 const int rowCount = m_model->rowCount();
379 const bool empty = text.isEmpty();
380 const QModelIndex parent = rootIndex();
381 for (int i = 0; i < rowCount; i++)
382 setRowHidden(i, parent, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive));
383}
384
385void ActionTreeView::dragEnterEvent(QDragEnterEvent *event)
386{
387#ifndef QT_NO_DRAGANDDROP
388 handleImageDragEnterMoveEvent(event);
389#endif
390}
391
392void ActionTreeView::dragMoveEvent(QDragMoveEvent *event)
393{
394#ifndef QT_NO_DRAGANDDROP
395 handleImageDragEnterMoveEvent(event);
396#endif
397}
398
399void ActionTreeView::dropEvent(QDropEvent *event)
400{
401#ifndef QT_NO_DRAGANDDROP
402 handleImageDropEvent(this, event, m_model);
403#endif
404}
405
406void ActionTreeView::focusInEvent(QFocusEvent *event)
407{
408 QTreeView::focusInEvent(event);
409 // Make property editor display current action
410 if (QAction *a = currentAction())
411 emit currentChanged(a);
412}
413
414void ActionTreeView::contextMenuEvent(QContextMenuEvent *event)
415{
416 emit contextMenuRequested(event, m_model->actionAt(indexAt(event->pos())));
417}
418
419void ActionTreeView::currentChanged(const QModelIndex &current, const QModelIndex &/*previous*/)
420{
421 emit currentChanged(m_model->actionAt(current));
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}
512
513void ActionListView::slotActivated(const QModelIndex &index)
514{
515 emit activated(m_model->actionAt(index));
516}
517
518void ActionListView::startDrag(Qt::DropActions supportedActions)
519{
520 startActionDrag(this, m_model, selectedIndexes(), supportedActions);
521}
522
523// ActionView
524ActionView::ActionView(QWidget *parent) :
525 QStackedWidget(parent),
526 m_model(new ActionModel(this)),
527 m_actionTreeView(new ActionTreeView(m_model)),
528 m_actionListView(new ActionListView(m_model))
529{
530 addWidget(m_actionListView);
531 addWidget(m_actionTreeView);
532 // Wire signals
533 connect(m_actionTreeView, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)),
534 this, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)));
535 connect(m_actionListView, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)),
536 this, SIGNAL(contextMenuRequested(QContextMenuEvent*,QAction*)));
537
538 // make it possible for vs integration to reimplement edit action dialog
539 // [which it shouldn't do actually]
540 connect(m_actionListView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*)));
541 connect(m_actionTreeView, SIGNAL(activated(QAction*)), this, SIGNAL(activated(QAction*)));
542
543 connect(m_actionListView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*)));
544 connect(m_actionTreeView, SIGNAL(currentChanged(QAction*)),this, SLOT(slotCurrentChanged(QAction*)));
545
546 connect(m_model, SIGNAL(resourceImageDropped(QString,QAction*)),
547 this, SIGNAL(resourceImageDropped(QString,QAction*)));
548
549 // sync selection models
550 QItemSelectionModel *selectionModel = m_actionTreeView->selectionModel();
551 m_actionListView->setSelectionModel(selectionModel);
552 connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
553 this, SIGNAL(selectionChanged(QItemSelection,QItemSelection)));
554}
555
556int ActionView::viewMode() const
557{
558 return currentWidget() == m_actionListView ? IconView : DetailedView;
559}
560
561void ActionView::setViewMode(int lm)
562{
563 if (viewMode() == lm)
564 return;
565
566 switch (lm) {
567 case IconView:
568 setCurrentWidget(m_actionListView);
569 break;
570 case DetailedView:
571 setCurrentWidget(m_actionTreeView);
572 break;
573 default:
574 break;
575 }
576}
577
578void ActionView::slotCurrentChanged(QAction *action)
579{
580 // emit only for currently visible
581 if (sender() == currentWidget())
582 emit currentChanged(action);
583}
584
585void ActionView::filter(const QString &text)
586{
587 m_actionTreeView->filter(text);
588 m_actionListView->filter(text);
589}
590
591void ActionView::selectAll()
592{
593 m_actionTreeView->selectAll();
594}
595
596void ActionView::clearSelection()
597{
598 m_actionTreeView->selectionModel()->clearSelection();
599}
600
601void ActionView::setCurrentIndex(const QModelIndex &index)
602{
603 m_actionTreeView->setCurrentIndex(index);
604}
605
606QAction *ActionView::currentAction() const
607{
608 return m_actionListView->currentAction();
609}
610
611void ActionView::setSelectionMode(QAbstractItemView::SelectionMode sm)
612{
613 m_actionTreeView->setSelectionMode(sm);
614 m_actionListView->setSelectionMode(sm);
615}
616
617QAbstractItemView::SelectionMode ActionView::selectionMode() const
618{
619 return m_actionListView->selectionMode();
620}
621
622QItemSelection ActionView::selection() const
623{
624 return m_actionListView->selectionModel()->selection();
625}
626
627ActionView::ActionList ActionView::selectedActions() const
628{
629 ActionList rc;
630 foreach (const QModelIndex &index, selection().indexes())
631 if (index.column() == 0)
632 rc += actionOfItem(m_model->itemFromIndex(index));
633 return rc;
634}
635// ---------- ActionRepositoryMimeData
636ActionRepositoryMimeData::ActionRepositoryMimeData(QAction *a, Qt::DropAction dropAction) :
637 m_dropAction(dropAction)
638{
639 m_actionList += a;
640}
641
642ActionRepositoryMimeData::ActionRepositoryMimeData(const ActionList &al, Qt::DropAction dropAction) :
643 m_dropAction(dropAction),
644 m_actionList(al)
645{
646}
647
648QStringList ActionRepositoryMimeData::formats() const
649{
650 return QStringList(QLatin1String(actionMimeType));
651}
652
653QPixmap ActionRepositoryMimeData::actionDragPixmap(const QAction *action)
654{
655
656 // Try to find a suitable pixmap. Grab either widget or icon.
657 const QIcon icon = action->icon();
658 if (!icon.isNull())
659 return icon.pixmap(QSize(22, 22));
660
661 foreach (QWidget *w, action->associatedWidgets())
662 if (QToolButton *tb = qobject_cast<QToolButton *>(w))
663 return QPixmap::grabWidget(tb);
664
665 // Create a QToolButton
666 QToolButton *tb = new QToolButton;
667 tb->setText(action->text());
668 tb->setToolButtonStyle(Qt::ToolButtonTextOnly);
669#ifdef Q_WS_WIN // Force alien off to make adjustSize() take the system minimumsize into account.
670 tb->createWinId();
671#endif
672 tb->adjustSize();
673 const QPixmap rc = QPixmap::grabWidget(tb);
674 tb->deleteLater();
675 return rc;
676}
677
678void ActionRepositoryMimeData::accept(QDragMoveEvent *event) const
679{
680#ifndef QT_NO_DRAGANDDROP
681 if (event->proposedAction() == m_dropAction) {
682 event->acceptProposedAction();
683 } else {
684 event->setDropAction(m_dropAction);
685 event->accept();
686 }
687#endif
688}
689
690} // namespace qdesigner_internal
691
692QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.