The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

2184 lines
65KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. static int getItemDepth (const TreeViewItem* item)
  21. {
  22. if (item == nullptr || item->getOwnerView() == nullptr)
  23. return 0;
  24. auto depth = item->getOwnerView()->isRootItemVisible() ? 0 : -1;
  25. for (auto* parent = item->getParentItem(); parent != nullptr; parent = parent->getParentItem())
  26. ++depth;
  27. return depth;
  28. }
  29. //==============================================================================
  30. class TreeView::ItemComponent : public Component
  31. {
  32. public:
  33. explicit ItemComponent (TreeViewItem& itemToRepresent)
  34. : item (itemToRepresent),
  35. customComponent (item.createItemComponent())
  36. {
  37. if (hasCustomComponent())
  38. addAndMakeVisible (*customComponent);
  39. }
  40. void paint (Graphics& g) override
  41. {
  42. item.draw (g, getWidth(), mouseIsOverButton);
  43. }
  44. void resized() override
  45. {
  46. if (hasCustomComponent())
  47. {
  48. auto itemPosition = item.getItemPosition (false);
  49. customComponent->setBounds (getLocalBounds().withX (itemPosition.getX())
  50. .withWidth (itemPosition.getWidth()));
  51. }
  52. }
  53. void setMouseIsOverButton (bool isOver)
  54. {
  55. mouseIsOverButton = isOver;
  56. repaint();
  57. }
  58. TreeViewItem& getRepresentedItem() const noexcept
  59. {
  60. return item;
  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::invalidPressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
  172. MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
  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, false);
  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