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

1928 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. const Array<MouseInputSource>& mouseSources = Desktop::getInstance().getMouseSources();
  304. for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi)
  305. {
  306. if (mi->isDragging())
  307. if (Component* const underMouse = mi->getComponentUnderMouse())
  308. if (comp == underMouse || comp->isParentOf (underMouse))
  309. return true;
  310. }
  311. return false;
  312. }
  313. void handleAsyncUpdate() override
  314. {
  315. owner.recalculateIfNeeded();
  316. }
  317. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentComponent)
  318. };
  319. //==============================================================================
  320. class TreeView::TreeViewport : public Viewport
  321. {
  322. public:
  323. TreeViewport() noexcept : lastX (-1) {}
  324. void updateComponents (const bool triggerResize)
  325. {
  326. if (ContentComponent* const tvc = getContentComp())
  327. {
  328. if (triggerResize)
  329. tvc->resized();
  330. else
  331. tvc->updateComponents();
  332. }
  333. repaint();
  334. }
  335. void visibleAreaChanged (const Rectangle<int>& newVisibleArea) override
  336. {
  337. const bool hasScrolledSideways = (newVisibleArea.getX() != lastX);
  338. lastX = newVisibleArea.getX();
  339. updateComponents (hasScrolledSideways);
  340. }
  341. ContentComponent* getContentComp() const noexcept
  342. {
  343. return static_cast<ContentComponent*> (getViewedComponent());
  344. }
  345. bool keyPressed (const KeyPress& key) override
  346. {
  347. Component* const tree = getParentComponent();
  348. return (tree != nullptr && tree->keyPressed (key))
  349. || Viewport::keyPressed (key);
  350. }
  351. private:
  352. int lastX;
  353. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewport)
  354. };
  355. //==============================================================================
  356. TreeView::TreeView (const String& name)
  357. : Component (name),
  358. viewport (new TreeViewport()),
  359. rootItem (nullptr),
  360. indentSize (-1),
  361. defaultOpenness (false),
  362. needsRecalculating (true),
  363. rootItemVisible (true),
  364. multiSelectEnabled (false),
  365. openCloseButtonsVisible (true)
  366. {
  367. addAndMakeVisible (viewport);
  368. viewport->setViewedComponent (new ContentComponent (*this));
  369. setWantsKeyboardFocus (true);
  370. }
  371. TreeView::~TreeView()
  372. {
  373. if (rootItem != nullptr)
  374. rootItem->setOwnerView (nullptr);
  375. }
  376. void TreeView::setRootItem (TreeViewItem* const newRootItem)
  377. {
  378. if (rootItem != newRootItem)
  379. {
  380. if (newRootItem != nullptr)
  381. {
  382. jassert (newRootItem->ownerView == nullptr); // can't use a tree item in more than one tree at once..
  383. if (newRootItem->ownerView != nullptr)
  384. newRootItem->ownerView->setRootItem (nullptr);
  385. }
  386. if (rootItem != nullptr)
  387. rootItem->setOwnerView (nullptr);
  388. rootItem = newRootItem;
  389. if (newRootItem != nullptr)
  390. newRootItem->setOwnerView (this);
  391. needsRecalculating = true;
  392. recalculateIfNeeded();
  393. if (rootItem != nullptr && (defaultOpenness || ! rootItemVisible))
  394. {
  395. rootItem->setOpen (false); // force a re-open
  396. rootItem->setOpen (true);
  397. }
  398. }
  399. }
  400. void TreeView::deleteRootItem()
  401. {
  402. const ScopedPointer<TreeViewItem> deleter (rootItem);
  403. setRootItem (nullptr);
  404. }
  405. void TreeView::setRootItemVisible (const bool shouldBeVisible)
  406. {
  407. rootItemVisible = shouldBeVisible;
  408. if (rootItem != nullptr && (defaultOpenness || ! rootItemVisible))
  409. {
  410. rootItem->setOpen (false); // force a re-open
  411. rootItem->setOpen (true);
  412. }
  413. itemsChanged();
  414. }
  415. void TreeView::colourChanged()
  416. {
  417. setOpaque (findColour (backgroundColourId).isOpaque());
  418. repaint();
  419. }
  420. void TreeView::setIndentSize (const int newIndentSize)
  421. {
  422. if (indentSize != newIndentSize)
  423. {
  424. indentSize = newIndentSize;
  425. resized();
  426. }
  427. }
  428. int TreeView::getIndentSize() noexcept
  429. {
  430. return indentSize >= 0 ? indentSize
  431. : getLookAndFeel().getTreeViewIndentSize (*this);
  432. }
  433. void TreeView::setDefaultOpenness (const bool isOpenByDefault)
  434. {
  435. if (defaultOpenness != isOpenByDefault)
  436. {
  437. defaultOpenness = isOpenByDefault;
  438. itemsChanged();
  439. }
  440. }
  441. void TreeView::setMultiSelectEnabled (const bool canMultiSelect)
  442. {
  443. multiSelectEnabled = canMultiSelect;
  444. }
  445. void TreeView::setOpenCloseButtonsVisible (const bool shouldBeVisible)
  446. {
  447. if (openCloseButtonsVisible != shouldBeVisible)
  448. {
  449. openCloseButtonsVisible = shouldBeVisible;
  450. itemsChanged();
  451. }
  452. }
  453. Viewport* TreeView::getViewport() const noexcept
  454. {
  455. return viewport;
  456. }
  457. //==============================================================================
  458. void TreeView::clearSelectedItems()
  459. {
  460. if (rootItem != nullptr)
  461. rootItem->deselectAllRecursively (nullptr);
  462. }
  463. int TreeView::getNumSelectedItems (int maximumDepthToSearchTo) const noexcept
  464. {
  465. return rootItem != nullptr ? rootItem->countSelectedItemsRecursively (maximumDepthToSearchTo) : 0;
  466. }
  467. TreeViewItem* TreeView::getSelectedItem (const int index) const noexcept
  468. {
  469. return rootItem != nullptr ? rootItem->getSelectedItemWithIndex (index) : 0;
  470. }
  471. int TreeView::getNumRowsInTree() const
  472. {
  473. return rootItem != nullptr ? (rootItem->getNumRows() - (rootItemVisible ? 0 : 1)) : 0;
  474. }
  475. TreeViewItem* TreeView::getItemOnRow (int index) const
  476. {
  477. if (! rootItemVisible)
  478. ++index;
  479. if (rootItem != nullptr && index >= 0)
  480. return rootItem->getItemOnRow (index);
  481. return nullptr;
  482. }
  483. TreeViewItem* TreeView::getItemAt (int y) const noexcept
  484. {
  485. ContentComponent* const tc = viewport->getContentComp();
  486. Rectangle<int> pos;
  487. return tc->findItemAt (tc->getLocalPoint (this, Point<int> (0, y)).y, pos);
  488. }
  489. TreeViewItem* TreeView::findItemFromIdentifierString (const String& identifierString) const
  490. {
  491. if (rootItem == nullptr)
  492. return nullptr;
  493. return rootItem->findItemFromIdentifierString (identifierString);
  494. }
  495. //==============================================================================
  496. static void addAllSelectedItemIds (TreeViewItem* item, XmlElement& parent)
  497. {
  498. if (item->isSelected())
  499. parent.createNewChildElement ("SELECTED")->setAttribute ("id", item->getItemIdentifierString());
  500. const int numSubItems = item->getNumSubItems();
  501. for (int i = 0; i < numSubItems; ++i)
  502. addAllSelectedItemIds (item->getSubItem(i), parent);
  503. }
  504. XmlElement* TreeView::getOpennessState (const bool alsoIncludeScrollPosition) const
  505. {
  506. XmlElement* e = nullptr;
  507. if (rootItem != nullptr)
  508. {
  509. e = rootItem->getOpennessState (false);
  510. if (e != nullptr)
  511. {
  512. if (alsoIncludeScrollPosition)
  513. e->setAttribute ("scrollPos", viewport->getViewPositionY());
  514. addAllSelectedItemIds (rootItem, *e);
  515. }
  516. }
  517. return e;
  518. }
  519. void TreeView::restoreOpennessState (const XmlElement& newState, const bool restoreStoredSelection)
  520. {
  521. if (rootItem != nullptr)
  522. {
  523. rootItem->restoreOpennessState (newState);
  524. if (newState.hasAttribute ("scrollPos"))
  525. viewport->setViewPosition (viewport->getViewPositionX(),
  526. newState.getIntAttribute ("scrollPos"));
  527. if (restoreStoredSelection)
  528. {
  529. clearSelectedItems();
  530. forEachXmlChildElementWithTagName (newState, e, "SELECTED")
  531. {
  532. if (TreeViewItem* const item = rootItem->findItemFromIdentifierString (e->getStringAttribute ("id")))
  533. item->setSelected (true, false);
  534. }
  535. }
  536. }
  537. }
  538. //==============================================================================
  539. void TreeView::paint (Graphics& g)
  540. {
  541. g.fillAll (findColour (backgroundColourId));
  542. }
  543. void TreeView::resized()
  544. {
  545. viewport->setBounds (getLocalBounds());
  546. itemsChanged();
  547. recalculateIfNeeded();
  548. }
  549. void TreeView::enablementChanged()
  550. {
  551. repaint();
  552. }
  553. void TreeView::moveSelectedRow (const int delta)
  554. {
  555. const int numRowsInTree = getNumRowsInTree();
  556. if (numRowsInTree > 0)
  557. {
  558. int rowSelected = 0;
  559. if (TreeViewItem* const firstSelected = getSelectedItem (0))
  560. rowSelected = firstSelected->getRowNumberInTree();
  561. rowSelected = jlimit (0, numRowsInTree - 1, rowSelected + delta);
  562. for (;;)
  563. {
  564. if (TreeViewItem* const item = getItemOnRow (rowSelected))
  565. {
  566. if (! item->canBeSelected())
  567. {
  568. // if the row we want to highlight doesn't allow it, try skipping
  569. // to the next item..
  570. const int nextRowToTry = jlimit (0, numRowsInTree - 1, rowSelected + (delta < 0 ? -1 : 1));
  571. if (rowSelected != nextRowToTry)
  572. {
  573. rowSelected = nextRowToTry;
  574. continue;
  575. }
  576. break;
  577. }
  578. item->setSelected (true, true);
  579. scrollToKeepItemVisible (item);
  580. }
  581. break;
  582. }
  583. }
  584. }
  585. void TreeView::scrollToKeepItemVisible (TreeViewItem* item)
  586. {
  587. if (item != nullptr && item->ownerView == this)
  588. {
  589. recalculateIfNeeded();
  590. item = item->getDeepestOpenParentItem();
  591. const int y = item->y;
  592. const int viewTop = viewport->getViewPositionY();
  593. if (y < viewTop)
  594. {
  595. viewport->setViewPosition (viewport->getViewPositionX(), y);
  596. }
  597. else if (y + item->itemHeight > viewTop + viewport->getViewHeight())
  598. {
  599. viewport->setViewPosition (viewport->getViewPositionX(),
  600. (y + item->itemHeight) - viewport->getViewHeight());
  601. }
  602. }
  603. }
  604. bool TreeView::toggleOpenSelectedItem()
  605. {
  606. if (TreeViewItem* const firstSelected = getSelectedItem (0))
  607. {
  608. if (firstSelected->mightContainSubItems())
  609. {
  610. firstSelected->setOpen (! firstSelected->isOpen());
  611. return true;
  612. }
  613. }
  614. return false;
  615. }
  616. void TreeView::moveOutOfSelectedItem()
  617. {
  618. if (TreeViewItem* const firstSelected = getSelectedItem (0))
  619. {
  620. if (firstSelected->isOpen())
  621. {
  622. firstSelected->setOpen (false);
  623. }
  624. else
  625. {
  626. TreeViewItem* parent = firstSelected->parentItem;
  627. if ((! rootItemVisible) && parent == rootItem)
  628. parent = nullptr;
  629. if (parent != nullptr)
  630. {
  631. parent->setSelected (true, true);
  632. scrollToKeepItemVisible (parent);
  633. }
  634. }
  635. }
  636. }
  637. void TreeView::moveIntoSelectedItem()
  638. {
  639. if (TreeViewItem* const firstSelected = getSelectedItem (0))
  640. {
  641. if (firstSelected->isOpen() || ! firstSelected->mightContainSubItems())
  642. moveSelectedRow (1);
  643. else
  644. firstSelected->setOpen (true);
  645. }
  646. }
  647. void TreeView::moveByPages (int numPages)
  648. {
  649. if (TreeViewItem* currentItem = getSelectedItem (0))
  650. {
  651. const Rectangle<int> pos (currentItem->getItemPosition (false));
  652. const int targetY = pos.getY() + numPages * (getHeight() - pos.getHeight());
  653. int currentRow = currentItem->getRowNumberInTree();
  654. for (;;)
  655. {
  656. moveSelectedRow (numPages);
  657. currentItem = getSelectedItem (0);
  658. if (currentItem == nullptr)
  659. break;
  660. const int y = currentItem->getItemPosition (false).getY();
  661. if ((numPages < 0 && y <= targetY) || (numPages > 0 && y >= targetY))
  662. break;
  663. const int newRow = currentItem->getRowNumberInTree();
  664. if (newRow == currentRow)
  665. break;
  666. currentRow = newRow;
  667. }
  668. }
  669. }
  670. bool TreeView::keyPressed (const KeyPress& key)
  671. {
  672. if (rootItem != nullptr)
  673. {
  674. if (key == KeyPress::upKey) { moveSelectedRow (-1); return true; }
  675. if (key == KeyPress::downKey) { moveSelectedRow (1); return true; }
  676. if (key == KeyPress::homeKey) { moveSelectedRow (-0x3fffffff); return true; }
  677. if (key == KeyPress::endKey) { moveSelectedRow (0x3fffffff); return true; }
  678. if (key == KeyPress::pageUpKey) { moveByPages (-1); return true; }
  679. if (key == KeyPress::pageDownKey) { moveByPages (1); return true; }
  680. if (key == KeyPress::returnKey) { return toggleOpenSelectedItem(); }
  681. if (key == KeyPress::leftKey) { moveOutOfSelectedItem(); return true; }
  682. if (key == KeyPress::rightKey) { moveIntoSelectedItem(); return true; }
  683. }
  684. return false;
  685. }
  686. void TreeView::itemsChanged() noexcept
  687. {
  688. needsRecalculating = true;
  689. repaint();
  690. viewport->getContentComp()->triggerAsyncUpdate();
  691. }
  692. void TreeView::recalculateIfNeeded()
  693. {
  694. if (needsRecalculating)
  695. {
  696. needsRecalculating = false;
  697. const ScopedLock sl (nodeAlterationLock);
  698. if (rootItem != nullptr)
  699. rootItem->updatePositions (rootItemVisible ? 0 : -rootItem->itemHeight);
  700. viewport->updateComponents (false);
  701. if (rootItem != nullptr)
  702. {
  703. viewport->getViewedComponent()
  704. ->setSize (jmax (viewport->getMaximumVisibleWidth(), rootItem->totalWidth),
  705. rootItem->totalHeight - (rootItemVisible ? 0 : rootItem->itemHeight));
  706. }
  707. else
  708. {
  709. viewport->getViewedComponent()->setSize (0, 0);
  710. }
  711. }
  712. }
  713. //==============================================================================
  714. struct TreeView::InsertPoint
  715. {
  716. InsertPoint (TreeView& view, const StringArray& files,
  717. const DragAndDropTarget::SourceDetails& dragSourceDetails)
  718. : pos (dragSourceDetails.localPosition),
  719. item (view.getItemAt (dragSourceDetails.localPosition.y)),
  720. insertIndex (0)
  721. {
  722. if (item != nullptr)
  723. {
  724. Rectangle<int> itemPos (item->getItemPosition (true));
  725. insertIndex = item->getIndexInParent();
  726. const int oldY = pos.y;
  727. pos.y = itemPos.getY();
  728. if (item->getNumSubItems() == 0 || ! item->isOpen())
  729. {
  730. if (files.size() > 0 ? item->isInterestedInFileDrag (files)
  731. : item->isInterestedInDragSource (dragSourceDetails))
  732. {
  733. // Check if we're trying to drag into an empty group item..
  734. if (oldY > itemPos.getY() + itemPos.getHeight() / 4
  735. && oldY < itemPos.getBottom() - itemPos.getHeight() / 4)
  736. {
  737. insertIndex = 0;
  738. pos.x = itemPos.getX() + view.getIndentSize();
  739. pos.y = itemPos.getBottom();
  740. return;
  741. }
  742. }
  743. }
  744. if (oldY > itemPos.getCentreY())
  745. {
  746. pos.y += item->getItemHeight();
  747. while (item->isLastOfSiblings() && item->getParentItem() != nullptr
  748. && item->getParentItem()->getParentItem() != nullptr)
  749. {
  750. if (pos.x > itemPos.getX())
  751. break;
  752. item = item->getParentItem();
  753. itemPos = item->getItemPosition (true);
  754. insertIndex = item->getIndexInParent();
  755. }
  756. ++insertIndex;
  757. }
  758. pos.x = itemPos.getX();
  759. item = item->getParentItem();
  760. }
  761. else if (TreeViewItem* root = view.getRootItem())
  762. {
  763. // If they're dragging beyond the bottom of the list, then insert at the end of the root item..
  764. item = root;
  765. insertIndex = root->getNumSubItems();
  766. pos = root->getItemPosition (true).getBottomLeft();
  767. pos.x += view.getIndentSize();
  768. }
  769. }
  770. Point<int> pos;
  771. TreeViewItem* item;
  772. int insertIndex;
  773. };
  774. //==============================================================================
  775. class TreeView::InsertPointHighlight : public Component
  776. {
  777. public:
  778. InsertPointHighlight()
  779. : lastItem (nullptr), lastIndex (0)
  780. {
  781. setSize (100, 12);
  782. setAlwaysOnTop (true);
  783. setInterceptsMouseClicks (false, false);
  784. }
  785. void setTargetPosition (const InsertPoint& insertPos, const int width) noexcept
  786. {
  787. lastItem = insertPos.item;
  788. lastIndex = insertPos.insertIndex;
  789. const int offset = getHeight() / 2;
  790. setBounds (insertPos.pos.x - offset, insertPos.pos.y - offset,
  791. width - (insertPos.pos.x - offset), getHeight());
  792. }
  793. void paint (Graphics& g) override
  794. {
  795. Path p;
  796. const float h = (float) getHeight();
  797. p.addEllipse (2.0f, 2.0f, h - 4.0f, h - 4.0f);
  798. p.startNewSubPath (h - 2.0f, h / 2.0f);
  799. p.lineTo ((float) getWidth(), h / 2.0f);
  800. g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
  801. g.strokePath (p, PathStrokeType (2.0f));
  802. }
  803. TreeViewItem* lastItem;
  804. int lastIndex;
  805. private:
  806. JUCE_DECLARE_NON_COPYABLE (InsertPointHighlight)
  807. };
  808. //==============================================================================
  809. class TreeView::TargetGroupHighlight : public Component
  810. {
  811. public:
  812. TargetGroupHighlight()
  813. {
  814. setAlwaysOnTop (true);
  815. setInterceptsMouseClicks (false, false);
  816. }
  817. void setTargetPosition (TreeViewItem* const item) noexcept
  818. {
  819. Rectangle<int> r (item->getItemPosition (true));
  820. r.setHeight (item->getItemHeight());
  821. setBounds (r);
  822. }
  823. void paint (Graphics& g) override
  824. {
  825. g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true));
  826. g.drawRoundedRectangle (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 3.0f, 2.0f);
  827. }
  828. private:
  829. JUCE_DECLARE_NON_COPYABLE (TargetGroupHighlight)
  830. };
  831. //==============================================================================
  832. void TreeView::showDragHighlight (const InsertPoint& insertPos) noexcept
  833. {
  834. beginDragAutoRepeat (100);
  835. if (dragInsertPointHighlight == nullptr)
  836. {
  837. addAndMakeVisible (dragInsertPointHighlight = new InsertPointHighlight());
  838. addAndMakeVisible (dragTargetGroupHighlight = new TargetGroupHighlight());
  839. }
  840. dragInsertPointHighlight->setTargetPosition (insertPos, viewport->getViewWidth());
  841. dragTargetGroupHighlight->setTargetPosition (insertPos.item);
  842. }
  843. void TreeView::hideDragHighlight() noexcept
  844. {
  845. dragInsertPointHighlight = nullptr;
  846. dragTargetGroupHighlight = nullptr;
  847. }
  848. void TreeView::handleDrag (const StringArray& files, const SourceDetails& dragSourceDetails)
  849. {
  850. const bool scrolled = viewport->autoScroll (dragSourceDetails.localPosition.x,
  851. dragSourceDetails.localPosition.y, 20, 10);
  852. InsertPoint insertPos (*this, files, dragSourceDetails);
  853. if (insertPos.item != nullptr)
  854. {
  855. if (scrolled || dragInsertPointHighlight == nullptr
  856. || dragInsertPointHighlight->lastItem != insertPos.item
  857. || dragInsertPointHighlight->lastIndex != insertPos.insertIndex)
  858. {
  859. if (files.size() > 0 ? insertPos.item->isInterestedInFileDrag (files)
  860. : insertPos.item->isInterestedInDragSource (dragSourceDetails))
  861. showDragHighlight (insertPos);
  862. else
  863. hideDragHighlight();
  864. }
  865. }
  866. else
  867. {
  868. hideDragHighlight();
  869. }
  870. }
  871. void TreeView::handleDrop (const StringArray& files, const SourceDetails& dragSourceDetails)
  872. {
  873. hideDragHighlight();
  874. InsertPoint insertPos (*this, files, dragSourceDetails);
  875. if (insertPos.item == nullptr)
  876. insertPos.item = rootItem;
  877. if (insertPos.item != nullptr)
  878. {
  879. if (files.size() > 0)
  880. {
  881. if (insertPos.item->isInterestedInFileDrag (files))
  882. insertPos.item->filesDropped (files, insertPos.insertIndex);
  883. }
  884. else
  885. {
  886. if (insertPos.item->isInterestedInDragSource (dragSourceDetails))
  887. insertPos.item->itemDropped (dragSourceDetails, insertPos.insertIndex);
  888. }
  889. }
  890. }
  891. //==============================================================================
  892. bool TreeView::isInterestedInFileDrag (const StringArray&)
  893. {
  894. return true;
  895. }
  896. void TreeView::fileDragEnter (const StringArray& files, int x, int y)
  897. {
  898. fileDragMove (files, x, y);
  899. }
  900. void TreeView::fileDragMove (const StringArray& files, int x, int y)
  901. {
  902. handleDrag (files, SourceDetails (String::empty, this, Point<int> (x, y)));
  903. }
  904. void TreeView::fileDragExit (const StringArray&)
  905. {
  906. hideDragHighlight();
  907. }
  908. void TreeView::filesDropped (const StringArray& files, int x, int y)
  909. {
  910. handleDrop (files, SourceDetails (String::empty, this, Point<int> (x, y)));
  911. }
  912. bool TreeView::isInterestedInDragSource (const SourceDetails& /*dragSourceDetails*/)
  913. {
  914. return true;
  915. }
  916. void TreeView::itemDragEnter (const SourceDetails& dragSourceDetails)
  917. {
  918. itemDragMove (dragSourceDetails);
  919. }
  920. void TreeView::itemDragMove (const SourceDetails& dragSourceDetails)
  921. {
  922. handleDrag (StringArray(), dragSourceDetails);
  923. }
  924. void TreeView::itemDragExit (const SourceDetails& /*dragSourceDetails*/)
  925. {
  926. hideDragHighlight();
  927. }
  928. void TreeView::itemDropped (const SourceDetails& dragSourceDetails)
  929. {
  930. handleDrop (StringArray(), dragSourceDetails);
  931. }
  932. //==============================================================================
  933. TreeViewItem::TreeViewItem()
  934. : ownerView (nullptr),
  935. parentItem (nullptr),
  936. y (0),
  937. itemHeight (0),
  938. totalHeight (0),
  939. itemWidth (0),
  940. totalWidth (0),
  941. selected (false),
  942. redrawNeeded (true),
  943. drawLinesInside (false),
  944. drawLinesSet (false),
  945. drawsInLeftMargin (false),
  946. drawsInRightMargin (false),
  947. openness (opennessDefault)
  948. {
  949. static int nextUID = 0;
  950. uid = nextUID++;
  951. }
  952. TreeViewItem::~TreeViewItem()
  953. {
  954. }
  955. String TreeViewItem::getUniqueName() const
  956. {
  957. return String::empty;
  958. }
  959. void TreeViewItem::itemOpennessChanged (bool)
  960. {
  961. }
  962. int TreeViewItem::getNumSubItems() const noexcept
  963. {
  964. return subItems.size();
  965. }
  966. TreeViewItem* TreeViewItem::getSubItem (const int index) const noexcept
  967. {
  968. return subItems [index];
  969. }
  970. void TreeViewItem::clearSubItems()
  971. {
  972. if (ownerView != nullptr)
  973. {
  974. const ScopedLock sl (ownerView->nodeAlterationLock);
  975. if (subItems.size() > 0)
  976. {
  977. removeAllSubItemsFromList();
  978. treeHasChanged();
  979. }
  980. }
  981. else
  982. {
  983. removeAllSubItemsFromList();
  984. }
  985. }
  986. void TreeViewItem::removeAllSubItemsFromList()
  987. {
  988. for (int i = subItems.size(); --i >= 0;)
  989. removeSubItemFromList (i, true);
  990. }
  991. void TreeViewItem::addSubItem (TreeViewItem* const newItem, const int insertPosition)
  992. {
  993. if (newItem != nullptr)
  994. {
  995. newItem->parentItem = this;
  996. newItem->setOwnerView (ownerView);
  997. newItem->y = 0;
  998. newItem->itemHeight = newItem->getItemHeight();
  999. newItem->totalHeight = 0;
  1000. newItem->itemWidth = newItem->getItemWidth();
  1001. newItem->totalWidth = 0;
  1002. if (ownerView != nullptr)
  1003. {
  1004. const ScopedLock sl (ownerView->nodeAlterationLock);
  1005. subItems.insert (insertPosition, newItem);
  1006. treeHasChanged();
  1007. if (newItem->isOpen())
  1008. newItem->itemOpennessChanged (true);
  1009. }
  1010. else
  1011. {
  1012. subItems.insert (insertPosition, newItem);
  1013. if (newItem->isOpen())
  1014. newItem->itemOpennessChanged (true);
  1015. }
  1016. }
  1017. }
  1018. void TreeViewItem::removeSubItem (int index, bool deleteItem)
  1019. {
  1020. if (ownerView != nullptr)
  1021. {
  1022. const ScopedLock sl (ownerView->nodeAlterationLock);
  1023. if (removeSubItemFromList (index, deleteItem))
  1024. treeHasChanged();
  1025. }
  1026. else
  1027. {
  1028. removeSubItemFromList (index, deleteItem);
  1029. }
  1030. }
  1031. bool TreeViewItem::removeSubItemFromList (int index, bool deleteItem)
  1032. {
  1033. if (TreeViewItem* child = subItems [index])
  1034. {
  1035. child->parentItem = nullptr;
  1036. subItems.remove (index, deleteItem);
  1037. return true;
  1038. }
  1039. return false;
  1040. }
  1041. TreeViewItem::Openness TreeViewItem::getOpenness() const noexcept
  1042. {
  1043. return (Openness) openness;
  1044. }
  1045. void TreeViewItem::setOpenness (Openness newOpenness)
  1046. {
  1047. const bool wasOpen = isOpen();
  1048. openness = newOpenness;
  1049. const bool isNowOpen = isOpen();
  1050. if (isNowOpen != wasOpen)
  1051. {
  1052. treeHasChanged();
  1053. itemOpennessChanged (isNowOpen);
  1054. }
  1055. }
  1056. bool TreeViewItem::isOpen() const noexcept
  1057. {
  1058. if (openness == opennessDefault)
  1059. return ownerView != nullptr && ownerView->defaultOpenness;
  1060. return openness == opennessOpen;
  1061. }
  1062. void TreeViewItem::setOpen (const bool shouldBeOpen)
  1063. {
  1064. if (isOpen() != shouldBeOpen)
  1065. setOpenness (shouldBeOpen ? opennessOpen
  1066. : opennessClosed);
  1067. }
  1068. bool TreeViewItem::isFullyOpen() const noexcept
  1069. {
  1070. if (! isOpen())
  1071. return false;
  1072. for (int i = 0; i < subItems.size(); ++i)
  1073. if (! subItems.getUnchecked(i)->isFullyOpen())
  1074. return false;
  1075. return true;
  1076. }
  1077. void TreeViewItem::restoreToDefaultOpenness()
  1078. {
  1079. setOpenness (opennessDefault);
  1080. }
  1081. bool TreeViewItem::isSelected() const noexcept
  1082. {
  1083. return selected;
  1084. }
  1085. void TreeViewItem::deselectAllRecursively (TreeViewItem* itemToIgnore)
  1086. {
  1087. if (this != itemToIgnore)
  1088. setSelected (false, false);
  1089. for (int i = 0; i < subItems.size(); ++i)
  1090. subItems.getUnchecked(i)->deselectAllRecursively (itemToIgnore);
  1091. }
  1092. void TreeViewItem::setSelected (const bool shouldBeSelected,
  1093. const bool deselectOtherItemsFirst,
  1094. const NotificationType notify)
  1095. {
  1096. if (shouldBeSelected && ! canBeSelected())
  1097. return;
  1098. if (deselectOtherItemsFirst)
  1099. getTopLevelItem()->deselectAllRecursively (this);
  1100. if (shouldBeSelected != selected)
  1101. {
  1102. selected = shouldBeSelected;
  1103. if (ownerView != nullptr)
  1104. ownerView->repaint();
  1105. if (notify != dontSendNotification)
  1106. itemSelectionChanged (shouldBeSelected);
  1107. }
  1108. }
  1109. void TreeViewItem::paintItem (Graphics&, int, int)
  1110. {
  1111. }
  1112. void TreeViewItem::paintOpenCloseButton (Graphics& g, const Rectangle<float>& area, Colour backgroundColour, bool isMouseOver)
  1113. {
  1114. getOwnerView()->getLookAndFeel()
  1115. .drawTreeviewPlusMinusBox (g, area, backgroundColour, isOpen(), isMouseOver);
  1116. }
  1117. void TreeViewItem::paintHorizontalConnectingLine (Graphics& g, const Line<float>& line)
  1118. {
  1119. g.setColour (ownerView->findColour (TreeView::linesColourId));
  1120. g.drawLine (line);
  1121. }
  1122. void TreeViewItem::paintVerticalConnectingLine (Graphics& g, const Line<float>& line)
  1123. {
  1124. g.setColour (ownerView->findColour (TreeView::linesColourId));
  1125. g.drawLine (line);
  1126. }
  1127. void TreeViewItem::itemClicked (const MouseEvent&)
  1128. {
  1129. }
  1130. void TreeViewItem::itemDoubleClicked (const MouseEvent&)
  1131. {
  1132. if (mightContainSubItems())
  1133. setOpen (! isOpen());
  1134. }
  1135. void TreeViewItem::itemSelectionChanged (bool)
  1136. {
  1137. }
  1138. String TreeViewItem::getTooltip()
  1139. {
  1140. return String::empty;
  1141. }
  1142. void TreeViewItem::ownerViewChanged (TreeView*)
  1143. {
  1144. }
  1145. var TreeViewItem::getDragSourceDescription()
  1146. {
  1147. return var();
  1148. }
  1149. bool TreeViewItem::isInterestedInFileDrag (const StringArray&)
  1150. {
  1151. return false;
  1152. }
  1153. void TreeViewItem::filesDropped (const StringArray& /*files*/, int /*insertIndex*/)
  1154. {
  1155. }
  1156. bool TreeViewItem::isInterestedInDragSource (const DragAndDropTarget::SourceDetails& /*dragSourceDetails*/)
  1157. {
  1158. return false;
  1159. }
  1160. void TreeViewItem::itemDropped (const DragAndDropTarget::SourceDetails& /*dragSourceDetails*/, int /*insertIndex*/)
  1161. {
  1162. }
  1163. Rectangle<int> TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const noexcept
  1164. {
  1165. const int indentX = getIndentX();
  1166. int width = itemWidth;
  1167. if (ownerView != nullptr && width < 0)
  1168. width = ownerView->viewport->getViewWidth() - indentX;
  1169. Rectangle<int> r (indentX, y, jmax (0, width), totalHeight);
  1170. if (relativeToTreeViewTopLeft && ownerView != nullptr)
  1171. r -= ownerView->viewport->getViewPosition();
  1172. return r;
  1173. }
  1174. void TreeViewItem::treeHasChanged() const noexcept
  1175. {
  1176. if (ownerView != nullptr)
  1177. ownerView->itemsChanged();
  1178. }
  1179. void TreeViewItem::repaintItem() const
  1180. {
  1181. if (ownerView != nullptr && areAllParentsOpen())
  1182. {
  1183. Rectangle<int> r (getItemPosition (true));
  1184. r.setLeft (0);
  1185. ownerView->viewport->repaint (r);
  1186. }
  1187. }
  1188. bool TreeViewItem::areAllParentsOpen() const noexcept
  1189. {
  1190. return parentItem == nullptr
  1191. || (parentItem->isOpen() && parentItem->areAllParentsOpen());
  1192. }
  1193. void TreeViewItem::updatePositions (int newY)
  1194. {
  1195. y = newY;
  1196. itemHeight = getItemHeight();
  1197. totalHeight = itemHeight;
  1198. itemWidth = getItemWidth();
  1199. totalWidth = jmax (itemWidth, 0) + getIndentX();
  1200. if (isOpen())
  1201. {
  1202. newY += totalHeight;
  1203. for (int i = 0; i < subItems.size(); ++i)
  1204. {
  1205. TreeViewItem* const ti = subItems.getUnchecked(i);
  1206. ti->updatePositions (newY);
  1207. newY += ti->totalHeight;
  1208. totalHeight += ti->totalHeight;
  1209. totalWidth = jmax (totalWidth, ti->totalWidth);
  1210. }
  1211. }
  1212. }
  1213. TreeViewItem* TreeViewItem::getDeepestOpenParentItem() noexcept
  1214. {
  1215. TreeViewItem* result = this;
  1216. TreeViewItem* item = this;
  1217. while (item->parentItem != nullptr)
  1218. {
  1219. item = item->parentItem;
  1220. if (! item->isOpen())
  1221. result = item;
  1222. }
  1223. return result;
  1224. }
  1225. void TreeViewItem::setOwnerView (TreeView* const newOwner) noexcept
  1226. {
  1227. ownerView = newOwner;
  1228. for (int i = subItems.size(); --i >= 0;)
  1229. {
  1230. TreeViewItem* subItem = subItems.getUnchecked(i);
  1231. subItem->setOwnerView (newOwner);
  1232. subItem->ownerViewChanged (newOwner);
  1233. }
  1234. }
  1235. int TreeViewItem::getIndentX() const noexcept
  1236. {
  1237. int x = ownerView->rootItemVisible ? 1 : 0;
  1238. if (! ownerView->openCloseButtonsVisible)
  1239. --x;
  1240. for (TreeViewItem* p = parentItem; p != nullptr; p = p->parentItem)
  1241. ++x;
  1242. return x * ownerView->getIndentSize();
  1243. }
  1244. void TreeViewItem::setDrawsInLeftMargin (bool canDrawInLeftMargin) noexcept
  1245. {
  1246. drawsInLeftMargin = canDrawInLeftMargin;
  1247. }
  1248. void TreeViewItem::setDrawsInRightMargin (bool canDrawInRightMargin) noexcept
  1249. {
  1250. drawsInRightMargin = canDrawInRightMargin;
  1251. }
  1252. namespace TreeViewHelpers
  1253. {
  1254. static int calculateDepth (const TreeViewItem* item, const bool rootIsVisible) noexcept
  1255. {
  1256. jassert (item != nullptr);
  1257. int depth = rootIsVisible ? 0 : -1;
  1258. for (const TreeViewItem* p = item->getParentItem(); p != nullptr; p = p->getParentItem())
  1259. ++depth;
  1260. return depth;
  1261. }
  1262. }
  1263. bool TreeViewItem::areLinesDrawn() const
  1264. {
  1265. return drawLinesSet ? drawLinesInside
  1266. : (ownerView != nullptr && ownerView->getLookAndFeel().areLinesDrawnForTreeView (*ownerView));
  1267. }
  1268. void TreeViewItem::paintRecursively (Graphics& g, int width)
  1269. {
  1270. jassert (ownerView != nullptr);
  1271. if (ownerView == nullptr)
  1272. return;
  1273. const int indent = getIndentX();
  1274. const int itemW = (itemWidth < 0 || drawsInRightMargin) ? width - indent : itemWidth;
  1275. {
  1276. Graphics::ScopedSaveState ss (g);
  1277. g.setOrigin (indent, 0);
  1278. if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0,
  1279. drawsInLeftMargin ? itemW + indent : itemW, itemHeight))
  1280. {
  1281. if (isSelected())
  1282. g.fillAll (ownerView->findColour (TreeView::selectedItemBackgroundColourId));
  1283. paintItem (g, itemWidth < 0 ? width - indent : itemWidth, itemHeight);
  1284. }
  1285. }
  1286. const float halfH = itemHeight * 0.5f;
  1287. const int indentWidth = ownerView->getIndentSize();
  1288. const int depth = TreeViewHelpers::calculateDepth (this, ownerView->rootItemVisible);
  1289. if (depth >= 0 && ownerView->openCloseButtonsVisible)
  1290. {
  1291. float x = (depth + 0.5f) * indentWidth;
  1292. const bool parentLinesDrawn = parentItem != nullptr && parentItem->areLinesDrawn();
  1293. if (parentLinesDrawn)
  1294. paintVerticalConnectingLine (g, Line<float> (x, 0, x, isLastOfSiblings() ? halfH : (float) itemHeight));
  1295. if (parentLinesDrawn || (parentItem == nullptr && areLinesDrawn()))
  1296. paintHorizontalConnectingLine (g, Line<float> (x, halfH, x + indentWidth / 2, halfH));
  1297. {
  1298. TreeViewItem* p = parentItem;
  1299. int d = depth;
  1300. while (p != nullptr && --d >= 0)
  1301. {
  1302. x -= (float) indentWidth;
  1303. if ((p->parentItem == nullptr || p->parentItem->areLinesDrawn()) && ! p->isLastOfSiblings())
  1304. p->paintVerticalConnectingLine (g, Line<float> (x, 0, x, (float) itemHeight));
  1305. p = p->parentItem;
  1306. }
  1307. }
  1308. if (mightContainSubItems())
  1309. paintOpenCloseButton (g, Rectangle<float> ((float) (depth * indentWidth), 0, (float) indentWidth, (float) itemHeight),
  1310. Colours::white,
  1311. ownerView->viewport->getContentComp()->isMouseOverButton (this));
  1312. }
  1313. if (isOpen())
  1314. {
  1315. const Rectangle<int> clip (g.getClipBounds());
  1316. for (int i = 0; i < subItems.size(); ++i)
  1317. {
  1318. TreeViewItem* const ti = subItems.getUnchecked(i);
  1319. const int relY = ti->y - y;
  1320. if (relY >= clip.getBottom())
  1321. break;
  1322. if (relY + ti->totalHeight >= clip.getY())
  1323. {
  1324. Graphics::ScopedSaveState ss (g);
  1325. g.setOrigin (0, relY);
  1326. if (g.reduceClipRegion (0, 0, width, ti->totalHeight))
  1327. ti->paintRecursively (g, width);
  1328. }
  1329. }
  1330. }
  1331. }
  1332. bool TreeViewItem::isLastOfSiblings() const noexcept
  1333. {
  1334. return parentItem == nullptr
  1335. || parentItem->subItems.getLast() == this;
  1336. }
  1337. int TreeViewItem::getIndexInParent() const noexcept
  1338. {
  1339. return parentItem == nullptr ? 0
  1340. : parentItem->subItems.indexOf (this);
  1341. }
  1342. TreeViewItem* TreeViewItem::getTopLevelItem() noexcept
  1343. {
  1344. return parentItem == nullptr ? this
  1345. : parentItem->getTopLevelItem();
  1346. }
  1347. int TreeViewItem::getNumRows() const noexcept
  1348. {
  1349. int num = 1;
  1350. if (isOpen())
  1351. {
  1352. for (int i = subItems.size(); --i >= 0;)
  1353. num += subItems.getUnchecked(i)->getNumRows();
  1354. }
  1355. return num;
  1356. }
  1357. TreeViewItem* TreeViewItem::getItemOnRow (int index) noexcept
  1358. {
  1359. if (index == 0)
  1360. return this;
  1361. if (index > 0 && isOpen())
  1362. {
  1363. --index;
  1364. for (int i = 0; i < subItems.size(); ++i)
  1365. {
  1366. TreeViewItem* const item = subItems.getUnchecked(i);
  1367. if (index == 0)
  1368. return item;
  1369. const int numRows = item->getNumRows();
  1370. if (numRows > index)
  1371. return item->getItemOnRow (index);
  1372. index -= numRows;
  1373. }
  1374. }
  1375. return nullptr;
  1376. }
  1377. TreeViewItem* TreeViewItem::findItemRecursively (int targetY) noexcept
  1378. {
  1379. if (isPositiveAndBelow (targetY, totalHeight))
  1380. {
  1381. const int h = itemHeight;
  1382. if (targetY < h)
  1383. return this;
  1384. if (isOpen())
  1385. {
  1386. targetY -= h;
  1387. for (int i = 0; i < subItems.size(); ++i)
  1388. {
  1389. TreeViewItem* const ti = subItems.getUnchecked(i);
  1390. if (targetY < ti->totalHeight)
  1391. return ti->findItemRecursively (targetY);
  1392. targetY -= ti->totalHeight;
  1393. }
  1394. }
  1395. }
  1396. return nullptr;
  1397. }
  1398. int TreeViewItem::countSelectedItemsRecursively (int depth) const noexcept
  1399. {
  1400. int total = isSelected() ? 1 : 0;
  1401. if (depth != 0)
  1402. for (int i = subItems.size(); --i >= 0;)
  1403. total += subItems.getUnchecked(i)->countSelectedItemsRecursively (depth - 1);
  1404. return total;
  1405. }
  1406. TreeViewItem* TreeViewItem::getSelectedItemWithIndex (int index) noexcept
  1407. {
  1408. if (isSelected())
  1409. {
  1410. if (index == 0)
  1411. return this;
  1412. --index;
  1413. }
  1414. if (index >= 0)
  1415. {
  1416. for (int i = 0; i < subItems.size(); ++i)
  1417. {
  1418. TreeViewItem* const item = subItems.getUnchecked(i);
  1419. if (TreeViewItem* const found = item->getSelectedItemWithIndex (index))
  1420. return found;
  1421. index -= item->countSelectedItemsRecursively (-1);
  1422. }
  1423. }
  1424. return nullptr;
  1425. }
  1426. int TreeViewItem::getRowNumberInTree() const noexcept
  1427. {
  1428. if (parentItem != nullptr && ownerView != nullptr)
  1429. {
  1430. int n = 1 + parentItem->getRowNumberInTree();
  1431. int ourIndex = parentItem->subItems.indexOf (this);
  1432. jassert (ourIndex >= 0);
  1433. while (--ourIndex >= 0)
  1434. n += parentItem->subItems [ourIndex]->getNumRows();
  1435. if (parentItem->parentItem == nullptr
  1436. && ! ownerView->rootItemVisible)
  1437. --n;
  1438. return n;
  1439. }
  1440. return 0;
  1441. }
  1442. void TreeViewItem::setLinesDrawnForSubItems (const bool drawLines) noexcept
  1443. {
  1444. drawLinesInside = drawLines;
  1445. drawLinesSet = true;
  1446. }
  1447. TreeViewItem* TreeViewItem::getNextVisibleItem (const bool recurse) const noexcept
  1448. {
  1449. if (recurse && isOpen() && subItems.size() > 0)
  1450. return subItems [0];
  1451. if (parentItem != nullptr)
  1452. {
  1453. const int nextIndex = parentItem->subItems.indexOf (this) + 1;
  1454. if (nextIndex >= parentItem->subItems.size())
  1455. return parentItem->getNextVisibleItem (false);
  1456. return parentItem->subItems [nextIndex];
  1457. }
  1458. return nullptr;
  1459. }
  1460. static String escapeSlashesInTreeViewItemName (const String& s)
  1461. {
  1462. return s.replaceCharacter ('/', '\\');
  1463. }
  1464. String TreeViewItem::getItemIdentifierString() const
  1465. {
  1466. String s;
  1467. if (parentItem != nullptr)
  1468. s = parentItem->getItemIdentifierString();
  1469. return s + "/" + escapeSlashesInTreeViewItemName (getUniqueName());
  1470. }
  1471. TreeViewItem* TreeViewItem::findItemFromIdentifierString (const String& identifierString)
  1472. {
  1473. const String thisId ("/" + escapeSlashesInTreeViewItemName (getUniqueName()));
  1474. if (thisId == identifierString)
  1475. return this;
  1476. if (identifierString.startsWith (thisId + "/"))
  1477. {
  1478. const String remainingPath (identifierString.substring (thisId.length()));
  1479. const bool wasOpen = isOpen();
  1480. setOpen (true);
  1481. for (int i = subItems.size(); --i >= 0;)
  1482. if (TreeViewItem* item = subItems.getUnchecked(i)->findItemFromIdentifierString (remainingPath))
  1483. return item;
  1484. setOpen (wasOpen);
  1485. }
  1486. return nullptr;
  1487. }
  1488. void TreeViewItem::restoreOpennessState (const XmlElement& e)
  1489. {
  1490. if (e.hasTagName ("CLOSED"))
  1491. {
  1492. setOpen (false);
  1493. }
  1494. else if (e.hasTagName ("OPEN"))
  1495. {
  1496. setOpen (true);
  1497. Array<TreeViewItem*> items;
  1498. items.addArray (subItems);
  1499. forEachXmlChildElement (e, n)
  1500. {
  1501. const String id (n->getStringAttribute ("id"));
  1502. for (int i = 0; i < items.size(); ++i)
  1503. {
  1504. TreeViewItem* const ti = items.getUnchecked(i);
  1505. if (ti->getUniqueName() == id)
  1506. {
  1507. ti->restoreOpennessState (*n);
  1508. items.remove (i);
  1509. break;
  1510. }
  1511. }
  1512. }
  1513. // for any items that weren't mentioned in the XML, reset them to default:
  1514. for (int i = 0; i < items.size(); ++i)
  1515. items.getUnchecked(i)->restoreToDefaultOpenness();
  1516. }
  1517. }
  1518. XmlElement* TreeViewItem::getOpennessState() const
  1519. {
  1520. return getOpennessState (true);
  1521. }
  1522. XmlElement* TreeViewItem::getOpennessState (const bool canReturnNull) const
  1523. {
  1524. const String name (getUniqueName());
  1525. if (name.isNotEmpty())
  1526. {
  1527. XmlElement* e;
  1528. if (isOpen())
  1529. {
  1530. if (canReturnNull && ownerView != nullptr && ownerView->defaultOpenness && isFullyOpen())
  1531. return nullptr;
  1532. e = new XmlElement ("OPEN");
  1533. for (int i = subItems.size(); --i >= 0;)
  1534. e->prependChildElement (subItems.getUnchecked(i)->getOpennessState (true));
  1535. }
  1536. else
  1537. {
  1538. if (canReturnNull && ownerView != nullptr && ! ownerView->defaultOpenness)
  1539. return nullptr;
  1540. e = new XmlElement ("CLOSED");
  1541. }
  1542. e->setAttribute ("id", name);
  1543. return e;
  1544. }
  1545. // trying to save the openness for an element that has no name - this won't
  1546. // work because it needs the names to identify what to open.
  1547. jassertfalse;
  1548. return nullptr;
  1549. }
  1550. //==============================================================================
  1551. TreeViewItem::OpennessRestorer::OpennessRestorer (TreeViewItem& item)
  1552. : treeViewItem (item),
  1553. oldOpenness (item.getOpennessState())
  1554. {
  1555. }
  1556. TreeViewItem::OpennessRestorer::~OpennessRestorer()
  1557. {
  1558. if (oldOpenness != nullptr)
  1559. treeViewItem.restoreOpennessState (*oldOpenness);
  1560. }