VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/snapshots/UISnapshotPane.cpp@ 102493

Last change on this file since 102493 was 102269, checked in by vboxsync, 10 months ago

FE/Qt: bugref:10450: macOS: No need to QIToolBar layout/paint hacks since Qt6.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 66.6 KB
Line 
1/* $Id: UISnapshotPane.cpp 102269 2023-11-22 18:50:08Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UISnapshotPane class implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QAccessibleWidget>
30#include <QApplication>
31#include <QDateTime>
32#include <QHeaderView>
33#include <QIcon>
34#include <QMenu>
35#include <QPointer>
36#include <QReadWriteLock>
37#include <QRegExp>
38#include <QScrollBar>
39#include <QTimer>
40#include <QVBoxLayout>
41#include <QWriteLocker>
42
43/* GUI includes: */
44#include "QIMessageBox.h"
45#include "QIToolBar.h"
46#include "QITreeWidget.h"
47#include "UIActionPoolManager.h"
48#include "UIConverter.h"
49#include "UIExtraDataManager.h"
50#include "UIIconPool.h"
51#include "UIMessageCenter.h"
52#include "UIModalWindowManager.h"
53#include "UINotificationCenter.h"
54#include "UISnapshotDetailsWidget.h"
55#include "UISnapshotPane.h"
56#include "UITakeSnapshotDialog.h"
57#include "UITranslator.h"
58#include "UIVirtualBoxEventHandler.h"
59#include "UIVirtualMachineItem.h"
60#include "UIVirtualMachineItemLocal.h"
61#include "UIWizardCloneVM.h"
62
63/* COM includes: */
64#include "CConsole.h"
65#include "CSnapshot.h"
66
67
68/** Snapshot tree column tags. */
69enum
70{
71 Column_Name,
72 Column_Taken,
73 Column_Max,
74};
75
76
77/** QITreeWidgetItem subclass for snapshots items. */
78class UISnapshotItem : public QITreeWidgetItem, public UIDataSnapshot
79{
80 Q_OBJECT;
81
82public:
83
84 /** Casts QTreeWidgetItem* to UISnapshotItem* if possible. */
85 static UISnapshotItem *toSnapshotItem(QTreeWidgetItem *pItem);
86 /** Casts const QTreeWidgetItem* to const UISnapshotItem* if possible. */
87 static const UISnapshotItem *toSnapshotItem(const QTreeWidgetItem *pItem);
88
89 /** Constructs normal snapshot item (child of tree-widget). */
90 UISnapshotItem(UISnapshotPane *pSnapshotWidget,
91 QITreeWidget *pTreeWidget,
92 const CSnapshot &comSnapshot,
93 bool fExtendedNameRequired);
94 /** Constructs normal snapshot item (child of tree-widget-item). */
95 UISnapshotItem(UISnapshotPane *pSnapshotWidget,
96 QITreeWidgetItem *pRootItem,
97 const CSnapshot &comSnapshot);
98
99 /** Constructs "current state" item (child of tree-widget). */
100 UISnapshotItem(UISnapshotPane *pSnapshotWidget,
101 QITreeWidget *pTreeWidget,
102 const CMachine &comMachine,
103 bool fExtendedNameRequired);
104 /** Constructs "current state" item (child of tree-widget-item). */
105 UISnapshotItem(UISnapshotPane *pSnapshotWidget,
106 QITreeWidgetItem *pRootItem,
107 const CMachine &comMachine);
108
109 /** Returns item machine. */
110 CMachine machine() const { return m_comMachine; }
111 /** Returns item machine ID. */
112 QUuid machineID() const { return m_uMachineID; }
113 /** Returns item snapshot. */
114 CSnapshot snapshot() const { return m_comSnapshot; }
115 /** Returns item snapshot ID. */
116 QUuid snapshotID() const { return m_uSnapshotID; }
117
118 /** Returns whether this is the "current state" item. */
119 bool isCurrentStateItem() const { return m_fCurrentStateItem; }
120
121 /** Returns whether this is the "current snapshot" item. */
122 bool isCurrentSnapshotItem() const { return m_fCurrentSnapshotItem; }
123 /** Defines whether this is the @a fCurrent snapshot item. */
124 void setCurrentSnapshotItem(bool fCurrent);
125
126 /** Calculates and returns the current item level. */
127 int level() const;
128
129 /** Recaches the item's contents. */
130 void recache();
131
132 /** Returns current machine state. */
133 KMachineState machineState() const;
134 /** Defines current machine @a enmState. */
135 void setMachineState(KMachineState enmState);
136
137 /** Updates item age. */
138 SnapshotAgeFormat updateAge();
139
140protected:
141
142 /** Recaches item tool-tip. */
143 void recacheToolTip();
144
145private:
146
147 /** Holds whether this item requires extended name. */
148 bool m_fExtendedNameRequired;
149
150 /** Holds the pointer to the snapshot-widget this item belongs to. */
151 QPointer<UISnapshotPane> m_pSnapshotWidget;
152
153 /** Holds whether this is a "current state" item. */
154 bool m_fCurrentStateItem;
155 /** Holds whether this is a "current snapshot" item. */
156 bool m_fCurrentSnapshotItem;
157
158 /** Holds the snapshot COM wrapper. */
159 CSnapshot m_comSnapshot;
160 /** Holds the machine COM wrapper. */
161 CMachine m_comMachine;
162
163 /** Holds the machine ID. */
164 QUuid m_uMachineID;
165 /** Holds the "current snapshot" ID. */
166 QUuid m_uSnapshotID;
167 /** Holds whether the "current snapshot" is online one. */
168 bool m_fOnline;
169
170 /** Holds the item timestamp. */
171 QDateTime m_timestamp;
172
173 /** Holds whether the current state is modified. */
174 bool m_fCurrentStateModified;
175 /** Holds the cached machine state. */
176 KMachineState m_enmMachineState;
177};
178
179
180/** QScrollBar subclass for snapshots widget. */
181class UISnapshotScrollBar : public QScrollBar
182{
183 Q_OBJECT;
184
185signals:
186
187 /** Notify listeners about our visibility changed. */
188 void sigNotifyAboutVisibilityChange();
189
190public:
191
192 /** Constructs scroll-bar passing @a enmOrientation and @a pParent to the base-class. */
193 UISnapshotScrollBar(Qt::Orientation enmOrientation, QWidget *pParent = 0);
194
195protected:
196
197 /** Handles show @a pEvent. */
198 virtual void showEvent(QShowEvent *pEvent) RT_OVERRIDE;
199};
200
201
202/** QITreeWidget subclass for snapshots items. */
203class UISnapshotTree : public QITreeWidget
204{
205 Q_OBJECT;
206
207signals:
208
209 /** Notify listeners about one of scroll-bars visibility changed. */
210 void sigNotifyAboutScrollBarVisibilityChange();
211
212public:
213
214 /** Constructs snapshot tree passing @a pParent to the base-class. */
215 UISnapshotTree(QWidget *pParent);
216};
217
218
219/*********************************************************************************************************************************
220* Class UISnapshotItem implementation. *
221*********************************************************************************************************************************/
222
223/* static */
224UISnapshotItem *UISnapshotItem::toSnapshotItem(QTreeWidgetItem *pItem)
225{
226 /* Get QITreeWidgetItem item first: */
227 QITreeWidgetItem *pIItem = QITreeWidgetItem::toItem(pItem);
228 if (!pIItem)
229 return 0;
230
231 /* Return casted UISnapshotItem then: */
232 return qobject_cast<UISnapshotItem*>(pIItem);
233}
234
235/* static */
236const UISnapshotItem *UISnapshotItem::toSnapshotItem(const QTreeWidgetItem *pItem)
237{
238 /* Get QITreeWidgetItem item first: */
239 const QITreeWidgetItem *pIItem = QITreeWidgetItem::toItem(pItem);
240 if (!pIItem)
241 return 0;
242
243 /* Return casted UISnapshotItem then: */
244 return qobject_cast<const UISnapshotItem*>(pIItem);
245}
246
247UISnapshotItem::UISnapshotItem(UISnapshotPane *pSnapshotWidget,
248 QITreeWidget *pTreeWidget,
249 const CSnapshot &comSnapshot,
250 bool fExtendedNameRequired)
251 : QITreeWidgetItem(pTreeWidget)
252 , m_fExtendedNameRequired(fExtendedNameRequired)
253 , m_pSnapshotWidget(pSnapshotWidget)
254 , m_fCurrentStateItem(false)
255 , m_fCurrentSnapshotItem(false)
256 , m_comSnapshot(comSnapshot)
257 , m_fOnline(false)
258 , m_fCurrentStateModified(false)
259 , m_enmMachineState(KMachineState_Null)
260{
261}
262
263UISnapshotItem::UISnapshotItem(UISnapshotPane *pSnapshotWidget,
264 QITreeWidgetItem *pRootItem,
265 const CSnapshot &comSnapshot)
266 : QITreeWidgetItem(pRootItem)
267 , m_fExtendedNameRequired(false)
268 , m_pSnapshotWidget(pSnapshotWidget)
269 , m_fCurrentStateItem(false)
270 , m_fCurrentSnapshotItem(false)
271 , m_comSnapshot(comSnapshot)
272 , m_fOnline(false)
273 , m_fCurrentStateModified(false)
274 , m_enmMachineState(KMachineState_Null)
275{
276}
277
278UISnapshotItem::UISnapshotItem(UISnapshotPane *pSnapshotWidget,
279 QITreeWidget *pTreeWidget,
280 const CMachine &comMachine,
281 bool fExtendedNameRequired)
282 : QITreeWidgetItem(pTreeWidget)
283 , m_fExtendedNameRequired(fExtendedNameRequired)
284 , m_pSnapshotWidget(pSnapshotWidget)
285 , m_fCurrentStateItem(true)
286 , m_fCurrentSnapshotItem(false)
287 , m_comMachine(comMachine)
288 , m_fOnline(false)
289 , m_fCurrentStateModified(false)
290 , m_enmMachineState(KMachineState_Null)
291{
292 /* Set the bold font state
293 * for "current state" item: */
294 QFont myFont = font(Column_Name);
295 myFont.setBold(true);
296 setFont(Column_Name, myFont);
297
298 /* Fetch current machine state: */
299 setMachineState(m_comMachine.GetState());
300}
301
302UISnapshotItem::UISnapshotItem(UISnapshotPane *pSnapshotWidget,
303 QITreeWidgetItem *pRootItem,
304 const CMachine &comMachine)
305 : QITreeWidgetItem(pRootItem)
306 , m_fExtendedNameRequired(false)
307 , m_pSnapshotWidget(pSnapshotWidget)
308 , m_fCurrentStateItem(true)
309 , m_fCurrentSnapshotItem(false)
310 , m_comMachine(comMachine)
311 , m_fOnline(false)
312 , m_fCurrentStateModified(false)
313 , m_enmMachineState(KMachineState_Null)
314{
315 /* Set the bold font state
316 * for "current state" item: */
317 QFont myFont = font(Column_Name);
318 myFont.setBold(true);
319 setFont(Column_Name, myFont);
320
321 /* Fetch current machine state: */
322 setMachineState(m_comMachine.GetState());
323}
324
325int UISnapshotItem::level() const
326{
327 const QTreeWidgetItem *pItem = this;
328 int iResult = 0;
329 while (pItem->parent())
330 {
331 ++iResult;
332 pItem = pItem->parent();
333 }
334 return iResult;
335}
336
337void UISnapshotItem::setCurrentSnapshotItem(bool fCurrent)
338{
339 /* Remember the state: */
340 m_fCurrentSnapshotItem = fCurrent;
341
342 /* Set/clear the bold font state
343 * for "current snapshot" item: */
344 QFont myFont = font(Column_Name);
345 myFont.setBold(fCurrent);
346 setFont(Column_Name, myFont);
347
348 /* Update tool-tip: */
349 recacheToolTip();
350}
351
352void UISnapshotItem::recache()
353{
354 /* For "current state" item: */
355 if (m_fCurrentStateItem)
356 {
357 /* Fetch machine information: */
358 AssertReturnVoid(m_comMachine.isNotNull());
359 m_uMachineID = m_comMachine.GetId();
360 m_fCurrentStateModified = m_comMachine.GetCurrentStateModified();
361 m_strName = m_fCurrentStateModified
362 ? tr("Current State (changed)", "Current State (Modified)")
363 : tr("Current State", "Current State (Unmodified)");
364 const QString strFinalName = m_fExtendedNameRequired
365 ? QString("%1 (%2)").arg(m_strName, m_comMachine.GetName())
366 : m_strName;
367 setText(Column_Name, strFinalName);
368 m_strDescription = m_fCurrentStateModified
369 ? tr("The current state differs from the state stored in the current snapshot")
370 : QTreeWidgetItem::parent() != 0
371 ? tr("The current state is identical to the state stored in the current snapshot")
372 : QString();
373 }
374 /* For snapshot item: */
375 else
376 {
377 /* Fetch snapshot information: */
378 AssertReturnVoid(m_comSnapshot.isNotNull());
379 const CMachine comMachine = m_comSnapshot.GetMachine();
380 m_uMachineID = comMachine.GetId();
381 m_uSnapshotID = m_comSnapshot.GetId();
382 m_strName = m_comSnapshot.GetName();
383 const QString strFinalName = m_fExtendedNameRequired
384 ? QString("%1 (%2)").arg(m_strName, comMachine.GetName())
385 : m_strName;
386 setText(Column_Name, strFinalName);
387 m_fOnline = m_comSnapshot.GetOnline();
388 setIcon(Column_Name, *m_pSnapshotWidget->snapshotItemIcon(m_fOnline));
389 m_strDescription = m_comSnapshot.GetDescription();
390#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
391 m_timestamp.setSecsSinceEpoch(m_comSnapshot.GetTimeStamp() / 1000);
392#else
393 m_timestamp.setTime_t(m_comSnapshot.GetTimeStamp() / 1000);
394#endif
395 m_fCurrentStateModified = false;
396 }
397
398 /* Update tool-tip: */
399 recacheToolTip();
400}
401
402KMachineState UISnapshotItem::machineState() const
403{
404 /* Make sure machine is valid: */
405 if (m_comMachine.isNull())
406 return KMachineState_Null;
407
408 /* Return cached state: */
409 return m_enmMachineState;
410}
411
412void UISnapshotItem::setMachineState(KMachineState enmState)
413{
414 /* Make sure machine is valid: */
415 if (m_comMachine.isNull())
416 return;
417
418 /* Cache new state: */
419 m_enmMachineState = enmState;
420 /* Set corresponding icon: */
421 setIcon(Column_Name, gpConverter->toIcon(m_enmMachineState));
422 /* Update timestamp: */
423#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
424 m_timestamp.setSecsSinceEpoch(m_comMachine.GetLastStateChange() / 1000);
425#else
426 m_timestamp.setTime_t(m_comMachine.GetLastStateChange() / 1000);
427#endif
428}
429
430SnapshotAgeFormat UISnapshotItem::updateAge()
431{
432 /* Prepare age: */
433 QString strAge;
434
435 /* Age: [date time|%1d ago|%1h ago|%1min ago|%1sec ago] */
436 SnapshotAgeFormat enmAgeFormat;
437 const QDateTime now = QDateTime::currentDateTime();
438 QDateTime then = m_timestamp;
439 if (then > now)
440 then = now; /* can happen if the host time is wrong */
441 if (then.daysTo(now) > 30)
442 {
443 strAge = QLocale::system().toString(then, QLocale::ShortFormat);
444 enmAgeFormat = SnapshotAgeFormat_Max;
445 }
446 else if (then.secsTo(now) > 60 * 60 * 24)
447 {
448 strAge = QString("%1 (%2)")
449 .arg(QLocale::system().toString(then, QLocale::ShortFormat),
450 UITranslator::daysToStringAgo(then.secsTo(now) / 60 / 60 / 24));
451 enmAgeFormat = SnapshotAgeFormat_InDays;
452 }
453 else if (then.secsTo(now) > 60 * 60)
454 {
455 strAge = QString("%1 (%2)")
456 .arg(QLocale::system().toString(then, QLocale::ShortFormat),
457 UITranslator::hoursToStringAgo(then.secsTo(now) / 60 / 60));
458 enmAgeFormat = SnapshotAgeFormat_InHours;
459 }
460 else if (then.secsTo(now) > 60)
461 {
462 strAge = QString("%1 (%2)")
463 .arg(QLocale::system().toString(then, QLocale::ShortFormat),
464 UITranslator::minutesToStringAgo(then.secsTo(now) / 60));
465 enmAgeFormat = SnapshotAgeFormat_InMinutes;
466 }
467 else
468 {
469 strAge = QString("%1 (%2)")
470 .arg(QLocale::system().toString(then, QLocale::ShortFormat),
471 UITranslator::secondsToStringAgo(then.secsTo(now)));
472 enmAgeFormat = SnapshotAgeFormat_InSeconds;
473 }
474
475 /* Update data: */
476 if (!m_fCurrentStateItem)
477 setText(Column_Taken, strAge);
478
479 /* Return age: */
480 return enmAgeFormat;
481}
482
483void UISnapshotItem::recacheToolTip()
484{
485 /* Is the saved date today? */
486 const bool fDateTimeToday = m_timestamp.date() == QDate::currentDate();
487
488 /* Compose date time: */
489 QString strDateTime = fDateTimeToday
490 ? QLocale::system().toString(m_timestamp.time(), QLocale::ShortFormat)
491 : QLocale::system().toString(m_timestamp, QLocale::ShortFormat);
492
493 /* Prepare details: */
494 QString strDetails;
495
496 /* For "current state" item: */
497 if (m_fCurrentStateItem)
498 {
499 strDateTime = tr("%1 since %2", "Current State (time or date + time)")
500 .arg(gpConverter->toString(m_enmMachineState)).arg(strDateTime);
501 }
502 /* For snapshot item: */
503 else
504 {
505 /* Gather details: */
506 QStringList details;
507 if (isCurrentSnapshotItem())
508 details << tr("current", "snapshot");
509 details << (m_fOnline ? tr("online", "snapshot")
510 : tr("offline", "snapshot"));
511 strDetails = QString(" (%1)").arg(details.join(", "));
512
513 /* Add date/time information: */
514 if (fDateTimeToday)
515 strDateTime = tr("Taken at %1", "Snapshot (time)").arg(strDateTime);
516 else
517 strDateTime = tr("Taken on %1", "Snapshot (date + time)").arg(strDateTime);
518 }
519
520 /* Prepare tool-tip: */
521 QString strToolTip = QString("<nobr><b>%1</b>%2</nobr><br><nobr>%3</nobr>")
522 .arg(name()).arg(strDetails).arg(strDateTime);
523
524 /* Append description if any: */
525 if (!m_strDescription.isEmpty())
526 strToolTip += "<hr>" + m_strDescription;
527
528 /* Assign tool-tip finally: */
529 setToolTip(Column_Name, strToolTip);
530}
531
532
533/*********************************************************************************************************************************
534* Class UISnapshotScrollBar implementation. *
535*********************************************************************************************************************************/
536UISnapshotScrollBar::UISnapshotScrollBar(Qt::Orientation enmOrientation, QWidget *pParent /* = 0 */)
537 : QScrollBar(enmOrientation, pParent)
538{
539}
540
541void UISnapshotScrollBar::showEvent(QShowEvent *pEvent)
542{
543 QScrollBar::showEvent(pEvent);
544 emit sigNotifyAboutVisibilityChange();
545}
546
547
548/*********************************************************************************************************************************
549* Class UISnapshotTree implementation. *
550*********************************************************************************************************************************/
551
552UISnapshotTree::UISnapshotTree(QWidget *pParent)
553 : QITreeWidget(pParent)
554{
555 /* Configure snapshot tree: */
556 setAutoScroll(false);
557 setColumnCount(Column_Max);
558 setAllColumnsShowFocus(true);
559 setAlternatingRowColors(true);
560 setExpandsOnDoubleClick(false);
561 setContextMenuPolicy(Qt::CustomContextMenu);
562 setEditTriggers( QAbstractItemView::SelectedClicked
563 | QAbstractItemView::EditKeyPressed);
564
565 /* Replace scroll-bars: */
566 UISnapshotScrollBar *pScrollBarH = new UISnapshotScrollBar(Qt::Horizontal, this);
567 if (pScrollBarH)
568 {
569 connect(pScrollBarH, &UISnapshotScrollBar::sigNotifyAboutVisibilityChange,
570 this, &UISnapshotTree::sigNotifyAboutScrollBarVisibilityChange);
571 setHorizontalScrollBar(pScrollBarH);
572 }
573 UISnapshotScrollBar *pScrollBarV = new UISnapshotScrollBar(Qt::Vertical, this);
574 if (pScrollBarV)
575 {
576 connect(pScrollBarV, &UISnapshotScrollBar::sigNotifyAboutVisibilityChange,
577 this, &UISnapshotTree::sigNotifyAboutScrollBarVisibilityChange);
578 setVerticalScrollBar(pScrollBarV);
579 }
580}
581
582
583/*********************************************************************************************************************************
584* Class UISnapshotPane implementation. *
585*********************************************************************************************************************************/
586
587UISnapshotPane::UISnapshotPane(UIActionPool *pActionPool, bool fShowToolbar /* = true */, QWidget *pParent /* = 0 */)
588 : QIWithRetranslateUI<QWidget>(pParent)
589 , m_pActionPool(pActionPool)
590 , m_fShowToolbar(fShowToolbar)
591 , m_pLockReadWrite(0)
592 , m_pIconSnapshotOffline(0)
593 , m_pIconSnapshotOnline(0)
594 , m_pTimerUpdateAge(0)
595 , m_pLayoutMain(0)
596 , m_pToolBar(0)
597 , m_pSnapshotTree(0)
598 , m_pDetailsWidget(0)
599{
600 prepare();
601}
602
603UISnapshotPane::~UISnapshotPane()
604{
605 cleanup();
606}
607
608void UISnapshotPane::setMachineItems(const QList<UIVirtualMachineItem*> &items)
609{
610 /* Wipe out old stuff: */
611 m_machines.clear();
612 m_sessionStates.clear();
613 m_operationAllowed.clear();
614
615 /* For each machine item: */
616 foreach (UIVirtualMachineItem *pItem, items)
617 {
618 /* Parse machine details: */
619 AssertPtrReturnVoid(pItem);
620 CMachine comMachine = pItem->toLocal()->machine();
621
622 /* Cache passed machine: */
623 if (!comMachine.isNull())
624 {
625 const QUuid uMachineId = comMachine.GetId();
626 const KSessionState enmSessionState = comMachine.GetSessionState();
627 const bool fAllowance = gEDataManager->machineSnapshotOperationsEnabled(uMachineId);
628 m_machines[uMachineId] = comMachine;
629 m_sessionStates[uMachineId] = enmSessionState;
630 m_operationAllowed[uMachineId] = fAllowance;
631 }
632 }
633
634 /* Refresh everything: */
635 refreshAll();
636}
637
638const QIcon *UISnapshotPane::snapshotItemIcon(bool fOnline) const
639{
640 return !fOnline ? m_pIconSnapshotOffline : m_pIconSnapshotOnline;
641}
642
643bool UISnapshotPane::isCurrentStateItemSelected() const
644{
645 UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
646 return m_currentStateItems.values().contains(pSnapshotItem);
647}
648
649QUuid UISnapshotPane::currentSnapshotId()
650{
651 UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
652 CSnapshot comSnapshot = pSnapshotItem ? pSnapshotItem->snapshot() : CSnapshot();
653 return comSnapshot.isNotNull() ? comSnapshot.GetId() : QUuid();
654}
655
656void UISnapshotPane::retranslateUi()
657{
658 /* Translate snapshot tree: */
659 m_pSnapshotTree->setWhatsThis(tr("Contains the snapshot tree of the current virtual machine"));
660
661 /* Translate snapshot tree: */
662 const QStringList fields = QStringList()
663 << tr("Name", "snapshot")
664 << tr("Taken", "snapshot");
665 m_pSnapshotTree->setHeaderLabels(fields);
666
667 /* Refresh whole the tree: */
668 refreshAll();
669}
670
671void UISnapshotPane::resizeEvent(QResizeEvent *pEvent)
672{
673 /* Call to base-class: */
674 QIWithRetranslateUI<QWidget>::resizeEvent(pEvent);
675
676 /* Adjust snapshot tree: */
677 adjustTreeWidget();
678}
679
680void UISnapshotPane::showEvent(QShowEvent *pEvent)
681{
682 /* Call to base-class: */
683 QIWithRetranslateUI<QWidget>::showEvent(pEvent);
684
685 /* Adjust snapshot tree: */
686 adjustTreeWidget();
687}
688
689void UISnapshotPane::sltHandleMachineDataChange(const QUuid &uMachineId)
690{
691 /* Make sure it's our VM: */
692 if (!m_machines.keys().contains(uMachineId))
693 return;
694
695 /* Prevent snapshot editing in the meantime: */
696 QWriteLocker locker(m_pLockReadWrite);
697
698 /* Recache "current state" item data: */
699 m_currentStateItems.value(uMachineId)->recache();
700
701 /* Choose current item again (to update details-widget): */
702 sltHandleCurrentItemChange();
703}
704
705void UISnapshotPane::sltHandleMachineStateChange(const QUuid &uMachineId, const KMachineState enmState)
706{
707 /* Make sure it's our VM: */
708 if (!m_machines.keys().contains(uMachineId))
709 return;
710
711 /* Prevent snapshot editing in the meantime: */
712 QWriteLocker locker(m_pLockReadWrite);
713
714 /* Recache "current state" item data and machine-state: */
715 m_currentStateItems.value(uMachineId)->recache();
716 m_currentStateItems.value(uMachineId)->setMachineState(enmState);
717}
718
719void UISnapshotPane::sltHandleSessionStateChange(const QUuid &uMachineId, const KSessionState enmState)
720{
721 /* Make sure it's our VM: */
722 if (!m_machines.keys().contains(uMachineId))
723 return;
724
725 /* Prevent snapshot editing in the meantime: */
726 QWriteLocker locker(m_pLockReadWrite);
727
728 /* Recache current session-state: */
729 m_sessionStates[uMachineId] = enmState;
730
731 /* Update action states: */
732 updateActionStates();
733}
734
735void UISnapshotPane::sltHandleSnapshotTake(const QUuid &uMachineId, const QUuid &uSnapshotId)
736{
737 /* Make sure it's our VM: */
738 if (!m_machines.keys().contains(uMachineId))
739 return;
740
741 LogRel(("GUI: Updating snapshot tree after TAKING snapshot with MachineID={%s}, SnapshotID={%s}...\n",
742 uMachineId.toString().toUtf8().constData(), uSnapshotId.toString().toUtf8().constData()));
743
744 /* Prepare result: */
745 bool fSuccess = true;
746 {
747 /* Prevent snapshot editing in the meantime: */
748 QWriteLocker locker(m_pLockReadWrite);
749
750 /* Acquire corresponding machine: */
751 CMachine comMachine = m_machines.value(uMachineId);
752
753 /* Search for corresponding snapshot: */
754 CSnapshot comSnapshot = comMachine.FindSnapshot(uSnapshotId.toString());
755 fSuccess = comMachine.isOk() && !comSnapshot.isNull();
756
757 /* Show error message if necessary: */
758 if (!fSuccess)
759 UINotificationMessage::cannotFindSnapshotById(comMachine, uSnapshotId);
760 else
761 {
762 /* Where will be the newly created item located? */
763 UISnapshotItem *pParentItem = 0;
764
765 /* Acquire snapshot parent: */
766 CSnapshot comParentSnapshot = comSnapshot.GetParent();
767 if (comParentSnapshot.isNotNull())
768 {
769 /* Acquire parent snapshot id: */
770 const QUuid uParentSnapshotId = comParentSnapshot.GetId();
771 fSuccess = comParentSnapshot.isOk();
772
773 /* Show error message if necessary: */
774 if (!fSuccess)
775 UINotificationMessage::cannotAcquireSnapshotParameter(comSnapshot);
776 else
777 {
778 /* Search for an existing parent-item with such id: */
779 pParentItem = findItem(uParentSnapshotId);
780 fSuccess = pParentItem;
781 }
782 }
783
784 /* Make sure this parent-item is a parent of "current state" item as well: */
785 if (fSuccess)
786 fSuccess = qobject_cast<UISnapshotItem*>(m_currentStateItems.value(uMachineId)->parentItem()) == pParentItem;
787 /* Make sure this parent-item is a "current snapshot" item as well: */
788 if (fSuccess)
789 fSuccess = m_currentSnapshotItems.value(uMachineId) == pParentItem;
790
791 /* Create new item: */
792 if (fSuccess)
793 {
794 /* Delete "current state" item first of all: */
795 UISnapshotItem *pCurrentStateItem = m_currentStateItems.value(uMachineId);
796 m_currentStateItems[uMachineId] = 0;
797 delete pCurrentStateItem;
798 pCurrentStateItem = 0;
799
800 /* Create "current snapshot" item for a newly taken snapshot: */
801 if (m_currentSnapshotItems.value(uMachineId))
802 m_currentSnapshotItems.value(uMachineId)->setCurrentSnapshotItem(false);
803 m_currentSnapshotItems[uMachineId] = pParentItem
804 ? new UISnapshotItem(this, pParentItem, comSnapshot)
805 : new UISnapshotItem(this, m_pSnapshotTree, comSnapshot, m_machines.size() > 1);
806 /* Mark it as current: */
807 m_currentSnapshotItems.value(uMachineId)->setCurrentSnapshotItem(true);
808 /* And recache it's content: */
809 m_currentSnapshotItems.value(uMachineId)->recache();
810
811 /* Create "current state" item as a child of "current snapshot" item: */
812 m_currentStateItems[uMachineId] = new UISnapshotItem(this, m_currentSnapshotItems.value(uMachineId), comMachine);
813 /* Recache it's content: */
814 m_currentStateItems.value(uMachineId)->recache();
815 /* And choose is as current one: */
816 m_pSnapshotTree->setCurrentItem(m_currentStateItems.value(uMachineId));
817 sltHandleCurrentItemChange();
818
819 LogRel(("GUI: Snapshot tree update successful!\n"));
820 }
821 }
822 }
823
824 /* Just refresh everything as fallback: */
825 if (!fSuccess)
826 {
827 LogRel(("GUI: Snapshot tree update failed! Rebuilding from scratch...\n"));
828 refreshAll();
829 }
830}
831
832void UISnapshotPane::sltHandleSnapshotDelete(const QUuid &uMachineId, const QUuid &uSnapshotId)
833{
834 /* Make sure it's our VM: */
835 if (!m_machines.keys().contains(uMachineId))
836 return;
837
838 LogRel(("GUI: Updating snapshot tree after DELETING snapshot with MachineID={%s}, SnapshotID={%s}...\n",
839 uMachineId.toString().toUtf8().constData(), uSnapshotId.toString().toUtf8().constData()));
840
841 /* Prepare result: */
842 bool fSuccess = false;
843 {
844 /* Prevent snapshot editing in the meantime: */
845 QWriteLocker locker(m_pLockReadWrite);
846
847 /* Search for an existing item with such id: */
848 UISnapshotItem *pItem = findItem(uSnapshotId);
849 fSuccess = pItem;
850
851 /* Make sure item has no more than one child: */
852 if (fSuccess)
853 fSuccess = pItem->childCount() <= 1;
854
855 /* Detach child before deleting item: */
856 QTreeWidgetItem *pChild = 0;
857 if (fSuccess && pItem->childCount() == 1)
858 pChild = pItem->takeChild(0);
859
860 /* Check whether item has parent: */
861 QTreeWidgetItem *pParent = 0;
862 if (fSuccess)
863 pParent = pItem->QTreeWidgetItem::parent();
864
865 /* If item has child: */
866 if (pChild)
867 {
868 /* Determine where the item located: */
869 int iIndexOfChild = -1;
870 if (fSuccess)
871 {
872 if (pParent)
873 iIndexOfChild = pParent->indexOfChild(pItem);
874 else
875 iIndexOfChild = m_pSnapshotTree->indexOfTopLevelItem(pItem);
876 fSuccess = iIndexOfChild != -1;
877 }
878
879 /* And move child into this place: */
880 if (fSuccess)
881 {
882 if (pParent)
883 pParent->insertChild(iIndexOfChild, pChild);
884 else
885 m_pSnapshotTree->insertTopLevelItem(iIndexOfChild, pChild);
886 expandItemChildren(pChild);
887 }
888 }
889
890 /* Delete item finally: */
891 if (fSuccess)
892 {
893 if (pItem == m_currentSnapshotItems.value(uMachineId))
894 {
895 m_currentSnapshotItems[uMachineId] = UISnapshotItem::toSnapshotItem(pParent);
896 if (m_currentSnapshotItems.value(uMachineId))
897 m_currentSnapshotItems.value(uMachineId)->setCurrentSnapshotItem(true);
898 }
899 delete pItem;
900 pItem = 0;
901
902 LogRel(("GUI: Snapshot tree update successful!\n"));
903 }
904 }
905
906 /* Just refresh everything as fallback: */
907 if (!fSuccess)
908 {
909 LogRel(("GUI: Snapshot tree update failed! Rebuilding from scratch...\n"));
910 refreshAll();
911 }
912}
913
914void UISnapshotPane::sltHandleSnapshotChange(const QUuid &uMachineId, const QUuid &uSnapshotId)
915{
916 /* Make sure it's our VM: */
917 if (!m_machines.keys().contains(uMachineId))
918 return;
919
920 LogRel(("GUI: Updating snapshot tree after CHANGING snapshot with MachineID={%s}, SnapshotID={%s}...\n",
921 uMachineId.toString().toUtf8().constData(), uSnapshotId.toString().toUtf8().constData()));
922
923 /* Prepare result: */
924 bool fSuccess = true;
925 {
926 /* Prevent snapshot editing in the meantime: */
927 QWriteLocker locker(m_pLockReadWrite);
928
929 /* Search for an existing item with such id: */
930 UISnapshotItem *pItem = findItem(uSnapshotId);
931 fSuccess = pItem;
932
933 /* Update the item: */
934 if (fSuccess)
935 {
936 /* Recache it: */
937 pItem->recache();
938 /* And choose it again if it's current one (to update details-widget): */
939 if (UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem()) == pItem)
940 sltHandleCurrentItemChange();
941
942 LogRel(("GUI: Snapshot tree update successful!\n"));
943 }
944 }
945
946 /* Just refresh everything as fallback: */
947 if (!fSuccess)
948 {
949 LogRel(("GUI: Snapshot tree update failed! Rebuilding from scratch...\n"));
950 refreshAll();
951 }
952}
953
954void UISnapshotPane::sltHandleSnapshotRestore(const QUuid &uMachineId, const QUuid &uSnapshotId)
955{
956 /* Make sure it's our VM: */
957 if (!m_machines.keys().contains(uMachineId))
958 return;
959
960 LogRel(("GUI: Updating snapshot tree after RESTORING snapshot with MachineID={%s}, SnapshotID={%s}...\n",
961 uMachineId.toString().toUtf8().constData(), uSnapshotId.toString().toUtf8().constData()));
962
963 /* Prepare result: */
964 bool fSuccess = true;
965 {
966 /* Prevent snapshot editing in the meantime: */
967 QWriteLocker locker(m_pLockReadWrite);
968
969 /* Search for an existing item with such id: */
970 UISnapshotItem *pItem = findItem(uSnapshotId);
971 fSuccess = pItem;
972
973 /* Choose this item as new "current snapshot": */
974 if (fSuccess)
975 {
976 /* Delete "current state" item first of all: */
977 UISnapshotItem *pCurrentStateItem = m_currentStateItems.value(uMachineId);
978 m_currentStateItems[uMachineId] = 0;
979 delete pCurrentStateItem;
980 pCurrentStateItem = 0;
981
982 /* Move the "current snapshot" token from one to another: */
983 AssertPtrReturnVoid(m_currentSnapshotItems.value(uMachineId));
984 m_currentSnapshotItems.value(uMachineId)->setCurrentSnapshotItem(false);
985 m_currentSnapshotItems[uMachineId] = pItem;
986 m_currentSnapshotItems.value(uMachineId)->setCurrentSnapshotItem(true);
987
988 /* Create "current state" item as a child of "current snapshot" item: */
989 m_currentStateItems[uMachineId] = new UISnapshotItem(this, m_currentSnapshotItems.value(uMachineId), m_machines.value(uMachineId));
990 m_currentStateItems.value(uMachineId)->recache();
991 /* And choose is as current one: */
992 m_pSnapshotTree->setCurrentItem(m_currentStateItems.value(uMachineId));
993 sltHandleCurrentItemChange();
994
995 LogRel(("GUI: Snapshot tree update successful!\n"));
996 }
997 }
998
999 /* Just refresh everything as fallback: */
1000 if (!fSuccess)
1001 {
1002 LogRel(("GUI: Snapshot tree update failed! Rebuilding from scratch...\n"));
1003 refreshAll();
1004 }
1005}
1006
1007void UISnapshotPane::sltUpdateSnapshotsAge()
1008{
1009 /* Stop timer if active: */
1010 if (m_pTimerUpdateAge->isActive())
1011 m_pTimerUpdateAge->stop();
1012
1013 /* Search for smallest snapshot age to optimize timer timeout: */
1014 const SnapshotAgeFormat enmAge = traverseSnapshotAge(m_pSnapshotTree->invisibleRootItem());
1015 switch (enmAge)
1016 {
1017 case SnapshotAgeFormat_InSeconds: m_pTimerUpdateAge->setInterval(5 * 1000); break;
1018 case SnapshotAgeFormat_InMinutes: m_pTimerUpdateAge->setInterval(60 * 1000); break;
1019 case SnapshotAgeFormat_InHours: m_pTimerUpdateAge->setInterval(60 * 60 * 1000); break;
1020 case SnapshotAgeFormat_InDays: m_pTimerUpdateAge->setInterval(24 * 60 * 60 * 1000); break;
1021 default: m_pTimerUpdateAge->setInterval(0); break;
1022 }
1023
1024 /* Restart timer if necessary: */
1025 if (m_pTimerUpdateAge->interval() > 0)
1026 m_pTimerUpdateAge->start();
1027}
1028
1029void UISnapshotPane::sltToggleSnapshotDetailsVisibility(bool fVisible)
1030{
1031 /* Save the setting: */
1032 gEDataManager->setSnapshotManagerDetailsExpanded(fVisible);
1033 /* Show/hide details-widget: */
1034 m_pDetailsWidget->setVisible(fVisible);
1035 /* If details-widget is visible: */
1036 if (m_pDetailsWidget->isVisible())
1037 {
1038 /* Acquire selected item: */
1039 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1040 AssertPtrReturnVoid(pSnapshotItem);
1041 /* Update details-widget: */
1042 if (pSnapshotItem->isCurrentStateItem())
1043 {
1044 if (m_machines.value(pSnapshotItem->machineID()).isNull())
1045 m_pDetailsWidget->clearData();
1046 else
1047 m_pDetailsWidget->setData(m_machines.value(pSnapshotItem->machineID()));
1048 }
1049 else
1050 m_pDetailsWidget->setData(*pSnapshotItem, pSnapshotItem->snapshot());
1051 }
1052 /* Cleanup invisible details-widget: */
1053 else
1054 m_pDetailsWidget->clearData();
1055}
1056
1057void UISnapshotPane::sltApplySnapshotDetailsChanges()
1058{
1059 /* Acquire selected item: */
1060 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1061 AssertPtrReturnVoid(pSnapshotItem);
1062
1063 /* For current state item: */
1064 if (pSnapshotItem->isCurrentStateItem())
1065 {
1066 /* Get item data: */
1067 UIDataSnapshot newData = m_pDetailsWidget->data();
1068
1069 /* Take snapshot: */
1070 UINotificationProgressSnapshotTake *pNotification = new UINotificationProgressSnapshotTake(m_machines.value(pSnapshotItem->machineID()),
1071 newData.name(),
1072 newData.description());
1073 gpNotificationCenter->append(pNotification);
1074 }
1075 /* For snapshot items: */
1076 else
1077 {
1078 /* Make sure nothing being edited in the meantime: */
1079 if (!m_pLockReadWrite->tryLockForWrite())
1080 return;
1081
1082 /* Make sure that's a snapshot item indeed: */
1083 CSnapshot comSnapshot = pSnapshotItem->snapshot();
1084 AssertReturnVoid(comSnapshot.isNotNull());
1085
1086 /* Get item data: */
1087 UIDataSnapshot oldData = *pSnapshotItem;
1088 UIDataSnapshot newData = m_pDetailsWidget->data();
1089 AssertReturnVoid(newData != oldData);
1090
1091 /* Open a session (this call will handle all errors): */
1092 CSession comSession;
1093 if (m_sessionStates.value(pSnapshotItem->machineID()) != KSessionState_Unlocked)
1094 comSession = uiCommon().openExistingSession(pSnapshotItem->machineID());
1095 else
1096 comSession = uiCommon().openSession(pSnapshotItem->machineID());
1097 if (comSession.isNotNull())
1098 {
1099 /* Get corresponding machine object: */
1100 CMachine comMachine = comSession.GetMachine();
1101
1102 /* Perform separate independent steps: */
1103 do
1104 {
1105 /* Save snapshot name: */
1106 if (newData.name() != oldData.name())
1107 {
1108 comSnapshot.SetName(newData.name());
1109 if (!comSnapshot.isOk())
1110 {
1111 UINotificationMessage::cannotChangeSnapshot(comSnapshot, oldData.name(), comMachine.GetName());
1112 break;
1113 }
1114 }
1115
1116 /* Save snapshot description: */
1117 if (newData.description() != oldData.description())
1118 {
1119 comSnapshot.SetDescription(newData.description());
1120 if (!comSnapshot.isOk())
1121 {
1122 UINotificationMessage::cannotChangeSnapshot(comSnapshot, oldData.name(), comMachine.GetName());
1123 break;
1124 }
1125 }
1126 }
1127 while (0);
1128
1129 /* Cleanup session: */
1130 comSession.UnlockMachine();
1131 }
1132
1133 /* Allows editing again: */
1134 m_pLockReadWrite->unlock();
1135 }
1136
1137 /* Adjust snapshot tree: */
1138 adjustTreeWidget();
1139}
1140
1141void UISnapshotPane::sltHandleCurrentItemChange()
1142{
1143 /* Acquire "current snapshot" item: */
1144 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1145
1146 /* Make the current item visible: */
1147 sltHandleScrollBarVisibilityChange();
1148
1149 /* Update action states: */
1150 updateActionStates();
1151
1152 /* Update details-widget if it's visible: */
1153 if (pSnapshotItem && !m_pDetailsWidget->isHidden())
1154 {
1155 if (pSnapshotItem->isCurrentStateItem())
1156 {
1157 CMachine comMachine = m_machines.value(pSnapshotItem->machineID());
1158 if (comMachine.isNull())
1159 m_pDetailsWidget->clearData();
1160 else
1161 m_pDetailsWidget->setData(comMachine);
1162 }
1163 else
1164 m_pDetailsWidget->setData(*pSnapshotItem, pSnapshotItem->snapshot());
1165 }
1166 /* Cleanup invisible details-widget: */
1167 else
1168 m_pDetailsWidget->clearData();
1169
1170 /* Notify listeners: */
1171 emit sigCurrentItemChange();
1172}
1173
1174void UISnapshotPane::sltHandleContextMenuRequest(const QPoint &position)
1175{
1176 /* Search for corresponding item: */
1177 const QTreeWidgetItem *pItem = m_pSnapshotTree->itemAt(position);
1178 if (!pItem)
1179 return;
1180
1181 /* Acquire corresponding snapshot item: */
1182 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(pItem);
1183 AssertReturnVoid(pSnapshotItem);
1184
1185 /* Prepare menu: */
1186 QMenu menu;
1187 /* For snapshot item: */
1188 if (m_currentSnapshotItems.value(pSnapshotItem->machineID()) && !pSnapshotItem->isCurrentStateItem())
1189 {
1190 menu.addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Delete));
1191 menu.addSeparator();
1192 menu.addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Restore));
1193 menu.addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_T_Properties));
1194 menu.addSeparator();
1195 menu.addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Clone));
1196 }
1197 /* For "current state" item: */
1198 else
1199 {
1200 menu.addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Take));
1201 menu.addSeparator();
1202 menu.addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Clone));
1203 }
1204
1205 /* Show menu: */
1206 menu.exec(m_pSnapshotTree->viewport()->mapToGlobal(position));
1207}
1208
1209void UISnapshotPane::sltHandleItemChange(QTreeWidgetItem *pItem)
1210{
1211 /* Make sure nothing being edited in the meantime: */
1212 if (!m_pLockReadWrite->tryLockForWrite())
1213 return;
1214
1215 /* Acquire corresponding snapshot item: */
1216 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(pItem);
1217 AssertPtr(pSnapshotItem);
1218 if (pSnapshotItem)
1219 {
1220 /* Make sure that's a snapshot item indeed: */
1221 CSnapshot comSnapshot = pSnapshotItem->snapshot();
1222 if (comSnapshot.isNotNull())
1223 {
1224 /* Rename corresponding snapshot if necessary: */
1225 if (comSnapshot.GetName() != pSnapshotItem->name())
1226 {
1227 /* We need to open a session when we manipulate the snapshot data of a machine: */
1228 CSession comSession = uiCommon().openExistingSession(comSnapshot.GetMachine().GetId());
1229 if (!comSession.isNull())
1230 {
1231 /// @todo Add settings save validation.
1232
1233 /* Save snapshot name: */
1234 comSnapshot.SetName(pSnapshotItem->name());
1235
1236 /* Close the session again: */
1237 comSession.UnlockMachine();
1238 }
1239 }
1240 }
1241 }
1242
1243 /* Allows editing again: */
1244 m_pLockReadWrite->unlock();
1245
1246 /* Adjust snapshot tree: */
1247 adjustTreeWidget();
1248}
1249
1250void UISnapshotPane::sltHandleItemDoubleClick(QTreeWidgetItem *pItem)
1251{
1252 /* Acquire corresponding snapshot item: */
1253 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(pItem);
1254 AssertReturnVoid(pSnapshotItem);
1255
1256 /* If this is a snapshot item: */
1257 if (pSnapshotItem)
1258 {
1259 /* Handle Ctrl+DoubleClick: */
1260 if (QApplication::keyboardModifiers() == Qt::ControlModifier)
1261 {
1262 /* As snapshot-restore procedure: */
1263 if (pSnapshotItem->isCurrentStateItem())
1264 takeSnapshot(true /* automatically */);
1265 else
1266 restoreSnapshot(true /* suppress non-critical warnings */);
1267 }
1268 /* Handle Ctrl+Shift+DoubleClick: */
1269 else if (QApplication::keyboardModifiers() == (Qt::KeyboardModifiers)(Qt::ControlModifier | Qt::ShiftModifier))
1270 {
1271 /* As snapshot-delete procedure: */
1272 if (!pSnapshotItem->isCurrentStateItem())
1273 deleteSnapshot(true /* automatically */);
1274 }
1275 /* Handle other kinds of DoubleClick: */
1276 else
1277 {
1278 /* As show details-widget procedure: */
1279 m_pActionPool->action(UIActionIndexMN_M_Snapshot_T_Properties)->setChecked(true);
1280 }
1281 }
1282}
1283
1284void UISnapshotPane::sltHandleScrollBarVisibilityChange()
1285{
1286 /* Acquire "current snapshot" item: */
1287 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1288
1289 /* Make the current item visible: */
1290 if (pSnapshotItem)
1291 {
1292 m_pSnapshotTree->horizontalScrollBar()->setValue(0);
1293 m_pSnapshotTree->scrollToItem(pSnapshotItem);
1294 m_pSnapshotTree->horizontalScrollBar()->setValue(m_pSnapshotTree->indentation() * pSnapshotItem->level());
1295 }
1296}
1297
1298void UISnapshotPane::prepare()
1299{
1300 /* Create read-write locker: */
1301 m_pLockReadWrite = new QReadWriteLock;
1302
1303 /* Create pixmaps: */
1304 m_pIconSnapshotOffline = new QIcon(UIIconPool::iconSet(":/snapshot_offline_16px.png"));
1305 m_pIconSnapshotOnline = new QIcon(UIIconPool::iconSet(":/snapshot_online_16px.png"));
1306
1307 /* Create timer: */
1308 m_pTimerUpdateAge = new QTimer;
1309 if (m_pTimerUpdateAge)
1310 {
1311 /* Configure timer: */
1312 m_pTimerUpdateAge->setSingleShot(true);
1313 connect(m_pTimerUpdateAge, &QTimer::timeout, this, &UISnapshotPane::sltUpdateSnapshotsAge);
1314 }
1315
1316 /* Prepare connections: */
1317 prepareConnections();
1318 /* Prepare actions: */
1319 prepareActions();
1320 /* Prepare widgets: */
1321 prepareWidgets();
1322
1323 /* Load settings: */
1324 loadSettings();
1325
1326 /* Register help topic: */
1327 uiCommon().setHelpKeyword(this, "snapshots");
1328
1329 /* Apply language settings: */
1330 retranslateUi();
1331}
1332
1333void UISnapshotPane::prepareConnections()
1334{
1335 /* Configure Main event connections: */
1336 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigMachineDataChange,
1337 this, &UISnapshotPane::sltHandleMachineDataChange);
1338 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigMachineStateChange,
1339 this, &UISnapshotPane::sltHandleMachineStateChange);
1340 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigSessionStateChange,
1341 this, &UISnapshotPane::sltHandleSessionStateChange);
1342 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigSnapshotTake,
1343 this, &UISnapshotPane::sltHandleSnapshotTake);
1344 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigSnapshotDelete,
1345 this, &UISnapshotPane::sltHandleSnapshotDelete);
1346 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigSnapshotChange,
1347 this, &UISnapshotPane::sltHandleSnapshotChange);
1348 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigSnapshotRestore,
1349 this, &UISnapshotPane::sltHandleSnapshotRestore);
1350}
1351
1352void UISnapshotPane::prepareActions()
1353{
1354 /* First of all, add actions which has smaller shortcut scope: */
1355 addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Take));
1356 addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Delete));
1357 addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Restore));
1358 addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_T_Properties));
1359 addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Clone));
1360
1361 /* Connect actions: */
1362 connect(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Take), &UIAction::triggered,
1363 this, &UISnapshotPane::sltTakeSnapshot);
1364 connect(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Delete), &UIAction::triggered,
1365 this, &UISnapshotPane::sltDeleteSnapshot);
1366 connect(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Restore), &UIAction::triggered,
1367 this, &UISnapshotPane::sltRestoreSnapshot);
1368 connect(m_pActionPool->action(UIActionIndexMN_M_Snapshot_T_Properties), &UIAction::toggled,
1369 this, &UISnapshotPane::sltToggleSnapshotDetailsVisibility);
1370}
1371
1372void UISnapshotPane::prepareWidgets()
1373{
1374 /* Create layout: */
1375 m_pLayoutMain = new QVBoxLayout(this);
1376 if (m_pLayoutMain)
1377 {
1378 /* Configure layout: */
1379 m_pLayoutMain->setContentsMargins(0, 0, 0, 0);
1380#ifdef VBOX_WS_MAC
1381 m_pLayoutMain->setSpacing(10);
1382#else
1383 m_pLayoutMain->setSpacing(qApp->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) / 2);
1384#endif
1385
1386 /* Prepare toolbar, if requested: */
1387 if (m_fShowToolbar)
1388 prepareToolbar();
1389 /* Prepare snapshot tree: */
1390 prepareTreeWidget();
1391 /* Prepare details-widget: */
1392 prepareDetailsWidget();
1393 }
1394}
1395
1396void UISnapshotPane::prepareToolbar()
1397{
1398 /* Create snapshot toolbar: */
1399 m_pToolBar = new QIToolBar(this);
1400 if (m_pToolBar)
1401 {
1402 /* Configure toolbar: */
1403 const int iIconMetric = (int)(QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize));
1404 m_pToolBar->setIconSize(QSize(iIconMetric, iIconMetric));
1405 m_pToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
1406
1407 /* Add toolbar actions: */
1408 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Take));
1409 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Delete));
1410 m_pToolBar->addSeparator();
1411 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Restore));
1412 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_T_Properties));
1413 m_pToolBar->addSeparator();
1414 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Clone));
1415
1416 /* Add into layout: */
1417 m_pLayoutMain->addWidget(m_pToolBar);
1418 }
1419}
1420
1421void UISnapshotPane::prepareTreeWidget()
1422{
1423 /* Create snapshot tree: */
1424 m_pSnapshotTree = new UISnapshotTree(this);
1425 if (m_pSnapshotTree)
1426 {
1427 /* Configure tree: */
1428 connect(m_pSnapshotTree, &UISnapshotTree::currentItemChanged,
1429 this, &UISnapshotPane::sltHandleCurrentItemChange);
1430 connect(m_pSnapshotTree, &UISnapshotTree::customContextMenuRequested,
1431 this, &UISnapshotPane::sltHandleContextMenuRequest);
1432 connect(m_pSnapshotTree, &UISnapshotTree::itemChanged,
1433 this, &UISnapshotPane::sltHandleItemChange);
1434 connect(m_pSnapshotTree, &UISnapshotTree::itemDoubleClicked,
1435 this, &UISnapshotPane::sltHandleItemDoubleClick);
1436 connect(m_pSnapshotTree, &UISnapshotTree::sigNotifyAboutScrollBarVisibilityChange,
1437 this, &UISnapshotPane::sltHandleScrollBarVisibilityChange, Qt::QueuedConnection);
1438
1439 /* Add into layout: */
1440 m_pLayoutMain->addWidget(m_pSnapshotTree, 1);
1441 }
1442}
1443
1444void UISnapshotPane::prepareDetailsWidget()
1445{
1446 /* Create details-widget: */
1447 m_pDetailsWidget = new UISnapshotDetailsWidget(this);
1448 if (m_pDetailsWidget)
1449 {
1450 /* Configure details-widget: */
1451 m_pDetailsWidget->setVisible(false);
1452 connect(m_pDetailsWidget, &UISnapshotDetailsWidget::sigDataChangeAccepted,
1453 this, &UISnapshotPane::sltApplySnapshotDetailsChanges);
1454
1455 /* Add into layout: */
1456 m_pLayoutMain->addWidget(m_pDetailsWidget, 1);
1457 }
1458}
1459
1460void UISnapshotPane::loadSettings()
1461{
1462 /* Details action/widget: */
1463 m_pActionPool->action(UIActionIndexMN_M_Snapshot_T_Properties)->
1464 setChecked(gEDataManager->snapshotManagerDetailsExpanded());
1465}
1466
1467void UISnapshotPane::refreshAll()
1468{
1469 /* Prevent snapshot editing in the meantime: */
1470 QWriteLocker locker(m_pLockReadWrite);
1471
1472 /* If VM list is empty, just updated the current item: */
1473 if (m_machines.isEmpty())
1474 {
1475 /* Clear the tree: */
1476 m_pSnapshotTree->clear();
1477 return;
1478 }
1479
1480 /* Remember the selected item and it's first child: */
1481 QUuid uSelectedItem, uFirstChildOfSelectedItem;
1482 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1483 if (pSnapshotItem)
1484 {
1485 uSelectedItem = pSnapshotItem->snapshotID();
1486 if (pSnapshotItem->child(0))
1487 uFirstChildOfSelectedItem = UISnapshotItem::toSnapshotItem(pSnapshotItem->child(0))->snapshotID();
1488 }
1489
1490 /* Clear the tree: */
1491 m_pSnapshotTree->clear();
1492
1493 /* Iterates over all the machines: */
1494 foreach (const QUuid &uMachineId, m_machines.keys())
1495 {
1496 CMachine comMachine = m_machines.value(uMachineId);
1497
1498 /* If machine has snapshots: */
1499 if (comMachine.GetSnapshotCount() > 0)
1500 {
1501 /* Get the first snapshot: */
1502 const CSnapshot comSnapshot = comMachine.FindSnapshot(QString());
1503
1504 /* Populate snapshot tree: */
1505 populateSnapshots(uMachineId, comSnapshot, 0);
1506 /* And make sure it has "current snapshot" item: */
1507 Assert(m_currentSnapshotItems.value(uMachineId));
1508
1509 /* Add the "current state" item as a child to "current snapshot" item: */
1510 m_currentStateItems[uMachineId] = new UISnapshotItem(this, m_currentSnapshotItems.value(uMachineId), comMachine);
1511 m_currentStateItems.value(uMachineId)->recache();
1512
1513 /* Search for a previously selected item: */
1514 UISnapshotItem *pCurrentItem = findItem(uSelectedItem);
1515 if (pCurrentItem == 0)
1516 pCurrentItem = findItem(uFirstChildOfSelectedItem);
1517 if (pCurrentItem == 0)
1518 pCurrentItem = m_currentStateItems.value(uMachineId);
1519
1520 /* Choose current item: */
1521 m_pSnapshotTree->setCurrentItem(pCurrentItem);
1522 sltHandleCurrentItemChange();
1523 }
1524 /* If machine has no snapshots: */
1525 else
1526 {
1527 /* There is no "current snapshot" item: */
1528 m_currentSnapshotItems[uMachineId] = 0;
1529
1530 /* Add the "current state" item as a child of snapshot tree: */
1531 m_currentStateItems[uMachineId] = new UISnapshotItem(this, m_pSnapshotTree, comMachine, m_machines.size() > 1);
1532 m_currentStateItems.value(uMachineId)->recache();
1533
1534 /* Choose current item: */
1535 m_pSnapshotTree->setCurrentItem(m_currentStateItems.value(uMachineId));
1536 sltHandleCurrentItemChange();
1537 }
1538 }
1539
1540 /* Update age: */
1541 sltUpdateSnapshotsAge();
1542
1543 /* Adjust snapshot tree: */
1544 adjustTreeWidget();
1545}
1546
1547void UISnapshotPane::populateSnapshots(const QUuid &uMachineId, const CSnapshot &comSnapshot, QITreeWidgetItem *pItem)
1548{
1549 /* Create a child of passed item: */
1550 UISnapshotItem *pSnapshotItem = pItem ? new UISnapshotItem(this, pItem, comSnapshot)
1551 : new UISnapshotItem(this, m_pSnapshotTree, comSnapshot, m_machines.size() > 1);
1552 /* And recache it's content: */
1553 pSnapshotItem->recache();
1554
1555 /* Mark snapshot item as "current" and remember it: */
1556 CSnapshot comCurrentSnapshot = m_machines.value(uMachineId).GetCurrentSnapshot();
1557 if (!comCurrentSnapshot.isNull() && comCurrentSnapshot.GetId() == comSnapshot.GetId())
1558 {
1559 pSnapshotItem->setCurrentSnapshotItem(true);
1560 m_currentSnapshotItems[uMachineId] = pSnapshotItem;
1561 }
1562
1563 /* Walk through the children recursively: */
1564 foreach (const CSnapshot &comIteratedSnapshot, comSnapshot.GetChildren())
1565 populateSnapshots(uMachineId, comIteratedSnapshot, pSnapshotItem);
1566
1567 /* Expand the newly created item: */
1568 pSnapshotItem->setExpanded(true);
1569}
1570
1571void UISnapshotPane::cleanup()
1572{
1573 /* Stop timer if active: */
1574 if (m_pTimerUpdateAge->isActive())
1575 m_pTimerUpdateAge->stop();
1576 /* Destroy timer: */
1577 delete m_pTimerUpdateAge;
1578 m_pTimerUpdateAge = 0;
1579
1580 /* Destroy icons: */
1581 delete m_pIconSnapshotOffline;
1582 delete m_pIconSnapshotOnline;
1583 m_pIconSnapshotOffline = 0;
1584 m_pIconSnapshotOnline = 0;
1585
1586 /* Destroy read-write locker: */
1587 delete m_pLockReadWrite;
1588 m_pLockReadWrite = 0;
1589}
1590
1591void UISnapshotPane::updateActionStates()
1592{
1593 /* Acquire "current snapshot" item: */
1594 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1595
1596 /* Check whether another direct session is opened: */
1597 const bool fBusy = !pSnapshotItem || m_sessionStates.value(pSnapshotItem->machineID()) != KSessionState_Unlocked;
1598
1599 /* Acquire machine-state of the "current state" item: */
1600 KMachineState enmState = KMachineState_Null;
1601 if ( pSnapshotItem
1602 && m_currentStateItems.value(pSnapshotItem->machineID()))
1603 enmState = m_currentStateItems.value(pSnapshotItem->machineID())->machineState();
1604
1605 /* Determine whether taking or deleting snapshots is possible: */
1606 const bool fCanTakeDeleteSnapshot = !fBusy
1607 || enmState == KMachineState_PoweredOff
1608 || enmState == KMachineState_Saved
1609 || enmState == KMachineState_Aborted
1610 || enmState == KMachineState_AbortedSaved
1611 || enmState == KMachineState_Running
1612 || enmState == KMachineState_Paused;
1613
1614 /* Update 'Take' action: */
1615 m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Take)->setEnabled(
1616 pSnapshotItem
1617 && m_operationAllowed.value(pSnapshotItem->machineID())
1618 && ( ( fCanTakeDeleteSnapshot
1619 && m_currentSnapshotItems.value(pSnapshotItem->machineID())
1620 && pSnapshotItem->isCurrentStateItem())
1621 || (!m_currentSnapshotItems.value(pSnapshotItem->machineID())))
1622 );
1623
1624 /* Update 'Delete' action: */
1625 m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Delete)->setEnabled(
1626 pSnapshotItem
1627 && m_operationAllowed.value(pSnapshotItem->machineID())
1628 && fCanTakeDeleteSnapshot
1629 && m_currentSnapshotItems.value(pSnapshotItem->machineID())
1630 && pSnapshotItem
1631 && !pSnapshotItem->isCurrentStateItem()
1632 );
1633
1634 /* Update 'Restore' action: */
1635 m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Restore)->setEnabled(
1636 !fBusy
1637 && pSnapshotItem
1638 && m_currentSnapshotItems.value(pSnapshotItem->machineID())
1639 && !pSnapshotItem->isCurrentStateItem()
1640 );
1641
1642 /* Update 'Show Details' action: */
1643 m_pActionPool->action(UIActionIndexMN_M_Snapshot_T_Properties)->setEnabled(
1644 pSnapshotItem
1645 );
1646
1647 /* Update 'Clone' action: */
1648 m_pActionPool->action(UIActionIndexMN_M_Snapshot_S_Clone)->setEnabled(
1649 pSnapshotItem
1650 && ( !pSnapshotItem->isCurrentStateItem()
1651 || !fBusy)
1652 );
1653}
1654
1655bool UISnapshotPane::takeSnapshot(bool fAutomatically /* = false */)
1656{
1657 /* Acquire "current snapshot" item: */
1658 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1659 AssertPtrReturn(pSnapshotItem, false);
1660
1661 /* Acquire machine: */
1662 const CMachine comMachine = m_machines.value(pSnapshotItem->machineID());
1663
1664 /* Search for a maximum existing snapshot index: */
1665 int iMaximumIndex = 0;
1666 const QString strNameTemplate = tr("Snapshot %1");
1667 const QRegExp reName(QString("^") + strNameTemplate.arg("([0-9]+)") + QString("$"));
1668 QTreeWidgetItemIterator iterator(m_pSnapshotTree);
1669 while (*iterator)
1670 {
1671 const QString strName = static_cast<UISnapshotItem*>(*iterator)->name();
1672 const int iPosition = reName.indexIn(strName);
1673 if (iPosition != -1)
1674 iMaximumIndex = reName.cap(1).toInt() > iMaximumIndex
1675 ? reName.cap(1).toInt()
1676 : iMaximumIndex;
1677 ++iterator;
1678 }
1679
1680 /* Prepare snapshot name/description: */
1681 QString strFinalName = strNameTemplate.arg(iMaximumIndex + 1);
1682 QString strFinalDescription;
1683
1684 /* In manual mode we should show take snapshot dialog: */
1685 if (!fAutomatically)
1686 {
1687 /* First of all, we should calculate amount of immutable images: */
1688 ulong cAmountOfImmutableMediums = 0;
1689 UICommon::acquireAmountOfImmutableImages(comMachine, cAmountOfImmutableMediums);
1690
1691 /* Create take-snapshot dialog: */
1692 QWidget *pDlgParent = windowManager().realParentWindow(this);
1693 QPointer<UITakeSnapshotDialog> pDlg = new UITakeSnapshotDialog(pDlgParent, cAmountOfImmutableMediums);
1694 windowManager().registerNewParent(pDlg, pDlgParent);
1695
1696 /* Assign corresponding icon: */
1697 QIcon icon = generalIconPool().userMachineIcon(comMachine);
1698 if (icon.isNull())
1699 icon = generalIconPool().guestOSTypeIcon(comMachine.GetOSTypeId());
1700 pDlg->setIcon(icon);
1701
1702 /* Assign corresponding snapshot name: */
1703 pDlg->setName(strFinalName);
1704
1705 /* Show Take Snapshot dialog: */
1706 if (pDlg->exec() != QDialog::Accepted)
1707 {
1708 /* Cleanup dialog if it wasn't destroyed in own loop: */
1709 delete pDlg;
1710 return false;
1711 }
1712
1713 /* Acquire final snapshot name/description: */
1714 strFinalName = pDlg->name().trimmed();
1715 strFinalDescription = pDlg->description();
1716
1717 /* Cleanup dialog: */
1718 delete pDlg;
1719 }
1720
1721 /* Take snapshot: */
1722 UINotificationProgressSnapshotTake *pNotification = new UINotificationProgressSnapshotTake(comMachine,
1723 strFinalName,
1724 strFinalDescription);
1725 gpNotificationCenter->append(pNotification);
1726
1727 /* Return result: */
1728 return true;
1729}
1730
1731bool UISnapshotPane::deleteSnapshot(bool fAutomatically /* = false */)
1732{
1733 /* Acquire "current snapshot" item: */
1734 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1735 AssertPtrReturn(pSnapshotItem, false);
1736
1737 /* Acquire machine: */
1738 const CMachine comMachine = m_machines.value(pSnapshotItem->machineID());
1739
1740 /* Get corresponding snapshot: */
1741 const CSnapshot comSnapshot = pSnapshotItem->snapshot();
1742 AssertReturn(!comSnapshot.isNull(), false);
1743
1744 /* In manual mode we should ask if user really wants to remove the selected snapshot: */
1745 if (!fAutomatically && !msgCenter().confirmSnapshotRemoval(comSnapshot.GetName()))
1746 return false;
1747
1748#if 0
1749 /** @todo check available space on the target filesystem etc etc. */
1750 if (!msgCenter().warnAboutSnapshotRemovalFreeSpace(comSnapshot.GetName(),
1751 "/home/juser/.VirtualBox/Machines/SampleVM/Snapshots/{01020304-0102-0102-0102-010203040506}.vdi",
1752 "59 GiB",
1753 "15 GiB"))
1754 return false;
1755#endif
1756
1757 /* Delete snapshot: */
1758 UINotificationProgressSnapshotDelete *pNotification = new UINotificationProgressSnapshotDelete(comMachine,
1759 pSnapshotItem->snapshotID());
1760 gpNotificationCenter->append(pNotification);
1761
1762 /* Return result: */
1763 return true;
1764}
1765
1766bool UISnapshotPane::restoreSnapshot(bool fAutomatically /* = false */)
1767{
1768 /* Acquire "current snapshot" item: */
1769 const UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(m_pSnapshotTree->currentItem());
1770 AssertPtrReturn(pSnapshotItem, false);
1771
1772 /* Acquire machine: */
1773 const CMachine comMachine = m_machines.value(pSnapshotItem->machineID());
1774
1775 /* Get corresponding snapshot: */
1776 const CSnapshot comSnapshot = pSnapshotItem->snapshot();
1777 AssertReturn(!comSnapshot.isNull(), false);
1778
1779 /* In manual mode we should check whether current state is changed: */
1780 if (!fAutomatically && comMachine.GetCurrentStateModified())
1781 {
1782 /* Ask if user really wants to restore the selected snapshot: */
1783 int iResultCode = msgCenter().confirmSnapshotRestoring(comSnapshot.GetName(), comMachine.GetCurrentStateModified());
1784 if (iResultCode & AlertButton_Cancel)
1785 return false;
1786
1787 /* Ask if user also wants to create new snapshot of current state which is changed: */
1788 if (iResultCode & AlertOption_CheckBox)
1789 {
1790 /* Take snapshot of changed current state: */
1791 m_pSnapshotTree->setCurrentItem(m_currentStateItems.value(pSnapshotItem->machineID()));
1792 if (!takeSnapshot())
1793 return false;
1794 }
1795 }
1796
1797 /* Restore snapshot: */
1798 UINotificationProgressSnapshotRestore *pNotification = new UINotificationProgressSnapshotRestore(comMachine, comSnapshot);
1799 gpNotificationCenter->append(pNotification);
1800
1801 /* Return result: */
1802 return true;
1803}
1804
1805void UISnapshotPane::adjustTreeWidget()
1806{
1807 /* Get the snapshot tree abstract interface: */
1808 QAbstractItemView *pItemView = m_pSnapshotTree;
1809 /* Get the snapshot tree header-view: */
1810 QHeaderView *pItemHeader = m_pSnapshotTree->header();
1811
1812 /* Calculate the total snapshot tree width: */
1813 const int iTotal = m_pSnapshotTree->viewport()->width();
1814
1815 /* Look for a minimum width hint for Taken column: */
1816 const int iMinWidth1 = qMax(pItemView->sizeHintForColumn(Column_Taken), pItemHeader->sectionSizeHint(Column_Taken));
1817 /* Propose suitable width hint for Taken column (but no more than the half of existing space): */
1818 const int iWidth1 = iMinWidth1 < iTotal / Column_Max ? iMinWidth1 : iTotal / Column_Max;
1819
1820 /* Look for a minimum width hint for Name column: */
1821 const int iMinWidth0 = qMax(pItemView->sizeHintForColumn(Column_Name), pItemHeader->sectionSizeHint(Column_Name));
1822 /* Propose suitable width hint for important column (at least all remaining space and no less than the hint itself): */
1823 const int iWidth0 = iMinWidth0 > iTotal - iWidth1 ? iMinWidth0 : iTotal - iWidth1;
1824
1825 /* Apply the proposal: */
1826 m_pSnapshotTree->setColumnWidth(Column_Taken, iWidth1);
1827 m_pSnapshotTree->setColumnWidth(Column_Name, iWidth0);
1828}
1829
1830UISnapshotItem *UISnapshotPane::findItem(const QUuid &uSnapshotID) const
1831{
1832 /* Search for the first item with required ID: */
1833 QTreeWidgetItemIterator it(m_pSnapshotTree);
1834 while (*it)
1835 {
1836 UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(*it);
1837 if (pSnapshotItem->snapshotID() == uSnapshotID)
1838 return pSnapshotItem;
1839 ++it;
1840 }
1841
1842 /* Null by default: */
1843 return 0;
1844}
1845
1846SnapshotAgeFormat UISnapshotPane::traverseSnapshotAge(QTreeWidgetItem *pItem) const
1847{
1848 /* Acquire corresponding snapshot item: */
1849 UISnapshotItem *pSnapshotItem = UISnapshotItem::toSnapshotItem(pItem);
1850
1851 /* Fetch the snapshot age of the root if it's valid: */
1852 SnapshotAgeFormat age = pSnapshotItem ? pSnapshotItem->updateAge() : SnapshotAgeFormat_Max;
1853
1854 /* Walk through the children recursively: */
1855 for (int i = 0; i < pItem->childCount(); ++i)
1856 {
1857 /* Fetch the smallest snapshot age of the children: */
1858 const SnapshotAgeFormat newAge = traverseSnapshotAge(pItem->child(i));
1859 /* Remember the smallest snapshot age among existing: */
1860 age = newAge < age ? newAge : age;
1861 }
1862
1863 /* Return result: */
1864 return age;
1865}
1866
1867void UISnapshotPane::expandItemChildren(QTreeWidgetItem *pItem)
1868{
1869 pItem ->setExpanded(true);
1870 for (int i = 0; i < pItem->childCount(); ++i)
1871 expandItemChildren(pItem->child(i));
1872}
1873
1874#include "UISnapshotPane.moc"
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle
ContactPrivacy/Do Not Sell My InfoTerms of Use