VirtualBox

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

Last change on this file since 104158 was 103550, checked in by vboxsync, 10 months ago

FE/Qt: UISnapshotPane: Get rid of UICommon include in the header.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette