VirtualBox

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

Last change on this file was 105081, checked in by vboxsync, 2 months ago

FE/Qt: Moving local machine related stuff from UICommon to new UILocalMachineStuff namespace; Reworking GUI to use it accordingly; Dependencies and includes cleanup.

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