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.

1845 lines
50KB

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