VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/medium/UIMediumManager.cpp@ 82781

Last change on this file since 82781 was 82471, checked in by vboxsync, 4 years ago

FE/Qt: Few more NLS fixes around various wizards and managers.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 62.5 KB
Line 
1/* $Id: UIMediumManager.cpp 82471 2019-12-06 18:34:00Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIMediumManager class implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/* Qt includes: */
19#include <QFrame>
20#include <QHBoxLayout>
21#include <QHeaderView>
22#include <QLabel>
23#include <QMenuBar>
24#include <QProgressBar>
25#include <QPushButton>
26
27/* GUI includes: */
28#include "QIDialogButtonBox.h"
29#include "QIFileDialog.h"
30#include "QILabel.h"
31#include "QIMessageBox.h"
32#include "QITabWidget.h"
33#include "UICommon.h"
34#include "UIActionPoolManager.h"
35#include "UIExtraDataManager.h"
36#include "UIMediumDetailsWidget.h"
37#include "UIMediumItem.h"
38#include "UIMediumManager.h"
39#include "UIMediumSearchWidget.h"
40#include "UIWizardCloneVD.h"
41#include "UIMessageCenter.h"
42#include "UIToolBar.h"
43#include "UIIconPool.h"
44#include "UIMedium.h"
45#include "UIVirtualBoxEventHandler.h"
46
47/* COM includes: */
48#include "COMEnums.h"
49#include "CMachine.h"
50#include "CMediumAttachment.h"
51#include "CMediumFormat.h"
52#include "CStorageController.h"
53#include "CSystemProperties.h"
54
55#ifdef VBOX_WS_MAC
56# include "UIWindowMenuManager.h"
57#endif /* VBOX_WS_MAC */
58
59
60
61/** Functor allowing to check if passed UIMediumItem is suitable by @a uID. */
62class CheckIfSuitableByID : public CheckIfSuitableBy
63{
64public:
65 /** Constructor accepting @a uID to compare with. */
66 CheckIfSuitableByID(const QUuid &uID) : m_uID(uID) {}
67
68private:
69 /** Determines whether passed UIMediumItem is suitable by @a uID. */
70 bool isItSuitable(UIMediumItem *pItem) const { return pItem->id() == m_uID; }
71 /** Holds the @a uID to compare to. */
72 QUuid m_uID;
73};
74
75/** Functor allowing to check if passed UIMediumItem is suitable by @a state. */
76class CheckIfSuitableByState : public CheckIfSuitableBy
77{
78public:
79 /** Constructor accepting @a state to compare with. */
80 CheckIfSuitableByState(KMediumState state) : m_state(state) {}
81
82private:
83 /** Determines whether passed UIMediumItem is suitable by @a state. */
84 bool isItSuitable(UIMediumItem *pItem) const { return pItem->state() == m_state; }
85 /** Holds the @a state to compare to. */
86 KMediumState m_state;
87};
88
89
90/*********************************************************************************************************************************
91* Class UIEnumerationProgressBar implementation. *
92*********************************************************************************************************************************/
93
94UIEnumerationProgressBar::UIEnumerationProgressBar(QWidget *pParent /* = 0 */)
95 : QWidget(pParent)
96{
97 /* Prepare: */
98 prepare();
99}
100
101void UIEnumerationProgressBar::setText(const QString &strText)
102{
103 m_pLabel->setText(strText);
104}
105
106int UIEnumerationProgressBar::value() const
107{
108 return m_pProgressBar->value();
109}
110
111void UIEnumerationProgressBar::setValue(int iValue)
112{
113 m_pProgressBar->setValue(iValue);
114}
115
116void UIEnumerationProgressBar::setMaximum(int iValue)
117{
118 m_pProgressBar->setMaximum(iValue);
119}
120
121void UIEnumerationProgressBar::prepare()
122{
123 /* Create layout: */
124 QHBoxLayout *pLayout = new QHBoxLayout(this);
125 {
126 /* Configure layout: */
127 pLayout->setContentsMargins(0, 0, 0, 0);
128 /* Create label: */
129 m_pLabel = new QLabel;
130 /* Create progress-bar: */
131 m_pProgressBar = new QProgressBar;
132 {
133 /* Configure progress-bar: */
134 m_pProgressBar->setTextVisible(false);
135 }
136 /* Add widgets into layout: */
137 pLayout->addWidget(m_pLabel);
138 pLayout->addWidget(m_pProgressBar);
139 }
140}
141
142
143/*********************************************************************************************************************************
144* Class UIMediumManagerWidget implementation. *
145*********************************************************************************************************************************/
146
147UIMediumManagerWidget::UIMediumManagerWidget(EmbedTo enmEmbedding, UIActionPool *pActionPool,
148 bool fShowToolbar /* = true */, QWidget *pParent /* = 0 */)
149 : QIWithRetranslateUI<QWidget>(pParent)
150 , m_enmEmbedding(enmEmbedding)
151 , m_pActionPool(pActionPool)
152 , m_fShowToolbar(fShowToolbar)
153 , m_fPreventChangeCurrentItem(false)
154 , m_pTabWidget(0)
155 , m_iTabCount(3)
156 , m_fInaccessibleHD(false)
157 , m_fInaccessibleCD(false)
158 , m_fInaccessibleFD(false)
159 , m_iconHD(UIIconPool::iconSet(":/hd_16px.png", ":/hd_disabled_16px.png"))
160 , m_iconCD(UIIconPool::iconSet(":/cd_16px.png", ":/cd_disabled_16px.png"))
161 , m_iconFD(UIIconPool::iconSet(":/fd_16px.png", ":/fd_disabled_16px.png"))
162 , m_pDetailsWidget(0)
163 , m_pToolBar(0)
164 , m_pProgressBar(0)
165 , m_pSearchWidget(0)
166{
167 /* Prepare: */
168 prepare();
169}
170
171QMenu *UIMediumManagerWidget::menu() const
172{
173 return m_pActionPool->action(UIActionIndexST_M_MediumWindow)->menu();
174}
175
176void UIMediumManagerWidget::setProgressBar(UIEnumerationProgressBar *pProgressBar)
177{
178 /* Cache progress-bar reference:*/
179 m_pProgressBar = pProgressBar;
180
181 /* Update translation: */
182 retranslateUi();
183}
184
185void UIMediumManagerWidget::retranslateUi()
186{
187 /* Adjust toolbar: */
188#ifdef VBOX_WS_MAC
189 // WORKAROUND:
190 // There is a bug in Qt Cocoa which result in showing a "more arrow" when
191 // the necessary size of the toolbar is increased. Also for some languages
192 // the with doesn't match if the text increase. So manually adjust the size
193 // after changing the text. */
194 if (m_pToolBar)
195 m_pToolBar->updateLayout();
196#endif
197
198 /* Translate tab-widget: */
199 if (m_pTabWidget)
200 {
201 m_pTabWidget->setTabText(tabIndex(UIMediumDeviceType_HardDisk), UIMediumManager::tr("&Hard disks"));
202 m_pTabWidget->setTabText(tabIndex(UIMediumDeviceType_DVD), UIMediumManager::tr("&Optical disks"));
203 m_pTabWidget->setTabText(tabIndex(UIMediumDeviceType_Floppy), UIMediumManager::tr("&Floppy disks"));
204 }
205
206 /* Translate HD tree-widget: */
207 QITreeWidget *pTreeWidgetHD = treeWidget(UIMediumDeviceType_HardDisk);
208 if (pTreeWidgetHD)
209 {
210 pTreeWidgetHD->headerItem()->setText(0, UIMediumManager::tr("Name"));
211 pTreeWidgetHD->headerItem()->setText(1, UIMediumManager::tr("Virtual Size"));
212 pTreeWidgetHD->headerItem()->setText(2, UIMediumManager::tr("Actual Size"));
213 }
214
215 /* Translate CD tree-widget: */
216 QITreeWidget *pTreeWidgetCD = treeWidget(UIMediumDeviceType_DVD);
217 if (pTreeWidgetCD)
218 {
219 pTreeWidgetCD->headerItem()->setText(0, UIMediumManager::tr("Name"));
220 pTreeWidgetCD->headerItem()->setText(1, UIMediumManager::tr("Size"));
221 }
222
223 /* Translate FD tree-widget: */
224 QITreeWidget *pTreeWidgetFD = treeWidget(UIMediumDeviceType_Floppy);
225 if (pTreeWidgetFD)
226 {
227 pTreeWidgetFD->headerItem()->setText(0, UIMediumManager::tr("Name"));
228 pTreeWidgetFD->headerItem()->setText(1, UIMediumManager::tr("Size"));
229 }
230
231 /* Translate progress-bar: */
232 if (m_pProgressBar)
233 {
234 m_pProgressBar->setText(UIMediumManager::tr("Checking accessibility"));
235#ifdef VBOX_WS_MAC
236 /* Make sure that the widgets aren't jumping around
237 * while the progress-bar get visible. */
238 m_pProgressBar->adjustSize();
239 //int h = m_pProgressBar->height();
240 //if (m_pButtonBox)
241 // m_pButtonBox->setMinimumHeight(h + 12);
242#endif
243 }
244
245 /* Full refresh if there is at least one item present: */
246 if ( (pTreeWidgetHD && pTreeWidgetHD->topLevelItemCount())
247 || (pTreeWidgetCD && pTreeWidgetCD->topLevelItemCount())
248 || (pTreeWidgetFD && pTreeWidgetFD->topLevelItemCount()))
249 sltRefreshAll();
250}
251
252void UIMediumManagerWidget::sltResetMediumDetailsChanges()
253{
254 /* Push the current item data into details-widget: */
255 sltHandleCurrentTabChanged();
256}
257
258void UIMediumManagerWidget::sltApplyMediumDetailsChanges()
259{
260 /* Get current medium-item: */
261 UIMediumItem *pMediumItem = currentMediumItem();
262 AssertMsgReturnVoid(pMediumItem, ("Current item must not be null"));
263 AssertReturnVoid(!pMediumItem->id().isNull());
264
265 /* Get item data: */
266 UIDataMedium oldData = *pMediumItem;
267 UIDataMedium newData = m_pDetailsWidget->data();
268
269 /* Search for corresponding medium: */
270 CMedium comMedium = uiCommon().medium(pMediumItem->id()).medium();
271
272 /* Try to assign new medium type: */
273 if ( comMedium.isOk()
274 && newData.m_options.m_enmMediumType != oldData.m_options.m_enmMediumType)
275 pMediumItem->changeMediumType(oldData.m_options.m_enmMediumType, newData.m_options.m_enmMediumType);
276
277 /* Try to assign new medium location: */
278 if ( comMedium.isOk()
279 && newData.m_options.m_strLocation != oldData.m_options.m_strLocation)
280 {
281 /* Prepare move storage progress: */
282 CProgress comProgress = comMedium.MoveTo(newData.m_options.m_strLocation);
283
284 /* Show error message if necessary: */
285 if (!comMedium.isOk())
286 msgCenter().cannotMoveMediumStorage(comMedium,
287 oldData.m_options.m_strLocation,
288 newData.m_options.m_strLocation,
289 this);
290 else
291 {
292 /* Show move storage progress: */
293 msgCenter().showModalProgressDialog(comProgress, UIMediumManager::tr("Moving medium ..."),
294 ":/progress_media_move_90px.png", this);
295
296 /* Show error message if necessary: */
297 if (!comProgress.isOk() || comProgress.GetResultCode() != 0)
298 msgCenter().cannotMoveMediumStorage(comProgress,
299 oldData.m_options.m_strLocation,
300 newData.m_options.m_strLocation,
301 this);
302 }
303 }
304
305 /* Try to assign new medium size: */
306 if ( comMedium.isOk()
307 && newData.m_options.m_uLogicalSize != oldData.m_options.m_uLogicalSize)
308 {
309 /* Prepare resize storage progress: */
310 CProgress comProgress = comMedium.Resize(newData.m_options.m_uLogicalSize);
311
312 /* Show error message if necessary: */
313 if (!comMedium.isOk())
314 msgCenter().cannotResizeHardDiskStorage(comMedium,
315 oldData.m_options.m_strLocation,
316 uiCommon().formatSize(oldData.m_options.m_uLogicalSize),
317 uiCommon().formatSize(newData.m_options.m_uLogicalSize),
318 this);
319 else
320 {
321 /* Show resize storage progress: */
322 msgCenter().showModalProgressDialog(comProgress, UIMediumManager::tr("Resizing medium ..."),
323 ":/progress_media_resize_90px.png", this);
324
325 /* Show error message if necessary: */
326 if (!comProgress.isOk() || comProgress.GetResultCode() != 0)
327 msgCenter().cannotResizeHardDiskStorage(comProgress,
328 oldData.m_options.m_strLocation,
329 uiCommon().formatSize(oldData.m_options.m_uLogicalSize),
330 uiCommon().formatSize(newData.m_options.m_uLogicalSize),
331 this);
332 }
333 }
334
335 /* Try to assign new medium description: */
336 if ( comMedium.isOk()
337 && newData.m_options.m_strDescription != oldData.m_options.m_strDescription)
338 {
339 comMedium.SetDescription(newData.m_options.m_strDescription);
340
341 /* Show error message if necessary: */
342 if (!comMedium.isOk())
343 msgCenter().cannotChangeMediumDescription(comMedium,
344 oldData.m_options.m_strLocation,
345 this);
346 }
347
348 /* Recache current item: */
349 pMediumItem->refreshAll();
350
351 /* Push the current item data into details-widget: */
352 sltHandleCurrentTabChanged();
353}
354
355void UIMediumManagerWidget::sltHandleMediumCreated(const QUuid &uMediumID)
356{
357 /* Search for corresponding medium: */
358 UIMedium medium = uiCommon().medium(uMediumID);
359
360 /* Ignore non-interesting media: */
361 if (medium.isNull() || medium.isHostDrive())
362 return;
363
364 /* Ignore media (and their children) which are
365 * marked as hidden or attached to hidden machines only: */
366 if (UIMedium::isMediumAttachedToHiddenMachinesOnly(medium))
367 return;
368
369 /* Create medium-item for corresponding medium: */
370 UIMediumItem *pMediumItem = createMediumItem(medium);
371
372 /* Make sure medium-item was created: */
373 if (!pMediumItem)
374 return;
375
376 /* If medium-item change allowed and
377 * 1. medium-enumeration is not currently in progress or
378 * 2. if there is no currently medium-item selected
379 * we have to choose newly added medium-item as current one: */
380 if ( !m_fPreventChangeCurrentItem
381 && ( !uiCommon().isMediumEnumerationInProgress()
382 || !mediumItem(medium.type())))
383 setCurrentItem(treeWidget(medium.type()), pMediumItem);
384}
385
386void UIMediumManagerWidget::sltHandleMediumDeleted(const QUuid &uMediumID)
387{
388 /* Make sure corresponding medium-item deleted: */
389 deleteMediumItem(uMediumID);
390}
391
392void UIMediumManagerWidget::sltHandleMediumEnumerationStart()
393{
394 /* Disable 'refresh' action: */
395 if (m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh))
396 m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh)->setEnabled(false);
397
398 /* Disable details-widget: */
399 if (m_pDetailsWidget)
400 m_pDetailsWidget->setOptionsEnabled(false);
401
402 /* Reset and show progress-bar: */
403 if (m_pProgressBar)
404 {
405 m_pProgressBar->setMaximum(uiCommon().mediumIDs().size());
406 m_pProgressBar->setValue(0);
407 m_pProgressBar->show();
408 }
409
410 /* Reset inaccessibility flags: */
411 m_fInaccessibleHD =
412 m_fInaccessibleCD =
413 m_fInaccessibleFD = false;
414
415 /* Reset tab-widget icons: */
416 if (m_pTabWidget)
417 {
418 m_pTabWidget->setTabIcon(tabIndex(UIMediumDeviceType_HardDisk), m_iconHD);
419 m_pTabWidget->setTabIcon(tabIndex(UIMediumDeviceType_DVD), m_iconCD);
420 m_pTabWidget->setTabIcon(tabIndex(UIMediumDeviceType_Floppy), m_iconFD);
421 }
422
423 /* Repopulate tree-widgets content: */
424 repopulateTreeWidgets();
425
426 /* Re-fetch all current medium-items: */
427 refetchCurrentMediumItems();
428 refetchCurrentChosenMediumItem();
429}
430
431void UIMediumManagerWidget::sltHandleMediumEnumerated(const QUuid &uMediumID)
432{
433 /* Search for corresponding medium: */
434 UIMedium medium = uiCommon().medium(uMediumID);
435
436 /* Ignore non-interesting media: */
437 if (medium.isNull() || medium.isHostDrive())
438 return;
439
440 /* Ignore media (and their children) which are
441 * marked as hidden or attached to hidden machines only: */
442 if (UIMedium::isMediumAttachedToHiddenMachinesOnly(medium))
443 return;
444
445 /* Update medium-item for corresponding medium: */
446 updateMediumItem(medium);
447
448 /* Advance progress-bar: */
449 if (m_pProgressBar)
450 m_pProgressBar->setValue(m_pProgressBar->value() + 1);
451}
452
453void UIMediumManagerWidget::sltHandleMediumEnumerationFinish()
454{
455 /* Hide progress-bar: */
456 if (m_pProgressBar)
457 m_pProgressBar->hide();
458
459 /* Enable details-widget: */
460 if (m_pDetailsWidget)
461 m_pDetailsWidget->setOptionsEnabled(true);
462
463 /* Enable 'refresh' action: */
464 if (m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh))
465 m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh)->setEnabled(true);
466
467 /* Re-fetch all current medium-items: */
468 refetchCurrentMediumItems();
469 refetchCurrentChosenMediumItem();
470}
471
472void UIMediumManagerWidget::sltHandleMachineStateChange(const QUuid &uId, const KMachineState state)
473{
474 UIMediumItem *pCurrentItem = currentMediumItem();
475 if (!pCurrentItem)
476 return;
477 /* If this machine is not using the current medium then we don't care about its state: */
478 if (!pCurrentItem->isMediumAttachedTo(uId))
479 return;
480 bool fMediumIsModifiable = true;
481 if (state != KMachineState_Aborted && state != KMachineState_PoweredOff)
482 fMediumIsModifiable = false;
483 m_pDetailsWidget->enableDisableMediumModificationWidgets(fMediumIsModifiable);
484}
485
486void UIMediumManagerWidget::sltAddMedium()
487{
488 QString strDefaultMachineFolder = uiCommon().virtualBox().GetSystemProperties().GetDefaultMachineFolder();
489 uiCommon().openMediumWithFileOpenDialog(currentMediumType(), this,
490 strDefaultMachineFolder, true /* use most recent medium folder */);
491}
492
493void UIMediumManagerWidget::sltCreateMedium()
494{
495 uiCommon().openMediumCreatorDialog(this, currentMediumType());
496}
497
498void UIMediumManagerWidget::sltCopyMedium()
499{
500 /* Get current medium-item: */
501 UIMediumItem *pMediumItem = currentMediumItem();
502 AssertMsgReturnVoid(pMediumItem, ("Current item must not be null"));
503 AssertReturnVoid(!pMediumItem->id().isNull());
504
505 /* Copy current medium-item: */
506 //pMediumItem->copy();
507
508 /* Show Clone VD wizard: */
509 UIMedium medium = pMediumItem->medium();
510 UISafePointerWizard pWizard = new UIWizardCloneVD(currentTreeWidget(), medium.medium());
511 pWizard->prepare();
512 pWizard->exec();
513
514 /* Delete if still exists: */
515 if (pWizard)
516 delete pWizard;
517}
518
519void UIMediumManagerWidget::sltMoveMedium()
520{
521 /* Get current medium-item: */
522 UIMediumItem *pMediumItem = currentMediumItem();
523 AssertMsgReturnVoid(pMediumItem, ("Current item must not be null"));
524 AssertReturnVoid(!pMediumItem->id().isNull());
525
526 /* Copy current medium-item: */
527 pMediumItem->move();
528
529 /* Push the current item data into details-widget: */
530 sltHandleCurrentTabChanged();
531}
532
533void UIMediumManagerWidget::sltRemoveMedium()
534{
535 /* Get current medium-item: */
536 UIMediumItem *pMediumItem = currentMediumItem();
537 AssertMsgReturnVoid(pMediumItem, ("Current item must not be null"));
538 AssertReturnVoid(!pMediumItem->id().isNull());
539
540 /* Remove current medium-item: */
541 pMediumItem->remove();
542}
543
544void UIMediumManagerWidget::sltReleaseMedium()
545{
546 /* Get current medium-item: */
547 UIMediumItem *pMediumItem = currentMediumItem();
548 AssertMsgReturnVoid(pMediumItem, ("Current item must not be null"));
549 AssertReturnVoid(!pMediumItem->id().isNull());
550
551 /* Remove current medium-item: */
552 bool fResult = pMediumItem->release();
553
554 /* Refetch currently chosen medium-item: */
555 if (fResult)
556 refetchCurrentChosenMediumItem();
557}
558
559void UIMediumManagerWidget::sltToggleMediumDetailsVisibility(bool fVisible)
560{
561 /* Save the setting: */
562 gEDataManager->setVirtualMediaManagerDetailsExpanded(fVisible);
563 /* Toggle medium details visibility: */
564 if (m_pDetailsWidget)
565 m_pDetailsWidget->setVisible(fVisible);
566 /* Notify external lsiteners: */
567 emit sigMediumDetailsVisibilityChanged(fVisible);
568}
569
570void UIMediumManagerWidget::sltToggleMediumSearchVisibility(bool fVisible)
571{
572 /* Save the setting: */
573 gEDataManager->setVirtualMediaManagerSearchWidgetExpanded(fVisible);
574 /* Toggle medium details visibility: */
575 if (m_pSearchWidget)
576 m_pSearchWidget->setVisible(fVisible);
577}
578
579void UIMediumManagerWidget::sltRefreshAll()
580{
581 /* Restart full medium-enumeration: */
582 uiCommon().enumerateMedia();
583}
584
585void UIMediumManagerWidget::sltHandleCurrentTabChanged()
586{
587 /* Get current tree-widget: */
588 QITreeWidget *pTreeWidget = currentTreeWidget();
589 if (pTreeWidget)
590 {
591 /* If another tree-widget was focused before,
592 * move focus to current tree-widget: */
593 if (qobject_cast<QITreeWidget*>(focusWidget()))
594 pTreeWidget->setFocus();
595 }
596
597 /* Update action icons: */
598 updateActionIcons();
599
600 /* Raise the required information-container: */
601 if (m_pDetailsWidget)
602 m_pDetailsWidget->setCurrentType(currentMediumType());
603 /* Re-fetch currently chosen medium-item: */
604 refetchCurrentChosenMediumItem();
605 sltHandlePerformSearch();
606}
607
608void UIMediumManagerWidget::sltHandleCurrentItemChanged()
609{
610 /* Get sender() tree-widget: */
611 QITreeWidget *pTreeWidget = qobject_cast<QITreeWidget*>(sender());
612 AssertMsgReturnVoid(pTreeWidget, ("This slot should be called by tree-widget only!\n"));
613
614 /* Re-fetch current medium-item of required type: */
615 refetchCurrentMediumItem(mediumType(pTreeWidget));
616}
617
618void UIMediumManagerWidget::sltHandleContextMenuRequest(const QPoint &position)
619{
620 /* Get current tree-widget: */
621 QITreeWidget *pTreeWidget = currentTreeWidget();
622 AssertPtrReturnVoid(pTreeWidget);
623
624 /* If underlaying item was found => make sure that item is current one: */
625 QTreeWidgetItem *pItem = pTreeWidget->itemAt(position);
626 if (pItem)
627 setCurrentItem(pTreeWidget, pItem);
628
629 /* Compose temporary context-menu: */
630 QMenu menu;
631 if (pTreeWidget->itemAt(position))
632 {
633 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Copy));
634 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Move));
635 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Remove));
636 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Release));
637 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_T_Search));
638 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_T_Details));
639 }
640 else
641 {
642 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Add));
643 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Create));
644 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_T_Search));
645 menu.addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh));
646 }
647 /* And show it: */
648 menu.exec(pTreeWidget->viewport()->mapToGlobal(position));
649}
650
651void UIMediumManagerWidget::sltPerformTablesAdjustment()
652{
653 /* Get all the tree-widgets: */
654 const QList<QITreeWidget*> trees = m_trees.values();
655
656 /* Calculate deduction for every header: */
657 QList<int> deductions;
658 foreach (QITreeWidget *pTreeWidget, trees)
659 {
660 int iDeduction = 0;
661 for (int iHeaderIndex = 1; iHeaderIndex < pTreeWidget->header()->count(); ++iHeaderIndex)
662 iDeduction += pTreeWidget->header()->sectionSize(iHeaderIndex);
663 deductions << iDeduction;
664 }
665
666 /* Adjust the table's first column: */
667 for (int iTreeIndex = 0; iTreeIndex < trees.size(); ++iTreeIndex)
668 {
669 QITreeWidget *pTreeWidget = trees[iTreeIndex];
670 int iSize0 = pTreeWidget->viewport()->width() - deductions[iTreeIndex];
671 if (pTreeWidget->header()->sectionSize(0) != iSize0)
672 pTreeWidget->header()->resizeSection(0, iSize0);
673 }
674}
675
676void UIMediumManagerWidget::sltHandlePerformSearch()
677{
678 performSearch(true);
679}
680
681void UIMediumManagerWidget::prepare()
682{
683 /* Prepare connections: */
684 prepareConnections();
685 /* Prepare actions: */
686 prepareActions();
687 /* Prepare widgets: */
688 prepareWidgets();
689
690 /* Load settings: */
691 loadSettings();
692
693 /* Apply language settings: */
694 retranslateUi();
695
696 /* Start full medium-enumeration (if necessary): */
697 if (!uiCommon().isFullMediumEnumerationRequested())
698 uiCommon().enumerateMedia();
699 /* Emulate medium-enumeration otherwise: */
700 else
701 {
702 /* Emulate medium-enumeration start: */
703 sltHandleMediumEnumerationStart();
704
705 /* Emulate medium-enumeration finish (if necessary): */
706 if (!uiCommon().isMediumEnumerationInProgress())
707 sltHandleMediumEnumerationFinish();
708 }
709}
710
711void UIMediumManagerWidget::prepareConnections()
712{
713 /* Listen to vm state changed event so that we can disable/enable widgets related to the current medium if neds be: */
714 connect(gVBoxEvents, &UIVirtualBoxEventHandler::sigMachineStateChange,
715 this, &UIMediumManagerWidget::sltHandleMachineStateChange);
716
717 /* Configure medium-processing connections: */
718 connect(&uiCommon(), &UICommon::sigMediumCreated,
719 this, &UIMediumManagerWidget::sltHandleMediumCreated);
720 connect(&uiCommon(), &UICommon::sigMediumDeleted,
721 this, &UIMediumManagerWidget::sltHandleMediumDeleted);
722
723 /* Configure medium-enumeration connections: */
724 connect(&uiCommon(), &UICommon::sigMediumEnumerationStarted,
725 this, &UIMediumManagerWidget::sltHandleMediumEnumerationStart);
726 connect(&uiCommon(), &UICommon::sigMediumEnumerated,
727 this, &UIMediumManagerWidget::sltHandleMediumEnumerated);
728 connect(&uiCommon(), &UICommon::sigMediumEnumerationFinished,
729 this, &UIMediumManagerWidget::sltHandleMediumEnumerationFinish);
730}
731
732void UIMediumManagerWidget::prepareActions()
733{
734 /* First of all, add actions which has smaller shortcut scope: */
735 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Add));
736 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Create));
737 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Copy));
738 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Move));
739 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Remove));
740 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Release));
741 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_T_Search));
742 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_T_Details));
743 addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh));
744
745 /* Connect actions: */
746 connect(m_pActionPool->action(UIActionIndexST_M_Medium_S_Add), &QAction::triggered,
747 this, &UIMediumManagerWidget::sltAddMedium);
748 connect(m_pActionPool->action(UIActionIndexST_M_Medium_S_Create), &QAction::triggered,
749 this, &UIMediumManagerWidget::sltCreateMedium);
750 connect(m_pActionPool->action(UIActionIndexST_M_Medium_S_Copy), &QAction::triggered,
751 this, &UIMediumManagerWidget::sltCopyMedium);
752 connect(m_pActionPool->action(UIActionIndexST_M_Medium_S_Move), &QAction::triggered,
753 this, &UIMediumManagerWidget::sltMoveMedium);
754 connect(m_pActionPool->action(UIActionIndexST_M_Medium_S_Remove), &QAction::triggered,
755 this, &UIMediumManagerWidget::sltRemoveMedium);
756 connect(m_pActionPool->action(UIActionIndexST_M_Medium_S_Release), &QAction::triggered,
757 this, &UIMediumManagerWidget::sltReleaseMedium);
758 connect(m_pActionPool->action(UIActionIndexST_M_Medium_T_Details), &QAction::toggled,
759 this, &UIMediumManagerWidget::sltToggleMediumDetailsVisibility);
760 connect(m_pActionPool->action(UIActionIndexST_M_Medium_T_Search), &QAction::toggled,
761 this, &UIMediumManagerWidget::sltToggleMediumSearchVisibility);
762 connect(m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh), &QAction::triggered,
763 this, &UIMediumManagerWidget::sltRefreshAll);
764
765 /* Update action icons: */
766 updateActionIcons();
767}
768
769void UIMediumManagerWidget::prepareWidgets()
770{
771 /* Create main-layout: */
772 new QVBoxLayout(this);
773 AssertPtrReturnVoid(layout());
774 {
775 /* Configure layout: */
776 layout()->setContentsMargins(0, 0, 0, 0);
777#ifdef VBOX_WS_MAC
778 layout()->setSpacing(10);
779#else
780 layout()->setSpacing(qApp->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) / 2);
781#endif
782
783 /* Prepare toolbar, if requested: */
784 if (m_fShowToolbar)
785 prepareToolBar();
786 /* Prepare tab-widget: */
787 prepareTabWidget();
788 /* Prepare search-widget: */
789 prepareSearchWidget();
790 /* Prepare details-widget: */
791 prepareDetailsWidget();
792 }
793}
794
795void UIMediumManagerWidget::prepareToolBar()
796{
797 /* Create toolbar: */
798 m_pToolBar = new UIToolBar(parentWidget());
799 AssertPtrReturnVoid(m_pToolBar);
800 {
801 /* Configure toolbar: */
802 const int iIconMetric = (int)(QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize));
803 m_pToolBar->setIconSize(QSize(iIconMetric, iIconMetric));
804 m_pToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
805
806 /* Add toolbar actions: */
807 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Add));
808 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Create));
809 m_pToolBar->addSeparator();
810 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Copy));
811 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Move));
812 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Remove));
813 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Release));
814 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_T_Search));
815 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_T_Details));
816 m_pToolBar->addSeparator();
817 m_pToolBar->addAction(m_pActionPool->action(UIActionIndexST_M_Medium_S_Refresh));
818
819#ifdef VBOX_WS_MAC
820 /* Check whether we are embedded into a stack: */
821 if (m_enmEmbedding == EmbedTo_Stack)
822 {
823 /* Add into layout: */
824 layout()->addWidget(m_pToolBar);
825 }
826#else
827 /* Add into layout: */
828 layout()->addWidget(m_pToolBar);
829#endif
830 }
831}
832
833void UIMediumManagerWidget::prepareTabWidget()
834{
835 /* Create tab-widget: */
836 m_pTabWidget = new QITabWidget;
837 AssertPtrReturnVoid(m_pTabWidget);
838 {
839 /* Create tabs: */
840 for (int i = 0; i < m_iTabCount; ++i)
841 prepareTab((UIMediumDeviceType)i);
842 /* Configure tab-widget: */
843 m_pTabWidget->setFocusPolicy(Qt::TabFocus);
844 m_pTabWidget->setTabIcon(tabIndex(UIMediumDeviceType_HardDisk), m_iconHD);
845 m_pTabWidget->setTabIcon(tabIndex(UIMediumDeviceType_DVD), m_iconCD);
846 m_pTabWidget->setTabIcon(tabIndex(UIMediumDeviceType_Floppy), m_iconFD);
847 connect(m_pTabWidget, &QITabWidget::currentChanged, this, &UIMediumManagerWidget::sltHandleCurrentTabChanged);
848
849 /* Add tab-widget into central layout: */
850 layout()->addWidget(m_pTabWidget);
851
852 /* Update other widgets according chosen tab: */
853 sltHandleCurrentTabChanged();
854 }
855}
856
857void UIMediumManagerWidget::prepareTab(UIMediumDeviceType type)
858{
859 /* Create tab: */
860 m_pTabWidget->addTab(new QWidget, QString());
861 QWidget *pTab = tab(type);
862 AssertPtrReturnVoid(pTab);
863 {
864 /* Create tab layout: */
865 QVBoxLayout *pLayout = new QVBoxLayout(pTab);
866 AssertPtrReturnVoid(pLayout);
867 {
868#ifdef VBOX_WS_MAC
869 /* Configure layout: */
870 pLayout->setContentsMargins(10, 10, 10, 10);
871#endif
872
873 /* Prepare tree-widget: */
874 prepareTreeWidget(type, type == UIMediumDeviceType_HardDisk ? 3 : 2);
875 }
876 }
877}
878
879void UIMediumManagerWidget::prepareTreeWidget(UIMediumDeviceType type, int iColumns)
880{
881 /* Create tree-widget: */
882 m_trees.insert(tabIndex(type), new QITreeWidget);
883 QITreeWidget *pTreeWidget = treeWidget(type);
884 AssertPtrReturnVoid(pTreeWidget);
885 {
886 /* Configure tree-widget: */
887 pTreeWidget->setExpandsOnDoubleClick(false);
888 pTreeWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
889 pTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
890 pTreeWidget->setAlternatingRowColors(true);
891 pTreeWidget->setAllColumnsShowFocus(true);
892 pTreeWidget->setAcceptDrops(true);
893 pTreeWidget->setColumnCount(iColumns);
894 pTreeWidget->sortItems(0, Qt::AscendingOrder);
895 if (iColumns > 0)
896 pTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Fixed);
897 if (iColumns > 1)
898 pTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
899 if (iColumns > 2)
900 pTreeWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
901 pTreeWidget->header()->setStretchLastSection(false);
902 pTreeWidget->setSortingEnabled(true);
903 connect(pTreeWidget, &QITreeWidget::currentItemChanged,
904 this, &UIMediumManagerWidget::sltHandleCurrentItemChanged);
905 connect(pTreeWidget, &QITreeWidget::itemDoubleClicked,
906 m_pActionPool->action(UIActionIndexST_M_Medium_T_Details), &QAction::setChecked);
907 connect(pTreeWidget, &QITreeWidget::customContextMenuRequested,
908 this, &UIMediumManagerWidget::sltHandleContextMenuRequest);
909 connect(pTreeWidget, &QITreeWidget::resized,
910 this, &UIMediumManagerWidget::sltPerformTablesAdjustment, Qt::QueuedConnection);
911 connect(pTreeWidget->header(), &QHeaderView::sectionResized,
912 this, &UIMediumManagerWidget::sltPerformTablesAdjustment, Qt::QueuedConnection);
913 /* Add tree-widget into tab layout: */
914 tab(type)->layout()->addWidget(pTreeWidget);
915 }
916}
917
918void UIMediumManagerWidget::prepareDetailsWidget()
919{
920 /* Create details-widget: */
921 m_pDetailsWidget = new UIMediumDetailsWidget(this, m_enmEmbedding);
922 AssertPtrReturnVoid(m_pDetailsWidget);
923 {
924 /* Configure details-widget: */
925 m_pDetailsWidget->setVisible(false);
926 m_pDetailsWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
927 connect(m_pDetailsWidget, &UIMediumDetailsWidget::sigAcceptAllowed,
928 this, &UIMediumManagerWidget::sigAcceptAllowed);
929 connect(m_pDetailsWidget, &UIMediumDetailsWidget::sigRejectAllowed,
930 this, &UIMediumManagerWidget::sigRejectAllowed);
931 connect(m_pDetailsWidget, &UIMediumDetailsWidget::sigDataChangeRejected,
932 this, &UIMediumManagerWidget::sltResetMediumDetailsChanges);
933 connect(m_pDetailsWidget, &UIMediumDetailsWidget::sigDataChangeAccepted,
934 this, &UIMediumManagerWidget::sltApplyMediumDetailsChanges);
935
936 /* Add into layout: */
937 layout()->addWidget(m_pDetailsWidget);
938 }
939}
940
941void UIMediumManagerWidget::prepareSearchWidget()
942{
943 m_pSearchWidget = new UIMediumSearchWidget(this);
944 AssertPtrReturnVoid(m_pSearchWidget);
945 {
946 m_pSearchWidget->setVisible(false);
947 m_pSearchWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
948 connect(m_pSearchWidget, &UIMediumSearchWidget::sigPerformSearch,
949 this, &UIMediumManagerWidget::sltHandlePerformSearch);
950
951 /* Add into layout: */
952 layout()->addWidget(m_pSearchWidget);
953 }
954
955}
956
957void UIMediumManagerWidget::loadSettings()
958{
959 /* Details action/widget: */
960 m_pActionPool->action(UIActionIndexST_M_Medium_T_Details)->setChecked(gEDataManager->virtualMediaManagerDetailsExpanded());
961 sltToggleMediumDetailsVisibility(m_pActionPool->action(UIActionIndexST_M_Medium_T_Details)->isChecked());
962
963 /* Search action/widget: */
964 m_pActionPool->action(UIActionIndexST_M_Medium_T_Search)->setChecked(gEDataManager->virtualMediaManagerSearchWidgetExpanded());
965 sltToggleMediumSearchVisibility(m_pActionPool->action(UIActionIndexST_M_Medium_T_Search)->isChecked());
966}
967
968void UIMediumManagerWidget::repopulateTreeWidgets()
969{
970 /* Remember current medium-items: */
971 if (UIMediumItem *pMediumItem = mediumItem(UIMediumDeviceType_HardDisk))
972 m_uCurrentIdHD = pMediumItem->id();
973 if (UIMediumItem *pMediumItem = mediumItem(UIMediumDeviceType_DVD))
974 m_uCurrentIdCD = pMediumItem->id();
975 if (UIMediumItem *pMediumItem = mediumItem(UIMediumDeviceType_Floppy))
976 m_uCurrentIdFD = pMediumItem->id();
977
978 /* Clear tree-widgets: */
979 QITreeWidget *pTreeWidgetHD = treeWidget(UIMediumDeviceType_HardDisk);
980 if (pTreeWidgetHD)
981 {
982 setCurrentItem(pTreeWidgetHD, 0);
983 pTreeWidgetHD->clear();
984 }
985 QITreeWidget *pTreeWidgetCD = treeWidget(UIMediumDeviceType_DVD);
986 if (pTreeWidgetCD)
987 {
988 setCurrentItem(pTreeWidgetCD, 0);
989 pTreeWidgetCD->clear();
990 }
991 QITreeWidget *pTreeWidgetFD = treeWidget(UIMediumDeviceType_Floppy);
992 if (pTreeWidgetFD)
993 {
994 setCurrentItem(pTreeWidgetFD, 0);
995 pTreeWidgetFD->clear();
996 }
997
998 /* Create medium-items (do not change current one): */
999 m_fPreventChangeCurrentItem = true;
1000 foreach (const QUuid &uMediumID, uiCommon().mediumIDs())
1001 sltHandleMediumCreated(uMediumID);
1002 m_fPreventChangeCurrentItem = false;
1003
1004 /* Select first item as current one if nothing selected: */
1005 if (pTreeWidgetHD && !mediumItem(UIMediumDeviceType_HardDisk))
1006 if (QTreeWidgetItem *pItem = pTreeWidgetHD->topLevelItem(0))
1007 setCurrentItem(pTreeWidgetHD, pItem);
1008 if (pTreeWidgetCD && !mediumItem(UIMediumDeviceType_DVD))
1009 if (QTreeWidgetItem *pItem = pTreeWidgetCD->topLevelItem(0))
1010 setCurrentItem(pTreeWidgetCD, pItem);
1011 if (pTreeWidgetFD && !mediumItem(UIMediumDeviceType_Floppy))
1012 if (QTreeWidgetItem *pItem = pTreeWidgetFD->topLevelItem(0))
1013 setCurrentItem(pTreeWidgetFD, pItem);
1014
1015 sltHandlePerformSearch();
1016}
1017
1018void UIMediumManagerWidget::refetchCurrentMediumItem(UIMediumDeviceType type)
1019{
1020 /* Get corresponding medium-item: */
1021 UIMediumItem *pMediumItem = mediumItem(type);
1022
1023#ifdef VBOX_WS_MAC
1024 /* Set the file for the proxy icon: */
1025 if (pMediumItem == currentMediumItem())
1026 setWindowFilePath(pMediumItem ? pMediumItem->location() : QString());
1027#endif /* VBOX_WS_MAC */
1028
1029 /* Make sure current medium-item visible: */
1030 if (pMediumItem)
1031 treeWidget(type)->scrollToItem(pMediumItem, QAbstractItemView::EnsureVisible);
1032
1033 /* Update actions: */
1034 updateActions();
1035
1036 /* Update details-widget: */
1037 if (m_pDetailsWidget)
1038 {
1039 m_pDetailsWidget->setData(pMediumItem ? *pMediumItem : UIDataMedium(type));
1040 if (pMediumItem && currentMediumItem())
1041 m_pDetailsWidget->enableDisableMediumModificationWidgets(currentMediumItem()->isMediumModifiable());
1042 }
1043}
1044
1045void UIMediumManagerWidget::refetchCurrentChosenMediumItem()
1046{
1047 refetchCurrentMediumItem(currentMediumType());
1048}
1049
1050void UIMediumManagerWidget::refetchCurrentMediumItems()
1051{
1052 refetchCurrentMediumItem(UIMediumDeviceType_HardDisk);
1053 refetchCurrentMediumItem(UIMediumDeviceType_DVD);
1054 refetchCurrentMediumItem(UIMediumDeviceType_Floppy);
1055}
1056
1057void UIMediumManagerWidget::updateActions()
1058{
1059 /* Get current medium-item: */
1060 UIMediumItem *pMediumItem = currentMediumItem();
1061
1062 /* Calculate actions accessibility: */
1063 bool fNotInEnumeration = !uiCommon().isMediumEnumerationInProgress();
1064
1065 /* Apply actions accessibility: */
1066 bool fActionEnabledCopy = fNotInEnumeration && pMediumItem && checkMediumFor(pMediumItem, Action_Copy);
1067 m_pActionPool->action(UIActionIndexST_M_Medium_S_Copy)->setEnabled(fActionEnabledCopy);
1068 bool fActionEnabledMove = fNotInEnumeration && pMediumItem && checkMediumFor(pMediumItem, Action_Edit);
1069 m_pActionPool->action(UIActionIndexST_M_Medium_S_Move)->setEnabled(fActionEnabledMove);
1070 bool fActionEnabledRemove = fNotInEnumeration && pMediumItem && checkMediumFor(pMediumItem, Action_Remove);
1071 m_pActionPool->action(UIActionIndexST_M_Medium_S_Remove)->setEnabled(fActionEnabledRemove);
1072 bool fActionEnabledRelease = fNotInEnumeration && pMediumItem && checkMediumFor(pMediumItem, Action_Release);
1073 m_pActionPool->action(UIActionIndexST_M_Medium_S_Release)->setEnabled(fActionEnabledRelease);
1074 bool fActionEnabledDetails = true;
1075 m_pActionPool->action(UIActionIndexST_M_Medium_T_Details)->setEnabled(fActionEnabledDetails);
1076}
1077
1078void UIMediumManagerWidget::updateActionIcons()
1079{
1080 const UIMediumDeviceType enmCurrentMediumType = currentMediumType();
1081 if (enmCurrentMediumType != UIMediumDeviceType_Invalid)
1082 {
1083 m_pActionPool->action(UIActionIndexST_M_Medium_S_Add)->setState((int)enmCurrentMediumType);
1084 m_pActionPool->action(UIActionIndexST_M_Medium_S_Create)->setState((int)enmCurrentMediumType);
1085 m_pActionPool->action(UIActionIndexST_M_Medium_S_Copy)->setState((int)enmCurrentMediumType);
1086 m_pActionPool->action(UIActionIndexST_M_Medium_S_Move)->setState((int)enmCurrentMediumType);
1087 m_pActionPool->action(UIActionIndexST_M_Medium_S_Remove)->setState((int)enmCurrentMediumType);
1088 m_pActionPool->action(UIActionIndexST_M_Medium_S_Release)->setState((int)enmCurrentMediumType);
1089 m_pActionPool->action(UIActionIndexST_M_Medium_T_Details)->setState((int)enmCurrentMediumType);
1090 }
1091}
1092
1093void UIMediumManagerWidget::updateTabIcons(UIMediumItem *pMediumItem, Action action)
1094{
1095 /* Make sure medium-item is valid: */
1096 AssertReturnVoid(pMediumItem);
1097
1098 /* Prepare data for tab: */
1099 const QIcon *pIcon = 0;
1100 bool *pfInaccessible = 0;
1101 const UIMediumDeviceType mediumType = pMediumItem->mediumType();
1102 switch (mediumType)
1103 {
1104 case UIMediumDeviceType_HardDisk:
1105 pIcon = &m_iconHD;
1106 pfInaccessible = &m_fInaccessibleHD;
1107 break;
1108 case UIMediumDeviceType_DVD:
1109 pIcon = &m_iconCD;
1110 pfInaccessible = &m_fInaccessibleCD;
1111 break;
1112 case UIMediumDeviceType_Floppy:
1113 pIcon = &m_iconFD;
1114 pfInaccessible = &m_fInaccessibleFD;
1115 break;
1116 default:
1117 AssertFailed();
1118 }
1119 AssertReturnVoid(pIcon && pfInaccessible);
1120
1121 switch (action)
1122 {
1123 case Action_Add:
1124 {
1125 /* Does it change the overall state? */
1126 if (*pfInaccessible || pMediumItem->state() != KMediumState_Inaccessible)
1127 break; /* no */
1128
1129 *pfInaccessible = true;
1130
1131 if (m_pTabWidget)
1132 m_pTabWidget->setTabIcon(tabIndex(mediumType), uiCommon().warningIcon());
1133
1134 break;
1135 }
1136 case Action_Edit:
1137 case Action_Remove:
1138 {
1139 bool fCheckRest = false;
1140
1141 if (action == Action_Edit)
1142 {
1143 /* Does it change the overall state? */
1144 if ((*pfInaccessible && pMediumItem->state() == KMediumState_Inaccessible) ||
1145 (!*pfInaccessible && pMediumItem->state() != KMediumState_Inaccessible))
1146 break; /* no */
1147
1148 /* Is the given item in charge? */
1149 if (!*pfInaccessible && pMediumItem->state() == KMediumState_Inaccessible)
1150 *pfInaccessible = true; /* yes */
1151 else
1152 fCheckRest = true; /* no */
1153 }
1154 else
1155 fCheckRest = true;
1156
1157 if (fCheckRest)
1158 {
1159 /* Find the first KMediumState_Inaccessible item to be in charge: */
1160 CheckIfSuitableByState lookForState(KMediumState_Inaccessible);
1161 CheckIfSuitableByID ignoreID(pMediumItem->id());
1162 UIMediumItem *pInaccessibleMediumItem = searchItem(pMediumItem->parentTree(), lookForState, &ignoreID);
1163 *pfInaccessible = !!pInaccessibleMediumItem;
1164 }
1165
1166 if (m_pTabWidget)
1167 {
1168 if (*pfInaccessible)
1169 m_pTabWidget->setTabIcon(tabIndex(mediumType), uiCommon().warningIcon());
1170 else
1171 m_pTabWidget->setTabIcon(tabIndex(mediumType), *pIcon);
1172 }
1173
1174 break;
1175 }
1176
1177 default:
1178 break;
1179 }
1180}
1181
1182UIMediumItem* UIMediumManagerWidget::createMediumItem(const UIMedium &medium)
1183{
1184 /* Get medium type: */
1185 UIMediumDeviceType type = medium.type();
1186
1187 /* Create medium-item: */
1188 UIMediumItem *pMediumItem = 0;
1189 switch (type)
1190 {
1191 /* Of hard-drive type: */
1192 case UIMediumDeviceType_HardDisk:
1193 {
1194 /* Make sure corresponding tree-widget exists: */
1195 QITreeWidget *pTreeWidget = treeWidget(UIMediumDeviceType_HardDisk);
1196 if (pTreeWidget)
1197 {
1198 /* Recursively create hard-drive item: */
1199 pMediumItem = createHardDiskItem(medium);
1200 /* Make sure item was created: */
1201 if (!pMediumItem)
1202 break;
1203 if (pMediumItem->id() == m_uCurrentIdHD)
1204 {
1205 setCurrentItem(pTreeWidget, pMediumItem);
1206 m_uCurrentIdHD = QUuid();
1207 }
1208 }
1209 break;
1210 }
1211 /* Of optical-image type: */
1212 case UIMediumDeviceType_DVD:
1213 {
1214 /* Make sure corresponding tree-widget exists: */
1215 QITreeWidget *pTreeWidget = treeWidget(UIMediumDeviceType_DVD);
1216 if (pTreeWidget)
1217 {
1218 /* Create optical-disk item: */
1219 pMediumItem = new UIMediumItemCD(medium, pTreeWidget);
1220 /* Make sure item was created: */
1221 if (!pMediumItem)
1222 break;
1223 LogRel2(("UIMediumManager: Optical medium-item with ID={%s} created.\n", medium.id().toString().toUtf8().constData()));
1224 if (pMediumItem->id() == m_uCurrentIdCD)
1225 {
1226 setCurrentItem(pTreeWidget, pMediumItem);
1227 m_uCurrentIdCD = QUuid();
1228 }
1229 }
1230 break;
1231 }
1232 /* Of floppy-image type: */
1233 case UIMediumDeviceType_Floppy:
1234 {
1235 /* Make sure corresponding tree-widget exists: */
1236 QITreeWidget *pTreeWidget = treeWidget(UIMediumDeviceType_Floppy);
1237 if (pTreeWidget)
1238 {
1239 /* Create floppy-disk item: */
1240 pMediumItem = new UIMediumItemFD(medium, pTreeWidget);
1241 /* Make sure item was created: */
1242 if (!pMediumItem)
1243 break;
1244 LogRel2(("UIMediumManager: Floppy medium-item with ID={%s} created.\n", medium.id().toString().toUtf8().constData()));
1245 if (pMediumItem->id() == m_uCurrentIdFD)
1246 {
1247 setCurrentItem(pTreeWidget, pMediumItem);
1248 m_uCurrentIdFD = QUuid();
1249 }
1250 }
1251 break;
1252 }
1253 default: AssertMsgFailed(("Medium-type unknown: %d\n", type)); break;
1254 }
1255
1256 /* Make sure item was created: */
1257 if (!pMediumItem)
1258 return 0;
1259
1260 /* Update tab-icons: */
1261 updateTabIcons(pMediumItem, Action_Add);
1262
1263 /* Reperform the medium search (don't jump to the found element): */
1264 performSearch(false);
1265
1266 /* Re-fetch medium-item if it is current one created: */
1267 if (pMediumItem == mediumItem(type))
1268 refetchCurrentMediumItem(type);
1269
1270 /* Return created medium-item: */
1271 return pMediumItem;
1272}
1273
1274UIMediumItem* UIMediumManagerWidget::createHardDiskItem(const UIMedium &medium)
1275{
1276 /* Make sure passed medium is valid: */
1277 AssertReturn(!medium.medium().isNull(), 0);
1278
1279 /* Make sure corresponding tree-widget exists: */
1280 QITreeWidget *pTreeWidget = treeWidget(UIMediumDeviceType_HardDisk);
1281 if (pTreeWidget)
1282 {
1283 /* Search for existing medium-item: */
1284 UIMediumItem *pMediumItem = searchItem(pTreeWidget, CheckIfSuitableByID(medium.id()));
1285
1286 /* If medium-item do not exists: */
1287 if (!pMediumItem)
1288 {
1289 /* If medium have a parent: */
1290 if (medium.parentID() != UIMedium::nullID())
1291 {
1292 /* Try to find parent medium-item: */
1293 UIMediumItem *pParentMediumItem = searchItem(pTreeWidget, CheckIfSuitableByID(medium.parentID()));
1294 /* If parent medium-item was not found: */
1295 if (!pParentMediumItem)
1296 {
1297 /* Make sure corresponding parent medium is already cached! */
1298 UIMedium parentMedium = uiCommon().medium(medium.parentID());
1299 if (parentMedium.isNull())
1300 AssertMsgFailed(("Parent medium with ID={%s} was not found!\n", medium.parentID().toString().toUtf8().constData()));
1301 /* Try to create parent medium-item: */
1302 else
1303 pParentMediumItem = createHardDiskItem(parentMedium);
1304 }
1305 /* If parent medium-item was found: */
1306 if (pParentMediumItem)
1307 {
1308 pMediumItem = new UIMediumItemHD(medium, pParentMediumItem);
1309 LogRel2(("UIMediumManager: Child hard-disk medium-item with ID={%s} created.\n", medium.id().toString().toUtf8().constData()));
1310 }
1311 }
1312 /* Else just create item as top-level one: */
1313 if (!pMediumItem)
1314 {
1315 pMediumItem = new UIMediumItemHD(medium, pTreeWidget);
1316 LogRel2(("UIMediumManager: Root hard-disk medium-item with ID={%s} created.\n", medium.id().toString().toUtf8().constData()));
1317 }
1318 }
1319
1320 /* Return created medium-item: */
1321 return pMediumItem;
1322 }
1323
1324 /* Return null by default: */
1325 return 0;
1326}
1327
1328void UIMediumManagerWidget::updateMediumItem(const UIMedium &medium)
1329{
1330 /* Get medium type: */
1331 UIMediumDeviceType type = medium.type();
1332
1333 /* Search for existing medium-item: */
1334 UIMediumItem *pMediumItem = searchItem(treeWidget(type), CheckIfSuitableByID(medium.id()));
1335
1336 /* Create item if doesn't exists: */
1337 if (!pMediumItem)
1338 pMediumItem = createMediumItem(medium);
1339
1340 /* Make sure item was created: */
1341 if (!pMediumItem)
1342 return;
1343
1344 /* Update medium-item: */
1345 pMediumItem->setMedium(medium);
1346 LogRel2(("UIMediumManager: Medium-item with ID={%s} updated.\n", medium.id().toString().toUtf8().constData()));
1347
1348 /* Update tab-icons: */
1349 updateTabIcons(pMediumItem, Action_Edit);
1350
1351 /* Re-fetch medium-item if it is current one updated: */
1352 if (pMediumItem == mediumItem(type))
1353 refetchCurrentMediumItem(type);
1354
1355 /* Update all the children recursively as well: */
1356 foreach(const QUuid &uMediumId, uiCommon().mediumIDs())
1357 {
1358 UIMedium guiMedium = uiCommon().medium(uMediumId);
1359 if ( !guiMedium.isNull()
1360 && guiMedium.parentID() == medium.id())
1361 updateMediumItem(guiMedium);
1362 }
1363}
1364
1365void UIMediumManagerWidget::deleteMediumItem(const QUuid &uMediumID)
1366{
1367 /* Search for corresponding tree-widget: */
1368 QList<UIMediumDeviceType> types;
1369 types << UIMediumDeviceType_HardDisk << UIMediumDeviceType_DVD << UIMediumDeviceType_Floppy;
1370 QITreeWidget *pTreeWidget = 0;
1371 UIMediumItem *pMediumItem = 0;
1372 foreach (UIMediumDeviceType type, types)
1373 {
1374 /* Get iterated tree-widget: */
1375 pTreeWidget = treeWidget(type);
1376 /* Search for existing medium-item: */
1377 pMediumItem = searchItem(pTreeWidget, CheckIfSuitableByID(uMediumID));
1378 if (pMediumItem)
1379 break;
1380 }
1381
1382 /* Make sure item was found: */
1383 if (!pMediumItem)
1384 return;
1385
1386 /* Update tab-icons: */
1387 updateTabIcons(pMediumItem, Action_Remove);
1388
1389 /* Delete medium-item: */
1390 delete pMediumItem;
1391 LogRel2(("UIMediumManager: Medium-item with ID={%s} deleted.\n", uMediumID.toString().toUtf8().constData()));
1392
1393 /* Reperform the medium search (don't jump to the found element): */
1394 performSearch(false);
1395
1396 /* If there is no current medium-item now selected
1397 * we have to choose first-available medium-item as current one: */
1398 if (!pTreeWidget->currentItem())
1399 setCurrentItem(pTreeWidget, pTreeWidget->topLevelItem(0));
1400}
1401
1402QWidget* UIMediumManagerWidget::tab(UIMediumDeviceType type) const
1403{
1404 /* Determine tab index for passed medium type: */
1405 int iIndex = tabIndex(type);
1406
1407 /* Return tab for known tab index: */
1408 if (iIndex >= 0 && iIndex < m_iTabCount)
1409 return iIndex < m_pTabWidget->count() ? m_pTabWidget->widget(iIndex) : 0;
1410
1411 /* Null by default: */
1412 return 0;
1413}
1414
1415QITreeWidget* UIMediumManagerWidget::treeWidget(UIMediumDeviceType type) const
1416{
1417 /* Determine tab index for passed medium type: */
1418 int iIndex = tabIndex(type);
1419
1420 /* Return tree-widget for known tab index: */
1421 if (iIndex >= 0 && iIndex < m_iTabCount)
1422 return m_trees.value(iIndex, 0);
1423
1424 /* Null by default: */
1425 return 0;
1426}
1427
1428UIMediumItem* UIMediumManagerWidget::mediumItem(UIMediumDeviceType type) const
1429{
1430 /* Get corresponding tree-widget: */
1431 QITreeWidget *pTreeWidget = treeWidget(type);
1432 /* Return corresponding medium-item: */
1433 return pTreeWidget ? toMediumItem(pTreeWidget->currentItem()) : 0;
1434}
1435
1436UIMediumDeviceType UIMediumManagerWidget::mediumType(QITreeWidget *pTreeWidget) const
1437{
1438 /* Determine tab index of passed tree-widget: */
1439 int iIndex = m_trees.key(pTreeWidget, -1);
1440
1441 /* Return medium type for known tab index: */
1442 if (iIndex >= 0 && iIndex < m_iTabCount)
1443 return (UIMediumDeviceType)iIndex;
1444
1445 /* Invalid by default: */
1446 AssertFailedReturn(UIMediumDeviceType_Invalid);
1447}
1448
1449UIMediumDeviceType UIMediumManagerWidget::currentMediumType() const
1450{
1451 /* Invalid if tab-widget doesn't exists: */
1452 if (!m_pTabWidget)
1453 return UIMediumDeviceType_Invalid;
1454
1455 /* Return current medium type: */
1456 return (UIMediumDeviceType)m_pTabWidget->currentIndex();
1457}
1458
1459QITreeWidget* UIMediumManagerWidget::currentTreeWidget() const
1460{
1461 /* Return current tree-widget: */
1462 return treeWidget(currentMediumType());
1463}
1464
1465UIMediumItem* UIMediumManagerWidget::currentMediumItem() const
1466{
1467 /* Return current medium-item: */
1468 return mediumItem(currentMediumType());
1469}
1470
1471void UIMediumManagerWidget::setCurrentItem(QITreeWidget *pTreeWidget, QTreeWidgetItem *pItem)
1472{
1473 /* Make sure passed tree-widget is valid: */
1474 AssertPtrReturnVoid(pTreeWidget);
1475
1476 /* Make passed item current for passed tree-widget: */
1477 pTreeWidget->setCurrentItem(pItem);
1478
1479 /* If non NULL item was passed: */
1480 if (pItem)
1481 {
1482 /* Make sure it's also selected, and visible: */
1483 pItem->setSelected(true);
1484 pTreeWidget->scrollToItem(pItem, QAbstractItemView::EnsureVisible);
1485 }
1486
1487 /* Re-fetch currently chosen medium-item: */
1488 refetchCurrentChosenMediumItem();
1489}
1490
1491void UIMediumManagerWidget::performSearch(bool fSelectNext)
1492{
1493 if (!m_pSearchWidget || !m_pTabWidget)
1494 return;
1495
1496 QITreeWidget *pTreeWidget = treeWidget(static_cast<UIMediumDeviceType>(m_pTabWidget->currentIndex()));
1497 if (!pTreeWidget)
1498 return;
1499 m_pSearchWidget->search(pTreeWidget, fSelectNext);
1500}
1501
1502/* static */
1503int UIMediumManagerWidget::tabIndex(UIMediumDeviceType type)
1504{
1505 /* Return tab index corresponding to known medium type: */
1506 switch (type)
1507 {
1508 case UIMediumDeviceType_HardDisk: return 0;
1509 case UIMediumDeviceType_DVD: return 1;
1510 case UIMediumDeviceType_Floppy: return 2;
1511 default: break;
1512 }
1513
1514 /* -1 by default: */
1515 return -1;
1516}
1517
1518/* static */
1519UIMediumItem* UIMediumManagerWidget::searchItem(QITreeWidget *pTreeWidget, const CheckIfSuitableBy &condition, CheckIfSuitableBy *pException)
1520{
1521 /* Make sure argument is valid: */
1522 if (!pTreeWidget)
1523 return 0;
1524
1525 /* Return wrapper: */
1526 return searchItem(pTreeWidget->invisibleRootItem(), condition, pException);
1527}
1528
1529/* static */
1530UIMediumItem* UIMediumManagerWidget::searchItem(QTreeWidgetItem *pParentItem, const CheckIfSuitableBy &condition, CheckIfSuitableBy *pException)
1531{
1532 /* Make sure argument is valid: */
1533 if (!pParentItem)
1534 return 0;
1535
1536 /* Verify passed item if it is of 'medium' type too: */
1537 if (UIMediumItem *pMediumParentItem = toMediumItem(pParentItem))
1538 if ( condition.isItSuitable(pMediumParentItem)
1539 && (!pException || !pException->isItSuitable(pMediumParentItem)))
1540 return pMediumParentItem;
1541
1542 /* Iterate other all the children: */
1543 for (int iChildIndex = 0; iChildIndex < pParentItem->childCount(); ++iChildIndex)
1544 if (UIMediumItem *pMediumChildItem = toMediumItem(pParentItem->child(iChildIndex)))
1545 if (UIMediumItem *pRequiredMediumChildItem = searchItem(pMediumChildItem, condition, pException))
1546 return pRequiredMediumChildItem;
1547
1548 /* Null by default: */
1549 return 0;
1550}
1551
1552/* static */
1553bool UIMediumManagerWidget::checkMediumFor(UIMediumItem *pItem, Action action)
1554{
1555 /* Make sure passed ID is valid: */
1556 AssertReturn(pItem, false);
1557
1558 switch (action)
1559 {
1560 case Action_Edit:
1561 {
1562 /* Edit means changing the description and alike; any media that is
1563 * not being read to or written from can be altered in these terms. */
1564 switch (pItem->state())
1565 {
1566 case KMediumState_NotCreated:
1567 case KMediumState_Inaccessible:
1568 case KMediumState_LockedRead:
1569 case KMediumState_LockedWrite:
1570 return false;
1571 default:
1572 break;
1573 }
1574 return true;
1575 }
1576 case Action_Copy:
1577 {
1578 return true;
1579 }
1580 case Action_Remove:
1581 {
1582 /* Removable if not attached to anything: */
1583 return !pItem->isUsed();
1584 }
1585 case Action_Release:
1586 {
1587 /* Releasable if attached but not in snapshots: */
1588 return pItem->isUsed() && !pItem->isUsedInSnapshots();
1589 }
1590
1591 default:
1592 break;
1593 }
1594
1595 AssertFailedReturn(false);
1596}
1597
1598/* static */
1599UIMediumItem* UIMediumManagerWidget::toMediumItem(QTreeWidgetItem *pItem)
1600{
1601 /* Cast passed QTreeWidgetItem to UIMediumItem if possible: */
1602 return pItem && pItem->type() == QITreeWidgetItem::ItemType ? static_cast<UIMediumItem*>(pItem) : 0;
1603}
1604
1605
1606/*********************************************************************************************************************************
1607* Class UIMediumManagerFactory implementation. *
1608*********************************************************************************************************************************/
1609
1610UIMediumManagerFactory::UIMediumManagerFactory(UIActionPool *pActionPool /* = 0 */)
1611 : m_pActionPool(pActionPool)
1612{
1613}
1614
1615void UIMediumManagerFactory::create(QIManagerDialog *&pDialog, QWidget *pCenterWidget)
1616{
1617 pDialog = new UIMediumManager(pCenterWidget, m_pActionPool);
1618}
1619
1620
1621/*********************************************************************************************************************************
1622* Class UIMediumManager implementation. *
1623*********************************************************************************************************************************/
1624
1625UIMediumManager::UIMediumManager(QWidget *pCenterWidget, UIActionPool *pActionPool)
1626 : QIWithRetranslateUI<QIManagerDialog>(pCenterWidget)
1627 , m_pActionPool(pActionPool)
1628 , m_pProgressBar(0)
1629{
1630}
1631
1632void UIMediumManager::sltHandleButtonBoxClick(QAbstractButton *pButton)
1633{
1634 /* Disable buttons first of all: */
1635 button(ButtonType_Reset)->setEnabled(false);
1636 button(ButtonType_Apply)->setEnabled(false);
1637
1638 /* Compare with known buttons: */
1639 if (pButton == button(ButtonType_Reset))
1640 emit sigDataChangeRejected();
1641 else
1642 if (pButton == button(ButtonType_Apply))
1643 emit sigDataChangeAccepted();
1644}
1645
1646void UIMediumManager::retranslateUi()
1647{
1648 /* Translate window title: */
1649 setWindowTitle(tr("Virtual Media Manager"));
1650
1651 /* Translate buttons: */
1652 button(ButtonType_Reset)->setText(tr("Reset"));
1653 button(ButtonType_Apply)->setText(tr("Apply"));
1654 button(ButtonType_Close)->setText(tr("Close"));
1655 button(ButtonType_Reset)->setStatusTip(tr("Reset changes in current medium details"));
1656 button(ButtonType_Apply)->setStatusTip(tr("Apply changes in current medium details"));
1657 button(ButtonType_Close)->setStatusTip(tr("Close dialog without saving"));
1658 button(ButtonType_Reset)->setShortcut(QString("Ctrl+Backspace"));
1659 button(ButtonType_Apply)->setShortcut(QString("Ctrl+Return"));
1660 button(ButtonType_Close)->setShortcut(Qt::Key_Escape);
1661 button(ButtonType_Reset)->setToolTip(tr("Reset Changes (%1)").arg(button(ButtonType_Reset)->shortcut().toString()));
1662 button(ButtonType_Apply)->setToolTip(tr("Apply Changes (%1)").arg(button(ButtonType_Apply)->shortcut().toString()));
1663 button(ButtonType_Close)->setToolTip(tr("Close Window (%1)").arg(button(ButtonType_Close)->shortcut().toString()));
1664}
1665
1666void UIMediumManager::configure()
1667{
1668 /* Apply window icons: */
1669 setWindowIcon(UIIconPool::iconSetFull(":/media_manager_32px.png", ":/media_manager_16px.png"));
1670}
1671
1672void UIMediumManager::configureCentralWidget()
1673{
1674 /* Create widget: */
1675 UIMediumManagerWidget *pWidget = new UIMediumManagerWidget(EmbedTo_Dialog, m_pActionPool, true, this);
1676 AssertPtrReturnVoid(pWidget);
1677 {
1678 /* Configure widget: */
1679 setWidget(pWidget);
1680 setWidgetMenu(pWidget->menu());
1681#ifdef VBOX_WS_MAC
1682 setWidgetToolbar(pWidget->toolbar());
1683#endif
1684 connect(this, &UIMediumManager::sigDataChangeRejected,
1685 pWidget, &UIMediumManagerWidget::sltResetMediumDetailsChanges);
1686 connect(this, &UIMediumManager::sigDataChangeAccepted,
1687 pWidget, &UIMediumManagerWidget::sltApplyMediumDetailsChanges);
1688
1689 /* Add into layout: */
1690 centralWidget()->layout()->addWidget(pWidget);
1691 }
1692}
1693
1694void UIMediumManager::configureButtonBox()
1695{
1696 /* Configure button-box: */
1697 connect(widget(), &UIMediumManagerWidget::sigMediumDetailsVisibilityChanged,
1698 button(ButtonType_Apply), &QPushButton::setVisible);
1699 connect(widget(), &UIMediumManagerWidget::sigMediumDetailsVisibilityChanged,
1700 button(ButtonType_Reset), &QPushButton::setVisible);
1701 connect(widget(), &UIMediumManagerWidget::sigAcceptAllowed,
1702 button(ButtonType_Apply), &QPushButton::setEnabled);
1703 connect(widget(), &UIMediumManagerWidget::sigRejectAllowed,
1704 button(ButtonType_Reset), &QPushButton::setEnabled);
1705 connect(buttonBox(), &QIDialogButtonBox::clicked,
1706 this, &UIMediumManager::sltHandleButtonBoxClick);
1707 // WORKAROUND:
1708 // Since we connected signals later than extra-data loaded
1709 // for signals above, we should handle that stuff here again:
1710 button(ButtonType_Apply)->setVisible(gEDataManager->virtualMediaManagerDetailsExpanded());
1711 button(ButtonType_Reset)->setVisible(gEDataManager->virtualMediaManagerDetailsExpanded());
1712
1713 /* Create progress-bar: */
1714 m_pProgressBar = new UIEnumerationProgressBar;
1715 AssertPtrReturnVoid(m_pProgressBar);
1716 {
1717 /* Configure progress-bar: */
1718 m_pProgressBar->hide();
1719 /* Add progress-bar into button-box layout: */
1720 buttonBox()->addExtraWidget(m_pProgressBar);
1721 /* Notify widget it has progress-bar: */
1722 widget()->setProgressBar(m_pProgressBar);
1723 }
1724}
1725
1726void UIMediumManager::finalize()
1727{
1728 /* Apply language settings: */
1729 retranslateUi();
1730}
1731
1732UIMediumManagerWidget *UIMediumManager::widget()
1733{
1734 return qobject_cast<UIMediumManagerWidget*>(QIManagerDialog::widget());
1735}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use