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.

1939 lines
54KB

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