VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/selector/UIVMListView.cpp@ 35740

Last change on this file since 35740 was 35737, checked in by vboxsync, 13 years ago

FE/Qt4: allow extpacks to be installed by D&D

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.7 KB
Line 
1/* $Id: UIVMListView.cpp 35737 2011-01-27 12:39:17Z vboxsync $ */
2/** @file
3 *
4 * VBox frontends: Qt GUI ("VirtualBox"):
5 * UIVMItemModel, UIVMListView, UIVMItemPainter class implementation
6 */
7
8/*
9 * Copyright (C) 2006-2010 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20#ifdef VBOX_WITH_PRECOMPILED_HEADERS
21# include "precomp.h"
22#else /* !VBOX_WITH_PRECOMPILED_HEADERS */
23
24/* Local includes */
25#include "UIVMListView.h"
26#include "VBoxProblemReporter.h"
27#include "VBoxSelectorWnd.h"
28
29/* Global includes */
30#include <QScrollBar>
31#include <QPainter>
32#include <QLinearGradient>
33#include <QPixmapCache>
34#include <QDropEvent>
35#include <QUrl>
36
37#endif /* !VBOX_WITH_PRECOMPILED_HEADERS */
38
39/* UIVMItemModel class */
40
41void UIVMItemModel::addItem(const CMachine &machine)
42{
43 addItem(new UIVMItem(machine));
44}
45
46void UIVMItemModel::addItem(UIVMItem *aItem)
47{
48 Assert(aItem);
49 insertItem(aItem, m_VMItemList.count());
50}
51
52void UIVMItemModel::insertItem(UIVMItem *pItem, int row)
53{
54 Assert(pItem);
55 beginInsertRows(QModelIndex(), row, row);
56 m_VMItemList.insert(row, pItem);
57 endInsertRows();
58 refreshItem(pItem);
59}
60
61void UIVMItemModel::removeItem(UIVMItem *aItem)
62{
63 Assert(aItem);
64 int row = m_VMItemList.indexOf(aItem);
65 removeRows(row, 1);
66}
67
68bool UIVMItemModel::removeRows(int aRow, int aCount, const QModelIndex &aParent /* = QModelIndex() */)
69{
70 beginRemoveRows(aParent, aRow, aRow + aCount - 1);
71 for (int i = aRow + aCount - 1; i >= aRow; --i)
72 delete m_VMItemList.takeAt(i);
73 endRemoveRows();
74 return true;
75}
76
77/**
78 * Refreshes the item corresponding to the given UUID.
79 */
80void UIVMItemModel::refreshItem(UIVMItem *aItem)
81{
82 Assert(aItem);
83 aItem->recache();
84 itemChanged(aItem);
85}
86
87void UIVMItemModel::itemChanged(UIVMItem *aItem)
88{
89 Assert(aItem);
90 int row = m_VMItemList.indexOf(aItem);
91 /* Emit an layout change signal for the case some dimensions of the item
92 * has changed also. */
93 emit layoutChanged();
94 /* Emit an data changed signal. */
95 emit dataChanged(index(row), index(row));
96}
97
98/**
99 * Clear the item model list. Please note that the items itself are also
100 * deleted.
101 */
102void UIVMItemModel::clear()
103{
104 qDeleteAll(m_VMItemList);
105}
106
107/**
108 * Returns the list item with the given UUID.
109 */
110UIVMItem *UIVMItemModel::itemById(const QString &aId) const
111{
112 foreach(UIVMItem *item, m_VMItemList)
113 if (item->id() == aId)
114 return item;
115 return NULL;
116}
117
118UIVMItem *UIVMItemModel::itemByRow(int aRow) const
119{
120 return m_VMItemList.at(aRow);
121}
122
123QModelIndex UIVMItemModel::indexById(const QString &aId) const
124{
125 int row = rowById(aId);
126 if (row >= 0)
127 return index(row);
128 else
129 return QModelIndex();
130}
131
132int UIVMItemModel::rowById(const QString &aId) const
133{
134 for (int i=0; i < m_VMItemList.count(); ++i)
135 {
136 UIVMItem *item = m_VMItemList.at(i);
137 if (item->id() == aId)
138 return i;
139 }
140 return -1;
141}
142
143QStringList UIVMItemModel::idList() const
144{
145 QStringList list;
146 foreach(UIVMItem *item, m_VMItemList)
147 list << item->id();
148 return list;
149}
150
151void UIVMItemModel::sortByIdList(const QStringList &list)
152{
153 emit layoutAboutToBeChanged();
154 QList<UIVMItem*> tmpVMItemList(m_VMItemList);
155 m_VMItemList.clear();
156 /* Iterate over all ids and move the corresponding items ordered in the new
157 list. */
158 foreach(QString id, list)
159 {
160 QMutableListIterator<UIVMItem*> it(tmpVMItemList);
161 while (it.hasNext())
162 {
163 UIVMItem *pItem = it.next();
164 if (pItem->id() == id)
165 {
166 m_VMItemList << pItem;
167 it.remove();
168 break;
169 }
170 }
171 }
172 /* If there are items which didn't have been in the id list, we sort them
173 by name and appending them to the new list afterward. This make sure the
174 old behavior of VBox is respected. */
175 if (tmpVMItemList.count() > 0)
176 {
177 qSort(tmpVMItemList.begin(), tmpVMItemList.end(), UIVMItemNameCompareLessThan);
178 QListIterator<UIVMItem*> it(tmpVMItemList);
179 while (it.hasNext())
180 m_VMItemList << it.next();
181 }
182 emit layoutChanged();
183}
184
185int UIVMItemModel::rowCount(const QModelIndex & /* aParent = QModelIndex() */) const
186{
187 return m_VMItemList.count();
188}
189
190QVariant UIVMItemModel::data(const QModelIndex &aIndex, int aRole) const
191{
192 if (!aIndex.isValid())
193 return QVariant();
194
195 if (aIndex.row() >= m_VMItemList.size())
196 return QVariant();
197
198 QVariant v;
199 switch (aRole)
200 {
201 case Qt::DisplayRole:
202 {
203 v = m_VMItemList.at(aIndex.row())->name();
204 break;
205 }
206 case Qt::DecorationRole:
207 {
208 v = m_VMItemList.at(aIndex.row())->osIcon();
209 break;
210 }
211 case Qt::ToolTipRole:
212 {
213 v = m_VMItemList.at(aIndex.row())->toolTipText();
214 break;
215 }
216 case Qt::FontRole:
217 {
218 QFont f = qApp->font();
219 f.setPointSize(f.pointSize() + 1);
220 f.setWeight(QFont::Bold);
221 v = f;
222 break;
223 }
224 case Qt::AccessibleTextRole:
225 {
226 UIVMItem *item = m_VMItemList.at(aIndex.row());
227 v = QString("%1 (%2)\n%3")
228 .arg(item->name())
229 .arg(item->snapshotName())
230 .arg(item->machineStateName());
231 break;
232 }
233 case SnapShotDisplayRole:
234 {
235 v = m_VMItemList.at(aIndex.row())->snapshotName();
236 break;
237 }
238 case SnapShotFontRole:
239 {
240 QFont f = qApp->font();
241 v = f;
242 break;
243 }
244 case MachineStateDisplayRole:
245 {
246 v = m_VMItemList.at(aIndex.row())->machineStateName();
247 break;
248 }
249 case MachineStateDecorationRole:
250 {
251 v = m_VMItemList.at(aIndex.row())->machineStateIcon();
252 break;
253 }
254 case MachineStateFontRole:
255 {
256 QFont f = qApp->font();
257 f.setPointSize(f.pointSize());
258 if (m_VMItemList.at(aIndex.row())->sessionState() != KSessionState_Unlocked)
259 f.setItalic(true);
260 v = f;
261 break;
262 }
263 case SessionStateDisplayRole:
264 {
265 v = m_VMItemList.at(aIndex.row())->sessionStateName();
266 break;
267 }
268 case OSTypeIdRole:
269 {
270 v = m_VMItemList.at(aIndex.row())->osTypeId();
271 break;
272 }
273 case UIVMItemPtrRole:
274 {
275 v = qVariantFromValue(m_VMItemList.at(aIndex.row()));
276 break;
277 }
278 }
279 return v;
280}
281
282QVariant UIVMItemModel::headerData(int /* aSection */, Qt::Orientation /* aOrientation */,
283 int /* aRole = Qt::DisplayRole */) const
284{
285 return QVariant();
286}
287
288bool UIVMItemModel::UIVMItemNameCompareLessThan(UIVMItem* aItem1, UIVMItem* aItem2)
289{
290 Assert(aItem1);
291 Assert(aItem2);
292 return aItem1->name().toLower() < aItem2->name().toLower();
293}
294
295Qt::ItemFlags UIVMItemModel::flags(const QModelIndex &index) const
296{
297 Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
298
299 if (index.isValid())
300 return Qt::ItemIsDragEnabled | defaultFlags;
301 else
302 return Qt::ItemIsDropEnabled | defaultFlags;
303}
304
305Qt::DropActions UIVMItemModel::supportedDragActions() const
306{
307 return Qt::MoveAction;
308}
309
310Qt::DropActions UIVMItemModel::supportedDropActions() const
311{
312 return Qt::MoveAction; //| Qt::CopyAction | Qt::LinkAction;
313}
314
315QStringList UIVMItemModel::mimeTypes() const
316{
317 QStringList types;
318 types << UIVMItemMimeData::type();
319 return types;
320}
321
322QMimeData *UIVMItemModel::mimeData(const QModelIndexList &indexes) const
323{
324 UIVMItemMimeData *pMimeData = new UIVMItemMimeData(m_VMItemList.at(indexes.at(0).row()));
325 return pMimeData;
326}
327
328bool UIVMItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
329{
330 if (action == Qt::IgnoreAction)
331 return true;
332
333 if (column > 0)
334 return false;
335
336 int beginRow;
337 if (row != -1)
338 beginRow = row;
339 else if (parent.isValid())
340 beginRow = parent.row();
341 else
342 beginRow = rowCount(QModelIndex());
343
344 if (data->hasFormat(UIVMItemMimeData::type()))
345 {
346 /* Only move allowed. */
347 if (action != Qt::MoveAction)
348 return false;
349 /* Get our own mime data type. */
350 const UIVMItemMimeData *pMimeData = qobject_cast<const UIVMItemMimeData*>(data);
351 if (pMimeData)
352 {
353 insertItem(new UIVMItem(pMimeData->item()->machine()), beginRow);
354 return true;
355 }
356 }
357 return false;
358}
359
360/* UIVMListView class */
361
362UIVMListView::UIVMListView(QAbstractListModel *pModel, QWidget *aParent /* = 0 */)
363 : QIListView(aParent)
364 , m_fItemInMove(false)
365{
366 /* For queued events Q_DECLARE_METATYPE isn't sufficient. */
367 qRegisterMetaType< QList<QUrl> >("QList<QUrl>");
368 setSelectionMode(QAbstractItemView::SingleSelection);
369 setDragEnabled(true);
370 setAcceptDrops(true);
371 setDropIndicatorShown(true);
372 /* Create & set our delegation class */
373 UIVMItemPainter *delegate = new UIVMItemPainter(this);
374 setItemDelegate(delegate);
375 setModel(pModel);
376 /* Default icon size */
377 setIconSize(QSize(32, 32));
378 /* Publish the activation of items */
379 connect(this, SIGNAL(activated(const QModelIndex &)),
380 this, SIGNAL(activated()));
381 /* Use the correct policy for the context menu */
382 setContextMenuPolicy(Qt::CustomContextMenu);
383 connect(pModel, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
384 this, SLOT(sltRowsAboutToBeInserted(const QModelIndex &, int, int)));
385}
386
387void UIVMListView::selectItemByRow(int row)
388{
389 setCurrentIndex(model()->index(row, 0));
390}
391
392void UIVMListView::selectItemById(const QString &aID)
393{
394 if (UIVMItemModel *m = qobject_cast <UIVMItemModel*>(model()))
395 {
396 QModelIndex i = m->indexById(aID);
397 if (i.isValid())
398 setCurrentIndex(i);
399 }
400}
401
402void UIVMListView::ensureSomeRowSelected(int aRowHint)
403{
404 UIVMItem *item = selectedItem();
405 if (!item)
406 {
407 aRowHint = qBound(0, aRowHint, model()->rowCount() - 1);
408 selectItemByRow(aRowHint);
409 item = selectedItem();
410 if (!item)
411 selectItemByRow(0);
412 }
413}
414
415UIVMItem *UIVMListView::selectedItem() const
416{
417 QModelIndexList indexes = selectedIndexes();
418 if (indexes.isEmpty())
419 return NULL;
420 return model()->data(indexes.first(), UIVMItemModel::UIVMItemPtrRole).value<UIVMItem*>();
421}
422
423void UIVMListView::ensureCurrentVisible()
424{
425 scrollTo(currentIndex(), QAbstractItemView::EnsureVisible);
426}
427
428void UIVMListView::selectionChanged(const QItemSelection &aSelected, const QItemSelection &aDeselected)
429{
430 QListView::selectionChanged(aSelected, aDeselected);
431 selectCurrent();
432 ensureCurrentVisible();
433 emit currentChanged();
434}
435
436void UIVMListView::currentChanged(const QModelIndex &aCurrent, const QModelIndex &aPrevious)
437{
438 QListView::currentChanged(aCurrent, aPrevious);
439 selectCurrent();
440 ensureCurrentVisible();
441 emit currentChanged();
442}
443
444void UIVMListView::dataChanged(const QModelIndex &aTopLeft, const QModelIndex &aBottomRight)
445{
446 QListView::dataChanged(aTopLeft, aBottomRight);
447 selectCurrent();
448// ensureCurrentVisible();
449 emit currentChanged();
450}
451
452void UIVMListView::sltRowsAboutToBeInserted(const QModelIndex & /* parent */, int /* start */, int /* end */)
453{
454 /* On D&D the items in the view jumps like hell, cause after inserting the
455 new item, but before deleting the old item an update is triggered. We
456 disable the updates now. */
457 if (m_fItemInMove)
458 setUpdatesEnabled(false);
459}
460
461void UIVMListView::rowsInserted(const QModelIndex & /* parent */, int start, int /* end */)
462{
463 /* Select the new item, after a D&D operation. Note: don't call the base
464 class, cause this will trigger an update. */
465 if (m_fItemInMove)
466 selectItemByRow(start);
467}
468
469bool UIVMListView::selectCurrent()
470{
471 QModelIndexList indexes = selectionModel()->selectedIndexes();
472 if (indexes.isEmpty() ||
473 indexes.first() != currentIndex())
474 {
475 /* Make sure that the current is always selected */
476 selectionModel()->select(currentIndex(), QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect);
477 return true;
478 }
479 return false;
480}
481
482void UIVMListView::dragEnterEvent(QDragEnterEvent *pEvent)
483{
484 if (qApp->activeModalWidget() != 0)
485 pEvent->ignore();
486 else
487 {
488 QListView::dragEnterEvent(pEvent);
489 checkDragEvent(pEvent);
490 }
491}
492
493void UIVMListView::dragMoveEvent(QDragMoveEvent *pEvent)
494{
495 QListView::dragMoveEvent(pEvent);
496 checkDragEvent(pEvent);
497}
498
499void UIVMListView::checkDragEvent(QDragMoveEvent *pEvent)
500{
501 if (pEvent->mimeData()->hasUrls())
502 {
503 QList<QUrl> list = pEvent->mimeData()->urls();
504 QString file = list.at(0).toLocalFile();
505 if (VBoxGlobal::hasAllowedExtension(file, VBoxDefs::VBoxFileExts))
506 {
507 Qt::DropAction action = Qt::IgnoreAction;
508 if (pEvent->possibleActions().testFlag(Qt::LinkAction))
509 action = Qt::LinkAction;
510 else if (pEvent->possibleActions().testFlag(Qt::CopyAction))
511 action = Qt::CopyAction;
512 if (action != Qt::IgnoreAction)
513 {
514 pEvent->setDropAction(action);
515 pEvent->accept();
516 }
517 }else if ( VBoxGlobal::hasAllowedExtension(file, VBoxDefs::OVFFileExts)
518 && pEvent->possibleActions().testFlag(Qt::CopyAction))
519 {
520 pEvent->setDropAction(Qt::CopyAction);
521 pEvent->accept();
522 }else if ( VBoxGlobal::hasAllowedExtension(file, VBoxDefs::VBoxExtPackFileExts)
523 && pEvent->possibleActions().testFlag(Qt::CopyAction))
524 {
525 pEvent->setDropAction(Qt::CopyAction);
526 pEvent->accept();
527 }
528 }
529}
530
531void UIVMListView::dropEvent(QDropEvent *pEvent)
532{
533 if (pEvent->mimeData()->hasFormat(UIVMItemMimeData::type()))
534 QListView::dropEvent(pEvent);
535 else if (pEvent->mimeData()->hasUrls())
536 {
537 QList<QUrl> list = pEvent->mimeData()->urls();
538 pEvent->acceptProposedAction();
539 emit sigUrlsDropped(list);
540 }
541}
542
543void UIVMListView::startDrag(Qt::DropActions supportedActions)
544{
545 /* Select all indexes which are currently selected and dragable. */
546 QModelIndexList indexes = selectedIndexes();
547 for(int i = indexes.count() - 1 ; i >= 0; --i)
548 {
549 if (!(model()->flags(indexes.at(i)) & Qt::ItemIsDragEnabled))
550 indexes.removeAt(i);
551 }
552 if (indexes.count() > 0)
553 {
554 m_fItemInMove = true;
555 QMimeData *data = model()->mimeData(indexes);
556 if (!data)
557 return;
558 /* We only support one at the time. Also we need a persistent index,
559 cause the position of the old index may change. */
560 QPersistentModelIndex oldIdx(indexes.at(0));
561 QRect rect;
562 QPixmap pixmap = dragPixmap(oldIdx);
563 rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
564 QDrag *drag = new QDrag(this);
565 drag->setPixmap(pixmap);
566 drag->setMimeData(data);
567 drag->setHotSpot(QPoint(5, 5));
568 Qt::DropAction defaultDropAction = Qt::MoveAction;
569 /* Now start the drag. */
570 if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction)
571 /* Remove the old item */
572 model()->removeRows(oldIdx.row(), 1, QModelIndex());
573 m_fItemInMove = false;
574 setUpdatesEnabled(true);
575 }
576}
577
578QPixmap UIVMListView::dragPixmap(const QModelIndex &index) const
579{
580 QString name = index.data(Qt::DisplayRole).toString();
581 if (name.length() > 100)
582 name = name.remove(97, name.length() - 97) + "...";
583 QSize nameSize = fontMetrics().boundingRect(name).size();
584 nameSize.setWidth(nameSize.width() + 30);
585 QPixmap osType = index.data(Qt::DecorationRole).value<QIcon>().pixmap(32, 32);
586 QSize osTypeSize = osType.size();
587 int space = 5, margin = 15;
588 QSize s(2 * margin + qMax(osTypeSize.width(), nameSize.width()),
589 2 * margin + osTypeSize.height() + space + nameSize.height());
590 QImage image(s, QImage::Format_ARGB32);
591 image.fill(QColor(Qt::transparent).rgba());
592 QPainter p(&image);
593 p.setRenderHint(QPainter::Antialiasing);
594 p.setPen(QPen(Qt::white, 2));
595 p.setBrush(QColor(80, 80, 80));
596 p.drawRoundedRect(1, 1, s.width() - 1 * 2, s.height() - 1 * 2, 6, 6);
597 p.drawPixmap((s.width() - osTypeSize.width()) / 2, margin, osType);
598 p.setPen(Qt::white);
599 p.setFont(font());
600 p.drawText(QRect(margin, margin + osTypeSize.height() + space, s.width() - 2 * margin, nameSize.height()), Qt::AlignCenter, name);
601 /* Transparent icons are not supported on all platforms. */
602#if defined(Q_WS_MAC) || defined(Q_WS_WIN)
603# ifdef Q_WS_WIN
604 /* Todo: check Win XP, Vista, 2003 */
605# if (QT_VERSION >= 0x040500)
606 if (QSysInfo::windowsVersion() == QSysInfo::WV_WINDOWS7)
607# endif /* QT_VERSION >= 0x040500 */
608# endif /* Q_WS_WIN */
609 {
610 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
611 p.fillRect(image.rect(), QColor(0, 0, 0, 177));
612 }
613#endif /* defined(Q_WS_MAC) || defined(Q_WS_WIN) */
614 p.end();
615 /* Some Qt versions seems buggy in creating QPixmap from QImage. Seems they
616 * don't clear the background. */
617 QPixmap i1(s);
618 i1.fill(Qt::transparent);
619 QPainter p1(&i1);
620 p1.drawImage(image.rect(), image);
621 p1.end();
622 return i1;
623}
624
625QModelIndex UIVMListView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
626{
627 if (modifiers.testFlag(Qt::ControlModifier))
628 {
629 switch (cursorAction)
630 {
631 case QAbstractItemView::MoveUp:
632 {
633 QModelIndex index = currentIndex();
634 return moveItemTo(index, index.row() - 1);
635 }
636 case QAbstractItemView::MoveDown:
637 {
638 QModelIndex index = currentIndex();
639 return moveItemTo(index, index.row() + 2);
640 }
641 case QAbstractItemView::MovePageUp:
642 {
643 QModelIndex index = currentIndex();
644 return moveItemTo(index, qMax(0, index.row() - verticalScrollBar()->pageStep()));
645 }
646 case QAbstractItemView::MovePageDown:
647 {
648 QModelIndex index = currentIndex();
649 return moveItemTo(index, qMin(model()->rowCount(), index.row() + verticalScrollBar()->pageStep() + 1));
650 }
651 case QAbstractItemView::MoveHome:
652 return moveItemTo(currentIndex(), 0);
653 case QAbstractItemView::MoveEnd:
654 return moveItemTo(currentIndex(), model()->rowCount());
655 default:
656 break;
657 }
658 }
659 return QListView::moveCursor(cursorAction, modifiers);
660}
661
662QModelIndex UIVMListView::moveItemTo(const QModelIndex &index, int row)
663{
664 if (!index.isValid())
665 return QModelIndex();
666
667 UIVMItemModel *pModel = static_cast<UIVMItemModel*>(model());
668 if (row < 0 || row > pModel->rowCount())
669 return QModelIndex();
670
671 QPersistentModelIndex perIndex(index);
672 UIVMItem *pItem = pModel->data(index, UIVMItemModel::UIVMItemPtrRole).value<UIVMItem*>();
673 m_fItemInMove = true;
674 pModel->insertItem(new UIVMItem(pItem->machine()), row);
675 QPersistentModelIndex newIndex = pModel->index(row);
676 pModel->removeRows(perIndex.row(), 1, QModelIndex());
677 m_fItemInMove = false;
678 setUpdatesEnabled(true);
679 return QModelIndex(newIndex);
680}
681
682/* UIVMItemPainter class */
683/*
684 +----------------------------------------------+
685 | marg |
686 | +----------+ m |
687 | m | | m a name_string___________ m |
688 | a | OSType | a r a |
689 | r | icon | r g +--+ r |
690 | g | | g / |si| state_string g |
691 | +----------+ 2 +--+ |
692 | marg |
693 +----------------------------------------------+
694
695 si = state icon
696
697*/
698
699/* Little helper class for layout calculation */
700class QRectList: public QList<QRect *>
701{
702public:
703 void alignVCenterTo(QRect* aWhich)
704 {
705 QRect b;
706 foreach(QRect *rect, *this)
707 if(rect != aWhich)
708 b |= *rect;
709 if (b.width() > aWhich->width())
710 aWhich->moveCenter(QPoint(aWhich->center().x(), b.center().y()));
711 else
712 {
713 foreach(QRect *rect, *this)
714 if(rect != aWhich)
715 rect->moveCenter(QPoint(rect->center().x(), aWhich->center().y()));
716 }
717 }
718};
719
720QSize UIVMItemPainter::sizeHint(const QStyleOptionViewItem &aOption,
721 const QModelIndex &aIndex) const
722{
723 /* Get the size of every item */
724 QRect osTypeRT = rect(aOption, aIndex, Qt::DecorationRole);
725 QRect vmNameRT = rect(aOption, aIndex, Qt::DisplayRole);
726 QRect shotRT = rect(aOption, aIndex, UIVMItemModel::SnapShotDisplayRole);
727 QRect stateIconRT = rect(aOption, aIndex, UIVMItemModel::MachineStateDecorationRole);
728 QRect stateRT = rect(aOption, aIndex, UIVMItemModel::MachineStateDisplayRole);
729 /* Calculate the position for every item */
730 calcLayout(aIndex, &osTypeRT, &vmNameRT, &shotRT, &stateIconRT, &stateRT);
731 /* Calc the bounding rect */
732 const QRect boundingRect = osTypeRT | vmNameRT | shotRT | stateIconRT | stateRT;
733 /* Return + left/top/right/bottom margin */
734 return (boundingRect.size() + QSize(2 * m_Margin, 2 * m_Margin));
735}
736
737void UIVMItemPainter::paint(QPainter *pPainter, const QStyleOptionViewItem &option,
738 const QModelIndex &index) const
739{
740 /* Generate the key used in the pixmap cache. Needs to be composed with all
741 * values which might be changed. */
742 QString key = QString("vbox:%1:%2:%3:%4:%5:%6:%7:%8:%9")
743 .arg(index.data(Qt::DisplayRole).toString())
744 .arg(index.data(UIVMItemModel::OSTypeIdRole).toString())
745 .arg(index.data(UIVMItemModel::SnapShotDisplayRole).toString())
746 .arg(index.data(UIVMItemModel::MachineStateDisplayRole).toString())
747 .arg(index.data(UIVMItemModel::SessionStateDisplayRole).toString())
748 .arg(option.state)
749 .arg(option.rect.width())
750 .arg(qobject_cast<QWidget*>(parent())->hasFocus())
751 .arg(qApp->focusWidget() == NULL);
752
753 /* Check if the pixmap already exists in the cache. */
754 QPixmap pixmap;
755 if (!QPixmapCache::find(key, pixmap))
756 {
757 /* If not, generate a new one */
758 QStyleOptionViewItem tmpOption(option);
759 /* Highlight background if an item is selected in any case.
760 * (Fix for selector in the windows style.) */
761 tmpOption.showDecorationSelected = true;
762
763 /* Create a temporary pixmap and painter to work on.*/
764 QPixmap tmpPixmap(option.rect.size());
765 tmpPixmap.fill(Qt::transparent);
766 QPainter tmpPainter(&tmpPixmap);
767
768 /* Normally we operate on a painter which is in the size of the list
769 * view widget. Here we process one item only, so shift all the stuff
770 * out of the view. It will be translated back in the following
771 * methods. */
772 tmpPainter.translate(-option.rect.x(), -option.rect.y());
773
774 /* Start drawing with the background */
775 drawBackground(&tmpPainter, tmpOption, index);
776
777 /* Blend the content */
778 blendContent(&tmpPainter, tmpOption, index);
779
780 /* Draw a focus rectangle when necessary */
781 drawFocus(&tmpPainter, tmpOption, tmpOption.rect);
782
783 /* Finish drawing */
784 tmpPainter.end();
785
786 pixmap = tmpPixmap;
787 /* Fill the cache */
788 QPixmapCache::insert(key, tmpPixmap);
789 }
790 pPainter->drawPixmap(option.rect, pixmap);
791}
792
793QRect UIVMItemPainter::paintContent(QPainter *pPainter, const QStyleOptionViewItem &option,
794 const QModelIndex &index) const
795{
796 /* Name and decoration */
797 const QString vmName = index.data(Qt::DisplayRole).toString();
798 const QFont nameFont = index.data(Qt::FontRole).value<QFont>();
799 const QPixmap osType = index.data(Qt::DecorationRole).value<QIcon>().pixmap(option.decorationSize, iconMode(option.state), iconState(option.state));
800
801 const QString shot = index.data(UIVMItemModel::SnapShotDisplayRole).toString();
802 const QFont shotFont = index.data(UIVMItemModel::SnapShotFontRole).value<QFont>();
803
804 const QString state = index.data(UIVMItemModel::MachineStateDisplayRole).toString();
805 const QFont stateFont = index.data(UIVMItemModel::MachineStateFontRole).value<QFont>();
806 const QPixmap stateIcon = index.data(UIVMItemModel::MachineStateDecorationRole).value<QIcon>().pixmap(QSize(16, 16), iconMode(option.state), iconState(option.state));
807
808 /* Get the sizes for all items */
809 QRect osTypeRT = rect(option, index, Qt::DecorationRole);
810 QRect vmNameRT = rect(option, index, Qt::DisplayRole);
811 QRect shotRT = rect(option, index, UIVMItemModel::SnapShotDisplayRole);
812 QRect stateIconRT = rect(option, index, UIVMItemModel::MachineStateDecorationRole);
813 QRect stateRT = rect(option, index, UIVMItemModel::MachineStateDisplayRole);
814
815 /* Calculate the positions for all items */
816 calcLayout(index, &osTypeRT, &vmNameRT, &shotRT, &stateIconRT, &stateRT);
817 /* Get the appropriate pen for the current state */
818 QPalette pal = option.palette;
819 QPen pen = pal.color(QPalette::Active, QPalette::Text);
820 if (option.state & QStyle::State_Selected &&
821 (option.state & QStyle::State_HasFocus ||
822 QApplication::style()->styleHint(QStyle::SH_ItemView_ChangeHighlightOnFocus, &option) == 0))
823 pen = pal.color(QPalette::Active, QPalette::HighlightedText);
824 /* Set the current pen */
825 pPainter->setPen(pen);
826 /* os type icon */
827 pPainter->drawPixmap(osTypeRT, osType);
828 /* vm name */
829 pPainter->setFont(nameFont);
830 pPainter->drawText(vmNameRT, vmName);
831 /* current snapshot in braces */
832 if (!shot.isEmpty())
833 {
834 pPainter->setFont(shotFont);
835 pPainter->drawText(shotRT, QString("(%1)").arg(shot));
836 }
837 /* state icon */
838 pPainter->drawPixmap(stateIconRT, stateIcon);
839 /* textual state */
840 pPainter->setFont(stateFont);
841 pPainter->drawText(stateRT, state);
842 QRect boundingRect = osTypeRT | vmNameRT | shotRT | stateIconRT | stateRT;
843 /* For debugging */
844// pPainter->drawRect(boundingRect);
845 return boundingRect;
846}
847
848void UIVMItemPainter::blendContent(QPainter *pPainter, const QStyleOptionViewItem &option,
849 const QModelIndex &index) const
850{
851 QRect r = option.rect;
852 QWidget *pParent = qobject_cast<QListView *>(parent())->viewport();
853 /* This is as always a big fat mess on Mac OS X. We can't use QImage for
854 * rendering text, cause this looks like shit. We can't do all the drawing
855 * on the widget, cause the composition modes are not working correctly.
856 * The same count for doing composition on a QPixmap. The work around is to
857 * draw all into a QPixmap (also the background color/gradient, otherwise
858 * the antialiasing is messed up), blitting this into a QImage to make the
859 * composition stuff and finally blitting this QImage into the QWidget.
860 * Yipi a yeah. Btw, no problem on Linux at all. */
861 QPixmap basePixmap(r.width(), r.height());//
862 /* Initialize with the base image color. */
863 basePixmap.fill(pParent->palette().base().color());
864 /* Create the painter to operate on. */
865 QPainter basePainter(&basePixmap);
866 /* Initialize the painter with the corresponding widget */
867 basePainter.initFrom(pParent);
868 basePainter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, true);
869 /* The translation is necessary, cause drawBackground expect the whole drawing area. */
870 basePainter.save();
871 basePainter.translate(-option.rect.x(), -option.rect.y());
872 drawBackground(&basePainter, option, index);
873 basePainter.restore();
874 /* Now paint the content. */
875 QRect usedRect = paintContent(&basePainter, option, index);
876 /* Finished with the OS dependent part. */
877 basePainter.end();
878 /* Time for the OS independent part (That is, use the QRasterEngine) */
879 QImage baseImage(r.width(), r.height(), QImage::Format_ARGB32_Premultiplied);
880 QPainter rasterPainter(&baseImage);
881 /* Initialize the painter with the corresponding widget */
882 rasterPainter.initFrom(pParent);
883 rasterPainter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, true);
884 /* Fully copy the source to the destination */
885 rasterPainter.setCompositionMode(QPainter::CompositionMode_Source);
886 rasterPainter.drawPixmap(0, 0, basePixmap);
887 if (usedRect.x() + usedRect.width() > option.rect.width())
888 {
889 /* Now use the alpha value of the source to blend the destination in. */
890 rasterPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
891 const int blendWidth = qMin(30, r.width());
892 QLinearGradient lg(r.width()-blendWidth, 0, r.width(), 0);
893 lg.setColorAt(0, QColor(Qt::white));
894 lg.setColorAt(0.95, QColor(Qt::transparent));
895 lg.setColorAt(1, QColor(Qt::transparent));
896 rasterPainter.fillRect(r.width()-blendWidth, 0, blendWidth, r.height(), lg);
897 }
898 /* Finished with the OS independent part. */
899 rasterPainter.end();
900
901 /* Finally blit our hard work on the widget. */
902 pPainter->drawImage(option.rect.x(), option.rect.y(), baseImage);
903}
904
905QRect UIVMItemPainter::rect(const QStyleOptionViewItem &aOption,
906 const QModelIndex &aIndex, int aRole) const
907{
908 switch (aRole)
909 {
910 case Qt::DisplayRole:
911 {
912 QString text = aIndex.data(Qt::DisplayRole).toString();
913 QFontMetrics fm(fontMetric(aIndex, Qt::FontRole));
914 return QRect(QPoint(0, 0), fm.size(0, text));
915 break;
916 }
917 case Qt::DecorationRole:
918 {
919 QIcon icon = aIndex.data(Qt::DecorationRole).value<QIcon>();
920 return QRect(QPoint(0, 0), icon.actualSize(aOption.decorationSize, iconMode(aOption.state), iconState(aOption.state)));
921 break;
922 }
923 case UIVMItemModel::SnapShotDisplayRole:
924 {
925 QString text = aIndex.data(UIVMItemModel::SnapShotDisplayRole).toString();
926 if (!text.isEmpty())
927 {
928 QFontMetrics fm(fontMetric(aIndex, UIVMItemModel::SnapShotFontRole));
929 return QRect(QPoint(0, 0), fm.size(0, QString("(%1)").arg(text)));
930 }else
931 return QRect();
932 break;
933 }
934 case UIVMItemModel::MachineStateDisplayRole:
935 {
936 QString text = aIndex.data(UIVMItemModel::MachineStateDisplayRole).toString();
937 QFontMetrics fm(fontMetric(aIndex, UIVMItemModel::MachineStateFontRole));
938 return QRect(QPoint(0, 0), fm.size(0, text));
939 break;
940 }
941 case UIVMItemModel::MachineStateDecorationRole:
942 {
943 QIcon icon = aIndex.data(UIVMItemModel::MachineStateDecorationRole).value<QIcon>();
944 return QRect(QPoint(0, 0), icon.actualSize(QSize(16, 16), iconMode(aOption.state), iconState(aOption.state)));
945 break;
946 }
947 }
948 return QRect();
949}
950
951void UIVMItemPainter::calcLayout(const QModelIndex &aIndex,
952 QRect *aOSType, QRect *aVMName, QRect *aShot,
953 QRect *aStateIcon, QRect *aState) const
954{
955 const int nameSpaceWidth = fontMetric(aIndex, Qt::FontRole).width(' ');
956 const int stateSpaceWidth = fontMetric(aIndex, UIVMItemModel::MachineStateFontRole).width(' ');
957 /* Really basic layout management.
958 * First layout as usual */
959 aOSType->moveTo(m_Margin, m_Margin);
960 aVMName->moveTo(m_Margin + aOSType->width() + m_Spacing, m_Margin);
961 aShot->moveTo(aVMName->right() + nameSpaceWidth, aVMName->top());
962 aStateIcon->moveTo(aVMName->left(), aVMName->bottom());
963 aState->moveTo(aStateIcon->right() + stateSpaceWidth, aStateIcon->top());
964 /* Do grouping for the automatic center routine.
965 * First the states group: */
966 QRectList statesLayout;
967 statesLayout << aStateIcon << aState;
968 /* All items in the layout: */
969 QRectList allLayout;
970 allLayout << aOSType << aVMName << aShot << statesLayout;
971 /* Now vertically center the items based on the reference item */
972 statesLayout.alignVCenterTo(aStateIcon);
973 allLayout.alignVCenterTo(aOSType);
974}
975
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use