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.

1820 lines
49KB

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