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.

2106 lines
63KB

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