VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp

Last change on this file was 106061, checked in by vboxsync, 3 weeks ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 76.0 KB
Line 
1/* $Id: UIChooserModel.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIChooserModel class implementation.
4 */
5
6/*
7 * Copyright (C) 2012-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QDrag>
30#include <QGraphicsScene>
31#include <QGraphicsSceneContextMenuEvent>
32#include <QGraphicsView>
33#include <QScrollBar>
34#include <QTimer>
35
36/* GUI includes: */
37#include "QIMessageBox.h"
38#include "UIActionPoolManager.h"
39#include "UIChooser.h"
40#include "UIChooserHandlerMouse.h"
41#include "UIChooserHandlerKeyboard.h"
42#include "UIChooserItemGroup.h"
43#include "UIChooserItemGlobal.h"
44#include "UIChooserItemMachine.h"
45#include "UIChooserModel.h"
46#include "UIChooserNode.h"
47#include "UIChooserNodeGroup.h"
48#include "UIChooserNodeGlobal.h"
49#include "UIChooserNodeMachine.h"
50#include "UIChooserView.h"
51#include "UICloudNetworkingStuff.h"
52#include "UIExtraDataManager.h"
53#include "UIMessageCenter.h"
54#include "UIModalWindowManager.h"
55#include "UINotificationCenter.h"
56#include "UIVirtualBoxManagerWidget.h"
57#include "UIVirtualMachineItemCloud.h"
58#include "UIVirtualMachineItemLocal.h"
59
60/* COM includes: */
61#include "CExtPack.h"
62#include "CExtPackManager.h"
63
64/* Type defs: */
65typedef QSet<QString> UIStringSet;
66
67
68UIChooserModel::UIChooserModel(UIChooser *pParent, UIActionPool *pActionPool)
69 : UIChooserAbstractModel(pParent)
70 , m_pActionPool(pActionPool)
71 , m_pScene(0)
72 , m_pMouseHandler(0)
73 , m_pKeyboardHandler(0)
74 , m_fSelectionSaveAllowed(false)
75 , m_iCurrentSearchResultIndex(-1)
76 , m_iScrollingTokenSize(30)
77 , m_fIsScrollingInProgress(false)
78 , m_iGlobalItemHeightHint(0)
79 , m_pTimerCloudProfileUpdate(0)
80{
81 prepare();
82}
83
84UIChooserModel::~UIChooserModel()
85{
86 cleanup();
87}
88
89void UIChooserModel::init()
90{
91 /* Call to base-class: */
92 UIChooserAbstractModel::init();
93
94 /* Build tree for main root: */
95 buildTreeForMainRoot();
96 /* Load settings: */
97 loadSettings();
98}
99
100UIActionPool *UIChooserModel::actionPool() const
101{
102 return m_pActionPool;
103}
104
105QGraphicsScene *UIChooserModel::scene() const
106{
107 return m_pScene;
108}
109
110UIChooserView *UIChooserModel::view() const
111{
112 return scene() && !scene()->views().isEmpty() ? qobject_cast<UIChooserView*>(scene()->views().first()) : 0;
113}
114
115QPaintDevice *UIChooserModel::paintDevice() const
116{
117 return scene() && !scene()->views().isEmpty() ? scene()->views().first() : 0;
118}
119
120QGraphicsItem *UIChooserModel::itemAt(const QPointF &position, const QTransform &deviceTransform /* = QTransform() */) const
121{
122 return scene() ? scene()->itemAt(position, deviceTransform) : 0;
123}
124
125void UIChooserModel::handleToolButtonClick(UIChooserItem *pItem)
126{
127 emit sigToolMenuRequested(pItem->mapToScene(QPointF(pItem->size().width(), 0)).toPoint(),
128 pItem->type() == UIChooserNodeType_Machine ? pItem->toMachineItem()->cache() : 0);
129}
130
131void UIChooserModel::handlePinButtonClick(UIChooserItem *pItem)
132{
133 switch (pItem->type())
134 {
135 case UIChooserNodeType_Global:
136 pItem->setFavorite(!pItem->isFavorite());
137 break;
138 default:
139 break;
140 }
141}
142
143void UIChooserModel::setSelectedItems(const QList<UIChooserItem*> &items)
144{
145 /* Is there something changed? */
146 if (m_selectedItems == items)
147 return;
148
149 /* Remember old selected-item list: */
150 const QList<UIChooserItem*> oldCurrentItems = m_selectedItems;
151
152 /* Clear current selected-item list: */
153 m_selectedItems.clear();
154
155 /* Iterate over all the passed items: */
156 foreach (UIChooserItem *pItem, items)
157 {
158 /* Add item to current selected-item list if navigation list contains it: */
159 if (pItem && navigationItems().contains(pItem))
160 m_selectedItems << pItem;
161 else
162 AssertMsgFailed(("Passed item is not in navigation list!"));
163 }
164
165 /* Make sure selection list is never empty if current-item present: */
166 if (m_selectedItems.isEmpty() && currentItem() && navigationItems().contains(currentItem()))
167 m_selectedItems << currentItem();
168
169 /* Is there something really changed? */
170 if (oldCurrentItems == m_selectedItems)
171 return;
172
173 /* Update all the old items (they are no longer selected): */
174 foreach (UIChooserItem *pItem, oldCurrentItems)
175 {
176 pItem->setSelected(false);
177 pItem->update();
178 }
179 /* Update all the new items (they are selected now): */
180 foreach (UIChooserItem *pItem, m_selectedItems)
181 {
182 pItem->setSelected(true);
183 pItem->update();
184 }
185
186 /* Should the selection changes be saved? */
187 if (m_fSelectionSaveAllowed)
188 {
189 /* Acquire first selected item: */
190 UIChooserItem *pFirstSelectedItem = m_selectedItems.value(0);
191 /* If this item is of machine type: */
192 if ( pFirstSelectedItem
193 && pFirstSelectedItem->type() == UIChooserNodeType_Machine)
194 {
195 /* Cast to machine item: */
196 UIChooserItemMachine *pMachineItem = pFirstSelectedItem->toMachineItem();
197 /* If this machine item is of cloud type =>
198 * Choose the parent (profile) group item as the last one selected: */
199 if ( pMachineItem
200 && ( pMachineItem->cacheType() == UIVirtualMachineItemType_CloudFake
201 || pMachineItem->cacheType() == UIVirtualMachineItemType_CloudReal))
202 pFirstSelectedItem = pMachineItem->parentItem();
203 }
204 /* Save last selected-item: */
205 gEDataManager->setSelectorWindowLastItemChosen(pFirstSelectedItem ? pFirstSelectedItem->definition() : QString());
206 }
207
208 /* Notify about selection changes: */
209 emit sigSelectionChanged();
210}
211
212void UIChooserModel::setSelectedItem(UIChooserItem *pItem)
213{
214 /* Call for wrapper above: */
215 QList<UIChooserItem*> items;
216 if (pItem)
217 items << pItem;
218 setSelectedItems(items);
219
220 /* Make selected-item current one as well: */
221 setCurrentItem(firstSelectedItem());
222}
223
224void UIChooserModel::setSelectedItem(const QString &strDefinition)
225{
226 /* Search an item by definition: */
227 UIChooserItem *pItem = searchItemByDefinition(strDefinition);
228
229 /* Make sure found item is in navigation list: */
230 if (!pItem || !navigationItems().contains(pItem))
231 return;
232
233 /* Call for wrapper above: */
234 setSelectedItem(pItem);
235}
236
237void UIChooserModel::clearSelectedItems()
238{
239 /* Call for wrapper above: */
240 setSelectedItem(0);
241}
242
243const QList<UIChooserItem*> &UIChooserModel::selectedItems() const
244{
245 return m_selectedItems;
246}
247
248void UIChooserModel::addToSelectedItems(UIChooserItem *pItem)
249{
250 /* Prepare updated list: */
251 QList<UIChooserItem*> list(selectedItems());
252 list << pItem;
253 /* Call for wrapper above: */
254 setSelectedItems(list);
255}
256
257void UIChooserModel::removeFromSelectedItems(UIChooserItem *pItem)
258{
259 /* Prepare updated list: */
260 QList<UIChooserItem*> list(selectedItems());
261 list.removeAll(pItem);
262 /* Call for wrapper above: */
263 setSelectedItems(list);
264}
265
266UIChooserItem *UIChooserModel::firstSelectedItem() const
267{
268 /* Return first of selected-items, if any: */
269 return selectedItems().value(0);
270}
271
272UIVirtualMachineItem *UIChooserModel::firstSelectedMachineItem() const
273{
274 /* Return first machine-item of the selected-item: */
275 return firstSelectedItem()
276 && firstSelectedItem()->firstMachineItem()
277 && firstSelectedItem()->firstMachineItem()->toMachineItem()
278 ? firstSelectedItem()->firstMachineItem()->toMachineItem()->cache()
279 : 0;
280}
281
282QList<UIVirtualMachineItem*> UIChooserModel::selectedMachineItems() const
283{
284 /* Gather list of selected unique machine-items: */
285 QList<UIChooserItemMachine*> currentMachineItemList;
286 UIChooserItemMachine::enumerateMachineItems(selectedItems(), currentMachineItemList,
287 UIChooserItemMachineEnumerationFlag_Unique);
288
289 /* Reintegrate machine-items into valid format: */
290 QList<UIVirtualMachineItem*> currentMachineList;
291 foreach (UIChooserItemMachine *pItem, currentMachineItemList)
292 currentMachineList << pItem->cache();
293 return currentMachineList;
294}
295
296bool UIChooserModel::isGroupItemSelected() const
297{
298 return firstSelectedItem() && firstSelectedItem()->type() == UIChooserNodeType_Group;
299}
300
301bool UIChooserModel::isGlobalItemSelected() const
302{
303 return firstSelectedItem() && firstSelectedItem()->type() == UIChooserNodeType_Global;
304}
305
306bool UIChooserModel::isMachineItemSelected() const
307{
308 return firstSelectedItem() && firstSelectedItem()->type() == UIChooserNodeType_Machine;
309}
310
311bool UIChooserModel::isLocalMachineItemSelected() const
312{
313 return isMachineItemSelected()
314 && firstSelectedItem()->toMachineItem()->cacheType() == UIVirtualMachineItemType_Local;
315}
316
317bool UIChooserModel::isCloudMachineItemSelected() const
318{
319 return isMachineItemSelected()
320 && firstSelectedItem()->toMachineItem()->cacheType() == UIVirtualMachineItemType_CloudReal;
321}
322
323bool UIChooserModel::isSingleGroupSelected() const
324{
325 return selectedItems().size() == 1
326 && firstSelectedItem()->type() == UIChooserNodeType_Group;
327}
328
329bool UIChooserModel::isSingleLocalGroupSelected() const
330{
331 return isSingleGroupSelected()
332 && firstSelectedItem()->toGroupItem()->groupType() == UIChooserNodeGroupType_Local;
333}
334
335bool UIChooserModel::isSingleCloudProviderGroupSelected() const
336{
337 return isSingleGroupSelected()
338 && firstSelectedItem()->toGroupItem()->groupType() == UIChooserNodeGroupType_Provider;
339}
340
341bool UIChooserModel::isSingleCloudProfileGroupSelected() const
342{
343 return isSingleGroupSelected()
344 && firstSelectedItem()->toGroupItem()->groupType() == UIChooserNodeGroupType_Profile;
345}
346
347bool UIChooserModel::isAllItemsOfOneGroupSelected() const
348{
349 /* Make sure at least one item selected: */
350 if (selectedItems().isEmpty())
351 return false;
352
353 /* Determine the parent group of the first item: */
354 UIChooserItem *pFirstParent = firstSelectedItem()->parentItem();
355
356 /* Make sure this parent is not main root-item: */
357 if (pFirstParent == root())
358 return false;
359
360 /* Enumerate selected-item set: */
361 QSet<UIChooserItem*> currentItemSet;
362 foreach (UIChooserItem *pCurrentItem, selectedItems())
363 currentItemSet << pCurrentItem;
364
365 /* Enumerate first parent children set: */
366 QSet<UIChooserItem*> firstParentItemSet;
367 foreach (UIChooserItem *pFirstParentItem, pFirstParent->items())
368 firstParentItemSet << pFirstParentItem;
369
370 /* Check if both sets contains the same: */
371 return currentItemSet == firstParentItemSet;
372}
373
374QString UIChooserModel::fullGroupName() const
375{
376 return isSingleGroupSelected() ? firstSelectedItem()->fullName() : firstSelectedItem()->parentItem()->fullName();
377}
378
379UIChooserItem *UIChooserModel::findClosestUnselectedItem() const
380{
381 /* Take the current-item (if any) as a starting point
382 * and find the closest non-selected-item. */
383 UIChooserItem *pItem = currentItem();
384 if (!pItem)
385 pItem = firstSelectedItem();
386 if (pItem)
387 {
388 int idxBefore = navigationItems().indexOf(pItem) - 1;
389 int idxAfter = idxBefore + 2;
390 while (idxBefore >= 0 || idxAfter < navigationItems().size())
391 {
392 if (idxAfter < navigationItems().size())
393 {
394 pItem = navigationItems().at(idxAfter);
395 if ( !selectedItems().contains(pItem)
396 && ( pItem->type() == UIChooserNodeType_Machine
397 || pItem->type() == UIChooserNodeType_Global))
398 return pItem;
399 ++idxAfter;
400 }
401 if (idxBefore >= 0)
402 {
403 pItem = navigationItems().at(idxBefore);
404 if ( !selectedItems().contains(pItem)
405 && ( pItem->type() == UIChooserNodeType_Machine
406 || pItem->type() == UIChooserNodeType_Global))
407 return pItem;
408 --idxBefore;
409 }
410 }
411 }
412 return 0;
413}
414
415void UIChooserModel::makeSureNoItemWithCertainIdSelected(const QUuid &uId)
416{
417 /* Look for all nodes with passed uId: */
418 QList<UIChooserNode*> matchedNodes;
419 invisibleRoot()->searchForNodes(uId.toString(),
420 UIChooserItemSearchFlag_Machine |
421 UIChooserItemSearchFlag_ExactId,
422 matchedNodes);
423
424 /* Compose a set of items with passed uId: */
425 QSet<UIChooserItem*> matchedItems;
426 foreach (UIChooserNode *pNode, matchedNodes)
427 if (pNode && pNode->item())
428 matchedItems << pNode->item();
429
430 /* If we have at least one of those items currently selected: */
431 const QList<UIChooserItem*> selectedItemsList = selectedItems();
432 const QSet<UIChooserItem*> selectedItemsSet(selectedItemsList.begin(), selectedItemsList.end());
433 if (selectedItemsSet.intersects(matchedItems))
434 setSelectedItem(findClosestUnselectedItem());
435
436 /* If global item is currently chosen, selection should be invalidated: */
437 if (firstSelectedItem() && firstSelectedItem()->type() == UIChooserNodeType_Global)
438 emit sigSelectionInvalidated();
439}
440
441void UIChooserModel::makeSureAtLeastOneItemSelected()
442{
443 /* If we have no item selected but
444 * at least one in the navigation list (global item): */
445 if (!firstSelectedItem() && !navigationItems().isEmpty())
446 {
447 /* We are choosing it, selection should be invalidated: */
448 setSelectedItem(navigationItems().first());
449 emit sigSelectionInvalidated();
450 }
451}
452
453void UIChooserModel::setCurrentItem(UIChooserItem *pItem)
454{
455 /* Make sure real focus unset: */
456 clearRealFocus();
457
458 /* Is there something changed? */
459 if (m_pCurrentItem == pItem)
460 return;
461
462 /* Remember old current-item: */
463 UIChooserItem *pOldCurrentItem = m_pCurrentItem;
464
465 /* Set new current-item: */
466 m_pCurrentItem = pItem;
467
468 /* Disconnect old current-item (if any): */
469 if (pOldCurrentItem)
470 disconnect(pOldCurrentItem, &UIChooserItem::destroyed, this, &UIChooserModel::sltCurrentItemDestroyed);
471 /* Connect new current-item (if any): */
472 if (m_pCurrentItem)
473 connect(m_pCurrentItem.data(), &UIChooserItem::destroyed, this, &UIChooserModel::sltCurrentItemDestroyed);
474
475 /* If dialog is visible and item exists => make it visible as well: */
476 if (view() && view()->window() && root())
477 if (view()->window()->isVisible() && pItem)
478 root()->toGroupItem()->makeSureItemIsVisible(pItem);
479
480 /* Make sure selection list is never empty if current-item present: */
481 if (!firstSelectedItem() && m_pCurrentItem)
482 setSelectedItem(m_pCurrentItem);
483}
484
485UIChooserItem *UIChooserModel::currentItem() const
486{
487 return m_pCurrentItem;
488}
489
490const QList<UIChooserItem*> &UIChooserModel::navigationItems() const
491{
492 return m_navigationItems;
493}
494
495void UIChooserModel::removeFromNavigationItems(UIChooserItem *pItem)
496{
497 AssertMsg(pItem, ("Passed item is invalid!"));
498 m_navigationItems.removeAll(pItem);
499}
500
501void UIChooserModel::updateNavigationItemList()
502{
503 m_navigationItems.clear();
504 m_navigationItems = createNavigationItemList(root());
505}
506
507UIChooserItem *UIChooserModel::searchItemByDefinition(const QString &strDefinition) const
508{
509 /* Null if empty definition passed: */
510 if (strDefinition.isEmpty())
511 return 0;
512
513 /* Parse definition: */
514 UIChooserItem *pItem = 0;
515 const QString strItemType = strDefinition.section('=', 0, 0);
516 const QString strItemDescriptor = strDefinition.section('=', 1, -1);
517 /* Its a local group-item definition? */
518 if (strItemType == prefixToString(UIChooserNodeDataPrefixType_Local))
519 {
520 /* Search for group-item with passed descriptor (name): */
521 pItem = root()->searchForItem(strItemDescriptor,
522 UIChooserItemSearchFlag_LocalGroup |
523 UIChooserItemSearchFlag_FullName);
524 }
525 /* Its a provider group-item definition? */
526 else if (strItemType == prefixToString(UIChooserNodeDataPrefixType_Provider))
527 {
528 /* Search for group-item with passed descriptor (name): */
529 pItem = root()->searchForItem(strItemDescriptor,
530 UIChooserItemSearchFlag_CloudProvider |
531 UIChooserItemSearchFlag_FullName);
532 }
533 /* Its a profile group-item definition? */
534 else if (strItemType == prefixToString(UIChooserNodeDataPrefixType_Profile))
535 {
536 /* Search for group-item with passed descriptor (name): */
537 pItem = root()->searchForItem(strItemDescriptor,
538 UIChooserItemSearchFlag_CloudProfile |
539 UIChooserItemSearchFlag_FullName);
540 }
541 /* Its a global-item definition? */
542 else if (strItemType == prefixToString(UIChooserNodeDataPrefixType_Global))
543 {
544 /* Search for global-item with required name: */
545 pItem = root()->searchForItem(strItemDescriptor,
546 UIChooserItemSearchFlag_Global |
547 UIChooserItemSearchFlag_ExactName);
548 }
549 /* Its a machine-item definition? */
550 else if (strItemType == prefixToString(UIChooserNodeDataPrefixType_Machine))
551 {
552 /* Search for machine-item with required ID: */
553 pItem = root()->searchForItem(strItemDescriptor,
554 UIChooserItemSearchFlag_Machine |
555 UIChooserItemSearchFlag_ExactId);
556 }
557
558 /* Return result: */
559 return pItem;
560}
561
562void UIChooserModel::performSearch(const QString &strSearchTerm, int iSearchFlags)
563{
564 /* Call to base-class: */
565 UIChooserAbstractModel::performSearch(strSearchTerm, iSearchFlags);
566
567 /* Select 1st found item: */
568 selectSearchResult(true);
569}
570
571QList<UIChooserNode*> UIChooserModel::resetSearch()
572{
573 /* Reset search result index: */
574 m_iCurrentSearchResultIndex = -1;
575
576 /* Call to base-class: */
577 return UIChooserAbstractModel::resetSearch();
578}
579
580void UIChooserModel::selectSearchResult(bool fIsNext)
581{
582 /* If nothing was found: */
583 if (searchResult().isEmpty())
584 {
585 /* Reset search result index: */
586 m_iCurrentSearchResultIndex = -1;
587 }
588 /* If something was found: */
589 else
590 {
591 /* Advance index forward: */
592 if (fIsNext)
593 {
594 if (++m_iCurrentSearchResultIndex >= searchResult().size())
595 m_iCurrentSearchResultIndex = 0;
596 }
597 /* Advance index backward: */
598 else
599 {
600 if (--m_iCurrentSearchResultIndex < 0)
601 m_iCurrentSearchResultIndex = searchResult().size() - 1;
602 }
603
604 /* If found item exists: */
605 if (searchResult().at(m_iCurrentSearchResultIndex))
606 {
607 /* Select curresponding found item, make sure it's visible, scroll if necessary: */
608 UIChooserItem *pItem = searchResult().at(m_iCurrentSearchResultIndex)->item();
609 if (pItem)
610 {
611 pItem->makeSureItsVisible();
612 setSelectedItem(pItem);
613 }
614 }
615 }
616
617 /* Update the search widget's match count(s): */
618 if (view())
619 view()->setSearchResultsCount(searchResult().size(), m_iCurrentSearchResultIndex);
620}
621
622void UIChooserModel::setSearchWidgetVisible(bool fVisible)
623{
624 if (view())
625 view()->setSearchWidgetVisible(fVisible);
626}
627
628UIChooserItem *UIChooserModel::root() const
629{
630 return m_pRoot.data();
631}
632
633void UIChooserModel::startEditingSelectedGroupItemName()
634{
635 /* Only for single selected local group: */
636 if (!isSingleLocalGroupSelected())
637 return;
638
639 /* Start editing first selected item name: */
640 firstSelectedItem()->startEditing();
641}
642
643void UIChooserModel::disbandSelectedGroupItem()
644{
645 /* Only for single selected local group: */
646 if (!isSingleLocalGroupSelected())
647 return;
648
649 /* Check if we have collisions between disbandable group children and their potential siblings: */
650 UIChooserItem *pCurrentItem = currentItem();
651 UIChooserNode *pCurrentNode = pCurrentItem->node();
652 UIChooserItem *pParentItem = pCurrentItem->parentItem();
653 UIChooserNode *pParentNode = pParentItem->node();
654 QList<UIChooserNode*> childrenToBeRenamed;
655 foreach (UIChooserNode *pChildNode, pCurrentNode->nodes())
656 {
657 /* Acquire disbandable group child name to check for collision with group siblings: */
658 const QString strChildName = pChildNode->name();
659 UIChooserNode *pCollisionSibling = 0;
660 /* And then compare this child name with all the sibling names: */
661 foreach (UIChooserNode *pSiblingNode, pParentNode->nodes())
662 {
663 /* There can't be a collision between local child and cloud provider sibling: */
664 if ( pSiblingNode->type() == UIChooserNodeType_Group
665 && pSiblingNode->toGroupNode()->groupType() == UIChooserNodeGroupType_Provider)
666 continue;
667 /* If sibling isn't disbandable group itself and has name similar to one of group children: */
668 if (pSiblingNode != pCurrentNode && pSiblingNode->name() == strChildName)
669 {
670 /* We have a collision sibling: */
671 pCollisionSibling = pSiblingNode;
672 break;
673 }
674 }
675 /* If there is a collision sibling: */
676 if (pCollisionSibling)
677 {
678 switch (pChildNode->type())
679 {
680 /* We can't resolve collision automatically for VMs: */
681 case UIChooserNodeType_Machine:
682 {
683 UINotificationMessage::cannotResolveCollisionAutomatically(strChildName, pParentNode->name());
684 return;
685 }
686 /* But we can do it for VM groups: */
687 case UIChooserNodeType_Group:
688 {
689 if (!msgCenter().confirmAutomaticCollisionResolve(strChildName, pParentNode->name()))
690 return;
691 childrenToBeRenamed << pChildNode;
692 break;
693 }
694 default:
695 break;
696 }
697 }
698 }
699
700 /* Copy all the children into our parent: */
701 QList<UIChooserItem*> ungroupedItems;
702 foreach (UIChooserNode *pNode, pCurrentNode->nodes())
703 {
704 switch (pNode->type())
705 {
706 case UIChooserNodeType_Group:
707 {
708 UIChooserNodeGroup *pGroupNode = new UIChooserNodeGroup(pParentNode,
709 pParentNode->nodes().size(),
710 pNode->toGroupNode());
711 UIChooserItemGroup *pGroupItem = new UIChooserItemGroup(pParentItem, pGroupNode);
712 if (childrenToBeRenamed.contains(pNode))
713 pGroupNode->setName(uniqueGroupName(pParentNode));
714 ungroupedItems << pGroupItem;
715 break;
716 }
717 case UIChooserNodeType_Machine:
718 {
719 UIChooserNodeMachine *pMachineNode = new UIChooserNodeMachine(pParentNode,
720 pParentNode->nodes().size(),
721 pNode->toMachineNode());
722 UIChooserItemMachine *pMachineItem = new UIChooserItemMachine(pParentItem, pMachineNode);
723 ungroupedItems << pMachineItem;
724 break;
725 }
726 default:
727 break;
728 }
729 }
730
731 /* Delete current group: */
732 delete pCurrentNode;
733
734 /* And update model: */
735 updateTreeForMainRoot();
736
737 /* Choose ungrouped items if present: */
738 if (!ungroupedItems.isEmpty())
739 {
740 setSelectedItems(ungroupedItems);
741 setCurrentItem(firstSelectedItem());
742 }
743 makeSureAtLeastOneItemSelected();
744
745 /* Save groups finally: */
746 saveGroups();
747}
748
749void UIChooserModel::removeSelectedMachineItems()
750{
751 /* Enumerate all the selected machine-items: */
752 QList<UIChooserItemMachine*> selectedMachineItemList;
753 UIChooserItemMachine::enumerateMachineItems(selectedItems(), selectedMachineItemList);
754 /* Enumerate all the existing machine-items: */
755 QList<UIChooserItemMachine*> existingMachineItemList;
756 UIChooserItemMachine::enumerateMachineItems(root()->items(), existingMachineItemList);
757
758 /* Prepare arrays: */
759 QMap<QUuid, bool> verdicts;
760 QList<UIChooserItemMachine*> localMachineItemsToRemove;
761 QList<CMachine> localMachinesToUnregister;
762 QList<UIChooserItemMachine*> cloudMachineItemsToUnregister;
763
764 /* For each selected machine-item: */
765 foreach (UIChooserItemMachine *pMachineItem, selectedMachineItemList)
766 {
767 /* Get machine-item id: */
768 AssertPtrReturnVoid(pMachineItem);
769 const QUuid uId = pMachineItem->id();
770
771 /* We already decided for that machine? */
772 if (verdicts.contains(uId))
773 {
774 /* To remove similar machine items? */
775 if (!verdicts.value(uId))
776 localMachineItemsToRemove << pMachineItem;
777 continue;
778 }
779
780 /* Selected copy count: */
781 int iSelectedCopyCount = 0;
782 foreach (UIChooserItemMachine *pSelectedItem, selectedMachineItemList)
783 {
784 AssertPtrReturnVoid(pSelectedItem);
785 if (pSelectedItem->id() == uId)
786 ++iSelectedCopyCount;
787 }
788 /* Existing copy count: */
789 int iExistingCopyCount = 0;
790 foreach (UIChooserItemMachine *pExistingItem, existingMachineItemList)
791 {
792 AssertPtrReturnVoid(pExistingItem);
793 if (pExistingItem->id() == uId)
794 ++iExistingCopyCount;
795 }
796 /* If selected copy count equal to existing copy count,
797 * we will propose ro unregister machine fully else
798 * we will just propose to remove selected-items: */
799 const bool fVerdict = iSelectedCopyCount == iExistingCopyCount;
800 verdicts.insert(uId, fVerdict);
801 if (fVerdict)
802 {
803 if (pMachineItem->cacheType() == UIVirtualMachineItemType_Local)
804 localMachinesToUnregister.append(pMachineItem->cache()->toLocal()->machine());
805 else if (pMachineItem->cacheType() == UIVirtualMachineItemType_CloudReal)
806 cloudMachineItemsToUnregister.append(pMachineItem);
807 }
808 else
809 localMachineItemsToRemove << pMachineItem;
810 }
811
812 /* If we have something to remove: */
813 if (!localMachineItemsToRemove.isEmpty())
814 removeLocalMachineItems(localMachineItemsToRemove);
815 /* If we have something local to unregister: */
816 if (!localMachinesToUnregister.isEmpty())
817 unregisterLocalMachines(localMachinesToUnregister);
818 /* If we have something cloud to unregister: */
819 if (!cloudMachineItemsToUnregister.isEmpty())
820 unregisterCloudMachineItems(cloudMachineItemsToUnregister);
821}
822
823void UIChooserModel::moveSelectedMachineItemsToGroupItem(const QString &strName /* = QString() */)
824{
825 /* Prepare target group pointers: */
826 UIChooserNodeGroup *pTargetGroupNode = 0;
827 UIChooserItemGroup *pTargetGroupItem = 0;
828 if (strName.isNull())
829 {
830 /* Create new group node in the current root: */
831 pTargetGroupNode = new UIChooserNodeGroup(invisibleRoot(),
832 invisibleRoot()->nodes().size() /* position */,
833 QUuid() /* id */,
834 uniqueGroupName(invisibleRoot()),
835 UIChooserNodeGroupType_Local,
836 true /* opened */);
837 pTargetGroupItem = new UIChooserItemGroup(root(), pTargetGroupNode);
838 }
839 else
840 {
841 /* Search for existing group with certain name: */
842 UIChooserItem *pTargetItem = root()->searchForItem(strName,
843 UIChooserItemSearchFlag_LocalGroup |
844 UIChooserItemSearchFlag_FullName);
845 AssertPtrReturnVoid(pTargetItem);
846 pTargetGroupItem = pTargetItem->toGroupItem();
847 UIChooserNode *pTargetNode = pTargetItem->node();
848 AssertPtrReturnVoid(pTargetNode);
849 pTargetGroupNode = pTargetNode->toGroupNode();
850 }
851 AssertPtrReturnVoid(pTargetGroupNode);
852 AssertPtrReturnVoid(pTargetGroupItem);
853
854 /* For each of currently selected-items: */
855 QStringList busyGroupNames;
856 QStringList busyMachineNames;
857 QList<UIChooserItem*> copiedItems;
858 foreach (UIChooserItem *pItem, selectedItems())
859 {
860 /* For each of known types: */
861 switch (pItem->type())
862 {
863 case UIChooserNodeType_Group:
864 {
865 /* Avoid name collisions: */
866 if (busyGroupNames.contains(pItem->name()))
867 break;
868 /* Add name to busy: */
869 busyGroupNames << pItem->name();
870 /* Copy or move group-item: */
871 UIChooserNodeGroup *pNewGroupSubNode = new UIChooserNodeGroup(pTargetGroupNode,
872 pTargetGroupNode->nodes().size(),
873 pItem->node()->toGroupNode());
874 copiedItems << new UIChooserItemGroup(pTargetGroupItem, pNewGroupSubNode);
875 delete pItem->node();
876 break;
877 }
878 case UIChooserNodeType_Machine:
879 {
880 /* Avoid name collisions: */
881 if (busyMachineNames.contains(pItem->name()))
882 break;
883 /* Add name to busy: */
884 busyMachineNames << pItem->name();
885 /* Copy or move machine-item: */
886 UIChooserNodeMachine *pNewMachineSubNode = new UIChooserNodeMachine(pTargetGroupNode,
887 pTargetGroupNode->nodes().size(),
888 pItem->node()->toMachineNode());
889 copiedItems << new UIChooserItemMachine(pTargetGroupItem, pNewMachineSubNode);
890 delete pItem->node();
891 break;
892 }
893 }
894 }
895
896 /* Update model: */
897 wipeOutEmptyGroups();
898 updateTreeForMainRoot();
899
900 /* Check if we can select copied items: */
901 QList<UIChooserItem*> itemsToSelect;
902 foreach (UIChooserItem *pCopiedItem, copiedItems)
903 if (navigationItems().contains(pCopiedItem))
904 itemsToSelect << pCopiedItem;
905 if (!itemsToSelect.isEmpty())
906 {
907 setSelectedItems(itemsToSelect);
908 setCurrentItem(firstSelectedItem());
909 }
910 else
911 {
912 /* Otherwise check if we can select one of our parents: */
913 UIChooserItem *pItemToSelect = pTargetGroupItem;
914 while ( !navigationItems().contains(pItemToSelect)
915 && pItemToSelect->parentItem() != root())
916 pItemToSelect = pItemToSelect->parentItem();
917 if (navigationItems().contains(pItemToSelect))
918 setSelectedItem(pItemToSelect);
919 }
920
921 /* Save groups finally: */
922 saveGroups();
923}
924
925void UIChooserModel::startOrShowSelectedItems()
926{
927 emit sigStartOrShowRequest();
928}
929
930void UIChooserModel::refreshSelectedMachineItems()
931{
932 /* Gather list of current unique inaccessible machine-items: */
933 QList<UIChooserItemMachine*> inaccessibleMachineItemList;
934 UIChooserItemMachine::enumerateMachineItems(selectedItems(), inaccessibleMachineItemList,
935 UIChooserItemMachineEnumerationFlag_Unique |
936 UIChooserItemMachineEnumerationFlag_Inaccessible);
937
938 /* Prepare item to be selected: */
939 UIChooserItem *pSelectedItem = 0;
940
941 /* For each machine-item: */
942 foreach (UIChooserItemMachine *pItem, inaccessibleMachineItemList)
943 {
944 AssertPtrReturnVoid(pItem);
945 switch (pItem->cacheType())
946 {
947 case UIVirtualMachineItemType_Local:
948 {
949 /* Recache: */
950 pItem->recache();
951
952 /* Became accessible? */
953 if (pItem->accessible())
954 {
955 /* Acquire machine ID: */
956 const QUuid uId = pItem->id();
957 /* Reload this machine: */
958 sltReloadMachine(uId);
959 /* Select first of reloaded items: */
960 if (!pSelectedItem)
961 pSelectedItem = root()->searchForItem(uId.toString(),
962 UIChooserItemSearchFlag_Machine |
963 UIChooserItemSearchFlag_ExactId);
964 }
965
966 break;
967 }
968 case UIVirtualMachineItemType_CloudFake:
969 {
970 /* Compose cloud entity key: */
971 UIChooserItem *pParent = pItem->parentItem();
972 AssertPtrReturnVoid(pParent);
973 UIChooserItem *pParentOfParent = pParent->parentItem();
974 AssertPtrReturnVoid(pParentOfParent);
975
976 /* Create read cloud machine list task: */
977 const UICloudEntityKey guiCloudProfileKey = UICloudEntityKey(pParentOfParent->name(), pParent->name());
978 createReadCloudMachineListTask(guiCloudProfileKey, true /* with refresh? */);
979
980 break;
981 }
982 case UIVirtualMachineItemType_CloudReal:
983 {
984 /* Much more simple than for local items, we are not reloading them, just refreshing: */
985 pItem->cache()->toCloud()->updateInfoAsync(false /* delayed */);
986
987 break;
988 }
989 default:
990 break;
991 }
992 }
993
994 /* Some item to be selected? */
995 if (pSelectedItem)
996 {
997 pSelectedItem->makeSureItsVisible();
998 setSelectedItem(pSelectedItem);
999 }
1000}
1001
1002void UIChooserModel::sortSelectedGroupItem()
1003{
1004 /* For single selected group, sort first selected item children: */
1005 if (isSingleGroupSelected())
1006 firstSelectedItem()->node()->sortNodes();
1007 /* Otherwise, sort first selected item neighbors: */
1008 else
1009 firstSelectedItem()->parentItem()->node()->sortNodes();
1010
1011 /* Rebuild tree for main root: */
1012 buildTreeForMainRoot(true /* preserve selection */);
1013}
1014
1015void UIChooserModel::setCurrentMachineItem(const QUuid &uId)
1016{
1017 /* Look whether we have such item at all: */
1018 UIChooserItem *pItem = root()->searchForItem(uId.toString(),
1019 UIChooserItemSearchFlag_Machine |
1020 UIChooserItemSearchFlag_ExactId);
1021
1022 /* Select item if exists: */
1023 if (pItem)
1024 setSelectedItem(pItem);
1025}
1026
1027void UIChooserModel::setCurrentGlobalItem()
1028{
1029 /* Look whether we have such item at all: */
1030 UIChooserItem *pItem = root()->searchForItem(QString(),
1031 UIChooserItemSearchFlag_Global);
1032
1033 /* Select item if exists: */
1034 if (pItem)
1035 setSelectedItem(pItem);
1036}
1037
1038void UIChooserModel::setCurrentDragObject(QDrag *pDragObject)
1039{
1040 /* Make sure real focus unset: */
1041 clearRealFocus();
1042
1043 /* Remember new drag-object: */
1044 m_pCurrentDragObject = pDragObject;
1045 connect(m_pCurrentDragObject.data(), &QDrag::destroyed,
1046 this, &UIChooserModel::sltCurrentDragObjectDestroyed);
1047}
1048
1049void UIChooserModel::lookFor(const QString &strLookupText)
1050{
1051 if (view())
1052 {
1053 view()->setSearchWidgetVisible(true);
1054 view()->appendToSearchString(strLookupText);
1055 }
1056}
1057
1058void UIChooserModel::updateLayout()
1059{
1060 /* Sanity check. This method can be called when invisible root is
1061 * temporary deleted. We should ignore request in such case. */
1062 if (!view() || !root())
1063 return;
1064
1065 /* Initialize variables: */
1066 const QSize viewportSize = view()->size();
1067 const int iViewportWidth = viewportSize.width();
1068 const int iViewportHeight = root()->minimumSizeHint().toSize().height();
1069
1070 /* Move root: */
1071 root()->setPos(0, 0);
1072 /* Resize root: */
1073 root()->resize(iViewportWidth, iViewportHeight);
1074 /* Layout root content: */
1075 root()->updateLayout();
1076}
1077
1078void UIChooserModel::setGlobalItemHeightHint(int iHint)
1079{
1080 /* Save and apply global item height hint: */
1081 m_iGlobalItemHeightHint = iHint;
1082 applyGlobalItemHeightHint();
1083}
1084
1085void UIChooserModel::sltHandleViewResized()
1086{
1087 /* Relayout: */
1088 updateLayout();
1089
1090 /* Make current item visible asynchronously: */
1091 QMetaObject::invokeMethod(this, "sltMakeSureCurrentItemVisible", Qt::QueuedConnection);
1092}
1093
1094bool UIChooserModel::eventFilter(QObject *pWatched, QEvent *pEvent)
1095{
1096 /* Process only scene events: */
1097 if (pWatched != scene())
1098 return QObject::eventFilter(pWatched, pEvent);
1099
1100 /* Process only item focused by model: */
1101 if (scene()->focusItem())
1102 return QObject::eventFilter(pWatched, pEvent);
1103
1104 /* Checking event-type: */
1105 switch (pEvent->type())
1106 {
1107 /* Keyboard handler: */
1108 case QEvent::KeyPress:
1109 return m_pKeyboardHandler->handle(static_cast<QKeyEvent*>(pEvent), UIKeyboardEventType_Press);
1110 case QEvent::KeyRelease:
1111 return m_pKeyboardHandler->handle(static_cast<QKeyEvent*>(pEvent), UIKeyboardEventType_Release);
1112 /* Mouse handler: */
1113 case QEvent::GraphicsSceneMousePress:
1114 return m_pMouseHandler->handle(static_cast<QGraphicsSceneMouseEvent*>(pEvent), UIMouseEventType_Press);
1115 case QEvent::GraphicsSceneMouseRelease:
1116 return m_pMouseHandler->handle(static_cast<QGraphicsSceneMouseEvent*>(pEvent), UIMouseEventType_Release);
1117 case QEvent::GraphicsSceneMouseDoubleClick:
1118 return m_pMouseHandler->handle(static_cast<QGraphicsSceneMouseEvent*>(pEvent), UIMouseEventType_DoubleClick);
1119 /* Context-menu handler: */
1120 case QEvent::GraphicsSceneContextMenu:
1121 return processContextMenuEvent(static_cast<QGraphicsSceneContextMenuEvent*>(pEvent));
1122 /* Drag&drop scroll-event (drag-move) handler: */
1123 case QEvent::GraphicsSceneDragMove:
1124 return processDragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(pEvent));
1125 /* Drag&drop scroll-event (drag-leave) handler: */
1126 case QEvent::GraphicsSceneDragLeave:
1127 return processDragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(pEvent));
1128 default: break; /* Shut up MSC */
1129 }
1130
1131 /* Call to base-class: */
1132 return QObject::eventFilter(pWatched, pEvent);
1133}
1134
1135void UIChooserModel::sltLocalMachineRegistrationChanged(const QUuid &uMachineId, const bool fRegistered)
1136{
1137 /* Existing VM unregistered => make sure no item with passed uMachineId is selected: */
1138 if (!fRegistered)
1139 makeSureNoItemWithCertainIdSelected(uMachineId);
1140
1141 /* Call to base-class: */
1142 UIChooserAbstractModel::sltLocalMachineRegistrationChanged(uMachineId, fRegistered);
1143
1144 /* Existing VM unregistered? */
1145 if (!fRegistered)
1146 {
1147 /* Update tree for main root: */
1148 updateTreeForMainRoot();
1149 }
1150 /* New VM registered? */
1151 else
1152 {
1153 /* Should we show this VM? */
1154 if (gEDataManager->showMachineInVirtualBoxManagerChooser(uMachineId))
1155 {
1156 /* Rebuild tree for main root: */
1157 buildTreeForMainRoot(true /* preserve selection */);
1158 /* Search for newly added item: */
1159 UIChooserItem *pNewItem = root()->searchForItem(uMachineId.toString(),
1160 UIChooserItemSearchFlag_Machine |
1161 UIChooserItemSearchFlag_ExactId);
1162 /* Select newly added item if any: */
1163 if (pNewItem)
1164 setSelectedItem(pNewItem);
1165 }
1166 }
1167}
1168
1169void UIChooserModel::sltHandleCloudProviderUninstall(const QUuid &uProviderId)
1170{
1171 /* Call to base-class: */
1172 UIChooserAbstractModel::sltHandleCloudProviderUninstall(uProviderId);
1173
1174 /* Notify about selection invalidated: */
1175 emit sigSelectionInvalidated();
1176}
1177
1178void UIChooserModel::sltReloadMachine(const QUuid &uMachineId)
1179{
1180 /* Call to base-class: */
1181 UIChooserAbstractModel::sltReloadMachine(uMachineId);
1182
1183 /* Should we show this VM? */
1184 if (gEDataManager->showMachineInVirtualBoxManagerChooser(uMachineId))
1185 {
1186 /* Rebuild tree for main root: */
1187 buildTreeForMainRoot(false /* preserve selection */);
1188 /* Select newly added item: */
1189 setSelectedItem(root()->searchForItem(uMachineId.toString(),
1190 UIChooserItemSearchFlag_Machine |
1191 UIChooserItemSearchFlag_ExactId));
1192 }
1193 makeSureAtLeastOneItemSelected();
1194
1195 /* Notify listeners about selection change: */
1196 emit sigSelectionChanged();
1197}
1198
1199void UIChooserModel::sltDetachCOM()
1200{
1201 /* Clean tree for main root: */
1202 clearTreeForMainRoot();
1203 emit sigSelectionInvalidated();
1204
1205 /* Call to base-class: */
1206 UIChooserAbstractModel::sltDetachCOM();
1207}
1208
1209void UIChooserModel::sltCloudMachineUnregistered(const QString &strProviderShortName,
1210 const QString &strProfileName,
1211 const QUuid &uId)
1212{
1213 /* Make sure no item with passed uId is selected: */
1214 makeSureNoItemWithCertainIdSelected(uId);
1215
1216 /* Call to base-class: */
1217 UIChooserAbstractModel::sltCloudMachineUnregistered(strProviderShortName, strProfileName, uId);
1218
1219 /* Rebuild tree for main root: */
1220 buildTreeForMainRoot(true /* preserve selection */);
1221}
1222
1223void UIChooserModel::sltCloudMachinesUnregistered(const QString &strProviderShortName,
1224 const QString &strProfileName,
1225 const QList<QUuid> &ids)
1226{
1227 /* Make sure no item with one of passed ids is selected: */
1228 foreach (const QUuid &uId, ids)
1229 makeSureNoItemWithCertainIdSelected(uId);
1230
1231 /* Call to base-class: */
1232 UIChooserAbstractModel::sltCloudMachinesUnregistered(strProviderShortName, strProfileName, ids);
1233
1234 /* Rebuild tree for main root: */
1235 buildTreeForMainRoot(true /* preserve selection */);
1236}
1237
1238void UIChooserModel::sltCloudMachineRegistered(const QString &strProviderShortName,
1239 const QString &strProfileName,
1240 const CCloudMachine &comMachine)
1241{
1242 /* Call to base-class: */
1243 UIChooserAbstractModel::sltCloudMachineRegistered(strProviderShortName, strProfileName, comMachine);
1244
1245 /* Rebuild tree for main root: */
1246 buildTreeForMainRoot(false /* preserve selection */);
1247
1248 /* Select newly added item: */
1249 QUuid uMachineId;
1250 if (cloudMachineId(comMachine, uMachineId))
1251 setSelectedItem(root()->searchForItem(uMachineId.toString(),
1252 UIChooserItemSearchFlag_Machine |
1253 UIChooserItemSearchFlag_ExactId));
1254}
1255
1256void UIChooserModel::sltCloudMachinesRegistered(const QString &strProviderShortName,
1257 const QString &strProfileName,
1258 const QVector<CCloudMachine> &machines)
1259{
1260 /* Call to base-class: */
1261 UIChooserAbstractModel::sltCloudMachinesRegistered(strProviderShortName, strProfileName, machines);
1262
1263 /* Rebuild tree for main root: */
1264 buildTreeForMainRoot(true /* preserve selection */);
1265}
1266
1267void UIChooserModel::sltHandleReadCloudMachineListTaskComplete()
1268{
1269 /* Call to base-class: */
1270 UIChooserAbstractModel::sltHandleReadCloudMachineListTaskComplete();
1271
1272 /* Postpone update since we have just updated: */
1273 if (m_pTimerCloudProfileUpdate)
1274 m_pTimerCloudProfileUpdate->start(10000);
1275}
1276
1277void UIChooserModel::sltHandleCloudProfileManagerCumulativeChange()
1278{
1279 /* Call to base-class: */
1280 UIChooserAbstractModel::sltHandleCloudProfileManagerCumulativeChange();
1281
1282 /* Build tree for main root: */
1283 buildTreeForMainRoot(true /* preserve selection */);
1284}
1285
1286void UIChooserModel::sltMakeSureCurrentItemVisible()
1287{
1288 root()->toGroupItem()->makeSureItemIsVisible(currentItem());
1289}
1290
1291void UIChooserModel::sltCurrentItemDestroyed()
1292{
1293 AssertMsgFailed(("Current-item destroyed!"));
1294}
1295
1296void UIChooserModel::sltStartScrolling()
1297{
1298 /* Make sure view exists: */
1299 AssertPtrReturnVoid(view());
1300
1301 /* Should we scroll? */
1302 if (!m_fIsScrollingInProgress)
1303 return;
1304
1305 /* Reset scrolling progress: */
1306 m_fIsScrollingInProgress = false;
1307
1308 /* Convert mouse position to view co-ordinates: */
1309 const QPoint mousePos = view()->mapFromGlobal(QCursor::pos());
1310 /* Mouse position is at the top of view? */
1311 if (mousePos.y() < m_iScrollingTokenSize && mousePos.y() > 0)
1312 {
1313 int iValue = mousePos.y();
1314 if (!iValue)
1315 iValue = 1;
1316 const int iDelta = m_iScrollingTokenSize / iValue;
1317 /* Backward scrolling: */
1318 root()->toGroupItem()->scrollBy(- 2 * iDelta);
1319 m_fIsScrollingInProgress = true;
1320 QTimer::singleShot(10, this, SLOT(sltStartScrolling()));
1321 }
1322 /* Mouse position is at the bottom of view? */
1323 else if (mousePos.y() > view()->height() - m_iScrollingTokenSize && mousePos.y() < view()->height())
1324 {
1325 int iValue = view()->height() - mousePos.y();
1326 if (!iValue)
1327 iValue = 1;
1328 const int iDelta = m_iScrollingTokenSize / iValue;
1329 /* Forward scrolling: */
1330 root()->toGroupItem()->scrollBy(2 * iDelta);
1331 m_fIsScrollingInProgress = true;
1332 QTimer::singleShot(10, this, SLOT(sltStartScrolling()));
1333 }
1334}
1335
1336void UIChooserModel::sltCurrentDragObjectDestroyed()
1337{
1338 root()->resetDragToken();
1339}
1340
1341void UIChooserModel::sltHandleCloudMachineRemoved(const QString &strProviderShortName,
1342 const QString &strProfileName,
1343 const QString &strName)
1344{
1345 Q_UNUSED(strName);
1346
1347 /* Update profile to make sure it has no stale instances: */
1348 const UICloudEntityKey cloudEntityKeyForProfile = UICloudEntityKey(strProviderShortName, strProfileName);
1349 createReadCloudMachineListTask(cloudEntityKeyForProfile, false /* with refresh? */);
1350}
1351
1352void UIChooserModel::sltUpdateSelectedCloudProfiles()
1353{
1354 /* Postpone update since we are already working on it: */
1355 if (m_pTimerCloudProfileUpdate)
1356 m_pTimerCloudProfileUpdate->start(10000);
1357
1358 /* Compose a list of items to update: */
1359 QList<UIChooserItem*> itemsToUpdate;
1360
1361 /* For the case if requested - just update everything: */
1362 if (isKeepCloudNodesUpdated())
1363 {
1364 /* Search for a list of provider nodes having items: */
1365 QList<UIChooserNode*> providerNodes;
1366 invisibleRoot()->searchForNodes(QString(),
1367 UIChooserItemSearchFlag_CloudProvider,
1368 providerNodes);
1369 foreach (UIChooserNode *pNode, providerNodes)
1370 if (UIChooserItem *pItem = pNode->item())
1371 itemsToUpdate << pItem;
1372
1373 /* Have at least something as a fallback: */
1374 if (itemsToUpdate.isEmpty())
1375 itemsToUpdate << selectedItems();
1376 }
1377 /* Otherwise update selected items only: */
1378 else
1379 itemsToUpdate << selectedItems();
1380
1381 /* For every selected item: */
1382 QSet<UICloudEntityKey> selectedCloudProfileKeys;
1383 foreach (UIChooserItem *pSelectedItem, itemsToUpdate)
1384 {
1385 /* Enumerate cloud profile keys to update: */
1386 switch (pSelectedItem->type())
1387 {
1388 case UIChooserNodeType_Group:
1389 {
1390 UIChooserItemGroup *pGroupItem = pSelectedItem->toGroupItem();
1391 AssertPtrReturnVoid(pGroupItem);
1392 switch (pGroupItem->groupType())
1393 {
1394 case UIChooserNodeGroupType_Provider:
1395 {
1396 const QString strProviderShortName = pSelectedItem->name();
1397 foreach (UIChooserItem *pChildItem, pSelectedItem->items(UIChooserNodeType_Group))
1398 {
1399 const QString strProfileName = pChildItem->name();
1400 const UICloudEntityKey guiCloudProfileKey = UICloudEntityKey(strProviderShortName, strProfileName);
1401 if (!selectedCloudProfileKeys.contains(guiCloudProfileKey))
1402 selectedCloudProfileKeys.insert(guiCloudProfileKey);
1403 }
1404 break;
1405 }
1406 case UIChooserNodeGroupType_Profile:
1407 {
1408 const QString strProviderShortName = pSelectedItem->parentItem()->name();
1409 const QString strProfileName = pSelectedItem->name();
1410 const UICloudEntityKey guiCloudProfileKey = UICloudEntityKey(strProviderShortName, strProfileName);
1411 if (!selectedCloudProfileKeys.contains(guiCloudProfileKey))
1412 selectedCloudProfileKeys.insert(guiCloudProfileKey);
1413 break;
1414 }
1415 default:
1416 break;
1417 }
1418 break;
1419 }
1420 case UIChooserNodeType_Machine:
1421 {
1422 UIChooserItemMachine *pMachineItem = pSelectedItem->toMachineItem();
1423 AssertPtrReturnVoid(pMachineItem);
1424 if ( pMachineItem->cacheType() == UIVirtualMachineItemType_CloudFake
1425 || pMachineItem->cacheType() == UIVirtualMachineItemType_CloudReal)
1426 {
1427 const QString strProviderShortName = pMachineItem->parentItem()->parentItem()->name();
1428 const QString strProfileName = pMachineItem->parentItem()->name();
1429 const UICloudEntityKey guiCloudProfileKey = UICloudEntityKey(strProviderShortName, strProfileName);
1430 if (!selectedCloudProfileKeys.contains(guiCloudProfileKey))
1431 selectedCloudProfileKeys.insert(guiCloudProfileKey);
1432 }
1433 break;
1434 }
1435 }
1436 }
1437
1438 /* Restart List Cloud Machines task for selected profile keys: */
1439 foreach (const UICloudEntityKey &guiCloudProfileKey, selectedCloudProfileKeys)
1440 createReadCloudMachineListTask(guiCloudProfileKey, false /* with refresh? */);
1441}
1442
1443void UIChooserModel::prepare()
1444{
1445 prepareScene();
1446 prepareContextMenu();
1447 prepareHandlers();
1448 prepareCloudUpdateTimer();
1449 prepareConnections();
1450}
1451
1452void UIChooserModel::prepareScene()
1453{
1454 m_pScene = new QGraphicsScene(this);
1455 if (m_pScene)
1456 m_pScene->installEventFilter(this);
1457}
1458
1459void UIChooserModel::prepareContextMenu()
1460{
1461 /* Context menu for global(s): */
1462 m_localMenus[UIChooserNodeType_Global] = new QMenu;
1463 if (QMenu *pMenuGlobal = m_localMenus.value(UIChooserNodeType_Global))
1464 {
1465#ifdef VBOX_WS_MAC
1466 pMenuGlobal->addAction(actionPool()->action(UIActionIndex_M_Application_S_About));
1467 pMenuGlobal->addSeparator();
1468 pMenuGlobal->addAction(actionPool()->action(UIActionIndex_M_Application_S_Preferences));
1469 pMenuGlobal->addSeparator();
1470 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_S_ImportAppliance));
1471 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_S_ExportAppliance));
1472# ifdef VBOX_GUI_WITH_EXTRADATA_MANAGER_UI
1473 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_S_ShowExtraDataManager));
1474 pMenuGlobal->addSeparator();
1475# endif
1476 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_M_Tools));
1477
1478#else /* !VBOX_WS_MAC */
1479
1480 pMenuGlobal->addAction(actionPool()->action(UIActionIndex_M_Application_S_Preferences));
1481 pMenuGlobal->addSeparator();
1482 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_S_ImportAppliance));
1483 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_S_ExportAppliance));
1484 pMenuGlobal->addSeparator();
1485# ifdef VBOX_GUI_WITH_EXTRADATA_MANAGER_UI
1486 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_S_ShowExtraDataManager));
1487 pMenuGlobal->addSeparator();
1488# endif
1489 pMenuGlobal->addAction(actionPool()->action(UIActionIndexMN_M_File_M_Tools));
1490 pMenuGlobal->addSeparator();
1491# ifdef VBOX_GUI_WITH_NETWORK_MANAGER
1492 if (gEDataManager->applicationUpdateEnabled())
1493 pMenuGlobal->addAction(actionPool()->action(UIActionIndex_M_Application_S_CheckForUpdates));
1494# endif
1495#endif /* !VBOX_WS_MAC */
1496 }
1497
1498 /* Context menu for local group(s): */
1499 m_localMenus[UIChooserNodeType_Group] = new QMenu;
1500 if (QMenu *pMenuGroup = m_localMenus.value(UIChooserNodeType_Group))
1501 {
1502 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_New));
1503 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Add));
1504 pMenuGroup->addSeparator();
1505 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Rename));
1506 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Remove));
1507 pMenuGroup->addMenu(actionPool()->action(UIActionIndexMN_M_Group_M_MoveToGroup)->menu());
1508 pMenuGroup->addSeparator();
1509 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_M_StartOrShow));
1510 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_T_Pause));
1511 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Reset));
1512 // pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Detach));
1513 pMenuGroup->addMenu(actionPool()->action(UIActionIndexMN_M_Group_M_Stop)->menu());
1514 pMenuGroup->addSeparator();
1515 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Discard));
1516 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Refresh));
1517 pMenuGroup->addSeparator();
1518 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_ShowInFileManager));
1519 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_CreateShortcut));
1520 pMenuGroup->addSeparator();
1521 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Sort));
1522 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_T_Search));
1523 }
1524
1525 /* Context menu for local machine(s): */
1526 m_localMenus[UIChooserNodeType_Machine] = new QMenu;
1527 if (QMenu *pMenuMachine = m_localMenus.value(UIChooserNodeType_Machine))
1528 {
1529 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Settings));
1530 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Clone));
1531 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Move));
1532 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_ExportToOCI));
1533 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Remove));
1534 pMenuMachine->addMenu(actionPool()->action(UIActionIndexMN_M_Machine_M_MoveToGroup)->menu());
1535 pMenuMachine->addSeparator();
1536 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_M_StartOrShow));
1537 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_T_Pause));
1538 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Reset));
1539 // pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Detach));
1540 pMenuMachine->addMenu(actionPool()->action(UIActionIndexMN_M_Machine_M_Stop)->menu());
1541 pMenuMachine->addSeparator();
1542 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Discard));
1543 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Refresh));
1544 pMenuMachine->addSeparator();
1545 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_ShowInFileManager));
1546 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_CreateShortcut));
1547 pMenuMachine->addSeparator();
1548 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_SortParent));
1549 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_T_Search));
1550 }
1551
1552 /* Context menu for cloud group(s): */
1553 m_cloudMenus[UIChooserNodeType_Group] = new QMenu;
1554 if (QMenu *pMenuGroup = m_cloudMenus.value(UIChooserNodeType_Group))
1555 {
1556 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_New));
1557 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Add));
1558 pMenuGroup->addSeparator();
1559 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_M_StartOrShow));
1560 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Reset));
1561 pMenuGroup->addMenu(actionPool()->action(UIActionIndexMN_M_Group_M_Console)->menu());
1562 pMenuGroup->addMenu(actionPool()->action(UIActionIndexMN_M_Group_M_Stop)->menu());
1563 pMenuGroup->addSeparator();
1564 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Refresh));
1565 pMenuGroup->addSeparator();
1566 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_S_Sort));
1567 pMenuGroup->addAction(actionPool()->action(UIActionIndexMN_M_Group_T_Search));
1568 }
1569
1570 /* Context menu for cloud machine(s): */
1571 m_cloudMenus[UIChooserNodeType_Machine] = new QMenu;
1572 if (QMenu *pMenuMachine = m_cloudMenus.value(UIChooserNodeType_Machine))
1573 {
1574 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Settings));
1575 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Clone));
1576 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Remove));
1577 pMenuMachine->addSeparator();
1578 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_M_StartOrShow));
1579 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Reset));
1580 pMenuMachine->addMenu(actionPool()->action(UIActionIndexMN_M_Machine_M_Console)->menu());
1581 pMenuMachine->addMenu(actionPool()->action(UIActionIndexMN_M_Machine_M_Stop)->menu());
1582 pMenuMachine->addSeparator();
1583 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_Refresh));
1584 pMenuMachine->addSeparator();
1585 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_S_SortParent));
1586 pMenuMachine->addAction(actionPool()->action(UIActionIndexMN_M_Machine_T_Search));
1587 }
1588}
1589
1590void UIChooserModel::prepareHandlers()
1591{
1592 m_pMouseHandler = new UIChooserHandlerMouse(this);
1593 m_pKeyboardHandler = new UIChooserHandlerKeyboard(this);
1594}
1595
1596void UIChooserModel::prepareCloudUpdateTimer()
1597{
1598 m_pTimerCloudProfileUpdate = new QTimer;
1599 if (m_pTimerCloudProfileUpdate)
1600 m_pTimerCloudProfileUpdate->start(10000);
1601}
1602
1603void UIChooserModel::prepareConnections()
1604{
1605 connect(this, &UIChooserModel::sigSelectionChanged,
1606 this, &UIChooserModel::sltUpdateSelectedCloudProfiles);
1607 connect(m_pTimerCloudProfileUpdate, &QTimer::timeout,
1608 this, &UIChooserModel::sltUpdateSelectedCloudProfiles);
1609}
1610
1611void UIChooserModel::loadSettings()
1612{
1613 /* Load last selected-item (choose first if unable to load): */
1614 setSelectedItem(gEDataManager->selectorWindowLastItemChosen());
1615 makeSureAtLeastOneItemSelected();
1616}
1617
1618void UIChooserModel::cleanupConnections()
1619{
1620 disconnect(this, &UIChooserModel::sigSelectionChanged,
1621 this, &UIChooserModel::sltUpdateSelectedCloudProfiles);
1622 disconnect(m_pTimerCloudProfileUpdate, &QTimer::timeout,
1623 this, &UIChooserModel::sltUpdateSelectedCloudProfiles);
1624}
1625
1626void UIChooserModel::cleanupCloudUpdateTimer()
1627{
1628 delete m_pTimerCloudProfileUpdate;
1629 m_pTimerCloudProfileUpdate = 0;
1630}
1631
1632void UIChooserModel::cleanupHandlers()
1633{
1634 delete m_pKeyboardHandler;
1635 m_pKeyboardHandler = 0;
1636 delete m_pMouseHandler;
1637 m_pMouseHandler = 0;
1638}
1639
1640void UIChooserModel::cleanupContextMenu()
1641{
1642 qDeleteAll(m_localMenus);
1643 m_localMenus.clear();
1644 qDeleteAll(m_cloudMenus);
1645 m_cloudMenus.clear();
1646}
1647
1648void UIChooserModel::cleanupScene()
1649{
1650 delete m_pScene;
1651 m_pScene = 0;
1652}
1653
1654void UIChooserModel::cleanup()
1655{
1656 cleanupConnections();
1657 cleanupCloudUpdateTimer();
1658 cleanupHandlers();
1659 cleanupContextMenu();
1660 cleanupScene();
1661}
1662
1663bool UIChooserModel::processContextMenuEvent(QGraphicsSceneContextMenuEvent *pEvent)
1664{
1665 /* Whats the reason? */
1666 switch (pEvent->reason())
1667 {
1668 case QGraphicsSceneContextMenuEvent::Mouse:
1669 {
1670 /* Look for an item under cursor: */
1671 if (QGraphicsItem *pItem = itemAt(pEvent->scenePos()))
1672 {
1673 switch (pItem->type())
1674 {
1675 case UIChooserNodeType_Global:
1676 {
1677 /* Global context menu for all global item cases: */
1678 m_localMenus.value(UIChooserNodeType_Global)->exec(pEvent->screenPos());
1679 break;
1680 }
1681 case UIChooserNodeType_Group:
1682 {
1683 /* Get group-item: */
1684 UIChooserItemGroup *pGroupItem = qgraphicsitem_cast<UIChooserItemGroup*>(pItem);
1685 /* Don't show context menu for root-item: */
1686 if (pGroupItem->isRoot())
1687 break;
1688 /* Make sure we have group-item selected exclusively: */
1689 if (selectedItems().contains(pGroupItem) && selectedItems().size() == 1)
1690 {
1691 /* Group context menu in that case: */
1692 if (pGroupItem->groupType() == UIChooserNodeGroupType_Local)
1693 m_localMenus.value(UIChooserNodeType_Group)->exec(pEvent->screenPos());
1694 else if ( pGroupItem->groupType() == UIChooserNodeGroupType_Provider
1695 || pGroupItem->groupType() == UIChooserNodeGroupType_Profile)
1696 m_cloudMenus.value(UIChooserNodeType_Group)->exec(pEvent->screenPos());
1697 break;
1698 }
1699 /* Otherwise we have to find a first child machine-item: */
1700 else
1701 pItem = qobject_cast<UIChooserItem*>(pGroupItem)->firstMachineItem();
1702 }
1703 RT_FALL_THRU();
1704 case UIChooserNodeType_Machine:
1705 {
1706 /* Get machine-item: */
1707 UIChooserItemMachine *pMachineItem = qgraphicsitem_cast<UIChooserItemMachine*>(pItem);
1708 /* Machine context menu for other Group/Machine cases: */
1709 if (pMachineItem->cacheType() == UIVirtualMachineItemType_Local)
1710 m_localMenus.value(UIChooserNodeType_Machine)->exec(pEvent->screenPos());
1711 else if (pMachineItem->cacheType() == UIVirtualMachineItemType_CloudReal)
1712 m_cloudMenus.value(UIChooserNodeType_Machine)->exec(pEvent->screenPos());
1713 break;
1714 }
1715 default:
1716 break;
1717 }
1718 }
1719 /* Filter out by default: */
1720 return true;
1721 }
1722 case QGraphicsSceneContextMenuEvent::Keyboard:
1723 {
1724 /* Get first selected-item: */
1725 if (UIChooserItem *pItem = firstSelectedItem())
1726 {
1727 switch (pItem->type())
1728 {
1729 case UIChooserNodeType_Global:
1730 {
1731 /* Global context menu for all global item cases: */
1732 m_localMenus.value(UIChooserNodeType_Global)->exec(pEvent->screenPos());
1733 break;
1734 }
1735 case UIChooserNodeType_Group:
1736 {
1737 /* Get group-item: */
1738 UIChooserItemGroup *pGroupItem = qgraphicsitem_cast<UIChooserItemGroup*>(pItem);
1739 /* Make sure we have group-item selected exclusively: */
1740 if (selectedItems().contains(pGroupItem) && selectedItems().size() == 1)
1741 {
1742 /* Group context menu in that case: */
1743 if (pGroupItem->groupType() == UIChooserNodeGroupType_Local)
1744 m_localMenus.value(UIChooserNodeType_Group)->exec(pEvent->screenPos());
1745 else if ( pGroupItem->groupType() == UIChooserNodeGroupType_Provider
1746 || pGroupItem->groupType() == UIChooserNodeGroupType_Profile)
1747 m_cloudMenus.value(UIChooserNodeType_Group)->exec(pEvent->screenPos());
1748 break;
1749 }
1750 /* Otherwise we have to find a first child machine-item: */
1751 else
1752 pItem = qobject_cast<UIChooserItem*>(pGroupItem)->firstMachineItem();
1753 }
1754 RT_FALL_THRU();
1755 case UIChooserNodeType_Machine:
1756 {
1757 /* Get machine-item: */
1758 UIChooserItemMachine *pMachineItem = qgraphicsitem_cast<UIChooserItemMachine*>(pItem);
1759 /* Machine context menu for other Group/Machine cases: */
1760 if (pMachineItem->cacheType() == UIVirtualMachineItemType_Local)
1761 m_localMenus.value(UIChooserNodeType_Machine)->exec(pEvent->screenPos());
1762 else if (pMachineItem->cacheType() == UIVirtualMachineItemType_CloudReal)
1763 m_cloudMenus.value(UIChooserNodeType_Machine)->exec(pEvent->screenPos());
1764 break;
1765 }
1766 default:
1767 break;
1768 }
1769 }
1770 /* Filter out by default: */
1771 return true;
1772 }
1773 default:
1774 break;
1775 }
1776 /* Pass others context menu events: */
1777 return false;
1778}
1779
1780void UIChooserModel::clearRealFocus()
1781{
1782 /* Set the real focus to null: */
1783 scene()->setFocusItem(0);
1784}
1785
1786QList<UIChooserItem*> UIChooserModel::createNavigationItemList(UIChooserItem *pItem)
1787{
1788 /* Prepare navigation list: */
1789 QList<UIChooserItem*> navigationItems;
1790
1791 /* Iterate over all the global-items: */
1792 foreach (UIChooserItem *pGlobalItem, pItem->items(UIChooserNodeType_Global))
1793 navigationItems << pGlobalItem;
1794 /* Iterate over all the group-items: */
1795 foreach (UIChooserItem *pGroupItem, pItem->items(UIChooserNodeType_Group))
1796 {
1797 navigationItems << pGroupItem;
1798 if (pGroupItem->toGroupItem()->isOpened())
1799 navigationItems << createNavigationItemList(pGroupItem);
1800 }
1801 /* Iterate over all the machine-items: */
1802 foreach (UIChooserItem *pMachineItem, pItem->items(UIChooserNodeType_Machine))
1803 navigationItems << pMachineItem;
1804
1805 /* Return navigation list: */
1806 return navigationItems;
1807}
1808
1809void UIChooserModel::clearTreeForMainRoot()
1810{
1811 /* Forbid to save selection changes: */
1812 m_fSelectionSaveAllowed = false;
1813
1814 /* Cleanup tree if exists: */
1815 delete m_pRoot;
1816 m_pRoot = 0;
1817}
1818
1819void UIChooserModel::buildTreeForMainRoot(bool fPreserveSelection /* = false */)
1820{
1821 /* This isn't safe if dragging is started and needs to be fixed properly,
1822 * but for now we will just ignore build request: */
1823 /// @todo Make sure D&D is safe on tree rebuild
1824 if (m_pCurrentDragObject)
1825 return;
1826
1827 /* Remember scrolling location: */
1828 const int iScrollLocation = m_pRoot ? m_pRoot->toGroupItem()->scrollingValue() : 0;
1829
1830 /* Remember all selected items if requested: */
1831 QStringList selectedItemDefinitions;
1832 if (fPreserveSelection && !selectedItems().isEmpty())
1833 {
1834 foreach (UIChooserItem *pSelectedItem, selectedItems())
1835 selectedItemDefinitions << pSelectedItem->definition();
1836 }
1837
1838 /* Clean tree for main root: */
1839 clearTreeForMainRoot();
1840
1841 /* Build whole tree for invisible root item: */
1842 m_pRoot = new UIChooserItemGroup(scene(), invisibleRoot()->toGroupNode());
1843
1844 /* Install root as event-filter for scene view,
1845 * we need QEvent::Scroll events from it: */
1846 root()->installEventFilterHelper(view());
1847
1848 /* Update tree for main root: */
1849 updateTreeForMainRoot();
1850
1851 /* Apply current global item height hint: */
1852 applyGlobalItemHeightHint();
1853
1854 /* Restore all selected items if requested: */
1855 if (fPreserveSelection)
1856 {
1857 QList<UIChooserItem*> selectedItems;
1858 foreach (const QString &strSelectedItemDefinition, selectedItemDefinitions)
1859 {
1860 UIChooserItem *pSelectedItem = searchItemByDefinition(strSelectedItemDefinition);
1861 if (pSelectedItem)
1862 selectedItems << pSelectedItem;
1863 }
1864 setSelectedItems(selectedItems);
1865 setCurrentItem(firstSelectedItem());
1866 makeSureAtLeastOneItemSelected();
1867 }
1868
1869 /* Restore scrolling location: */
1870 m_pRoot->toGroupItem()->setScrollingValue(iScrollLocation);
1871
1872 /* Repeat search if search widget is visible: */
1873 if (view() && view()->isSearchWidgetVisible())
1874 view()->redoSearch();
1875
1876 /* Allow to save selection changes: */
1877 m_fSelectionSaveAllowed = true;
1878}
1879
1880void UIChooserModel::updateTreeForMainRoot()
1881{
1882 updateNavigationItemList();
1883 updateLayout();
1884}
1885
1886void UIChooserModel::removeLocalMachineItems(const QList<UIChooserItemMachine*> &machineItems)
1887{
1888 /* Confirm machine-items removal: */
1889 QStringList names;
1890 foreach (UIChooserItemMachine *pItem, machineItems)
1891 names << pItem->name();
1892 if (!msgCenter().confirmMachineItemRemoval(names))
1893 return;
1894
1895 /* Find and select closest unselected item: */
1896 setSelectedItem(findClosestUnselectedItem());
1897
1898 /* Remove nodes of all the passed items: */
1899 foreach (UIChooserItemMachine *pItem, machineItems)
1900 delete pItem->node();
1901
1902 /* And update model: */
1903 wipeOutEmptyGroups();
1904 updateTreeForMainRoot();
1905
1906 /* Save groups finally: */
1907 saveGroups();
1908}
1909
1910void UIChooserModel::unregisterLocalMachines(const QList<CMachine> &machines)
1911{
1912 /* Confirm machine removal: */
1913 const int iResultCode = msgCenter().confirmMachineRemoval(machines);
1914 if (iResultCode == AlertButton_Cancel)
1915 return;
1916
1917 /* For every selected machine: */
1918 foreach (CMachine comMachine, machines)
1919 {
1920 if (iResultCode == AlertButton_Choice1)
1921 {
1922 /* Unregister machine first: */
1923 CMediumVector media = comMachine.Unregister(KCleanupMode_DetachAllReturnHardDisksOnly);
1924 if (!comMachine.isOk())
1925 {
1926 UINotificationMessage::cannotRemoveMachine(comMachine);
1927 continue;
1928 }
1929 /* Removing machine: */
1930 UINotificationProgressMachineMediaRemove *pNotification = new UINotificationProgressMachineMediaRemove(comMachine, media);
1931 gpNotificationCenter->append(pNotification);
1932 }
1933 else if (iResultCode == AlertButton_Choice2 || iResultCode == AlertButton_Ok)
1934 {
1935 /* Unregister machine first: */
1936 CMediumVector media = comMachine.Unregister(KCleanupMode_DetachAllReturnHardDisksOnly);
1937 if (!comMachine.isOk())
1938 {
1939 UINotificationMessage::cannotRemoveMachine(comMachine);
1940 continue;
1941 }
1942 /* Finally close all media, deliberately ignoring errors: */
1943 foreach (CMedium comMedium, media)
1944 {
1945 if (!comMedium.isNull())
1946 comMedium.Close();
1947 }
1948 }
1949 }
1950}
1951
1952void UIChooserModel::unregisterCloudMachineItems(const QList<UIChooserItemMachine*> &machineItems)
1953{
1954 /* Compose a list of machines: */
1955 QList<CCloudMachine> machines;
1956 foreach (UIChooserItemMachine *pMachineItem, machineItems)
1957 machines << pMachineItem->cache()->toCloud()->machine();
1958
1959 /* Stop cloud profile update prematurely: */
1960 if (m_pTimerCloudProfileUpdate)
1961 m_pTimerCloudProfileUpdate->stop();
1962
1963 /* Confirm machine removal: */
1964 const int iResultCode = msgCenter().confirmCloudMachineRemoval(machines);
1965 if (iResultCode == AlertButton_Cancel)
1966 {
1967 /* Resume cloud profile update if cancelled: */
1968 if (m_pTimerCloudProfileUpdate)
1969 m_pTimerCloudProfileUpdate->start(10000);
1970 return;
1971 }
1972
1973 /* For every selected machine-item: */
1974 foreach (UIChooserItemMachine *pMachineItem, machineItems)
1975 {
1976 /* Compose cloud entity keys for profile and machine: */
1977 const QString strProviderShortName = pMachineItem->parentItem()->parentItem()->name();
1978 const QString strProfileName = pMachineItem->parentItem()->name();
1979 const QUuid uMachineId = pMachineItem->id();
1980 const UICloudEntityKey cloudEntityKeyForMachine = UICloudEntityKey(strProviderShortName, strProfileName, uMachineId);
1981
1982 /* Stop refreshing machine being deleted: */
1983 if (containsCloudEntityKey(cloudEntityKeyForMachine))
1984 pMachineItem->cache()->toCloud()->waitForAsyncInfoUpdateFinished();
1985
1986 /* Acquire cloud machine: */
1987 CCloudMachine comMachine = pMachineItem->cache()->toCloud()->machine();
1988
1989 /* Removing cloud machine: */
1990 UINotificationProgressCloudMachineRemove *pNotification =
1991 new UINotificationProgressCloudMachineRemove(comMachine,
1992 iResultCode == AlertButton_Choice1,
1993 strProviderShortName,
1994 strProfileName);
1995 connect(pNotification, &UINotificationProgressCloudMachineRemove::sigCloudMachineRemoved,
1996 this, &UIChooserModel::sltHandleCloudMachineRemoved);
1997 gpNotificationCenter->append(pNotification);
1998 }
1999
2000 /* Resume cloud profile update after all: */
2001 if (m_pTimerCloudProfileUpdate)
2002 m_pTimerCloudProfileUpdate->start(10000);
2003}
2004
2005bool UIChooserModel::processDragMoveEvent(QGraphicsSceneDragDropEvent *pEvent)
2006{
2007 /* Make sure view exists: */
2008 AssertPtrReturn(view(), false);
2009
2010 /* Do we scrolling already? */
2011 if (m_fIsScrollingInProgress)
2012 return false;
2013
2014 /* Check scroll-area: */
2015 const QPoint eventPoint = view()->mapFromGlobal(pEvent->screenPos());
2016 if ( (eventPoint.y() < m_iScrollingTokenSize)
2017 || (eventPoint.y() > view()->height() - m_iScrollingTokenSize))
2018 {
2019 /* Set scrolling in progress: */
2020 m_fIsScrollingInProgress = true;
2021 /* Start scrolling: */
2022 QTimer::singleShot(200, this, SLOT(sltStartScrolling()));
2023 }
2024
2025 /* Pass event: */
2026 return false;
2027}
2028
2029bool UIChooserModel::processDragLeaveEvent(QGraphicsSceneDragDropEvent *pEvent)
2030{
2031 /* Event object is not required here: */
2032 Q_UNUSED(pEvent);
2033
2034 /* Make sure to stop scrolling as drag-leave event happened: */
2035 if (m_fIsScrollingInProgress)
2036 m_fIsScrollingInProgress = false;
2037
2038 /* Pass event: */
2039 return false;
2040}
2041
2042void UIChooserModel::applyGlobalItemHeightHint()
2043{
2044 /* Make sure there is something to apply: */
2045 if (m_iGlobalItemHeightHint == 0)
2046 return;
2047
2048 /* Walk thrugh all the items of navigation list: */
2049 foreach (UIChooserItem *pItem, navigationItems())
2050 {
2051 /* And for each global item: */
2052 if (pItem->type() == UIChooserNodeType_Global)
2053 {
2054 /* Apply the height hint we have: */
2055 UIChooserItemGlobal *pGlobalItem = pItem->toGlobalItem();
2056 if (pGlobalItem)
2057 pGlobalItem->setHeightHint(m_iGlobalItemHeightHint);
2058 }
2059 }
2060}
Note: See TracBrowser for help on using the repository browser.

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