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

1908 lines
52KB

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