1 | /* $Id: VBoxSnapshotsWgt.cpp 34983 2010-12-13 10:14:08Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | *
|
---|
4 | * VBox frontends: Qt4 GUI ("VirtualBox"):
|
---|
5 | * VBoxSnapshotsWgt class implementation
|
---|
6 | */
|
---|
7 |
|
---|
8 | /*
|
---|
9 | * Copyright (C) 2006-2010 Oracle Corporation
|
---|
10 | *
|
---|
11 | * This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
12 | * available from http://www.virtualbox.org. This file is free software;
|
---|
13 | * you can redistribute it and/or modify it under the terms of the GNU
|
---|
14 | * General Public License (GPL) as published by the Free Software
|
---|
15 | * Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
16 | * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
17 | * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
18 | */
|
---|
19 |
|
---|
20 | #ifdef VBOX_WITH_PRECOMPILED_HEADERS
|
---|
21 | # include "precomp.h"
|
---|
22 | #else /* !VBOX_WITH_PRECOMPILED_HEADERS */
|
---|
23 | /* Local includes */
|
---|
24 | #include "UIIconPool.h"
|
---|
25 | #include "VBoxProblemReporter.h"
|
---|
26 | #include "VBoxSnapshotDetailsDlg.h"
|
---|
27 | #include "VBoxSnapshotsWgt.h"
|
---|
28 | #include "VBoxTakeSnapshotDlg.h"
|
---|
29 | #include "UIToolBar.h"
|
---|
30 | #include "UIVirtualBoxEventHandler.h"
|
---|
31 |
|
---|
32 | /* Global includes */
|
---|
33 | #include <QDateTime>
|
---|
34 | #include <QHeaderView>
|
---|
35 | #include <QMenu>
|
---|
36 | #include <QScrollBar>
|
---|
37 | #include <QWindowsStyle>
|
---|
38 |
|
---|
39 | #endif /* !VBOX_WITH_PRECOMPILED_HEADERS */
|
---|
40 |
|
---|
41 | /**
|
---|
42 | * QTreeWidgetItem subclass for snapshots items
|
---|
43 | */
|
---|
44 | class SnapshotWgtItem : public QTreeWidgetItem
|
---|
45 | {
|
---|
46 | public:
|
---|
47 |
|
---|
48 | enum { ItemType = QTreeWidgetItem::UserType + 1 };
|
---|
49 |
|
---|
50 | /* Normal snapshot item (child of tree-widget) */
|
---|
51 | SnapshotWgtItem (QTreeWidget *aTreeWidget, const CSnapshot &aSnapshot)
|
---|
52 | : QTreeWidgetItem (aTreeWidget, ItemType)
|
---|
53 | , mIsCurrentState (false)
|
---|
54 | , mSnapshot (aSnapshot)
|
---|
55 | {
|
---|
56 | }
|
---|
57 |
|
---|
58 | /* Normal snapshot item (child of tree-widget-item) */
|
---|
59 | SnapshotWgtItem (QTreeWidgetItem *aRootItem, const CSnapshot &aSnapshot)
|
---|
60 | : QTreeWidgetItem (aRootItem, ItemType)
|
---|
61 | , mIsCurrentState (false)
|
---|
62 | , mSnapshot (aSnapshot)
|
---|
63 | {
|
---|
64 | }
|
---|
65 |
|
---|
66 | /* Current state item (child of tree-widget) */
|
---|
67 | SnapshotWgtItem (QTreeWidget *aTreeWidget, const CMachine &aMachine)
|
---|
68 | : QTreeWidgetItem (aTreeWidget, ItemType)
|
---|
69 | , mIsCurrentState (true)
|
---|
70 | , mMachine (aMachine)
|
---|
71 | {
|
---|
72 | updateCurrentState (mMachine.GetState());
|
---|
73 | }
|
---|
74 |
|
---|
75 | /* Current state item (child of tree-widget-item) */
|
---|
76 | SnapshotWgtItem (QTreeWidgetItem *aRootItem, const CMachine &aMachine)
|
---|
77 | : QTreeWidgetItem (aRootItem, ItemType)
|
---|
78 | , mIsCurrentState (true)
|
---|
79 | , mMachine (aMachine)
|
---|
80 | {
|
---|
81 | updateCurrentState (mMachine.GetState());
|
---|
82 | }
|
---|
83 |
|
---|
84 | QVariant data (int aColumn, int aRole) const
|
---|
85 | {
|
---|
86 | switch (aRole)
|
---|
87 | {
|
---|
88 | case Qt::DisplayRole:
|
---|
89 | return mIsCurrentState ? QTreeWidgetItem::data (aColumn, aRole) : QVariant (QString ("%1%2")
|
---|
90 | .arg (QTreeWidgetItem::data (aColumn, Qt::DisplayRole).toString())
|
---|
91 | .arg (QTreeWidgetItem::data (aColumn, Qt::UserRole).toString()));
|
---|
92 | default:
|
---|
93 | break;
|
---|
94 | }
|
---|
95 | return QTreeWidgetItem::data (aColumn, aRole);
|
---|
96 | }
|
---|
97 |
|
---|
98 | QString text (int aColumn) const
|
---|
99 | {
|
---|
100 | return QTreeWidgetItem::data (aColumn, Qt::DisplayRole).toString();
|
---|
101 | }
|
---|
102 |
|
---|
103 | bool isCurrentStateItem() const
|
---|
104 | {
|
---|
105 | return mSnapshot.isNull();
|
---|
106 | }
|
---|
107 |
|
---|
108 | int level()
|
---|
109 | {
|
---|
110 | QTreeWidgetItem *item = this;
|
---|
111 | int result = 0;
|
---|
112 | while (item->parent())
|
---|
113 | {
|
---|
114 | ++ result;
|
---|
115 | item = item->parent();
|
---|
116 | }
|
---|
117 | return result;
|
---|
118 | }
|
---|
119 |
|
---|
120 | bool bold() const { return font (0).bold(); }
|
---|
121 | void setBold (bool aBold)
|
---|
122 | {
|
---|
123 | QFont myFont = font (0);
|
---|
124 | myFont.setBold (aBold);
|
---|
125 | setFont (0, myFont);
|
---|
126 | adjustText();
|
---|
127 | }
|
---|
128 |
|
---|
129 | bool italic() const { return font (0).italic(); }
|
---|
130 | void setItalic (bool aItalic)
|
---|
131 | {
|
---|
132 | QFont myFont = font (0);
|
---|
133 | myFont.setItalic (aItalic);
|
---|
134 | setFont (0, myFont);
|
---|
135 | adjustText();
|
---|
136 | }
|
---|
137 |
|
---|
138 | CSnapshot snapshot() const { return mSnapshot; }
|
---|
139 | QString snapshotId() const { return mId; }
|
---|
140 |
|
---|
141 | void recache()
|
---|
142 | {
|
---|
143 | if (mIsCurrentState)
|
---|
144 | {
|
---|
145 | Assert (!mMachine.isNull());
|
---|
146 | mCurStateModified = mMachine.GetCurrentStateModified();
|
---|
147 | setText (0, mCurStateModified ?
|
---|
148 | VBoxSnapshotsWgt::tr ("Current State (changed)", "Current State (Modified)") :
|
---|
149 | VBoxSnapshotsWgt::tr ("Current State", "Current State (Unmodified)"));
|
---|
150 | mDesc = mCurStateModified ?
|
---|
151 | VBoxSnapshotsWgt::tr ("The current state differs from the state stored in the current snapshot") :
|
---|
152 | parent() != 0 ?
|
---|
153 | VBoxSnapshotsWgt::tr ("The current state is identical to the state stored in the current snapshot") :
|
---|
154 | QString::null;
|
---|
155 | }
|
---|
156 | else
|
---|
157 | {
|
---|
158 | Assert (!mSnapshot.isNull());
|
---|
159 | mId = mSnapshot.GetId();
|
---|
160 | setText (0, mSnapshot.GetName());
|
---|
161 | mOnline = mSnapshot.GetOnline();
|
---|
162 | setIcon (0, vboxGlobal().snapshotIcon (mOnline));
|
---|
163 | mDesc = mSnapshot.GetDescription();
|
---|
164 | mTimestamp.setTime_t (mSnapshot.GetTimeStamp() / 1000);
|
---|
165 | mCurStateModified = false;
|
---|
166 | }
|
---|
167 | adjustText();
|
---|
168 | recacheToolTip();
|
---|
169 | }
|
---|
170 |
|
---|
171 | KMachineState getCurrentState()
|
---|
172 | {
|
---|
173 | if (mMachine.isNull())
|
---|
174 | return KMachineState_Null;
|
---|
175 |
|
---|
176 | return mMachineState;
|
---|
177 | }
|
---|
178 |
|
---|
179 | void updateCurrentState (KMachineState aState)
|
---|
180 | {
|
---|
181 | if (mMachine.isNull())
|
---|
182 | return;
|
---|
183 |
|
---|
184 | setIcon (0, vboxGlobal().toIcon (aState));
|
---|
185 | mMachineState = aState;
|
---|
186 | mTimestamp.setTime_t (mMachine.GetLastStateChange() / 1000);
|
---|
187 | }
|
---|
188 |
|
---|
189 | SnapshotAgeFormat updateAge()
|
---|
190 | {
|
---|
191 | QString age;
|
---|
192 |
|
---|
193 | /* Age: [date time|%1d ago|%1h ago|%1min ago|%1sec ago] */
|
---|
194 | SnapshotAgeFormat ageFormat;
|
---|
195 | if (mTimestamp.daysTo (QDateTime::currentDateTime()) > 30)
|
---|
196 | {
|
---|
197 | age = VBoxSnapshotsWgt::tr (" (%1)").arg (mTimestamp.toString (Qt::LocalDate));
|
---|
198 | ageFormat = AgeMax;
|
---|
199 | }
|
---|
200 | else if (mTimestamp.secsTo (QDateTime::currentDateTime()) > 60 * 60 * 24)
|
---|
201 | {
|
---|
202 | age = VBoxSnapshotsWgt::tr (" (%1 ago)").arg(VBoxGlobal::daysToString(mTimestamp.secsTo (QDateTime::currentDateTime()) / 60 / 60 / 24));
|
---|
203 | ageFormat = AgeInDays;
|
---|
204 | }
|
---|
205 | else if (mTimestamp.secsTo (QDateTime::currentDateTime()) > 60 * 60)
|
---|
206 | {
|
---|
207 | age = VBoxSnapshotsWgt::tr (" (%1 ago)").arg(VBoxGlobal::hoursToString(mTimestamp.secsTo (QDateTime::currentDateTime()) / 60 / 60));
|
---|
208 | ageFormat = AgeInHours;
|
---|
209 | }
|
---|
210 | else if (mTimestamp.secsTo (QDateTime::currentDateTime()) > 60)
|
---|
211 | {
|
---|
212 | age = VBoxSnapshotsWgt::tr (" (%1 ago)").arg(VBoxGlobal::minutesToString(mTimestamp.secsTo (QDateTime::currentDateTime()) / 60));
|
---|
213 | ageFormat = AgeInMinutes;
|
---|
214 | }
|
---|
215 | else
|
---|
216 | {
|
---|
217 | age = VBoxSnapshotsWgt::tr (" (%1 ago)").arg(VBoxGlobal::secondsToString(mTimestamp.secsTo (QDateTime::currentDateTime())));
|
---|
218 | ageFormat = AgeInSeconds;
|
---|
219 | }
|
---|
220 |
|
---|
221 | /* Update data */
|
---|
222 | setData (0, Qt::UserRole, age);
|
---|
223 |
|
---|
224 | return ageFormat;
|
---|
225 | }
|
---|
226 |
|
---|
227 | private:
|
---|
228 |
|
---|
229 | void adjustText()
|
---|
230 | {
|
---|
231 | if (!treeWidget()) return; /* only for initialised items */
|
---|
232 | QFontMetrics metrics (font (0));
|
---|
233 | int hei0 = (metrics.height() > 16 ?
|
---|
234 | metrics.height() /* text */ : 16 /* icon */) +
|
---|
235 | 2 * 2 /* 2 pixel per margin */;
|
---|
236 | int wid0 = metrics.width (text (0)) /* text */ +
|
---|
237 | treeWidget()->indentation() /* indent */ +
|
---|
238 | 16 /* icon */;
|
---|
239 | setSizeHint (0, QSize (wid0, hei0));
|
---|
240 | }
|
---|
241 |
|
---|
242 | void recacheToolTip()
|
---|
243 | {
|
---|
244 | QString name = text (0);
|
---|
245 |
|
---|
246 | bool dateTimeToday = mTimestamp.date() == QDate::currentDate();
|
---|
247 | QString dateTime = dateTimeToday ?
|
---|
248 | mTimestamp.time().toString (Qt::LocalDate) :
|
---|
249 | mTimestamp.toString (Qt::LocalDate);
|
---|
250 |
|
---|
251 | QString details;
|
---|
252 |
|
---|
253 | if (!mSnapshot.isNull())
|
---|
254 | {
|
---|
255 | /* The current snapshot is always bold */
|
---|
256 | if (bold())
|
---|
257 | details = VBoxSnapshotsWgt::tr (" (current, ", "Snapshot details");
|
---|
258 | else
|
---|
259 | details = " (";
|
---|
260 | details += mOnline ? VBoxSnapshotsWgt::tr ("online)", "Snapshot details")
|
---|
261 | : VBoxSnapshotsWgt::tr ("offline)", "Snapshot details");
|
---|
262 |
|
---|
263 | if (dateTimeToday)
|
---|
264 | dateTime = VBoxSnapshotsWgt::tr ("Taken at %1", "Snapshot (time)").arg (dateTime);
|
---|
265 | else
|
---|
266 | dateTime = VBoxSnapshotsWgt::tr ("Taken on %1", "Snapshot (date + time)").arg (dateTime);
|
---|
267 | }
|
---|
268 | else
|
---|
269 | {
|
---|
270 | dateTime = VBoxSnapshotsWgt::tr ("%1 since %2", "Current State (time or date + time)")
|
---|
271 | .arg (vboxGlobal().toString (mMachineState)).arg (dateTime);
|
---|
272 | }
|
---|
273 |
|
---|
274 | QString toolTip = QString ("<nobr><b>%1</b>%2</nobr><br><nobr>%3</nobr>")
|
---|
275 | .arg (name) .arg (details).arg (dateTime);
|
---|
276 |
|
---|
277 | if (!mDesc.isEmpty())
|
---|
278 | toolTip += "<hr>" + mDesc;
|
---|
279 |
|
---|
280 | setToolTip (0, toolTip);
|
---|
281 | }
|
---|
282 |
|
---|
283 | bool mIsCurrentState;
|
---|
284 |
|
---|
285 | CSnapshot mSnapshot;
|
---|
286 | CMachine mMachine;
|
---|
287 |
|
---|
288 | QString mId;
|
---|
289 | bool mOnline;
|
---|
290 | QString mDesc;
|
---|
291 | QDateTime mTimestamp;
|
---|
292 |
|
---|
293 | bool mCurStateModified;
|
---|
294 | KMachineState mMachineState;
|
---|
295 | };
|
---|
296 |
|
---|
297 | /**
|
---|
298 | * Simple guard block to prevent cyclic call caused by:
|
---|
299 | * changing tree-widget item content (rename) leads to snapshot update &
|
---|
300 | * snapshot update leads to changing tree-widget item content.
|
---|
301 | */
|
---|
302 | class SnapshotEditBlocker
|
---|
303 | {
|
---|
304 | public:
|
---|
305 |
|
---|
306 | SnapshotEditBlocker (bool &aProtector)
|
---|
307 | : mProtector (aProtector)
|
---|
308 | {
|
---|
309 | mProtector = true;
|
---|
310 | }
|
---|
311 |
|
---|
312 | ~SnapshotEditBlocker()
|
---|
313 | {
|
---|
314 | mProtector = false;
|
---|
315 | }
|
---|
316 |
|
---|
317 | private:
|
---|
318 |
|
---|
319 | bool &mProtector;
|
---|
320 | };
|
---|
321 |
|
---|
322 | VBoxSnapshotsWgt::VBoxSnapshotsWgt (QWidget *aParent)
|
---|
323 | : QIWithRetranslateUI <QWidget> (aParent)
|
---|
324 | , mCurSnapshotItem (0)
|
---|
325 | , mEditProtector (false)
|
---|
326 | , mSnapshotActionGroup (new QActionGroup (this))
|
---|
327 | , mCurStateActionGroup (new QActionGroup (this))
|
---|
328 | , mRestoreSnapshotAction (new QAction (mSnapshotActionGroup))
|
---|
329 | , mDeleteSnapshotAction (new QAction (mSnapshotActionGroup))
|
---|
330 | , mShowSnapshotDetailsAction (new QAction (mSnapshotActionGroup))
|
---|
331 | , mTakeSnapshotAction (new QAction (mCurStateActionGroup))
|
---|
332 | {
|
---|
333 | /* Apply UI decorations */
|
---|
334 | Ui::VBoxSnapshotsWgt::setupUi (this);
|
---|
335 |
|
---|
336 | mTreeWidget->header()->hide();
|
---|
337 |
|
---|
338 | /* The snapshots widget is not very useful if there are a lot
|
---|
339 | * of snapshots in a tree and the current Qt style decides not
|
---|
340 | * to draw lines (branches) between the snapshot nodes; it is
|
---|
341 | * then often unclear which snapshot is a child of another.
|
---|
342 | * So on platforms whose styles do not normally draw branches,
|
---|
343 | * we use QWindowsStyle which is present on every platform and
|
---|
344 | * draws required thing like we want. */
|
---|
345 | // #if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
|
---|
346 | QWindowsStyle *treeWidgetStyle = new QWindowsStyle;
|
---|
347 | mTreeWidget->setStyle (treeWidgetStyle);
|
---|
348 | connect (mTreeWidget, SIGNAL (destroyed (QObject *)), treeWidgetStyle, SLOT (deleteLater()));
|
---|
349 | // #endif
|
---|
350 |
|
---|
351 | /* ToolBar creation */
|
---|
352 | UIToolBar *toolBar = new UIToolBar (this);
|
---|
353 | toolBar->setUsesTextLabel (false);
|
---|
354 | toolBar->setIconSize (QSize (22, 22));
|
---|
355 | toolBar->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed);
|
---|
356 |
|
---|
357 | toolBar->addAction (mTakeSnapshotAction);
|
---|
358 | toolBar->addSeparator();
|
---|
359 | toolBar->addAction (mRestoreSnapshotAction);
|
---|
360 | toolBar->addAction (mDeleteSnapshotAction);
|
---|
361 | toolBar->addSeparator();
|
---|
362 | toolBar->addAction (mShowSnapshotDetailsAction);
|
---|
363 |
|
---|
364 | ((QVBoxLayout*)layout())->insertWidget (0, toolBar);
|
---|
365 |
|
---|
366 | /* Setup actions */
|
---|
367 | mRestoreSnapshotAction->setIcon(UIIconPool::iconSetFull(
|
---|
368 | QSize (22, 22), QSize (16, 16),
|
---|
369 | ":/discard_cur_state_22px.png", ":/discard_cur_state_16px.png", // TODO: Update Icons!
|
---|
370 | ":/discard_cur_state_dis_22px.png", ":/discard_cur_state_dis_16px.png")); // TODO: Update Icons!
|
---|
371 | mDeleteSnapshotAction->setIcon(UIIconPool::iconSetFull(
|
---|
372 | QSize (22, 22), QSize (16, 16),
|
---|
373 | ":/delete_snapshot_22px.png", ":/delete_snapshot_16px.png",
|
---|
374 | ":/delete_snapshot_dis_22px.png", ":/delete_snapshot_dis_16px.png"));
|
---|
375 | mShowSnapshotDetailsAction->setIcon(UIIconPool::iconSetFull(
|
---|
376 | QSize (22, 22), QSize (16, 16),
|
---|
377 | ":/show_snapshot_details_22px.png", ":/show_snapshot_details_16px.png",
|
---|
378 | ":/show_snapshot_details_dis_22px.png", ":/show_snapshot_details_dis_16px.png"));
|
---|
379 | mTakeSnapshotAction->setIcon(UIIconPool::iconSetFull(
|
---|
380 | QSize (22, 22), QSize (16, 16),
|
---|
381 | ":/take_snapshot_22px.png", ":/take_snapshot_16px.png",
|
---|
382 | ":/take_snapshot_dis_22px.png", ":/take_snapshot_dis_16px.png"));
|
---|
383 |
|
---|
384 | mRestoreSnapshotAction->setShortcut (QString ("Ctrl+Shift+R"));
|
---|
385 | mDeleteSnapshotAction->setShortcut (QString ("Ctrl+Shift+D"));
|
---|
386 | mShowSnapshotDetailsAction->setShortcut (QString ("Ctrl+Space"));
|
---|
387 | mTakeSnapshotAction->setShortcut (QString ("Ctrl+Shift+S"));
|
---|
388 |
|
---|
389 | mAgeUpdateTimer.setSingleShot (true);
|
---|
390 |
|
---|
391 | /* Setup connections */
|
---|
392 | connect (mTreeWidget, SIGNAL (currentItemChanged (QTreeWidgetItem*, QTreeWidgetItem*)),
|
---|
393 | this, SLOT (onCurrentChanged (QTreeWidgetItem*)));
|
---|
394 | connect (mTreeWidget, SIGNAL (customContextMenuRequested (const QPoint&)),
|
---|
395 | this, SLOT (onContextMenuRequested (const QPoint&)));
|
---|
396 | connect (mTreeWidget, SIGNAL (itemChanged (QTreeWidgetItem*, int)),
|
---|
397 | this, SLOT (onItemChanged (QTreeWidgetItem*)));
|
---|
398 |
|
---|
399 | connect (mRestoreSnapshotAction, SIGNAL (triggered()), this, SLOT (restoreSnapshot()));
|
---|
400 | connect (mDeleteSnapshotAction, SIGNAL (triggered()), this, SLOT (deleteSnapshot()));
|
---|
401 | connect (mShowSnapshotDetailsAction, SIGNAL (triggered()), this, SLOT (showSnapshotDetails()));
|
---|
402 | connect (mTakeSnapshotAction, SIGNAL (triggered()), this, SLOT (takeSnapshot()));
|
---|
403 |
|
---|
404 | connect (gVBoxEvents, SIGNAL(sigMachineDataChange(QString)),
|
---|
405 | this, SLOT(machineDataChanged(QString)));
|
---|
406 | connect (gVBoxEvents, SIGNAL(sigMachineStateChange(QString, KMachineState)),
|
---|
407 | this, SLOT(machineStateChanged(QString, KMachineState)));
|
---|
408 | connect (gVBoxEvents, SIGNAL(sigSessionStateChange(QString, KSessionState)),
|
---|
409 | this, SLOT(sessionStateChanged(QString, KSessionState)));
|
---|
410 |
|
---|
411 | connect (&mAgeUpdateTimer, SIGNAL (timeout()), this, SLOT (updateSnapshotsAge()));
|
---|
412 |
|
---|
413 | retranslateUi();
|
---|
414 | }
|
---|
415 |
|
---|
416 | void VBoxSnapshotsWgt::setMachine (const CMachine &aMachine)
|
---|
417 | {
|
---|
418 | mMachine = aMachine;
|
---|
419 |
|
---|
420 | if (aMachine.isNull())
|
---|
421 | {
|
---|
422 | mMachineId = QString::null;
|
---|
423 | mSessionState = KSessionState_Null;
|
---|
424 | }
|
---|
425 | else
|
---|
426 | {
|
---|
427 | mMachineId = aMachine.GetId();
|
---|
428 | mSessionState = aMachine.GetSessionState();
|
---|
429 | }
|
---|
430 |
|
---|
431 | refreshAll();
|
---|
432 | }
|
---|
433 |
|
---|
434 |
|
---|
435 | void VBoxSnapshotsWgt::onCurrentChanged (QTreeWidgetItem *aItem)
|
---|
436 | {
|
---|
437 | /* Make the selected item visible */
|
---|
438 | SnapshotWgtItem *item = aItem ? static_cast <SnapshotWgtItem*> (aItem) : 0;
|
---|
439 | if (item)
|
---|
440 | {
|
---|
441 | mTreeWidget->horizontalScrollBar()->setValue (0);
|
---|
442 | mTreeWidget->scrollToItem (item);
|
---|
443 | mTreeWidget->horizontalScrollBar()->setValue (mTreeWidget->indentation() * item->level());
|
---|
444 | }
|
---|
445 |
|
---|
446 | /* Whether another direct session is open or not */
|
---|
447 | bool busy = mSessionState != KSessionState_Unlocked;
|
---|
448 |
|
---|
449 | /* Machine state of the current state item */
|
---|
450 | KMachineState s = KMachineState_Null;
|
---|
451 | if (curStateItem())
|
---|
452 | s = curStateItem()->getCurrentState();
|
---|
453 |
|
---|
454 | /* Whether taking or deleting snapshots is possible right now */
|
---|
455 | bool canTakeDeleteSnapshot = !busy
|
---|
456 | || s == KMachineState_PoweredOff
|
---|
457 | || s == KMachineState_Saved
|
---|
458 | || s == KMachineState_Aborted
|
---|
459 | || s == KMachineState_Running
|
---|
460 | || s == KMachineState_Paused;
|
---|
461 |
|
---|
462 | /* Enable/disable restoring snapshot */
|
---|
463 | mRestoreSnapshotAction->setEnabled (!busy && mCurSnapshotItem && item && !item->isCurrentStateItem());
|
---|
464 |
|
---|
465 | /* Enable/disable deleting snapshot */
|
---|
466 | mDeleteSnapshotAction->setEnabled ( canTakeDeleteSnapshot
|
---|
467 | && mCurSnapshotItem && item && !item->isCurrentStateItem());
|
---|
468 |
|
---|
469 | /* Enable/disable the details action regardless of the session state */
|
---|
470 | mShowSnapshotDetailsAction->setEnabled (mCurSnapshotItem && item && !item->isCurrentStateItem());
|
---|
471 |
|
---|
472 | /* Enable/disable taking snapshots */
|
---|
473 | mTakeSnapshotAction->setEnabled ( ( canTakeDeleteSnapshot
|
---|
474 | && mCurSnapshotItem && item && item->isCurrentStateItem())
|
---|
475 | || (item && !mCurSnapshotItem));
|
---|
476 | }
|
---|
477 |
|
---|
478 | void VBoxSnapshotsWgt::onContextMenuRequested (const QPoint &aPoint)
|
---|
479 | {
|
---|
480 | QTreeWidgetItem *item = mTreeWidget->itemAt (aPoint);
|
---|
481 | SnapshotWgtItem *snapshotItem = item ? static_cast <SnapshotWgtItem*> (item) : 0;
|
---|
482 | if (!snapshotItem)
|
---|
483 | return;
|
---|
484 |
|
---|
485 | QMenu menu;
|
---|
486 |
|
---|
487 | if (mCurSnapshotItem && !snapshotItem->isCurrentStateItem())
|
---|
488 | {
|
---|
489 | menu.addAction (mRestoreSnapshotAction);
|
---|
490 | menu.addAction (mDeleteSnapshotAction);
|
---|
491 | menu.addSeparator();
|
---|
492 | menu.addAction (mShowSnapshotDetailsAction);
|
---|
493 | }
|
---|
494 | else
|
---|
495 | menu.addAction (mTakeSnapshotAction);
|
---|
496 |
|
---|
497 | menu.exec (mTreeWidget->viewport()->mapToGlobal (aPoint));
|
---|
498 | }
|
---|
499 |
|
---|
500 | void VBoxSnapshotsWgt::onItemChanged (QTreeWidgetItem *aItem)
|
---|
501 | {
|
---|
502 | if (mEditProtector)
|
---|
503 | return;
|
---|
504 |
|
---|
505 | SnapshotWgtItem *item = aItem ? static_cast <SnapshotWgtItem*> (aItem) : 0;
|
---|
506 |
|
---|
507 | if (item)
|
---|
508 | {
|
---|
509 | CSnapshot snap = item->snapshotId().isNull() ? CSnapshot() : mMachine.FindSnapshot(item->snapshotId());
|
---|
510 | if (!snap.isNull() && snap.isOk() && snap.GetName() != item->text (0))
|
---|
511 | snap.SetName (item->text (0));
|
---|
512 | }
|
---|
513 | }
|
---|
514 |
|
---|
515 | void VBoxSnapshotsWgt::restoreSnapshot()
|
---|
516 | {
|
---|
517 | SnapshotWgtItem *item = !mTreeWidget->currentItem() ? 0 :
|
---|
518 | static_cast <SnapshotWgtItem*> (mTreeWidget->currentItem());
|
---|
519 | AssertReturn (item, (void) 0);
|
---|
520 |
|
---|
521 | QString snapId = item->snapshotId();
|
---|
522 | AssertReturn (!snapId.isNull(), (void) 0);
|
---|
523 | CSnapshot snapshot = mMachine.FindSnapshot(snapId);
|
---|
524 |
|
---|
525 | if (!vboxProblem().askAboutSnapshotRestoring (snapshot.GetName()))
|
---|
526 | return;
|
---|
527 |
|
---|
528 | /* Open a direct session (this call will handle all errors) */
|
---|
529 | CSession session = vboxGlobal().openSession (mMachineId);
|
---|
530 | if (session.isNull())
|
---|
531 | return;
|
---|
532 |
|
---|
533 | CConsole console = session.GetConsole();
|
---|
534 | CProgress progress = console.RestoreSnapshot (snapshot);
|
---|
535 | if (console.isOk())
|
---|
536 | {
|
---|
537 | /* Show the progress dialog */
|
---|
538 | vboxProblem().showModalProgressDialog (progress, mMachine.GetName(), ":/progress_snapshot_restore_90px.png",
|
---|
539 | vboxProblem().mainWindowShown(), true);
|
---|
540 |
|
---|
541 | if (progress.GetResultCode() != 0)
|
---|
542 | vboxProblem().cannotRestoreSnapshot (progress, snapshot.GetName());
|
---|
543 | }
|
---|
544 | else
|
---|
545 | vboxProblem().cannotRestoreSnapshot (progress, snapshot.GetName());
|
---|
546 |
|
---|
547 | session.UnlockMachine();
|
---|
548 | }
|
---|
549 |
|
---|
550 | void VBoxSnapshotsWgt::deleteSnapshot()
|
---|
551 | {
|
---|
552 | SnapshotWgtItem *item = !mTreeWidget->currentItem() ? 0 :
|
---|
553 | static_cast <SnapshotWgtItem*> (mTreeWidget->currentItem());
|
---|
554 | AssertReturn (item, (void) 0);
|
---|
555 |
|
---|
556 | QString snapId = item->snapshotId();
|
---|
557 | AssertReturn (!snapId.isNull(), (void) 0);
|
---|
558 | CSnapshot snapshot = mMachine.FindSnapshot(snapId);
|
---|
559 |
|
---|
560 | if (!vboxProblem().askAboutSnapshotDeleting (snapshot.GetName()))
|
---|
561 | return;
|
---|
562 |
|
---|
563 | /** @todo check available space on the target filesystem etc etc. */
|
---|
564 | #if 0
|
---|
565 | if (!vboxProblem().askAboutSnapshotDeletingFreeSpace (snapshot.GetName(),
|
---|
566 | "/home/juser/.VirtualBox/Machines/SampleVM/Snapshots/{01020304-0102-0102-0102-010203040506}.vdi",
|
---|
567 | "59 GiB",
|
---|
568 | "15 GiB"))
|
---|
569 | return;
|
---|
570 | #endif
|
---|
571 |
|
---|
572 |
|
---|
573 | /* Open a direct session (this call will handle all errors) */
|
---|
574 | bool busy = mSessionState != KSessionState_Unlocked;
|
---|
575 | CSession session = vboxGlobal().openSession (mMachineId, busy /* aExisting */);
|
---|
576 | if (session.isNull())
|
---|
577 | return;
|
---|
578 |
|
---|
579 | CConsole console = session.GetConsole();
|
---|
580 | CProgress progress = console.DeleteSnapshot (snapId);
|
---|
581 | if (console.isOk())
|
---|
582 | {
|
---|
583 | /* Show the progress dialog */
|
---|
584 | vboxProblem().showModalProgressDialog (progress, mMachine.GetName(), ":/progress_snapshot_discard_90px.png",
|
---|
585 | vboxProblem().mainWindowShown(), true);
|
---|
586 |
|
---|
587 | if (progress.GetResultCode() != 0)
|
---|
588 | vboxProblem().cannotDeleteSnapshot (progress, snapshot.GetName());
|
---|
589 | }
|
---|
590 | else
|
---|
591 | vboxProblem().cannotDeleteSnapshot (console, snapshot.GetName());
|
---|
592 |
|
---|
593 | session.UnlockMachine();
|
---|
594 | }
|
---|
595 |
|
---|
596 | void VBoxSnapshotsWgt::showSnapshotDetails()
|
---|
597 | {
|
---|
598 | SnapshotWgtItem *item = !mTreeWidget->currentItem() ? 0 :
|
---|
599 | static_cast <SnapshotWgtItem*> (mTreeWidget->currentItem());
|
---|
600 | AssertReturn (item, (void) 0);
|
---|
601 |
|
---|
602 | CSnapshot snap = item->snapshot();
|
---|
603 | AssertReturn (!snap.isNull(), (void) 0);
|
---|
604 |
|
---|
605 | CMachine snapMachine = snap.GetMachine();
|
---|
606 |
|
---|
607 | VBoxSnapshotDetailsDlg dlg (this);
|
---|
608 | dlg.getFromSnapshot (snap);
|
---|
609 |
|
---|
610 | if (dlg.exec() == QDialog::Accepted)
|
---|
611 | dlg.putBackToSnapshot();
|
---|
612 | }
|
---|
613 |
|
---|
614 | void VBoxSnapshotsWgt::takeSnapshot()
|
---|
615 | {
|
---|
616 | SnapshotWgtItem *item = !mTreeWidget->currentItem() ? 0 :
|
---|
617 | static_cast <SnapshotWgtItem*> (mTreeWidget->currentItem());
|
---|
618 | AssertReturn (item, (void) 0);
|
---|
619 |
|
---|
620 | VBoxTakeSnapshotDlg dlg (this, mMachine);
|
---|
621 |
|
---|
622 | QString typeId = mMachine.GetOSTypeId();
|
---|
623 | dlg.mLbIcon->setPixmap (vboxGlobal().vmGuestOSTypeIcon (typeId));
|
---|
624 |
|
---|
625 | /* Search for the max available filter index */
|
---|
626 | int maxSnapShotIndex = 0;
|
---|
627 | QString snapShotName = tr ("Snapshot %1");
|
---|
628 | QRegExp regExp (QString ("^") + snapShotName.arg ("([0-9]+)") + QString ("$"));
|
---|
629 | QTreeWidgetItemIterator iterator (mTreeWidget);
|
---|
630 | while (*iterator)
|
---|
631 | {
|
---|
632 | QString snapShot = static_cast <SnapshotWgtItem*> (*iterator)->text (0);
|
---|
633 | int pos = regExp.indexIn (snapShot);
|
---|
634 | if (pos != -1)
|
---|
635 | maxSnapShotIndex = regExp.cap (1).toInt() > maxSnapShotIndex ?
|
---|
636 | regExp.cap (1).toInt() : maxSnapShotIndex;
|
---|
637 | ++ iterator;
|
---|
638 | }
|
---|
639 | dlg.mLeName->setText (snapShotName.arg (maxSnapShotIndex + 1));
|
---|
640 |
|
---|
641 | if (dlg.exec() == QDialog::Accepted)
|
---|
642 | {
|
---|
643 | /* Open a direct session (this call will handle all errors) */
|
---|
644 | bool busy = mSessionState != KSessionState_Unlocked;
|
---|
645 | CSession session = vboxGlobal().openSession (mMachineId, busy /* aExisting */);
|
---|
646 | if (session.isNull())
|
---|
647 | return;
|
---|
648 |
|
---|
649 | CConsole console = session.GetConsole();
|
---|
650 | CProgress progress = console.TakeSnapshot (dlg.mLeName->text().trimmed(),
|
---|
651 | dlg.mTeDescription->toPlainText());
|
---|
652 | if (console.isOk())
|
---|
653 | {
|
---|
654 | /* Show the progress dialog */
|
---|
655 | vboxProblem().showModalProgressDialog (progress, mMachine.GetName(), ":/progress_snapshot_create_90px.png",
|
---|
656 | vboxProblem().mainWindowShown(), true);
|
---|
657 |
|
---|
658 | if (progress.GetResultCode() != 0)
|
---|
659 | vboxProblem().cannotTakeSnapshot (progress);
|
---|
660 | }
|
---|
661 | else
|
---|
662 | vboxProblem().cannotTakeSnapshot (console);
|
---|
663 |
|
---|
664 | session.UnlockMachine();
|
---|
665 | }
|
---|
666 | }
|
---|
667 |
|
---|
668 |
|
---|
669 | void VBoxSnapshotsWgt::machineDataChanged(QString strId)
|
---|
670 | {
|
---|
671 | SnapshotEditBlocker guardBlock (mEditProtector);
|
---|
672 |
|
---|
673 | if (strId != mMachineId)
|
---|
674 | return;
|
---|
675 |
|
---|
676 | curStateItem()->recache();
|
---|
677 | }
|
---|
678 |
|
---|
679 | void VBoxSnapshotsWgt::machineStateChanged(QString strId, KMachineState state)
|
---|
680 | {
|
---|
681 | SnapshotEditBlocker guardBlock (mEditProtector);
|
---|
682 |
|
---|
683 | if (strId != mMachineId)
|
---|
684 | return;
|
---|
685 |
|
---|
686 | curStateItem()->recache();
|
---|
687 | curStateItem()->updateCurrentState(state);
|
---|
688 | }
|
---|
689 |
|
---|
690 | void VBoxSnapshotsWgt::sessionStateChanged(QString strId, KSessionState state)
|
---|
691 | {
|
---|
692 | SnapshotEditBlocker guardBlock (mEditProtector);
|
---|
693 |
|
---|
694 | if (strId != mMachineId)
|
---|
695 | return;
|
---|
696 |
|
---|
697 | mSessionState = state;
|
---|
698 | onCurrentChanged (mTreeWidget->currentItem());
|
---|
699 | }
|
---|
700 |
|
---|
701 | void VBoxSnapshotsWgt::updateSnapshotsAge()
|
---|
702 | {
|
---|
703 | if (mAgeUpdateTimer.isActive())
|
---|
704 | mAgeUpdateTimer.stop();
|
---|
705 |
|
---|
706 | SnapshotAgeFormat age = traverseSnapshotAge (mTreeWidget->invisibleRootItem());
|
---|
707 |
|
---|
708 | switch (age)
|
---|
709 | {
|
---|
710 | case AgeInSeconds:
|
---|
711 | mAgeUpdateTimer.setInterval (5 * 1000);
|
---|
712 | break;
|
---|
713 | case AgeInMinutes:
|
---|
714 | mAgeUpdateTimer.setInterval (60 * 1000);
|
---|
715 | break;
|
---|
716 | case AgeInHours:
|
---|
717 | mAgeUpdateTimer.setInterval (60 * 60 * 1000);
|
---|
718 | break;
|
---|
719 | case AgeInDays:
|
---|
720 | mAgeUpdateTimer.setInterval (24 * 60 * 60 * 1000);
|
---|
721 | break;
|
---|
722 | default:
|
---|
723 | mAgeUpdateTimer.setInterval (0);
|
---|
724 | break;
|
---|
725 | }
|
---|
726 |
|
---|
727 | if (mAgeUpdateTimer.interval() > 0)
|
---|
728 | mAgeUpdateTimer.start();
|
---|
729 | }
|
---|
730 |
|
---|
731 | void VBoxSnapshotsWgt::retranslateUi()
|
---|
732 | {
|
---|
733 | /* Translate uic generated strings */
|
---|
734 | Ui::VBoxSnapshotsWgt::retranslateUi (this);
|
---|
735 |
|
---|
736 | mRestoreSnapshotAction->setText (tr ("&Restore Snapshot"));
|
---|
737 | mDeleteSnapshotAction->setText (tr ("&Delete Snapshot"));
|
---|
738 | mShowSnapshotDetailsAction->setText (tr ("S&how Details"));
|
---|
739 | mTakeSnapshotAction->setText (tr ("Take &Snapshot"));
|
---|
740 |
|
---|
741 | mRestoreSnapshotAction->setStatusTip (tr ("Restore the selected snapshot of the virtual machine"));
|
---|
742 | mDeleteSnapshotAction->setStatusTip (tr ("Delete the selected snapshot of the virtual machine"));
|
---|
743 | mShowSnapshotDetailsAction->setStatusTip (tr ("Show the details of the selected snapshot"));
|
---|
744 | mTakeSnapshotAction->setStatusTip (tr ("Take a snapshot of the current virtual machine state"));
|
---|
745 |
|
---|
746 | mRestoreSnapshotAction->setToolTip (mRestoreSnapshotAction->text().remove ('&').remove ('.') +
|
---|
747 | QString (" (%1)").arg (mRestoreSnapshotAction->shortcut().toString()));
|
---|
748 | mDeleteSnapshotAction->setToolTip (mDeleteSnapshotAction->text().remove ('&').remove ('.') +
|
---|
749 | QString (" (%1)").arg (mDeleteSnapshotAction->shortcut().toString()));
|
---|
750 | mShowSnapshotDetailsAction->setToolTip (mShowSnapshotDetailsAction->text().remove ('&').remove ('.') +
|
---|
751 | QString (" (%1)").arg (mShowSnapshotDetailsAction->shortcut().toString()));
|
---|
752 | mTakeSnapshotAction->setToolTip (mTakeSnapshotAction->text().remove ('&').remove ('.') +
|
---|
753 | QString (" (%1)").arg (mTakeSnapshotAction->shortcut().toString()));
|
---|
754 | }
|
---|
755 |
|
---|
756 | void VBoxSnapshotsWgt::refreshAll()
|
---|
757 | {
|
---|
758 | SnapshotEditBlocker guardBlock (mEditProtector);
|
---|
759 |
|
---|
760 | if (mMachine.isNull())
|
---|
761 | {
|
---|
762 | onCurrentChanged();
|
---|
763 | return;
|
---|
764 | }
|
---|
765 |
|
---|
766 | QString selectedItem, firstChildOfSelectedItem;
|
---|
767 | SnapshotWgtItem *cur = !mTreeWidget->currentItem() ? 0 :
|
---|
768 | static_cast <SnapshotWgtItem*> (mTreeWidget->currentItem());
|
---|
769 | if (cur)
|
---|
770 | {
|
---|
771 | selectedItem = cur->snapshotId();
|
---|
772 | if (cur->child (0))
|
---|
773 | firstChildOfSelectedItem = static_cast <SnapshotWgtItem*> (cur->child (0))->snapshotId();
|
---|
774 | }
|
---|
775 |
|
---|
776 | mTreeWidget->clear();
|
---|
777 |
|
---|
778 | /* Get the first snapshot */
|
---|
779 | if (mMachine.GetSnapshotCount() > 0)
|
---|
780 | {
|
---|
781 | CSnapshot snapshot = mMachine.FindSnapshot(QString::null);
|
---|
782 |
|
---|
783 | populateSnapshots (snapshot, 0);
|
---|
784 | Assert (mCurSnapshotItem);
|
---|
785 |
|
---|
786 | /* Add the "current state" item */
|
---|
787 | SnapshotWgtItem *csi = new SnapshotWgtItem (mCurSnapshotItem, mMachine);
|
---|
788 | csi->setBold (true);
|
---|
789 | csi->recache();
|
---|
790 |
|
---|
791 | SnapshotWgtItem *cur = findItem (selectedItem);
|
---|
792 | if (cur == 0)
|
---|
793 | cur = findItem (firstChildOfSelectedItem);
|
---|
794 | if (cur == 0)
|
---|
795 | cur = curStateItem();
|
---|
796 |
|
---|
797 | mTreeWidget->scrollToItem (cur);
|
---|
798 | mTreeWidget->setCurrentItem (cur);
|
---|
799 | onCurrentChanged (cur);
|
---|
800 | }
|
---|
801 | else
|
---|
802 | {
|
---|
803 | mCurSnapshotItem = 0;
|
---|
804 |
|
---|
805 | /* Add the "current state" item */
|
---|
806 | SnapshotWgtItem *csi = new SnapshotWgtItem (mTreeWidget, mMachine);
|
---|
807 | csi->setBold (true);
|
---|
808 | csi->recache();
|
---|
809 |
|
---|
810 | mTreeWidget->setCurrentItem (csi);
|
---|
811 | onCurrentChanged (csi);
|
---|
812 | }
|
---|
813 |
|
---|
814 | /* Updating age */
|
---|
815 | updateSnapshotsAge();
|
---|
816 |
|
---|
817 | mTreeWidget->resizeColumnToContents (0);
|
---|
818 | }
|
---|
819 |
|
---|
820 | SnapshotWgtItem* VBoxSnapshotsWgt::findItem (const QString &aSnapshotId)
|
---|
821 | {
|
---|
822 | QTreeWidgetItemIterator it (mTreeWidget);
|
---|
823 | while (*it)
|
---|
824 | {
|
---|
825 | SnapshotWgtItem *lvi = static_cast <SnapshotWgtItem*> (*it);
|
---|
826 | if (lvi->snapshotId() == aSnapshotId)
|
---|
827 | return lvi;
|
---|
828 | ++ it;
|
---|
829 | }
|
---|
830 |
|
---|
831 | return 0;
|
---|
832 | }
|
---|
833 |
|
---|
834 | SnapshotWgtItem *VBoxSnapshotsWgt::curStateItem()
|
---|
835 | {
|
---|
836 | QTreeWidgetItem *csi = mCurSnapshotItem ?
|
---|
837 | mCurSnapshotItem->child (mCurSnapshotItem->childCount() - 1) :
|
---|
838 | mTreeWidget->invisibleRootItem()->child (0);
|
---|
839 | return static_cast <SnapshotWgtItem*> (csi);
|
---|
840 | }
|
---|
841 |
|
---|
842 | void VBoxSnapshotsWgt::populateSnapshots (const CSnapshot &aSnapshot, QTreeWidgetItem *aItem)
|
---|
843 | {
|
---|
844 | SnapshotWgtItem *item = aItem ? new SnapshotWgtItem (aItem, aSnapshot) :
|
---|
845 | new SnapshotWgtItem (mTreeWidget, aSnapshot);
|
---|
846 | item->recache();
|
---|
847 |
|
---|
848 | if (mMachine.GetCurrentSnapshot().GetId() == aSnapshot.GetId())
|
---|
849 | {
|
---|
850 | item->setBold (true);
|
---|
851 | mCurSnapshotItem = item;
|
---|
852 | }
|
---|
853 |
|
---|
854 | CSnapshotVector snapshots = aSnapshot.GetChildren();
|
---|
855 | foreach (const CSnapshot &snapshot, snapshots)
|
---|
856 | populateSnapshots (snapshot, item);
|
---|
857 |
|
---|
858 | item->setExpanded (true);
|
---|
859 | item->setFlags (item->flags() | Qt::ItemIsEditable);
|
---|
860 | }
|
---|
861 |
|
---|
862 | SnapshotAgeFormat VBoxSnapshotsWgt::traverseSnapshotAge (QTreeWidgetItem *aParentItem)
|
---|
863 | {
|
---|
864 | SnapshotWgtItem *parentItem = aParentItem->type() == SnapshotWgtItem::ItemType ?
|
---|
865 | static_cast <SnapshotWgtItem*> (aParentItem) : 0;
|
---|
866 |
|
---|
867 | SnapshotAgeFormat age = parentItem ? parentItem->updateAge() : AgeMax;
|
---|
868 | for (int i = 0; i < aParentItem->childCount(); ++ i)
|
---|
869 | {
|
---|
870 | SnapshotAgeFormat newAge = traverseSnapshotAge (aParentItem->child (i));
|
---|
871 | age = newAge < age ? newAge : age;
|
---|
872 | }
|
---|
873 |
|
---|
874 | return age;
|
---|
875 | }
|
---|
876 |
|
---|