VirtualBox

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

Last change on this file was 104358, checked in by vboxsync, 5 weeks ago

FE/Qt. bugref:10622. More refactoring around the retranslation functionality.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use