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.

1937 lines
54KB

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