Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2183 lines
65KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For the technical preview this file cannot be licensed commercially.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. namespace juce
  14. {
  15. static int getItemDepth (const TreeViewItem* item)
  16. {
  17. if (item == nullptr || item->getOwnerView() == nullptr)
  18. return 0;
  19. auto depth = item->getOwnerView()->isRootItemVisible() ? 0 : -1;
  20. for (auto* parent = item->getParentItem(); parent != nullptr; parent = parent->getParentItem())
  21. ++depth;
  22. return depth;
  23. }
  24. //==============================================================================
  25. class TreeView::ItemComponent : public Component,
  26. public TooltipClient
  27. {
  28. public:
  29. explicit ItemComponent (TreeViewItem& itemToRepresent)
  30. : item (itemToRepresent),
  31. customComponent (item.createItemComponent())
  32. {
  33. if (hasCustomComponent())
  34. addAndMakeVisible (*customComponent);
  35. }
  36. void paint (Graphics& g) override
  37. {
  38. item.draw (g, getWidth(), mouseIsOverButton);
  39. }
  40. void resized() override
  41. {
  42. if (hasCustomComponent())
  43. {
  44. auto itemPosition = item.getItemPosition (false);
  45. customComponent->setBounds (getLocalBounds().withX (itemPosition.getX())
  46. .withWidth (itemPosition.getWidth()));
  47. }
  48. }
  49. void setMouseIsOverButton (bool isOver)
  50. {
  51. mouseIsOverButton = isOver;
  52. repaint();
  53. }
  54. TreeViewItem& getRepresentedItem() const noexcept
  55. {
  56. return item;
  57. }
  58. String getTooltip() override
  59. {
  60. return item.getTooltip();
  61. }
  62. private:
  63. //==============================================================================
  64. class ItemAccessibilityHandler : public AccessibilityHandler
  65. {
  66. public:
  67. explicit ItemAccessibilityHandler (ItemComponent& comp)
  68. : AccessibilityHandler (comp,
  69. AccessibilityRole::treeItem,
  70. getAccessibilityActions (comp),
  71. { std::make_unique<ItemCellInterface> (comp) }),
  72. itemComponent (comp)
  73. {
  74. }
  75. String getTitle() const override
  76. {
  77. return itemComponent.getRepresentedItem().getAccessibilityName();
  78. }
  79. String getHelp() const override
  80. {
  81. return itemComponent.getRepresentedItem().getTooltip();
  82. }
  83. AccessibleState getCurrentState() const override
  84. {
  85. auto& treeItem = itemComponent.getRepresentedItem();
  86. auto state = AccessibilityHandler::getCurrentState().withAccessibleOffscreen();
  87. if (auto* tree = treeItem.getOwnerView())
  88. {
  89. if (tree->isMultiSelectEnabled())
  90. state = state.withMultiSelectable();
  91. else
  92. state = state.withSelectable();
  93. }
  94. if (treeItem.mightContainSubItems())
  95. {
  96. state = state.withExpandable();
  97. if (treeItem.isOpen())
  98. state = state.withExpanded();
  99. else
  100. state = state.withCollapsed();
  101. }
  102. if (treeItem.isSelected())
  103. state = state.withSelected();
  104. return state;
  105. }
  106. class ItemCellInterface : public AccessibilityCellInterface
  107. {
  108. public:
  109. explicit ItemCellInterface (ItemComponent& c) : itemComponent (c) {}
  110. int getColumnIndex() const override { return 0; }
  111. int getColumnSpan() const override { return 1; }
  112. int getRowIndex() const override
  113. {
  114. return itemComponent.getRepresentedItem().getRowNumberInTree();
  115. }
  116. int getRowSpan() const override
  117. {
  118. return 1;
  119. }
  120. int getDisclosureLevel() const override
  121. {
  122. return getItemDepth (&itemComponent.getRepresentedItem());
  123. }
  124. const AccessibilityHandler* getTableHandler() const override
  125. {
  126. if (auto* tree = itemComponent.getRepresentedItem().getOwnerView())
  127. return tree->getAccessibilityHandler();
  128. return nullptr;
  129. }
  130. private:
  131. ItemComponent& itemComponent;
  132. };
  133. private:
  134. static AccessibilityActions getAccessibilityActions (ItemComponent& itemComponent)
  135. {
  136. auto onFocus = [&itemComponent]
  137. {
  138. auto& treeItem = itemComponent.getRepresentedItem();
  139. if (auto* tree = treeItem.getOwnerView())
  140. tree->scrollToKeepItemVisible (&treeItem);
  141. };
  142. auto onPress = [&itemComponent]
  143. {
  144. itemComponent.getRepresentedItem().itemClicked (generateMouseEvent (itemComponent, { ModifierKeys::leftButtonModifier }));
  145. };
  146. auto onShowMenu = [&itemComponent]
  147. {
  148. itemComponent.getRepresentedItem().itemClicked (generateMouseEvent (itemComponent, { ModifierKeys::popupMenuClickModifier }));
  149. };
  150. auto onToggle = [&itemComponent, onFocus]
  151. {
  152. if (auto* handler = itemComponent.getAccessibilityHandler())
  153. {
  154. auto isSelected = handler->getCurrentState().isSelected();
  155. if (! isSelected)
  156. onFocus();
  157. itemComponent.getRepresentedItem().setSelected (! isSelected, true);
  158. }
  159. };
  160. auto actions = AccessibilityActions().addAction (AccessibilityActionType::focus, std::move (onFocus))
  161. .addAction (AccessibilityActionType::press, std::move (onPress))
  162. .addAction (AccessibilityActionType::showMenu, std::move (onShowMenu))
  163. .addAction (AccessibilityActionType::toggle, std::move (onToggle));
  164. return actions;
  165. }
  166. ItemComponent& itemComponent;
  167. static MouseEvent generateMouseEvent (ItemComponent& itemComp, ModifierKeys mods)
  168. {
  169. auto topLeft = itemComp.getRepresentedItem().getItemPosition (false).toFloat().getTopLeft();
  170. return { Desktop::getInstance().getMainMouseSource(), topLeft, mods,
  171. MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
  172. MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
  173. &itemComp, &itemComp, Time::getCurrentTime(), topLeft, Time::getCurrentTime(), 0, false };
  174. }
  175. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemAccessibilityHandler)
  176. };
  177. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  178. {
  179. if (hasCustomComponent() && customComponent->getAccessibilityHandler() != nullptr)
  180. return nullptr;
  181. return std::make_unique<ItemAccessibilityHandler> (*this);
  182. }
  183. bool hasCustomComponent() const noexcept { return customComponent.get() != nullptr; }
  184. TreeViewItem& item;
  185. std::unique_ptr<Component> customComponent;
  186. bool mouseIsOverButton = false;
  187. //==============================================================================
  188. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
  189. };
  190. //==============================================================================
  191. class TreeView::ContentComponent : public Component,
  192. public TooltipClient,
  193. public AsyncUpdater
  194. {
  195. public:
  196. ContentComponent (TreeView& tree) : owner (tree)
  197. {
  198. }
  199. //==============================================================================
  200. void resized() override
  201. {
  202. triggerAsyncUpdate();
  203. }
  204. String getTooltip() override
  205. {
  206. if (auto* itemComponent = getItemComponentAt (getMouseXYRelative()))
  207. return itemComponent->getRepresentedItem().getTooltip();
  208. return owner.getTooltip();
  209. }
  210. void mouseDown (const MouseEvent& e) override { mouseDownInternal (e.getEventRelativeTo (this)); }
  211. void mouseUp (const MouseEvent& e) override { mouseUpInternal (e.getEventRelativeTo (this)); }
  212. void mouseDoubleClick (const MouseEvent& e) override { mouseDoubleClickInternal (e.getEventRelativeTo (this));}
  213. void mouseDrag (const MouseEvent& e) override { mouseDragInternal (e.getEventRelativeTo (this));}
  214. void mouseMove (const MouseEvent& e) override { mouseMoveInternal (e.getEventRelativeTo (this)); }
  215. void mouseExit (const MouseEvent& e) override { mouseExitInternal (e.getEventRelativeTo (this)); }
  216. //==============================================================================
  217. ItemComponent* getItemComponentAt (Point<int> p)
  218. {
  219. auto iter = std::find_if (itemComponents.cbegin(), itemComponents.cend(),
  220. [p] (const std::unique_ptr<ItemComponent>& c)
  221. {
  222. return c->getBounds().contains (p);
  223. });
  224. if (iter != itemComponents.cend())
  225. return iter->get();
  226. return nullptr;
  227. }
  228. ItemComponent* getComponentForItem (const TreeViewItem* item) const
  229. {
  230. const auto iter = std::find_if (itemComponents.begin(), itemComponents.end(),
  231. [item] (const std::unique_ptr<ItemComponent>& c)
  232. {
  233. return &c->getRepresentedItem() == item;
  234. });
  235. if (iter != itemComponents.end())
  236. return iter->get();
  237. return nullptr;
  238. }
  239. void itemBeingDeleted (const TreeViewItem* item)
  240. {
  241. const auto iter = std::find_if (itemComponents.begin(), itemComponents.end(),
  242. [item] (const std::unique_ptr<ItemComponent>& c)
  243. {
  244. return &c->getRepresentedItem() == item;
  245. });
  246. if (iter != itemComponents.end())
  247. {
  248. if (itemUnderMouse == iter->get())
  249. itemUnderMouse = nullptr;
  250. if (isMouseDraggingInChildComp (*(iter->get())))
  251. owner.hideDragHighlight();
  252. itemComponents.erase (iter);
  253. }
  254. }
  255. void updateComponents()
  256. {
  257. std::set<ItemComponent*> componentsToKeep;
  258. for (auto* treeItem : getAllVisibleItems())
  259. {
  260. if (auto* itemComp = getComponentForItem (treeItem))
  261. {
  262. componentsToKeep.insert (itemComp);
  263. }
  264. else
  265. {
  266. auto newComp = std::make_unique<ItemComponent> (*treeItem);
  267. addAndMakeVisible (*newComp);
  268. newComp->addMouseListener (this, treeItem->customComponentUsesTreeViewMouseHandler());
  269. componentsToKeep.insert (newComp.get());
  270. itemComponents.push_back (std::move (newComp));
  271. }
  272. }
  273. auto removePredicate = [&] (auto& item)
  274. {
  275. if (item == nullptr)
  276. return true;
  277. return componentsToKeep.find (item.get()) == componentsToKeep.end()
  278. && ! isMouseDraggingInChildComp (*item);
  279. };
  280. const auto iter = std::remove_if (itemComponents.begin(), itemComponents.end(), std::move (removePredicate));
  281. itemComponents.erase (iter, itemComponents.end());
  282. for (auto& comp : itemComponents)
  283. {
  284. auto& treeItem = comp->getRepresentedItem();
  285. comp->setBounds ({ 0, treeItem.y, getWidth(), treeItem.itemHeight });
  286. }
  287. }
  288. private:
  289. //==============================================================================
  290. struct ScopedDisableViewportScroll
  291. {
  292. explicit ScopedDisableViewportScroll (ItemComponent& c)
  293. : item (&c)
  294. {
  295. item->setViewportIgnoreDragFlag (true);
  296. }
  297. ~ScopedDisableViewportScroll()
  298. {
  299. if (item != nullptr)
  300. item->setViewportIgnoreDragFlag (false);
  301. }
  302. SafePointer<ItemComponent> item;
  303. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedDisableViewportScroll)
  304. };
  305. //==============================================================================
  306. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  307. {
  308. return createIgnoredAccessibilityHandler (*this);
  309. }
  310. void mouseDownInternal (const MouseEvent& e)
  311. {
  312. updateItemUnderMouse (e);
  313. isDragging = false;
  314. scopedScrollDisabler = nullptr;
  315. needSelectionOnMouseUp = false;
  316. if (! isEnabled())
  317. return;
  318. if (auto* itemComponent = getItemComponentAt (e.getPosition()))
  319. {
  320. auto& item = itemComponent->getRepresentedItem();
  321. auto pos = item.getItemPosition (false);
  322. // (if the open/close buttons are hidden, we'll treat clicks to the left of the item
  323. // as selection clicks)
  324. if (e.x < pos.getX() && owner.openCloseButtonsVisible)
  325. {
  326. // (clicks to the left of an open/close button are ignored)
  327. if (e.x >= pos.getX() - owner.getIndentSize())
  328. item.setOpen (! item.isOpen());
  329. }
  330. else
  331. {
  332. // mouse-down inside the body of the item..
  333. if (! owner.isMultiSelectEnabled())
  334. item.setSelected (true, true);
  335. else if (item.isSelected())
  336. needSelectionOnMouseUp = ! e.mods.isPopupMenu();
  337. else
  338. selectBasedOnModifiers (item, e.mods);
  339. if (e.x >= pos.getX())
  340. item.itemClicked (e.withNewPosition (e.position - pos.getPosition().toFloat()));
  341. }
  342. }
  343. }
  344. void mouseUpInternal (const MouseEvent& e)
  345. {
  346. updateItemUnderMouse (e);
  347. if (isEnabled() && needSelectionOnMouseUp && e.mouseWasClicked())
  348. if (auto* itemComponent = getItemComponentAt (e.getPosition()))
  349. selectBasedOnModifiers (itemComponent->getRepresentedItem(), e.mods);
  350. }
  351. void mouseDoubleClickInternal (const MouseEvent& e)
  352. {
  353. if (isEnabled() && e.getNumberOfClicks() != 3) // ignore triple clicks
  354. {
  355. if (auto* itemComponent = getItemComponentAt (e.getPosition()))
  356. {
  357. auto& item = itemComponent->getRepresentedItem();
  358. auto pos = item.getItemPosition (false);
  359. if (e.x >= pos.getX() || ! owner.openCloseButtonsVisible)
  360. item.itemDoubleClicked (e.withNewPosition (e.position - pos.getPosition().toFloat()));
  361. }
  362. }
  363. }
  364. void mouseDragInternal (const MouseEvent& e)
  365. {
  366. if (isEnabled()
  367. && ! (isDragging || e.mouseWasClicked()
  368. || e.getDistanceFromDragStart() < 5
  369. || e.mods.isPopupMenu()))
  370. {
  371. isDragging = true;
  372. if (auto* itemComponent = getItemComponentAt (e.getMouseDownPosition()))
  373. {
  374. auto& item = itemComponent->getRepresentedItem();
  375. auto pos = item.getItemPosition (false);
  376. if (e.getMouseDownX() >= pos.getX())
  377. {
  378. auto dragDescription = item.getDragSourceDescription();
  379. if (! (dragDescription.isVoid() || (dragDescription.isString() && dragDescription.toString().isEmpty())))
  380. {
  381. if (auto* dragContainer = DragAndDropContainer::findParentDragContainerFor (this))
  382. {
  383. pos.setSize (pos.getWidth(), item.itemHeight);
  384. const auto additionalScale = 2.0f;
  385. auto dragImage = Component::createComponentSnapshot (pos,
  386. true,
  387. Component::getApproximateScaleFactorForComponent (itemComponent) * additionalScale);
  388. dragImage.multiplyAllAlphas (0.6f);
  389. auto imageOffset = pos.getPosition() - e.getPosition();
  390. dragContainer->startDragging (dragDescription, &owner, { dragImage, additionalScale }, true, &imageOffset, &e.source);
  391. scopedScrollDisabler = std::make_unique<ScopedDisableViewportScroll> (*itemComponent);
  392. }
  393. else
  394. {
  395. // to be able to do a drag-and-drop operation, the treeview needs to
  396. // be inside a component which is also a DragAndDropContainer.
  397. jassertfalse;
  398. }
  399. }
  400. }
  401. }
  402. }
  403. }
  404. void mouseMoveInternal (const MouseEvent& e) { updateItemUnderMouse (e); }
  405. void mouseExitInternal (const MouseEvent& e) { updateItemUnderMouse (e); }
  406. static bool isMouseDraggingInChildComp (const Component& comp)
  407. {
  408. for (auto& ms : Desktop::getInstance().getMouseSources())
  409. if (ms.isDragging())
  410. if (auto* underMouse = ms.getComponentUnderMouse())
  411. return (&comp == underMouse || comp.isParentOf (underMouse));
  412. return false;
  413. }
  414. void updateItemUnderMouse (const MouseEvent& e)
  415. {
  416. if (! owner.openCloseButtonsVisible)
  417. return;
  418. auto* newItem = [this, &e]() -> ItemComponent*
  419. {
  420. if (auto* itemComponent = getItemComponentAt (e.getPosition()))
  421. {
  422. auto& item = itemComponent->getRepresentedItem();
  423. if (item.mightContainSubItems())
  424. {
  425. const auto xPos = item.getItemPosition (false).getX();
  426. if (xPos - owner.getIndentSize() <= e.x && e.x < xPos)
  427. return itemComponent;
  428. }
  429. }
  430. return nullptr;
  431. }();
  432. if (itemUnderMouse != newItem)
  433. {
  434. if (itemUnderMouse != nullptr)
  435. itemUnderMouse->setMouseIsOverButton (false);
  436. if (newItem != nullptr)
  437. newItem->setMouseIsOverButton (true);
  438. itemUnderMouse = newItem;
  439. }
  440. }
  441. void handleAsyncUpdate() override
  442. {
  443. owner.updateVisibleItems();
  444. }
  445. //==============================================================================
  446. void selectBasedOnModifiers (TreeViewItem& item, const ModifierKeys modifiers)
  447. {
  448. TreeViewItem* firstSelected = nullptr;
  449. if (modifiers.isShiftDown() && ((firstSelected = owner.getSelectedItem (0)) != nullptr))
  450. {
  451. auto* lastSelected = owner.getSelectedItem (owner.getNumSelectedItems() - 1);
  452. if (lastSelected == nullptr)
  453. {
  454. jassertfalse;
  455. return;
  456. }
  457. auto rowStart = firstSelected->getRowNumberInTree();
  458. auto rowEnd = lastSelected->getRowNumberInTree();
  459. if (rowStart > rowEnd)
  460. std::swap (rowStart, rowEnd);
  461. auto ourRow = item.getRowNumberInTree();
  462. auto otherEnd = ourRow < rowEnd ? rowStart : rowEnd;
  463. if (ourRow > otherEnd)
  464. std::swap (ourRow, otherEnd);
  465. for (int i = ourRow; i <= otherEnd; ++i)
  466. owner.getItemOnRow (i)->setSelected (true, false);
  467. }
  468. else
  469. {
  470. const auto cmd = modifiers.isCommandDown();
  471. item.setSelected ((! cmd) || ! item.isSelected(), ! cmd);
  472. }
  473. }
  474. static TreeViewItem* getNextVisibleItem (TreeViewItem* item, bool forwards)
  475. {
  476. if (item == nullptr || item->ownerView == nullptr)
  477. return nullptr;
  478. auto* nextItem = item->ownerView->getItemOnRow (item->getRowNumberInTree() + (forwards ? 1 : -1));
  479. return nextItem == item->ownerView->rootItem && ! item->ownerView->rootItemVisible ? nullptr
  480. : nextItem;
  481. }
  482. std::vector<TreeViewItem*> getAllVisibleItems() const
  483. {
  484. if (owner.rootItem == nullptr)
  485. return {};
  486. const auto visibleTop = -getY();
  487. const auto visibleBottom = visibleTop + getParentHeight();
  488. std::vector<TreeViewItem*> visibleItems;
  489. auto* item = [&]
  490. {
  491. auto* i = owner.rootItemVisible ? owner.rootItem
  492. : owner.rootItem->subItems.getFirst();
  493. while (i != nullptr && i->y < visibleTop)
  494. i = getNextVisibleItem (i, true);
  495. return i;
  496. }();
  497. auto addOffscreenItemBuffer = [&visibleItems] (TreeViewItem* i, int num, bool forwards)
  498. {
  499. while (--num >= 0)
  500. {
  501. i = getNextVisibleItem (i, forwards);
  502. if (i == nullptr)
  503. return;
  504. visibleItems.push_back (i);
  505. }
  506. };
  507. addOffscreenItemBuffer (item, 2, false);
  508. while (item != nullptr && item->y < visibleBottom)
  509. {
  510. visibleItems.push_back (item);
  511. item = getNextVisibleItem (item, true);
  512. }
  513. if (item != nullptr)
  514. visibleItems.push_back (item);
  515. addOffscreenItemBuffer (item, 2, true);
  516. return visibleItems;
  517. }
  518. //==============================================================================
  519. TreeView& owner;
  520. std::vector<std::unique_ptr<ItemComponent>> itemComponents;
  521. ItemComponent* itemUnderMouse = nullptr;
  522. std::unique_ptr<ScopedDisableViewportScroll> scopedScrollDisabler;
  523. bool isDragging = false, needSelectionOnMouseUp = false;
  524. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentComponent)
  525. };
  526. //==============================================================================
  527. class TreeView::TreeViewport : public Viewport,
  528. private AsyncUpdater
  529. {
  530. public:
  531. explicit TreeViewport (TreeView& treeView) : owner (treeView) {}
  532. void visibleAreaChanged (const Rectangle<int>& newVisibleArea) override
  533. {
  534. const auto hasScrolledSideways = (newVisibleArea.getX() != lastX);
  535. lastX = newVisibleArea.getX();
  536. updateComponents (hasScrolledSideways);
  537. structureChanged = true;
  538. triggerAsyncUpdate();
  539. }
  540. bool keyPressed (const KeyPress& key) override
  541. {
  542. if (auto* tree = getParentComponent())
  543. if (tree->keyPressed (key))
  544. return true;
  545. return Viewport::keyPressed (key);
  546. }
  547. ContentComponent* getContentComp() const noexcept
  548. {
  549. return static_cast<ContentComponent*> (getViewedComponent());
  550. }
  551. enum class Async { yes, no };
  552. void recalculatePositions (Async useAsyncUpdate)
  553. {
  554. needsRecalculating = true;
  555. if (useAsyncUpdate == Async::yes)
  556. triggerAsyncUpdate();
  557. else
  558. handleAsyncUpdate();
  559. }
  560. private:
  561. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  562. {
  563. return createIgnoredAccessibilityHandler (*this);
  564. }
  565. void handleAsyncUpdate() override
  566. {
  567. if (structureChanged)
  568. {
  569. if (auto* handler = owner.getAccessibilityHandler())
  570. handler->notifyAccessibilityEvent (AccessibilityEvent::structureChanged);
  571. structureChanged = false;
  572. }
  573. if (needsRecalculating)
  574. {
  575. if (auto* root = owner.rootItem)
  576. {
  577. const auto startY = owner.rootItemVisible ? 0 : -root->itemHeight;
  578. root->updatePositions (startY);
  579. getViewedComponent()->setSize (jmax (getMaximumVisibleWidth(), root->totalWidth + 50),
  580. root->totalHeight + startY);
  581. }
  582. else
  583. {
  584. getViewedComponent()->setSize (0, 0);
  585. }
  586. updateComponents (false);
  587. needsRecalculating = false;
  588. }
  589. }
  590. void updateComponents (bool triggerResize)
  591. {
  592. if (auto* content = getContentComp())
  593. {
  594. if (triggerResize)
  595. content->resized();
  596. else
  597. content->updateComponents();
  598. }
  599. repaint();
  600. }
  601. TreeView& owner;
  602. int lastX = -1;
  603. bool structureChanged = false, needsRecalculating = false;
  604. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewport)
  605. };
  606. //==============================================================================
  607. TreeView::TreeView (const String& name) : Component (name)
  608. {
  609. viewport = std::make_unique<TreeViewport> (*this);
  610. addAndMakeVisible (viewport.get());
  611. viewport->setViewedComponent (new ContentComponent (*this));
  612. setWantsKeyboardFocus (true);
  613. setFocusContainerType (FocusContainerType::focusContainer);
  614. }
  615. TreeView::~TreeView()
  616. {
  617. if (rootItem != nullptr)
  618. rootItem->setOwnerView (nullptr);
  619. }
  620. void TreeView::setRootItem (TreeViewItem* const newRootItem)
  621. {
  622. if (rootItem != newRootItem)
  623. {
  624. if (newRootItem != nullptr)
  625. {
  626. // can't use a tree item in more than one tree at once..
  627. jassert (newRootItem->ownerView == nullptr);
  628. if (newRootItem->ownerView != nullptr)
  629. newRootItem->ownerView->setRootItem (nullptr);
  630. }
  631. if (rootItem != nullptr)
  632. rootItem->setOwnerView (nullptr);
  633. rootItem = newRootItem;
  634. if (newRootItem != nullptr)
  635. newRootItem->setOwnerView (this);
  636. if (rootItem != nullptr && (defaultOpenness || ! rootItemVisible))
  637. {
  638. rootItem->setOpen (false); // force a re-open
  639. rootItem->setOpen (true);
  640. }
  641. viewport->recalculatePositions (TreeViewport::Async::no);
  642. }
  643. }
  644. void TreeView::deleteRootItem()
  645. {
  646. const std::unique_ptr<TreeViewItem> deleter (rootItem);
  647. setRootItem (nullptr);
  648. }
  649. void TreeView::setRootItemVisible (const bool shouldBeVisible)
  650. {
  651. rootItemVisible = shouldBeVisible;
  652. if (rootItem != nullptr && (defaultOpenness || ! rootItemVisible))
  653. {
  654. rootItem->setOpen (false); // force a re-open
  655. rootItem->setOpen (true);
  656. }
  657. updateVisibleItems();
  658. }
  659. void TreeView::colourChanged()
  660. {
  661. setOpaque (findColour (backgroundColourId).isOpaque());
  662. repaint();
  663. }
  664. void TreeView::setIndentSize (const int newIndentSize)
  665. {
  666. if (indentSize != newIndentSize)
  667. {
  668. indentSize = newIndentSize;
  669. resized();
  670. }
  671. }
  672. int TreeView::getIndentSize() noexcept
  673. {
  674. return indentSize >= 0 ? indentSize
  675. : getLookAndFeel().getTreeViewIndentSize (*this);
  676. }
  677. void TreeView::setDefaultOpenness (const bool isOpenByDefault)
  678. {
  679. if (defaultOpenness != isOpenByDefault)
  680. {
  681. defaultOpenness = isOpenByDefault;
  682. updateVisibleItems();
  683. }
  684. }
  685. void TreeView::setMultiSelectEnabled (const bool canMultiSelect)
  686. {
  687. multiSelectEnabled = canMultiSelect;
  688. }
  689. void TreeView::setOpenCloseButtonsVisible (const bool shouldBeVisible)
  690. {
  691. if (openCloseButtonsVisible != shouldBeVisible)
  692. {
  693. openCloseButtonsVisible = shouldBeVisible;
  694. updateVisibleItems();
  695. }
  696. }
  697. Viewport* TreeView::getViewport() const noexcept
  698. {
  699. return viewport.get();
  700. }
  701. //==============================================================================
  702. void TreeView::clearSelectedItems()
  703. {
  704. if (rootItem != nullptr)
  705. rootItem->deselectAllRecursively (nullptr);
  706. }
  707. int TreeView::getNumSelectedItems (int maximumDepthToSearchTo) const noexcept
  708. {
  709. return rootItem != nullptr ? rootItem->countSelectedItemsRecursively (maximumDepthToSearchTo) : 0;
  710. }
  711. TreeViewItem* TreeView::getSelectedItem (const int index) const noexcept
  712. {
  713. return rootItem != nullptr ? rootItem->getSelectedItemWithIndex (index) : nullptr;
  714. }
  715. int TreeView::getNumRowsInTree() const
  716. {
  717. return rootItem != nullptr ? (rootItem->getNumRows() - (rootItemVisible ? 0 : 1)) : 0;
  718. }
  719. TreeViewItem* TreeView::getItemOnRow (int index) const
  720. {
  721. if (! rootItemVisible)
  722. ++index;
  723. if (rootItem != nullptr && index >= 0)
  724. return rootItem->getItemOnRow (index);
  725. return nullptr;
  726. }
  727. TreeViewItem* TreeView::getItemAt (int y) const noexcept
  728. {
  729. if (auto* contentComp = viewport->getContentComp())
  730. if (auto* itemComponent = contentComp->getItemComponentAt (contentComp->getLocalPoint (this, Point<int> (0, y))))
  731. return &itemComponent->getRepresentedItem();
  732. return nullptr;
  733. }
  734. TreeViewItem* TreeView::findItemFromIdentifierString (const String& identifierString) const
  735. {
  736. if (rootItem == nullptr)
  737. return nullptr;
  738. return rootItem->findItemFromIdentifierString (identifierString);
  739. }
  740. Component* TreeView::getItemComponent (const TreeViewItem* item) const
  741. {
  742. return viewport->getContentComp()->getComponentForItem (item);
  743. }
  744. //==============================================================================
  745. static void addAllSelectedItemIds (TreeViewItem* item, XmlElement& parent)
  746. {
  747. if (item->isSelected())
  748. parent.createNewChildElement ("SELECTED")->setAttribute ("id", item->getItemIdentifierString());
  749. auto numSubItems = item->getNumSubItems();
  750. for (int i = 0; i < numSubItems; ++i)
  751. addAllSelectedItemIds (item->getSubItem (i), parent);
  752. }
  753. std::unique_ptr<XmlElement> TreeView::getOpennessState (bool alsoIncludeScrollPosition) const
  754. {
  755. if (rootItem != nullptr)
  756. {
  757. if (auto rootOpenness = rootItem->getOpennessState (false))
  758. {
  759. if (alsoIncludeScrollPosition)
  760. rootOpenness->setAttribute ("scrollPos", viewport->getViewPositionY());
  761. addAllSelectedItemIds (rootItem, *rootOpenness);
  762. return rootOpenness;
  763. }
  764. }
  765. return {};
  766. }
  767. void TreeView::restoreOpennessState (const XmlElement& newState, bool restoreStoredSelection)
  768. {
  769. if (rootItem != nullptr)
  770. {
  771. rootItem->restoreOpennessState (newState);
  772. if (newState.hasAttribute ("scrollPos"))
  773. viewport->setViewPosition (viewport->getViewPositionX(),
  774. newState.getIntAttribute ("scrollPos"));
  775. if (restoreStoredSelection)
  776. {
  777. clearSelectedItems();
  778. for (auto* e : newState.getChildWithTagNameIterator ("SELECTED"))
  779. if (auto* item = rootItem->findItemFromIdentifierString (e->getStringAttribute ("id")))
  780. item->setSelected (true, false);
  781. }
  782. updateVisibleItems();
  783. }
  784. }
  785. //==============================================================================
  786. void TreeView::paint (Graphics& g)
  787. {
  788. g.fillAll (findColour (backgroundColourId));
  789. }
  790. void TreeView::resized()
  791. {
  792. viewport->setBounds (getLocalBounds());
  793. updateVisibleItems();
  794. }
  795. void TreeView::enablementChanged()
  796. {
  797. repaint();
  798. }
  799. void TreeView::moveSelectedRow (int delta)
  800. {
  801. auto numRowsInTree = getNumRowsInTree();
  802. if (numRowsInTree > 0)
  803. {
  804. int rowSelected = 0;
  805. if (auto* firstSelected = getSelectedItem (0))
  806. rowSelected = firstSelected->getRowNumberInTree();
  807. rowSelected = jlimit (0, numRowsInTree - 1, rowSelected + delta);
  808. for (;;)
  809. {
  810. if (auto* item = getItemOnRow (rowSelected))
  811. {
  812. if (! item->canBeSelected())
  813. {
  814. // if the row we want to highlight doesn't allow it, try skipping
  815. // to the next item..
  816. auto nextRowToTry = jlimit (0, numRowsInTree - 1, rowSelected + (delta < 0 ? -1 : 1));
  817. if (rowSelected != nextRowToTry)
  818. {
  819. rowSelected = nextRowToTry;
  820. continue;
  821. }
  822. break;
  823. }
  824. item->setSelected (true, true);
  825. scrollToKeepItemVisible (item);
  826. }
  827. break;
  828. }
  829. }
  830. }
  831. void TreeView::scrollToKeepItemVisible (TreeViewItem* item)
  832. {
  833. if (item != nullptr && item->ownerView == this)
  834. {
  835. updateVisibleItems();
  836. item = item->getDeepestOpenParentItem();
  837. auto y = item->y;
  838. auto viewTop = viewport->getViewPositionY();
  839. if (y < viewTop)
  840. {
  841. viewport->setViewPosition (viewport->getViewPositionX(), y);
  842. }
  843. else if (y + item->itemHeight > viewTop + viewport->getViewHeight())
  844. {
  845. viewport->setViewPosition (viewport->getViewPositionX(),
  846. (y + item->itemHeight) - viewport->getViewHeight());
  847. }
  848. }
  849. }
  850. bool TreeView::toggleOpenSelectedItem()
  851. {
  852. if (auto* firstSelected = getSelectedItem (0))
  853. {
  854. if (firstSelected->mightContainSubItems())
  855. {
  856. firstSelected->setOpen (! firstSelected->isOpen());
  857. return true;
  858. }
  859. }
  860. return false;
  861. }
  862. void TreeView::moveOutOfSelectedItem()
  863. {
  864. if (auto* firstSelected = getSelectedItem (0))
  865. {
  866. if (firstSelected->isOpen())
  867. {
  868. firstSelected->setOpen (false);
  869. }
  870. else
  871. {
  872. auto* parent = firstSelected->parentItem;
  873. if ((! rootItemVisible) && parent == rootItem)
  874. parent = nullptr;
  875. if (parent != nullptr)
  876. {
  877. parent->setSelected (true, true);
  878. scrollToKeepItemVisible (parent);
  879. }
  880. }
  881. }
  882. }
  883. void TreeView::moveIntoSelectedItem()
  884. {
  885. if (auto* firstSelected = getSelectedItem (0))
  886. {
  887. if (firstSelected->isOpen() || ! firstSelected->mightContainSubItems())
  888. moveSelectedRow (1);
  889. else
  890. firstSelected->setOpen (true);
  891. }
  892. }
  893. void TreeView::moveByPages (int numPages)
  894. {
  895. if (auto* currentItem = getSelectedItem (0))
  896. {
  897. auto pos = currentItem->getItemPosition (false);
  898. auto targetY = pos.getY() + numPages * (getHeight() - pos.getHeight());
  899. auto currentRow = currentItem->getRowNumberInTree();
  900. for (;;)
  901. {
  902. moveSelectedRow (numPages);
  903. currentItem = getSelectedItem (0);
  904. if (currentItem == nullptr)
  905. break;
  906. auto y = currentItem->getItemPosition (false).getY();
  907. if ((numPages < 0 && y <= targetY) || (numPages > 0 && y >= targetY))
  908. break;
  909. auto newRow = currentItem->getRowNumberInTree();
  910. if (newRow == currentRow)
  911. break;
  912. currentRow = newRow;
  913. }
  914. }
  915. }
  916. bool TreeView::keyPressed (const KeyPress& key)
  917. {
  918. if (rootItem != nullptr)
  919. {
  920. if (key == KeyPress::upKey) { moveSelectedRow (-1); return true; }
  921. if (key == KeyPress::downKey) { moveSelectedRow (1); return true; }
  922. if (key == KeyPress::homeKey) { moveSelectedRow (-0x3fffffff); return true; }
  923. if (key == KeyPress::endKey) { moveSelectedRow (0x3fffffff); return true; }
  924. if (key == KeyPress::pageUpKey) { moveByPages (-1); return true; }
  925. if (key == KeyPress::pageDownKey) { moveByPages (1); return true; }
  926. if (key == KeyPress::returnKey) { return toggleOpenSelectedItem(); }
  927. if (key == KeyPress::leftKey) { moveOutOfSelectedItem(); return true; }
  928. if (key == KeyPress::rightKey) { moveIntoSelectedItem(); return true; }
  929. }
  930. return false;
  931. }
  932. void TreeView::updateVisibleItems()
  933. {
  934. viewport->recalculatePositions (TreeViewport::Async::yes);
  935. }
  936. //==============================================================================
  937. struct TreeView::InsertPoint
  938. {
  939. InsertPoint (TreeView& view, const StringArray& files,
  940. const DragAndDropTarget::SourceDetails& dragSourceDetails)
  941. : pos (dragSourceDetails.localPosition),
  942. item (view.getItemAt (dragSourceDetails.localPosition.y))
  943. {
  944. if (item != nullptr)
  945. {
  946. auto itemPos = item->getItemPosition (true);
  947. insertIndex = item->getIndexInParent();
  948. auto oldY = pos.y;
  949. pos.y = itemPos.getY();
  950. if (item->getNumSubItems() == 0 || ! item->isOpen())
  951. {
  952. if (files.size() > 0 ? item->isInterestedInFileDrag (files)
  953. : item->isInterestedInDragSource (dragSourceDetails))
  954. {
  955. // Check if we're trying to drag into an empty group item..
  956. if (oldY > itemPos.getY() + itemPos.getHeight() / 4
  957. && oldY < itemPos.getBottom() - itemPos.getHeight() / 4)
  958. {
  959. insertIndex = 0;
  960. pos.x = itemPos.getX() + view.getIndentSize();
  961. pos.y = itemPos.getBottom();
  962. return;
  963. }
  964. }
  965. }
  966. if (oldY > itemPos.getCentreY())
  967. {
  968. pos.y += item->getItemHeight();
  969. while (item->isLastOfSiblings() && item->getParentItem() != nullptr
  970. && item->getParentItem()->getParentItem() != nullptr)
  971. {
  972. if (pos.x > itemPos.getX())
  973. break;
  974. item = item->getParentItem();
  975. itemPos = item->getItemPosition (true);
  976. insertIndex = item->getIndexInParent();
  977. }
  978. ++insertIndex;
  979. }
  980. pos.x = itemPos.getX();
  981. item = item->getParentItem();
  982. }
  983. else if (auto* root = view.getRootItem())
  984. {
  985. // If they're dragging beyond the bottom of the list, then insert at the end of the root item..
  986. item = root;
  987. insertIndex = root->getNumSubItems();
  988. pos = root->getItemPosition (true).getBottomLeft();
  989. pos.x += view.getIndentSize();
  990. }
  991. }
  992. Point<int> pos;
  993. TreeViewItem* item;
  994. int insertIndex = 0;
  995. };
  996. //==============================================================================
  997. class TreeView::InsertPointHighlight : public Component
  998. {
  999. public:
  1000. InsertPointHighlight()
  1001. {
  1002. setSize (100, 12);
  1003. setAlwaysOnTop (true);
  1004. setInterceptsMouseClicks (false, false);
  1005. }
  1006. void setTargetPosition (const InsertPoint& insertPos, const int width) noexcept
  1007. {
  1008. lastItem = insertPos.item;
  1009. lastIndex = insertPos.insertIndex;
  1010. auto offset = getHeight() / 2;
  1011. setBounds (insertPos.pos.x - offset, insertPos.pos.y - offset,
  1012. width - (insertPos.pos.x - offset), getHeight());
  1013. }
  1014. void paint (Graphics& g) override
  1015. {
  1016. Path p;
  1017. auto h = (float) getHeight();
  1018. p.addEllipse (2.0f, 2.0f, h - 4.0f, h - 4.0f);
  1019. p.startNewSubPath (h - 2.0f, h / 2.0f);
  1020. p.lineTo ((float) getWidth(), h / 2.0f);
  1021. g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
  1022. g.strokePath (p, PathStrokeType (2.0f));
  1023. }
  1024. TreeViewItem* lastItem = nullptr;
  1025. int lastIndex = 0;
  1026. private:
  1027. JUCE_DECLARE_NON_COPYABLE (InsertPointHighlight)
  1028. };
  1029. //==============================================================================
  1030. class TreeView::TargetGroupHighlight : public Component
  1031. {
  1032. public:
  1033. TargetGroupHighlight()
  1034. {
  1035. setAlwaysOnTop (true);
  1036. setInterceptsMouseClicks (false, false);
  1037. }
  1038. void setTargetPosition (TreeViewItem* const item) noexcept
  1039. {
  1040. setBounds (item->getItemPosition (true)
  1041. .withHeight (item->getItemHeight()));
  1042. }
  1043. void paint (Graphics& g) override
  1044. {
  1045. g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
  1046. g.drawRoundedRectangle (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 3.0f, 2.0f);
  1047. }
  1048. private:
  1049. JUCE_DECLARE_NON_COPYABLE (TargetGroupHighlight)
  1050. };
  1051. //==============================================================================
  1052. void TreeView::showDragHighlight (const InsertPoint& insertPos) noexcept
  1053. {
  1054. beginDragAutoRepeat (100);
  1055. if (dragInsertPointHighlight == nullptr)
  1056. {
  1057. dragInsertPointHighlight = std::make_unique<InsertPointHighlight>();
  1058. dragTargetGroupHighlight = std::make_unique<TargetGroupHighlight>();
  1059. addAndMakeVisible (dragInsertPointHighlight.get());
  1060. addAndMakeVisible (dragTargetGroupHighlight.get());
  1061. }
  1062. dragInsertPointHighlight->setTargetPosition (insertPos, viewport->getViewWidth());
  1063. dragTargetGroupHighlight->setTargetPosition (insertPos.item);
  1064. }
  1065. void TreeView::hideDragHighlight() noexcept
  1066. {
  1067. dragInsertPointHighlight = nullptr;
  1068. dragTargetGroupHighlight = nullptr;
  1069. }
  1070. void TreeView::handleDrag (const StringArray& files, const SourceDetails& dragSourceDetails)
  1071. {
  1072. const auto scrolled = viewport->autoScroll (dragSourceDetails.localPosition.x,
  1073. dragSourceDetails.localPosition.y, 20, 10);
  1074. InsertPoint insertPos (*this, files, dragSourceDetails);
  1075. if (insertPos.item != nullptr)
  1076. {
  1077. if (scrolled || dragInsertPointHighlight == nullptr
  1078. || dragInsertPointHighlight->lastItem != insertPos.item
  1079. || dragInsertPointHighlight->lastIndex != insertPos.insertIndex)
  1080. {
  1081. if (files.size() > 0 ? insertPos.item->isInterestedInFileDrag (files)
  1082. : insertPos.item->isInterestedInDragSource (dragSourceDetails))
  1083. showDragHighlight (insertPos);
  1084. else
  1085. hideDragHighlight();
  1086. }
  1087. }
  1088. else
  1089. {
  1090. hideDragHighlight();
  1091. }
  1092. }
  1093. void TreeView::handleDrop (const StringArray& files, const SourceDetails& dragSourceDetails)
  1094. {
  1095. hideDragHighlight();
  1096. InsertPoint insertPos (*this, files, dragSourceDetails);
  1097. if (insertPos.item == nullptr)
  1098. insertPos.item = rootItem;
  1099. if (insertPos.item != nullptr)
  1100. {
  1101. if (files.size() > 0)
  1102. {
  1103. if (insertPos.item->isInterestedInFileDrag (files))
  1104. insertPos.item->filesDropped (files, insertPos.insertIndex);
  1105. }
  1106. else
  1107. {
  1108. if (insertPos.item->isInterestedInDragSource (dragSourceDetails))
  1109. insertPos.item->itemDropped (dragSourceDetails, insertPos.insertIndex);
  1110. }
  1111. }
  1112. }
  1113. //==============================================================================
  1114. bool TreeView::isInterestedInFileDrag (const StringArray&)
  1115. {
  1116. return true;
  1117. }
  1118. void TreeView::fileDragEnter (const StringArray& files, int x, int y)
  1119. {
  1120. fileDragMove (files, x, y);
  1121. }
  1122. void TreeView::fileDragMove (const StringArray& files, int x, int y)
  1123. {
  1124. handleDrag (files, SourceDetails (var(), this, { x, y }));
  1125. }
  1126. void TreeView::fileDragExit (const StringArray&)
  1127. {
  1128. hideDragHighlight();
  1129. }
  1130. void TreeView::filesDropped (const StringArray& files, int x, int y)
  1131. {
  1132. handleDrop (files, SourceDetails (var(), this, { x, y }));
  1133. }
  1134. bool TreeView::isInterestedInDragSource (const SourceDetails& /*dragSourceDetails*/)
  1135. {
  1136. return true;
  1137. }
  1138. void TreeView::itemDragEnter (const SourceDetails& dragSourceDetails)
  1139. {
  1140. itemDragMove (dragSourceDetails);
  1141. }
  1142. void TreeView::itemDragMove (const SourceDetails& dragSourceDetails)
  1143. {
  1144. handleDrag (StringArray(), dragSourceDetails);
  1145. }
  1146. void TreeView::itemDragExit (const SourceDetails& /*dragSourceDetails*/)
  1147. {
  1148. hideDragHighlight();
  1149. }
  1150. void TreeView::itemDropped (const SourceDetails& dragSourceDetails)
  1151. {
  1152. handleDrop (StringArray(), dragSourceDetails);
  1153. }
  1154. //==============================================================================
  1155. std::unique_ptr<AccessibilityHandler> TreeView::createAccessibilityHandler()
  1156. {
  1157. class TableInterface : public AccessibilityTableInterface
  1158. {
  1159. public:
  1160. explicit TableInterface (TreeView& treeViewToWrap) : treeView (treeViewToWrap) {}
  1161. int getNumRows() const override { return treeView.getNumRowsInTree(); }
  1162. int getNumColumns() const override { return 1; }
  1163. const AccessibilityHandler* getCellHandler (int row, int) const override
  1164. {
  1165. if (auto* itemComp = treeView.getItemComponent (treeView.getItemOnRow (row)))
  1166. return itemComp->getAccessibilityHandler();
  1167. return nullptr;
  1168. }
  1169. private:
  1170. TreeView& treeView;
  1171. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface)
  1172. };
  1173. return std::make_unique<AccessibilityHandler> (*this,
  1174. AccessibilityRole::tree,
  1175. AccessibilityActions{},
  1176. AccessibilityHandler::Interfaces { std::make_unique<TableInterface> (*this) });
  1177. }
  1178. //==============================================================================
  1179. TreeViewItem::TreeViewItem()
  1180. {
  1181. static int nextUID = 0;
  1182. uid = nextUID++;
  1183. }
  1184. TreeViewItem::~TreeViewItem()
  1185. {
  1186. if (ownerView != nullptr)
  1187. ownerView->viewport->getContentComp()->itemBeingDeleted (this);
  1188. }
  1189. String TreeViewItem::getUniqueName() const
  1190. {
  1191. return {};
  1192. }
  1193. void TreeViewItem::itemOpennessChanged (bool)
  1194. {
  1195. }
  1196. int TreeViewItem::getNumSubItems() const noexcept
  1197. {
  1198. return subItems.size();
  1199. }
  1200. TreeViewItem* TreeViewItem::getSubItem (const int index) const noexcept
  1201. {
  1202. return subItems[index];
  1203. }
  1204. void TreeViewItem::clearSubItems()
  1205. {
  1206. if (ownerView != nullptr)
  1207. {
  1208. if (! subItems.isEmpty())
  1209. {
  1210. removeAllSubItemsFromList();
  1211. treeHasChanged();
  1212. }
  1213. }
  1214. else
  1215. {
  1216. removeAllSubItemsFromList();
  1217. }
  1218. }
  1219. void TreeViewItem::removeAllSubItemsFromList()
  1220. {
  1221. for (int i = subItems.size(); --i >= 0;)
  1222. removeSubItemFromList (i, true);
  1223. }
  1224. void TreeViewItem::addSubItem (TreeViewItem* const newItem, const int insertPosition)
  1225. {
  1226. if (newItem != nullptr)
  1227. {
  1228. newItem->parentItem = nullptr;
  1229. newItem->setOwnerView (ownerView);
  1230. newItem->y = 0;
  1231. newItem->itemHeight = newItem->getItemHeight();
  1232. newItem->totalHeight = 0;
  1233. newItem->itemWidth = newItem->getItemWidth();
  1234. newItem->totalWidth = 0;
  1235. newItem->parentItem = this;
  1236. if (ownerView != nullptr)
  1237. {
  1238. subItems.insert (insertPosition, newItem);
  1239. treeHasChanged();
  1240. if (newItem->isOpen())
  1241. newItem->itemOpennessChanged (true);
  1242. }
  1243. else
  1244. {
  1245. subItems.insert (insertPosition, newItem);
  1246. if (newItem->isOpen())
  1247. newItem->itemOpennessChanged (true);
  1248. }
  1249. }
  1250. }
  1251. void TreeViewItem::removeSubItem (int index, bool deleteItem)
  1252. {
  1253. if (ownerView != nullptr)
  1254. {
  1255. if (removeSubItemFromList (index, deleteItem))
  1256. treeHasChanged();
  1257. }
  1258. else
  1259. {
  1260. removeSubItemFromList (index, deleteItem);
  1261. }
  1262. }
  1263. bool TreeViewItem::removeSubItemFromList (int index, bool deleteItem)
  1264. {
  1265. if (auto* child = subItems[index])
  1266. {
  1267. child->parentItem = nullptr;
  1268. subItems.remove (index, deleteItem);
  1269. return true;
  1270. }
  1271. return false;
  1272. }
  1273. TreeViewItem::Openness TreeViewItem::getOpenness() const noexcept
  1274. {
  1275. return openness;
  1276. }
  1277. void TreeViewItem::setOpenness (Openness newOpenness)
  1278. {
  1279. auto wasOpen = isOpen();
  1280. openness = newOpenness;
  1281. auto isNowOpen = isOpen();
  1282. if (isNowOpen != wasOpen)
  1283. {
  1284. treeHasChanged();
  1285. itemOpennessChanged (isNowOpen);
  1286. }
  1287. }
  1288. bool TreeViewItem::isOpen() const noexcept
  1289. {
  1290. if (openness == Openness::opennessDefault)
  1291. return ownerView != nullptr && ownerView->defaultOpenness;
  1292. return openness == Openness::opennessOpen;
  1293. }
  1294. void TreeViewItem::setOpen (const bool shouldBeOpen)
  1295. {
  1296. if (isOpen() != shouldBeOpen)
  1297. setOpenness (shouldBeOpen ? Openness::opennessOpen
  1298. : Openness::opennessClosed);
  1299. }
  1300. bool TreeViewItem::isFullyOpen() const noexcept
  1301. {
  1302. if (! isOpen())
  1303. return false;
  1304. for (auto* i : subItems)
  1305. if (! i->isFullyOpen())
  1306. return false;
  1307. return true;
  1308. }
  1309. void TreeViewItem::restoreToDefaultOpenness()
  1310. {
  1311. setOpenness (Openness::opennessDefault);
  1312. }
  1313. bool TreeViewItem::isSelected() const noexcept
  1314. {
  1315. return selected;
  1316. }
  1317. void TreeViewItem::deselectAllRecursively (TreeViewItem* itemToIgnore)
  1318. {
  1319. if (this != itemToIgnore)
  1320. setSelected (false, false);
  1321. for (auto* i : subItems)
  1322. i->deselectAllRecursively (itemToIgnore);
  1323. }
  1324. void TreeViewItem::setSelected (const bool shouldBeSelected,
  1325. const bool deselectOtherItemsFirst,
  1326. const NotificationType notify)
  1327. {
  1328. if (shouldBeSelected && ! canBeSelected())
  1329. return;
  1330. if (deselectOtherItemsFirst)
  1331. getTopLevelItem()->deselectAllRecursively (this);
  1332. if (shouldBeSelected != selected)
  1333. {
  1334. selected = shouldBeSelected;
  1335. if (ownerView != nullptr)
  1336. {
  1337. ownerView->repaint();
  1338. if (selected)
  1339. {
  1340. if (auto* itemComponent = ownerView->getItemComponent (this))
  1341. if (auto* itemHandler = itemComponent->getAccessibilityHandler())
  1342. itemHandler->grabFocus();
  1343. }
  1344. if (auto* handler = ownerView->getAccessibilityHandler())
  1345. handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
  1346. }
  1347. if (notify != dontSendNotification)
  1348. itemSelectionChanged (shouldBeSelected);
  1349. }
  1350. }
  1351. void TreeViewItem::paintItem (Graphics&, int, int)
  1352. {
  1353. }
  1354. void TreeViewItem::paintOpenCloseButton (Graphics& g, const Rectangle<float>& area, Colour backgroundColour, bool isMouseOver)
  1355. {
  1356. getOwnerView()->getLookAndFeel()
  1357. .drawTreeviewPlusMinusBox (g, area, backgroundColour, isOpen(), isMouseOver);
  1358. }
  1359. void TreeViewItem::paintHorizontalConnectingLine (Graphics& g, const Line<float>& line)
  1360. {
  1361. g.setColour (ownerView->findColour (TreeView::linesColourId));
  1362. g.drawLine (line);
  1363. }
  1364. void TreeViewItem::paintVerticalConnectingLine (Graphics& g, const Line<float>& line)
  1365. {
  1366. g.setColour (ownerView->findColour (TreeView::linesColourId));
  1367. g.drawLine (line);
  1368. }
  1369. void TreeViewItem::itemClicked (const MouseEvent&)
  1370. {
  1371. }
  1372. void TreeViewItem::itemDoubleClicked (const MouseEvent&)
  1373. {
  1374. if (mightContainSubItems())
  1375. setOpen (! isOpen());
  1376. }
  1377. void TreeViewItem::itemSelectionChanged (bool)
  1378. {
  1379. }
  1380. String TreeViewItem::getTooltip()
  1381. {
  1382. return {};
  1383. }
  1384. String TreeViewItem::getAccessibilityName()
  1385. {
  1386. auto tooltipString = getTooltip();
  1387. return tooltipString.isNotEmpty()
  1388. ? tooltipString
  1389. : "Level " + String (getItemDepth (this)) + " row " + String (getIndexInParent());
  1390. }
  1391. void TreeViewItem::ownerViewChanged (TreeView*)
  1392. {
  1393. }
  1394. var TreeViewItem::getDragSourceDescription()
  1395. {
  1396. return {};
  1397. }
  1398. bool TreeViewItem::isInterestedInFileDrag (const StringArray&)
  1399. {
  1400. return false;
  1401. }
  1402. void TreeViewItem::filesDropped (const StringArray& /*files*/, int /*insertIndex*/)
  1403. {
  1404. }
  1405. bool TreeViewItem::isInterestedInDragSource (const DragAndDropTarget::SourceDetails& /*dragSourceDetails*/)
  1406. {
  1407. return false;
  1408. }
  1409. void TreeViewItem::itemDropped (const DragAndDropTarget::SourceDetails& /*dragSourceDetails*/, int /*insertIndex*/)
  1410. {
  1411. }
  1412. Rectangle<int> TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const noexcept
  1413. {
  1414. auto indentX = getIndentX();
  1415. auto width = itemWidth;
  1416. if (ownerView != nullptr && width < 0)
  1417. width = ownerView->viewport->getViewWidth() - indentX;
  1418. Rectangle<int> r (indentX, y, jmax (0, width), totalHeight);
  1419. if (relativeToTreeViewTopLeft && ownerView != nullptr)
  1420. r -= ownerView->viewport->getViewPosition();
  1421. return r;
  1422. }
  1423. void TreeViewItem::treeHasChanged() const noexcept
  1424. {
  1425. if (ownerView != nullptr)
  1426. ownerView->updateVisibleItems();
  1427. }
  1428. void TreeViewItem::repaintItem() const
  1429. {
  1430. if (ownerView != nullptr && areAllParentsOpen())
  1431. if (auto* component = ownerView->getItemComponent (this))
  1432. component->repaint();
  1433. }
  1434. bool TreeViewItem::areAllParentsOpen() const noexcept
  1435. {
  1436. return parentItem == nullptr
  1437. || (parentItem->isOpen() && parentItem->areAllParentsOpen());
  1438. }
  1439. void TreeViewItem::updatePositions (int newY)
  1440. {
  1441. y = newY;
  1442. itemHeight = getItemHeight();
  1443. totalHeight = itemHeight;
  1444. itemWidth = getItemWidth();
  1445. totalWidth = jmax (itemWidth, 0) + getIndentX();
  1446. if (isOpen())
  1447. {
  1448. newY += totalHeight;
  1449. for (auto* i : subItems)
  1450. {
  1451. i->updatePositions (newY);
  1452. newY += i->totalHeight;
  1453. totalHeight += i->totalHeight;
  1454. totalWidth = jmax (totalWidth, i->totalWidth);
  1455. }
  1456. }
  1457. }
  1458. TreeViewItem* TreeViewItem::getDeepestOpenParentItem() noexcept
  1459. {
  1460. auto* result = this;
  1461. auto* item = this;
  1462. while (item->parentItem != nullptr)
  1463. {
  1464. item = item->parentItem;
  1465. if (! item->isOpen())
  1466. result = item;
  1467. }
  1468. return result;
  1469. }
  1470. void TreeViewItem::setOwnerView (TreeView* const newOwner) noexcept
  1471. {
  1472. ownerView = newOwner;
  1473. for (auto* i : subItems)
  1474. {
  1475. i->setOwnerView (newOwner);
  1476. i->ownerViewChanged (newOwner);
  1477. }
  1478. }
  1479. int TreeViewItem::getIndentX() const noexcept
  1480. {
  1481. if (ownerView == nullptr)
  1482. return 0;
  1483. int x = ownerView->rootItemVisible ? 1 : 0;
  1484. if (! ownerView->openCloseButtonsVisible)
  1485. --x;
  1486. for (auto* p = parentItem; p != nullptr; p = p->parentItem)
  1487. ++x;
  1488. return x * ownerView->getIndentSize();
  1489. }
  1490. void TreeViewItem::setDrawsInLeftMargin (bool canDrawInLeftMargin) noexcept
  1491. {
  1492. drawsInLeftMargin = canDrawInLeftMargin;
  1493. }
  1494. void TreeViewItem::setDrawsInRightMargin (bool canDrawInRightMargin) noexcept
  1495. {
  1496. drawsInRightMargin = canDrawInRightMargin;
  1497. }
  1498. bool TreeViewItem::areLinesDrawn() const
  1499. {
  1500. return drawLinesSet ? drawLinesInside
  1501. : (ownerView != nullptr && ownerView->getLookAndFeel().areLinesDrawnForTreeView (*ownerView));
  1502. }
  1503. bool TreeViewItem::isLastOfSiblings() const noexcept
  1504. {
  1505. return parentItem == nullptr
  1506. || parentItem->subItems.getLast() == this;
  1507. }
  1508. int TreeViewItem::getIndexInParent() const noexcept
  1509. {
  1510. return parentItem == nullptr ? 0
  1511. : parentItem->subItems.indexOf (this);
  1512. }
  1513. TreeViewItem* TreeViewItem::getTopLevelItem() noexcept
  1514. {
  1515. return parentItem == nullptr ? this
  1516. : parentItem->getTopLevelItem();
  1517. }
  1518. int TreeViewItem::getNumRows() const noexcept
  1519. {
  1520. int num = 1;
  1521. if (isOpen())
  1522. for (auto* i : subItems)
  1523. num += i->getNumRows();
  1524. return num;
  1525. }
  1526. TreeViewItem* TreeViewItem::getItemOnRow (int index) noexcept
  1527. {
  1528. if (index == 0)
  1529. return this;
  1530. if (index > 0 && isOpen())
  1531. {
  1532. --index;
  1533. for (auto* i : subItems)
  1534. {
  1535. if (index == 0)
  1536. return i;
  1537. auto numRows = i->getNumRows();
  1538. if (numRows > index)
  1539. return i->getItemOnRow (index);
  1540. index -= numRows;
  1541. }
  1542. }
  1543. return nullptr;
  1544. }
  1545. int TreeViewItem::countSelectedItemsRecursively (int depth) const noexcept
  1546. {
  1547. int total = isSelected() ? 1 : 0;
  1548. if (depth != 0)
  1549. for (auto* i : subItems)
  1550. total += i->countSelectedItemsRecursively (depth - 1);
  1551. return total;
  1552. }
  1553. TreeViewItem* TreeViewItem::getSelectedItemWithIndex (int index) noexcept
  1554. {
  1555. if (isSelected())
  1556. {
  1557. if (index == 0)
  1558. return this;
  1559. --index;
  1560. }
  1561. if (index >= 0)
  1562. {
  1563. for (auto* i : subItems)
  1564. {
  1565. if (auto* found = i->getSelectedItemWithIndex (index))
  1566. return found;
  1567. index -= i->countSelectedItemsRecursively (-1);
  1568. }
  1569. }
  1570. return nullptr;
  1571. }
  1572. int TreeViewItem::getRowNumberInTree() const noexcept
  1573. {
  1574. if (parentItem != nullptr && ownerView != nullptr)
  1575. {
  1576. if (! parentItem->isOpen())
  1577. return parentItem->getRowNumberInTree();
  1578. auto n = 1 + parentItem->getRowNumberInTree();
  1579. auto ourIndex = parentItem->subItems.indexOf (this);
  1580. jassert (ourIndex >= 0);
  1581. while (--ourIndex >= 0)
  1582. n += parentItem->subItems [ourIndex]->getNumRows();
  1583. if (parentItem->parentItem == nullptr
  1584. && ! ownerView->rootItemVisible)
  1585. --n;
  1586. return n;
  1587. }
  1588. return 0;
  1589. }
  1590. void TreeViewItem::setLinesDrawnForSubItems (bool drawLines) noexcept
  1591. {
  1592. drawLinesInside = drawLines;
  1593. drawLinesSet = true;
  1594. }
  1595. static String escapeSlashesInTreeViewItemName (const String& s)
  1596. {
  1597. return s.replaceCharacter ('/', '\\');
  1598. }
  1599. String TreeViewItem::getItemIdentifierString() const
  1600. {
  1601. String s;
  1602. if (parentItem != nullptr)
  1603. s = parentItem->getItemIdentifierString();
  1604. return s + "/" + escapeSlashesInTreeViewItemName (getUniqueName());
  1605. }
  1606. TreeViewItem* TreeViewItem::findItemFromIdentifierString (const String& identifierString)
  1607. {
  1608. auto thisId = "/" + escapeSlashesInTreeViewItemName (getUniqueName());
  1609. if (thisId == identifierString)
  1610. return this;
  1611. if (identifierString.startsWith (thisId + "/"))
  1612. {
  1613. auto remainingPath = identifierString.substring (thisId.length());
  1614. const auto wasOpen = isOpen();
  1615. setOpen (true);
  1616. for (auto* i : subItems)
  1617. if (auto* item = i->findItemFromIdentifierString (remainingPath))
  1618. return item;
  1619. setOpen (wasOpen);
  1620. }
  1621. return nullptr;
  1622. }
  1623. void TreeViewItem::restoreOpennessState (const XmlElement& e)
  1624. {
  1625. if (e.hasTagName ("CLOSED"))
  1626. {
  1627. setOpen (false);
  1628. }
  1629. else if (e.hasTagName ("OPEN"))
  1630. {
  1631. setOpen (true);
  1632. Array<TreeViewItem*> items;
  1633. items.addArray (subItems);
  1634. for (auto* n : e.getChildIterator())
  1635. {
  1636. auto id = n->getStringAttribute ("id");
  1637. for (int i = 0; i < items.size(); ++i)
  1638. {
  1639. auto* ti = items.getUnchecked (i);
  1640. if (ti->getUniqueName() == id)
  1641. {
  1642. ti->restoreOpennessState (*n);
  1643. items.remove (i);
  1644. break;
  1645. }
  1646. }
  1647. }
  1648. // for any items that weren't mentioned in the XML, reset them to default:
  1649. for (auto* i : items)
  1650. i->restoreToDefaultOpenness();
  1651. }
  1652. }
  1653. std::unique_ptr<XmlElement> TreeViewItem::getOpennessState() const
  1654. {
  1655. return getOpennessState (true);
  1656. }
  1657. std::unique_ptr<XmlElement> TreeViewItem::getOpennessState (bool canReturnNull) const
  1658. {
  1659. auto name = getUniqueName();
  1660. if (name.isNotEmpty())
  1661. {
  1662. std::unique_ptr<XmlElement> e;
  1663. if (isOpen())
  1664. {
  1665. if (canReturnNull && ownerView != nullptr && ownerView->defaultOpenness && isFullyOpen())
  1666. return nullptr;
  1667. e = std::make_unique<XmlElement> ("OPEN");
  1668. for (int i = subItems.size(); --i >= 0;)
  1669. e->prependChildElement (subItems.getUnchecked (i)->getOpennessState (true).release());
  1670. }
  1671. else
  1672. {
  1673. if (canReturnNull && ownerView != nullptr && ! ownerView->defaultOpenness)
  1674. return nullptr;
  1675. e = std::make_unique<XmlElement> ("CLOSED");
  1676. }
  1677. e->setAttribute ("id", name);
  1678. return e;
  1679. }
  1680. // trying to save the openness for an element that has no name - this won't
  1681. // work because it needs the names to identify what to open.
  1682. jassertfalse;
  1683. return {};
  1684. }
  1685. //==============================================================================
  1686. TreeViewItem::OpennessRestorer::OpennessRestorer (TreeViewItem& item)
  1687. : treeViewItem (item),
  1688. oldOpenness (item.getOpennessState())
  1689. {
  1690. }
  1691. TreeViewItem::OpennessRestorer::~OpennessRestorer()
  1692. {
  1693. if (oldOpenness != nullptr)
  1694. treeViewItem.restoreOpennessState (*oldOpenness);
  1695. }
  1696. void TreeViewItem::draw (Graphics& g, int width, bool isMouseOverButton)
  1697. {
  1698. if (ownerView == nullptr)
  1699. return;
  1700. const auto indent = getIndentX();
  1701. const auto itemW = (itemWidth < 0 || drawsInRightMargin) ? width - indent : itemWidth;
  1702. {
  1703. Graphics::ScopedSaveState ss (g);
  1704. g.setOrigin (indent, 0);
  1705. if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0,
  1706. drawsInLeftMargin ? itemW + indent : itemW, itemHeight))
  1707. {
  1708. if (isSelected())
  1709. g.fillAll (ownerView->findColour (TreeView::selectedItemBackgroundColourId));
  1710. else
  1711. g.fillAll ((getRowNumberInTree() % 2 == 0) ? ownerView->findColour (TreeView::oddItemsColourId)
  1712. : ownerView->findColour (TreeView::evenItemsColourId));
  1713. paintItem (g, itemWidth < 0 ? width - indent : itemWidth, itemHeight);
  1714. }
  1715. }
  1716. const auto halfH = (float) itemHeight * 0.5f;
  1717. const auto indentWidth = ownerView->getIndentSize();
  1718. const auto depth = getItemDepth (this);
  1719. if (depth >= 0 && ownerView->openCloseButtonsVisible)
  1720. {
  1721. auto x = ((float) depth + 0.5f) * (float) indentWidth;
  1722. const auto parentLinesDrawn = parentItem != nullptr && parentItem->areLinesDrawn();
  1723. if (parentLinesDrawn)
  1724. paintVerticalConnectingLine (g, Line<float> (x, 0, x, isLastOfSiblings() ? halfH : (float) itemHeight));
  1725. if (parentLinesDrawn || (parentItem == nullptr && areLinesDrawn()))
  1726. paintHorizontalConnectingLine (g, Line<float> (x, halfH, x + (float) indentWidth * 0.5f, halfH));
  1727. {
  1728. auto* p = parentItem;
  1729. auto d = depth;
  1730. while (p != nullptr && --d >= 0)
  1731. {
  1732. x -= (float) indentWidth;
  1733. if ((p->parentItem == nullptr || p->parentItem->areLinesDrawn()) && ! p->isLastOfSiblings())
  1734. p->paintVerticalConnectingLine (g, Line<float> (x, 0, x, (float) itemHeight));
  1735. p = p->parentItem;
  1736. }
  1737. }
  1738. if (mightContainSubItems())
  1739. {
  1740. auto backgroundColour = ownerView->findColour (TreeView::backgroundColourId);
  1741. paintOpenCloseButton (g, Rectangle<float> ((float) (depth * indentWidth), 0, (float) indentWidth, (float) itemHeight),
  1742. backgroundColour.isTransparent() ? Colours::white : backgroundColour,
  1743. isMouseOverButton);
  1744. }
  1745. }
  1746. }
  1747. } // namespace juce