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.

1464 lines
38KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_TreeView.h"
  21. #include "../lookandfeel/juce_LookAndFeel.h"
  22. #include "../../../containers/juce_BitArray.h"
  23. #include "../mouse/juce_DragAndDropContainer.h"
  24. #include "../../graphics/imaging/juce_Image.h"
  25. //==============================================================================
  26. class TreeViewContentComponent : public Component,
  27. public TooltipClient
  28. {
  29. public:
  30. TreeViewContentComponent (TreeView* const owner_)
  31. : owner (owner_),
  32. buttonUnderMouse (0),
  33. isDragging (false)
  34. {
  35. }
  36. ~TreeViewContentComponent()
  37. {
  38. deleteAllChildren();
  39. }
  40. void mouseDown (const MouseEvent& e)
  41. {
  42. updateButtonUnderMouse (e);
  43. isDragging = false;
  44. needSelectionOnMouseUp = false;
  45. Rectangle pos;
  46. TreeViewItem* const item = findItemAt (e.y, pos);
  47. if (item == 0)
  48. return;
  49. // (if the open/close buttons are hidden, we'll treat clicks to the left of the item
  50. // as selection clicks)
  51. if (e.x < pos.getX() && owner->openCloseButtonsVisible)
  52. {
  53. if (e.x >= pos.getX() - owner->getIndentSize())
  54. item->setOpen (! item->isOpen());
  55. // (clicks to the left of an open/close button are ignored)
  56. }
  57. else
  58. {
  59. // mouse-down inside the body of the item..
  60. if (! owner->isMultiSelectEnabled())
  61. item->setSelected (true, true);
  62. else if (item->isSelected())
  63. needSelectionOnMouseUp = ! e.mods.isPopupMenu();
  64. else
  65. selectBasedOnModifiers (item, e.mods);
  66. MouseEvent e2 (e);
  67. e2.x -= pos.getX();
  68. e2.y -= pos.getY();
  69. if (e2.x >= 0)
  70. item->itemClicked (e2);
  71. }
  72. }
  73. void mouseUp (const MouseEvent& e)
  74. {
  75. updateButtonUnderMouse (e);
  76. if (needSelectionOnMouseUp && e.mouseWasClicked())
  77. {
  78. Rectangle pos;
  79. TreeViewItem* const item = findItemAt (e.y, pos);
  80. if (item != 0)
  81. selectBasedOnModifiers (item, e.mods);
  82. }
  83. }
  84. void mouseDoubleClick (const MouseEvent& e)
  85. {
  86. if (e.getNumberOfClicks() != 3) // ignore triple clicks
  87. {
  88. Rectangle pos;
  89. TreeViewItem* const item = findItemAt (e.y, pos);
  90. if (item != 0 && (e.x >= pos.getX() || ! owner->openCloseButtonsVisible))
  91. {
  92. MouseEvent e2 (e);
  93. e2.x -= pos.getX();
  94. e2.y -= pos.getY();
  95. item->itemDoubleClicked (e2);
  96. }
  97. }
  98. }
  99. void mouseDrag (const MouseEvent& e)
  100. {
  101. if (isEnabled() && ! (e.mouseWasClicked() || isDragging))
  102. {
  103. isDragging = true;
  104. Rectangle pos;
  105. TreeViewItem* const item = findItemAt (e.getMouseDownY(), pos);
  106. if (item != 0 && e.getMouseDownX() >= pos.getX())
  107. {
  108. const String dragDescription (item->getDragSourceDescription());
  109. if (dragDescription.isNotEmpty())
  110. {
  111. DragAndDropContainer* const dragContainer
  112. = DragAndDropContainer::findParentDragContainerFor (this);
  113. if (dragContainer != 0)
  114. {
  115. pos.setSize (pos.getWidth(), item->itemHeight);
  116. Image* dragImage = Component::createComponentSnapshot (pos, true);
  117. dragImage->multiplyAllAlphas (0.6f);
  118. dragContainer->startDragging (dragDescription, owner, dragImage, true);
  119. }
  120. else
  121. {
  122. // to be able to do a drag-and-drop operation, the treeview needs to
  123. // be inside a component which is also a DragAndDropContainer.
  124. jassertfalse
  125. }
  126. }
  127. }
  128. }
  129. }
  130. void mouseMove (const MouseEvent& e)
  131. {
  132. updateButtonUnderMouse (e);
  133. }
  134. void mouseExit (const MouseEvent& e)
  135. {
  136. updateButtonUnderMouse (e);
  137. }
  138. void paint (Graphics& g);
  139. TreeViewItem* findItemAt (int y, Rectangle& itemPosition) const;
  140. void updateComponents()
  141. {
  142. const int visibleTop = -getY();
  143. const int visibleBottom = visibleTop + getParentHeight();
  144. BitArray itemsToKeep;
  145. TreeViewItem* item = owner->rootItem;
  146. int y = (item != 0 && !owner->rootItemVisible) ? -item->itemHeight : 0;
  147. while (item != 0 && y < visibleBottom)
  148. {
  149. y += item->itemHeight;
  150. if (y >= visibleTop)
  151. {
  152. const int index = rowComponentIds.indexOf (item->uid);
  153. if (index < 0)
  154. {
  155. Component* const comp = item->createItemComponent();
  156. if (comp != 0)
  157. {
  158. addAndMakeVisible (comp);
  159. itemsToKeep.setBit (rowComponentItems.size());
  160. rowComponentItems.add (item);
  161. rowComponentIds.add (item->uid);
  162. rowComponents.add (comp);
  163. }
  164. }
  165. else
  166. {
  167. itemsToKeep.setBit (index);
  168. }
  169. }
  170. item = item->getNextVisibleItem (true);
  171. }
  172. for (int i = rowComponentItems.size(); --i >= 0;)
  173. {
  174. Component* const comp = (Component*) (rowComponents.getUnchecked(i));
  175. bool keep = false;
  176. if ((itemsToKeep[i] || (comp == Component::getComponentUnderMouse() && comp->isMouseButtonDown()))
  177. && isParentOf (comp))
  178. {
  179. if (itemsToKeep[i])
  180. {
  181. const TreeViewItem* const item = (TreeViewItem*) rowComponentItems.getUnchecked(i);
  182. Rectangle pos (item->getItemPosition (false));
  183. pos.setSize (pos.getWidth(), item->itemHeight);
  184. if (pos.getBottom() >= visibleTop && pos.getY() < visibleBottom)
  185. {
  186. keep = true;
  187. comp->setBounds (pos);
  188. }
  189. }
  190. else
  191. {
  192. comp->setSize (0, 0);
  193. }
  194. }
  195. if (! keep)
  196. {
  197. delete comp;
  198. rowComponents.remove (i);
  199. rowComponentIds.remove (i);
  200. rowComponentItems.remove (i);
  201. }
  202. }
  203. }
  204. void updateButtonUnderMouse (const MouseEvent& e)
  205. {
  206. TreeViewItem* newItem = 0;
  207. if (owner->openCloseButtonsVisible)
  208. {
  209. Rectangle pos;
  210. TreeViewItem* item = findItemAt (e.y, pos);
  211. if (item != 0 && e.x < pos.getX() && e.x >= pos.getX() - owner->getIndentSize())
  212. {
  213. newItem = item;
  214. if (! newItem->mightContainSubItems())
  215. newItem = 0;
  216. }
  217. }
  218. if (buttonUnderMouse != newItem)
  219. {
  220. if (buttonUnderMouse != 0 && containsItem (buttonUnderMouse))
  221. {
  222. const Rectangle r (buttonUnderMouse->getItemPosition (false));
  223. repaint (0, r.getY(), r.getX(), buttonUnderMouse->getItemHeight());
  224. }
  225. buttonUnderMouse = newItem;
  226. if (buttonUnderMouse != 0)
  227. {
  228. const Rectangle r (buttonUnderMouse->getItemPosition (false));
  229. repaint (0, r.getY(), r.getX(), buttonUnderMouse->getItemHeight());
  230. }
  231. }
  232. }
  233. bool isMouseOverButton (TreeViewItem* item) const throw()
  234. {
  235. return item == buttonUnderMouse;
  236. }
  237. void resized()
  238. {
  239. owner->itemsChanged();
  240. }
  241. const String getTooltip()
  242. {
  243. int x, y;
  244. getMouseXYRelative (x, y);
  245. Rectangle pos;
  246. TreeViewItem* const item = findItemAt (y, pos);
  247. if (item != 0)
  248. return item->getTooltip();
  249. return owner->getTooltip();
  250. }
  251. //==============================================================================
  252. juce_UseDebuggingNewOperator
  253. private:
  254. TreeView* const owner;
  255. VoidArray rowComponentItems;
  256. Array <int> rowComponentIds;
  257. VoidArray rowComponents;
  258. TreeViewItem* buttonUnderMouse;
  259. bool isDragging, needSelectionOnMouseUp;
  260. TreeViewContentComponent (const TreeViewContentComponent&);
  261. const TreeViewContentComponent& operator= (const TreeViewContentComponent&);
  262. void selectBasedOnModifiers (TreeViewItem* const item, const ModifierKeys& modifiers)
  263. {
  264. TreeViewItem* firstSelected = 0;
  265. if (modifiers.isShiftDown() && ((firstSelected = owner->getSelectedItem (0)) != 0))
  266. {
  267. TreeViewItem* const lastSelected = owner->getSelectedItem (owner->getNumSelectedItems() - 1);
  268. jassert (lastSelected != 0);
  269. int rowStart = firstSelected->getRowNumberInTree();
  270. int rowEnd = lastSelected->getRowNumberInTree();
  271. if (rowStart > rowEnd)
  272. swapVariables (rowStart, rowEnd);
  273. int ourRow = item->getRowNumberInTree();
  274. int otherEnd = ourRow < rowEnd ? rowStart : rowEnd;
  275. if (ourRow > otherEnd)
  276. swapVariables (ourRow, otherEnd);
  277. for (int i = ourRow; i <= otherEnd; ++i)
  278. owner->getItemOnRow (i)->setSelected (true, false);
  279. }
  280. else
  281. {
  282. const bool cmd = modifiers.isCommandDown();
  283. item->setSelected ((! cmd) || (! item->isSelected()), ! cmd);
  284. }
  285. }
  286. bool containsItem (TreeViewItem* const item) const
  287. {
  288. for (int i = rowComponentItems.size(); --i >= 0;)
  289. if ((TreeViewItem*) rowComponentItems.getUnchecked (i) == item)
  290. return true;
  291. return false;
  292. }
  293. };
  294. //==============================================================================
  295. class TreeViewport : public Viewport
  296. {
  297. public:
  298. TreeViewport() throw() {}
  299. ~TreeViewport() throw() {}
  300. void updateComponents()
  301. {
  302. if (getViewedComponent() != 0)
  303. ((TreeViewContentComponent*) getViewedComponent())->updateComponents();
  304. repaint();
  305. }
  306. void visibleAreaChanged (int, int, int, int)
  307. {
  308. updateComponents();
  309. }
  310. //==============================================================================
  311. juce_UseDebuggingNewOperator
  312. private:
  313. TreeViewport (const TreeViewport&);
  314. const TreeViewport& operator= (const TreeViewport&);
  315. };
  316. //==============================================================================
  317. TreeView::TreeView (const String& componentName)
  318. : Component (componentName),
  319. rootItem (0),
  320. indentSize (24),
  321. defaultOpenness (false),
  322. needsRecalculating (true),
  323. rootItemVisible (true),
  324. multiSelectEnabled (false),
  325. openCloseButtonsVisible (true)
  326. {
  327. addAndMakeVisible (viewport = new TreeViewport());
  328. viewport->setViewedComponent (new TreeViewContentComponent (this));
  329. viewport->setWantsKeyboardFocus (false);
  330. setWantsKeyboardFocus (true);
  331. }
  332. TreeView::~TreeView()
  333. {
  334. if (rootItem != 0)
  335. rootItem->setOwnerView (0);
  336. deleteAllChildren();
  337. }
  338. void TreeView::setRootItem (TreeViewItem* const newRootItem)
  339. {
  340. if (rootItem != newRootItem)
  341. {
  342. if (newRootItem != 0)
  343. {
  344. jassert (newRootItem->ownerView == 0); // can't use a tree item in more than one tree at once..
  345. if (newRootItem->ownerView != 0)
  346. newRootItem->ownerView->setRootItem (0);
  347. }
  348. if (rootItem != 0)
  349. rootItem->setOwnerView (0);
  350. rootItem = newRootItem;
  351. if (newRootItem != 0)
  352. newRootItem->setOwnerView (this);
  353. needsRecalculating = true;
  354. handleAsyncUpdate();
  355. if (rootItem != 0 && (defaultOpenness || ! rootItemVisible))
  356. {
  357. rootItem->setOpen (false); // force a re-open
  358. rootItem->setOpen (true);
  359. }
  360. }
  361. }
  362. void TreeView::deleteRootItem()
  363. {
  364. TreeViewItem* const oldItem = rootItem;
  365. setRootItem (0);
  366. delete oldItem;
  367. }
  368. void TreeView::setRootItemVisible (const bool shouldBeVisible)
  369. {
  370. rootItemVisible = shouldBeVisible;
  371. if (rootItem != 0 && (defaultOpenness || ! rootItemVisible))
  372. {
  373. rootItem->setOpen (false); // force a re-open
  374. rootItem->setOpen (true);
  375. }
  376. itemsChanged();
  377. }
  378. void TreeView::colourChanged()
  379. {
  380. setOpaque (findColour (backgroundColourId).isOpaque());
  381. repaint();
  382. }
  383. void TreeView::setIndentSize (const int newIndentSize)
  384. {
  385. if (indentSize != newIndentSize)
  386. {
  387. indentSize = newIndentSize;
  388. resized();
  389. }
  390. }
  391. void TreeView::setDefaultOpenness (const bool isOpenByDefault)
  392. {
  393. if (defaultOpenness != isOpenByDefault)
  394. {
  395. defaultOpenness = isOpenByDefault;
  396. itemsChanged();
  397. }
  398. }
  399. void TreeView::setMultiSelectEnabled (const bool canMultiSelect)
  400. {
  401. multiSelectEnabled = canMultiSelect;
  402. }
  403. void TreeView::setOpenCloseButtonsVisible (const bool shouldBeVisible)
  404. {
  405. if (openCloseButtonsVisible != shouldBeVisible)
  406. {
  407. openCloseButtonsVisible = shouldBeVisible;
  408. itemsChanged();
  409. }
  410. }
  411. //==============================================================================
  412. void TreeView::clearSelectedItems()
  413. {
  414. if (rootItem != 0)
  415. rootItem->deselectAllRecursively();
  416. }
  417. int TreeView::getNumSelectedItems() const throw()
  418. {
  419. return (rootItem != 0) ? rootItem->countSelectedItemsRecursively() : 0;
  420. }
  421. TreeViewItem* TreeView::getSelectedItem (const int index) const throw()
  422. {
  423. return (rootItem != 0) ? rootItem->getSelectedItemWithIndex (index) : 0;
  424. }
  425. int TreeView::getNumRowsInTree() const
  426. {
  427. if (rootItem != 0)
  428. return rootItem->getNumRows() - (rootItemVisible ? 0 : 1);
  429. return 0;
  430. }
  431. TreeViewItem* TreeView::getItemOnRow (int index) const
  432. {
  433. if (! rootItemVisible)
  434. ++index;
  435. if (rootItem != 0 && index >= 0)
  436. return rootItem->getItemOnRow (index);
  437. return 0;
  438. }
  439. //==============================================================================
  440. XmlElement* TreeView::getOpennessState (const bool alsoIncludeScrollPosition) const
  441. {
  442. XmlElement* e = 0;
  443. if (rootItem != 0)
  444. {
  445. e = rootItem->createXmlOpenness();
  446. if (e != 0 && alsoIncludeScrollPosition)
  447. e->setAttribute (T("scrollPos"), viewport->getViewPositionY());
  448. }
  449. return e;
  450. }
  451. void TreeView::restoreOpennessState (const XmlElement& newState)
  452. {
  453. if (rootItem != 0)
  454. {
  455. rootItem->restoreFromXml (newState);
  456. if (newState.hasAttribute (T("scrollPos")))
  457. viewport->setViewPosition (viewport->getViewPositionX(),
  458. newState.getIntAttribute (T("scrollPos")));
  459. }
  460. }
  461. //==============================================================================
  462. void TreeView::paint (Graphics& g)
  463. {
  464. g.fillAll (findColour (backgroundColourId));
  465. }
  466. void TreeView::resized()
  467. {
  468. viewport->setBounds (0, 0, getWidth(), getHeight());
  469. itemsChanged();
  470. handleAsyncUpdate();
  471. }
  472. void TreeView::enablementChanged()
  473. {
  474. repaint();
  475. }
  476. void TreeView::moveSelectedRow (int delta)
  477. {
  478. if (delta == 0)
  479. return;
  480. int rowSelected = 0;
  481. TreeViewItem* const firstSelected = getSelectedItem (0);
  482. if (firstSelected != 0)
  483. rowSelected = firstSelected->getRowNumberInTree();
  484. rowSelected = jlimit (0, getNumRowsInTree() - 1, rowSelected + delta);
  485. for (;;)
  486. {
  487. TreeViewItem* item = getItemOnRow (rowSelected);
  488. if (item != 0)
  489. {
  490. if (! item->canBeSelected())
  491. {
  492. // if the row we want to highlight doesn't allow it, try skipping
  493. // to the next item..
  494. const int nextRowToTry = jlimit (0, getNumRowsInTree() - 1,
  495. rowSelected + (delta < 0 ? -1 : 1));
  496. if (rowSelected != nextRowToTry)
  497. {
  498. rowSelected = nextRowToTry;
  499. continue;
  500. }
  501. else
  502. {
  503. break;
  504. }
  505. }
  506. item->setSelected (true, true);
  507. scrollToKeepItemVisible (item);
  508. }
  509. break;
  510. }
  511. }
  512. void TreeView::scrollToKeepItemVisible (TreeViewItem* item)
  513. {
  514. if (item != 0 && item->ownerView == this)
  515. {
  516. handleAsyncUpdate();
  517. item = item->getDeepestOpenParentItem();
  518. int y = item->y;
  519. int viewTop = viewport->getViewPositionY();
  520. if (y < viewTop)
  521. {
  522. viewport->setViewPosition (viewport->getViewPositionX(), y);
  523. }
  524. else if (y + item->itemHeight > viewTop + viewport->getViewHeight())
  525. {
  526. viewport->setViewPosition (viewport->getViewPositionX(),
  527. (y + item->itemHeight) - viewport->getViewHeight());
  528. }
  529. }
  530. }
  531. bool TreeView::keyPressed (const KeyPress& key)
  532. {
  533. if (key.isKeyCode (KeyPress::upKey))
  534. {
  535. moveSelectedRow (-1);
  536. }
  537. else if (key.isKeyCode (KeyPress::downKey))
  538. {
  539. moveSelectedRow (1);
  540. }
  541. else if (key.isKeyCode (KeyPress::pageDownKey) || key.isKeyCode (KeyPress::pageUpKey))
  542. {
  543. if (rootItem != 0)
  544. {
  545. int rowsOnScreen = getHeight() / jmax (1, rootItem->itemHeight);
  546. if (key.isKeyCode (KeyPress::pageUpKey))
  547. rowsOnScreen = -rowsOnScreen;
  548. moveSelectedRow (rowsOnScreen);
  549. }
  550. }
  551. else if (key.isKeyCode (KeyPress::homeKey))
  552. {
  553. moveSelectedRow (-0x3fffffff);
  554. }
  555. else if (key.isKeyCode (KeyPress::endKey))
  556. {
  557. moveSelectedRow (0x3fffffff);
  558. }
  559. else if (key.isKeyCode (KeyPress::returnKey))
  560. {
  561. TreeViewItem* const firstSelected = getSelectedItem (0);
  562. if (firstSelected != 0)
  563. firstSelected->setOpen (! firstSelected->isOpen());
  564. }
  565. else if (key.isKeyCode (KeyPress::leftKey))
  566. {
  567. TreeViewItem* const firstSelected = getSelectedItem (0);
  568. if (firstSelected != 0)
  569. {
  570. if (firstSelected->isOpen())
  571. {
  572. firstSelected->setOpen (false);
  573. }
  574. else
  575. {
  576. TreeViewItem* parent = firstSelected->parentItem;
  577. if ((! rootItemVisible) && parent == rootItem)
  578. parent = 0;
  579. if (parent != 0)
  580. {
  581. parent->setSelected (true, true);
  582. scrollToKeepItemVisible (parent);
  583. }
  584. }
  585. }
  586. }
  587. else if (key.isKeyCode (KeyPress::rightKey))
  588. {
  589. TreeViewItem* const firstSelected = getSelectedItem (0);
  590. if (firstSelected != 0)
  591. {
  592. if (firstSelected->isOpen() || ! firstSelected->mightContainSubItems())
  593. moveSelectedRow (1);
  594. else
  595. firstSelected->setOpen (true);
  596. }
  597. }
  598. else
  599. {
  600. return false;
  601. }
  602. return true;
  603. }
  604. void TreeView::itemsChanged() throw()
  605. {
  606. needsRecalculating = true;
  607. repaint();
  608. triggerAsyncUpdate();
  609. }
  610. void TreeView::handleAsyncUpdate()
  611. {
  612. if (needsRecalculating)
  613. {
  614. needsRecalculating = false;
  615. const ScopedLock sl (nodeAlterationLock);
  616. if (rootItem != 0)
  617. rootItem->updatePositions (rootItemVisible ? 0 : -rootItem->itemHeight);
  618. ((TreeViewport*) viewport)->updateComponents();
  619. if (rootItem != 0)
  620. {
  621. viewport->getViewedComponent()
  622. ->setSize (jmax (viewport->getMaximumVisibleWidth(), rootItem->totalWidth),
  623. rootItem->totalHeight - (rootItemVisible ? 0 : rootItem->itemHeight));
  624. }
  625. else
  626. {
  627. viewport->getViewedComponent()->setSize (0, 0);
  628. }
  629. }
  630. }
  631. //==============================================================================
  632. void TreeViewContentComponent::paint (Graphics& g)
  633. {
  634. if (owner->rootItem != 0)
  635. {
  636. owner->handleAsyncUpdate();
  637. if (! owner->rootItemVisible)
  638. g.setOrigin (0, -owner->rootItem->itemHeight);
  639. owner->rootItem->paintRecursively (g, getWidth());
  640. }
  641. }
  642. TreeViewItem* TreeViewContentComponent::findItemAt (int y, Rectangle& itemPosition) const
  643. {
  644. if (owner->rootItem != 0)
  645. {
  646. owner->handleAsyncUpdate();
  647. if (! owner->rootItemVisible)
  648. y += owner->rootItem->itemHeight;
  649. TreeViewItem* const ti = owner->rootItem->findItemRecursively (y);
  650. if (ti != 0)
  651. itemPosition = ti->getItemPosition (false);
  652. return ti;
  653. }
  654. return 0;
  655. }
  656. //==============================================================================
  657. #define opennessDefault 0
  658. #define opennessClosed 1
  659. #define opennessOpen 2
  660. TreeViewItem::TreeViewItem()
  661. : ownerView (0),
  662. parentItem (0),
  663. subItems (8),
  664. y (0),
  665. itemHeight (0),
  666. totalHeight (0),
  667. selected (false),
  668. redrawNeeded (true),
  669. drawLinesInside (true),
  670. drawsInLeftMargin (false),
  671. openness (opennessDefault)
  672. {
  673. static int nextUID = 0;
  674. uid = nextUID++;
  675. }
  676. TreeViewItem::~TreeViewItem()
  677. {
  678. }
  679. const String TreeViewItem::getUniqueName() const
  680. {
  681. return String::empty;
  682. }
  683. void TreeViewItem::itemOpennessChanged (bool)
  684. {
  685. }
  686. int TreeViewItem::getNumSubItems() const throw()
  687. {
  688. return subItems.size();
  689. }
  690. TreeViewItem* TreeViewItem::getSubItem (const int index) const throw()
  691. {
  692. return subItems [index];
  693. }
  694. void TreeViewItem::clearSubItems()
  695. {
  696. if (subItems.size() > 0)
  697. {
  698. if (ownerView != 0)
  699. {
  700. const ScopedLock sl (ownerView->nodeAlterationLock);
  701. subItems.clear();
  702. treeHasChanged();
  703. }
  704. else
  705. {
  706. subItems.clear();
  707. }
  708. }
  709. }
  710. void TreeViewItem::addSubItem (TreeViewItem* const newItem, const int insertPosition)
  711. {
  712. if (newItem != 0)
  713. {
  714. newItem->parentItem = this;
  715. newItem->setOwnerView (ownerView);
  716. newItem->y = 0;
  717. newItem->itemHeight = newItem->getItemHeight();
  718. newItem->totalHeight = 0;
  719. newItem->itemWidth = newItem->getItemWidth();
  720. newItem->totalWidth = 0;
  721. if (ownerView != 0)
  722. {
  723. const ScopedLock sl (ownerView->nodeAlterationLock);
  724. subItems.insert (insertPosition, newItem);
  725. treeHasChanged();
  726. if (newItem->isOpen())
  727. newItem->itemOpennessChanged (true);
  728. }
  729. else
  730. {
  731. subItems.insert (insertPosition, newItem);
  732. if (newItem->isOpen())
  733. newItem->itemOpennessChanged (true);
  734. }
  735. }
  736. }
  737. void TreeViewItem::removeSubItem (const int index, const bool deleteItem)
  738. {
  739. if (ownerView != 0)
  740. ownerView->nodeAlterationLock.enter();
  741. if (((unsigned int) index) < (unsigned int) subItems.size())
  742. {
  743. subItems.remove (index, deleteItem);
  744. treeHasChanged();
  745. }
  746. if (ownerView != 0)
  747. ownerView->nodeAlterationLock.exit();
  748. }
  749. bool TreeViewItem::isOpen() const throw()
  750. {
  751. if (openness == opennessDefault)
  752. return ownerView != 0 && ownerView->defaultOpenness;
  753. else
  754. return openness == opennessOpen;
  755. }
  756. void TreeViewItem::setOpen (const bool shouldBeOpen)
  757. {
  758. if (isOpen() != shouldBeOpen)
  759. {
  760. openness = shouldBeOpen ? opennessOpen
  761. : opennessClosed;
  762. treeHasChanged();
  763. itemOpennessChanged (isOpen());
  764. }
  765. }
  766. bool TreeViewItem::isSelected() const throw()
  767. {
  768. return selected;
  769. }
  770. void TreeViewItem::deselectAllRecursively()
  771. {
  772. setSelected (false, false);
  773. for (int i = 0; i < subItems.size(); ++i)
  774. subItems.getUnchecked(i)->deselectAllRecursively();
  775. }
  776. void TreeViewItem::setSelected (const bool shouldBeSelected,
  777. const bool deselectOtherItemsFirst)
  778. {
  779. if (shouldBeSelected && ! canBeSelected())
  780. return;
  781. if (deselectOtherItemsFirst)
  782. getTopLevelItem()->deselectAllRecursively();
  783. if (shouldBeSelected != selected)
  784. {
  785. selected = shouldBeSelected;
  786. if (ownerView != 0)
  787. ownerView->repaint();
  788. itemSelectionChanged (shouldBeSelected);
  789. }
  790. }
  791. void TreeViewItem::paintItem (Graphics&, int, int)
  792. {
  793. }
  794. void TreeViewItem::paintOpenCloseButton (Graphics& g, int width, int height, bool isMouseOver)
  795. {
  796. ownerView->getLookAndFeel()
  797. .drawTreeviewPlusMinusBox (g, 0, 0, width, height, ! isOpen(), isMouseOver);
  798. }
  799. void TreeViewItem::itemClicked (const MouseEvent&)
  800. {
  801. }
  802. void TreeViewItem::itemDoubleClicked (const MouseEvent&)
  803. {
  804. if (mightContainSubItems())
  805. setOpen (! isOpen());
  806. }
  807. void TreeViewItem::itemSelectionChanged (bool)
  808. {
  809. }
  810. const String TreeViewItem::getTooltip()
  811. {
  812. return String::empty;
  813. }
  814. const String TreeViewItem::getDragSourceDescription()
  815. {
  816. return String::empty;
  817. }
  818. const Rectangle TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const throw()
  819. {
  820. const int indentX = getIndentX();
  821. int width = itemWidth;
  822. if (ownerView != 0 && width < 0)
  823. width = ownerView->viewport->getViewWidth() - indentX;
  824. Rectangle r (indentX, y, jmax (0, width), totalHeight);
  825. if (relativeToTreeViewTopLeft)
  826. r.setPosition (r.getX() - ownerView->viewport->getViewPositionX(),
  827. r.getY() - ownerView->viewport->getViewPositionY());
  828. return r;
  829. }
  830. void TreeViewItem::treeHasChanged() const throw()
  831. {
  832. if (ownerView != 0)
  833. ownerView->itemsChanged();
  834. }
  835. void TreeViewItem::repaintItem() const
  836. {
  837. if (ownerView != 0 && areAllParentsOpen())
  838. {
  839. const Rectangle r (getItemPosition (true));
  840. ownerView->viewport->repaint (0, r.getY(), r.getRight(), r.getHeight());
  841. }
  842. }
  843. bool TreeViewItem::areAllParentsOpen() const throw()
  844. {
  845. return parentItem == 0
  846. || (parentItem->isOpen() && parentItem->areAllParentsOpen());
  847. }
  848. void TreeViewItem::updatePositions (int newY)
  849. {
  850. y = newY;
  851. itemHeight = getItemHeight();
  852. totalHeight = itemHeight;
  853. itemWidth = getItemWidth();
  854. totalWidth = jmax (itemWidth, 0) + getIndentX();
  855. if (isOpen())
  856. {
  857. newY += totalHeight;
  858. for (int i = 0; i < subItems.size(); ++i)
  859. {
  860. TreeViewItem* const ti = subItems.getUnchecked(i);
  861. ti->updatePositions (newY);
  862. newY += ti->totalHeight;
  863. totalHeight += ti->totalHeight;
  864. totalWidth = jmax (totalWidth, ti->totalWidth);
  865. }
  866. }
  867. }
  868. TreeViewItem* TreeViewItem::getDeepestOpenParentItem() throw()
  869. {
  870. TreeViewItem* result = this;
  871. TreeViewItem* item = this;
  872. while (item->parentItem != 0)
  873. {
  874. item = item->parentItem;
  875. if (! item->isOpen())
  876. result = item;
  877. }
  878. return result;
  879. }
  880. void TreeViewItem::setOwnerView (TreeView* const newOwner) throw()
  881. {
  882. ownerView = newOwner;
  883. for (int i = subItems.size(); --i >= 0;)
  884. subItems.getUnchecked(i)->setOwnerView (newOwner);
  885. }
  886. int TreeViewItem::getIndentX() const throw()
  887. {
  888. const int indentWidth = ownerView->getIndentSize();
  889. int x = ownerView->rootItemVisible ? indentWidth : 0;
  890. if (! ownerView->openCloseButtonsVisible)
  891. x -= indentWidth;
  892. TreeViewItem* p = parentItem;
  893. while (p != 0)
  894. {
  895. x += indentWidth;
  896. p = p->parentItem;
  897. }
  898. return x;
  899. }
  900. void TreeViewItem::setDrawsInLeftMargin (bool canDrawInLeftMargin) throw()
  901. {
  902. drawsInLeftMargin = canDrawInLeftMargin;
  903. }
  904. void TreeViewItem::paintRecursively (Graphics& g, int width)
  905. {
  906. jassert (ownerView != 0);
  907. if (ownerView == 0)
  908. return;
  909. const int indent = getIndentX();
  910. const int itemW = itemWidth < 0 ? width - indent : itemWidth;
  911. g.setColour (ownerView->findColour (TreeView::linesColourId));
  912. const float halfH = itemHeight * 0.5f;
  913. int depth = 0;
  914. TreeViewItem* p = parentItem;
  915. while (p != 0)
  916. {
  917. ++depth;
  918. p = p->parentItem;
  919. }
  920. if (! ownerView->rootItemVisible)
  921. --depth;
  922. const int indentWidth = ownerView->getIndentSize();
  923. if (depth >= 0 && ownerView->openCloseButtonsVisible)
  924. {
  925. float x = (depth + 0.5f) * indentWidth;
  926. if (depth >= 0)
  927. {
  928. if (parentItem != 0 && parentItem->drawLinesInside)
  929. g.drawLine (x, 0, x, isLastOfSiblings() ? halfH : (float) itemHeight);
  930. if ((parentItem != 0 && parentItem->drawLinesInside)
  931. || (parentItem == 0 && drawLinesInside))
  932. g.drawLine (x, halfH, x + indentWidth / 2, halfH);
  933. }
  934. p = parentItem;
  935. int d = depth;
  936. while (p != 0 && --d >= 0)
  937. {
  938. x -= (float) indentWidth;
  939. if ((p->parentItem == 0 || p->parentItem->drawLinesInside)
  940. && ! p->isLastOfSiblings())
  941. {
  942. g.drawLine (x, 0, x, (float) itemHeight);
  943. }
  944. p = p->parentItem;
  945. }
  946. if (mightContainSubItems())
  947. {
  948. g.saveState();
  949. g.setOrigin (depth * indentWidth, 0);
  950. g.reduceClipRegion (0, 0, indentWidth, itemHeight);
  951. paintOpenCloseButton (g, indentWidth, itemHeight,
  952. ((TreeViewContentComponent*) ownerView->viewport->getViewedComponent())
  953. ->isMouseOverButton (this));
  954. g.restoreState();
  955. }
  956. }
  957. {
  958. g.saveState();
  959. g.setOrigin (indent, 0);
  960. if (g.reduceClipRegion (drawsInLeftMargin ? -indent : 0, 0,
  961. drawsInLeftMargin ? itemW + indent : itemW, itemHeight))
  962. paintItem (g, itemW, itemHeight);
  963. g.restoreState();
  964. }
  965. if (isOpen())
  966. {
  967. const Rectangle clip (g.getClipBounds());
  968. for (int i = 0; i < subItems.size(); ++i)
  969. {
  970. TreeViewItem* const ti = subItems.getUnchecked(i);
  971. const int relY = ti->y - y;
  972. if (relY >= clip.getBottom())
  973. break;
  974. if (relY + ti->totalHeight >= clip.getY())
  975. {
  976. g.saveState();
  977. g.setOrigin (0, relY);
  978. if (g.reduceClipRegion (0, 0, width, ti->totalHeight))
  979. ti->paintRecursively (g, width);
  980. g.restoreState();
  981. }
  982. }
  983. }
  984. }
  985. bool TreeViewItem::isLastOfSiblings() const throw()
  986. {
  987. return parentItem == 0
  988. || parentItem->subItems.getLast() == this;
  989. }
  990. TreeViewItem* TreeViewItem::getTopLevelItem() throw()
  991. {
  992. return (parentItem == 0) ? this
  993. : parentItem->getTopLevelItem();
  994. }
  995. int TreeViewItem::getNumRows() const throw()
  996. {
  997. int num = 1;
  998. if (isOpen())
  999. {
  1000. for (int i = subItems.size(); --i >= 0;)
  1001. num += subItems.getUnchecked(i)->getNumRows();
  1002. }
  1003. return num;
  1004. }
  1005. TreeViewItem* TreeViewItem::getItemOnRow (int index) throw()
  1006. {
  1007. if (index == 0)
  1008. return this;
  1009. if (index > 0 && isOpen())
  1010. {
  1011. --index;
  1012. for (int i = 0; i < subItems.size(); ++i)
  1013. {
  1014. TreeViewItem* const item = subItems.getUnchecked(i);
  1015. if (index == 0)
  1016. return item;
  1017. const int numRows = item->getNumRows();
  1018. if (numRows > index)
  1019. return item->getItemOnRow (index);
  1020. index -= numRows;
  1021. }
  1022. }
  1023. return 0;
  1024. }
  1025. TreeViewItem* TreeViewItem::findItemRecursively (int y) throw()
  1026. {
  1027. if (((unsigned int) y) < (unsigned int) totalHeight)
  1028. {
  1029. const int h = itemHeight;
  1030. if (y < h)
  1031. return this;
  1032. if (isOpen())
  1033. {
  1034. y -= h;
  1035. for (int i = 0; i < subItems.size(); ++i)
  1036. {
  1037. TreeViewItem* const ti = subItems.getUnchecked(i);
  1038. if (ti->totalHeight >= y)
  1039. return ti->findItemRecursively (y);
  1040. y -= ti->totalHeight;
  1041. }
  1042. }
  1043. }
  1044. return 0;
  1045. }
  1046. int TreeViewItem::countSelectedItemsRecursively() const throw()
  1047. {
  1048. int total = 0;
  1049. if (isSelected())
  1050. ++total;
  1051. for (int i = subItems.size(); --i >= 0;)
  1052. total += subItems.getUnchecked(i)->countSelectedItemsRecursively();
  1053. return total;
  1054. }
  1055. TreeViewItem* TreeViewItem::getSelectedItemWithIndex (int index) throw()
  1056. {
  1057. if (isSelected())
  1058. {
  1059. if (index == 0)
  1060. return this;
  1061. --index;
  1062. }
  1063. if (index >= 0)
  1064. {
  1065. for (int i = 0; i < subItems.size(); ++i)
  1066. {
  1067. TreeViewItem* const item = subItems.getUnchecked(i);
  1068. TreeViewItem* const found = item->getSelectedItemWithIndex (index);
  1069. if (found != 0)
  1070. return found;
  1071. index -= item->countSelectedItemsRecursively();
  1072. }
  1073. }
  1074. return 0;
  1075. }
  1076. int TreeViewItem::getRowNumberInTree() const throw()
  1077. {
  1078. if (parentItem != 0 && ownerView != 0)
  1079. {
  1080. int n = 1 + parentItem->getRowNumberInTree();
  1081. int ourIndex = parentItem->subItems.indexOf (this);
  1082. jassert (ourIndex >= 0);
  1083. while (--ourIndex >= 0)
  1084. n += parentItem->subItems [ourIndex]->getNumRows();
  1085. if (parentItem->parentItem == 0
  1086. && ! ownerView->rootItemVisible)
  1087. --n;
  1088. return n;
  1089. }
  1090. else
  1091. {
  1092. return 0;
  1093. }
  1094. }
  1095. void TreeViewItem::setLinesDrawnForSubItems (const bool drawLines) throw()
  1096. {
  1097. drawLinesInside = drawLines;
  1098. }
  1099. TreeViewItem* TreeViewItem::getNextVisibleItem (const bool recurse) const throw()
  1100. {
  1101. if (recurse && isOpen() && subItems.size() > 0)
  1102. return subItems [0];
  1103. if (parentItem != 0)
  1104. {
  1105. const int nextIndex = parentItem->subItems.indexOf (this) + 1;
  1106. if (nextIndex >= parentItem->subItems.size())
  1107. return parentItem->getNextVisibleItem (false);
  1108. return parentItem->subItems [nextIndex];
  1109. }
  1110. return 0;
  1111. }
  1112. void TreeViewItem::restoreFromXml (const XmlElement& e)
  1113. {
  1114. if (e.hasTagName (T("CLOSED")))
  1115. {
  1116. setOpen (false);
  1117. }
  1118. else if (e.hasTagName (T("OPEN")))
  1119. {
  1120. setOpen (true);
  1121. forEachXmlChildElement (e, n)
  1122. {
  1123. const String id (n->getStringAttribute (T("id")));
  1124. for (int i = 0; i < subItems.size(); ++i)
  1125. {
  1126. TreeViewItem* const ti = subItems.getUnchecked(i);
  1127. if (ti->getUniqueName() == id)
  1128. {
  1129. ti->restoreFromXml (*n);
  1130. break;
  1131. }
  1132. }
  1133. }
  1134. }
  1135. }
  1136. XmlElement* TreeViewItem::createXmlOpenness() const
  1137. {
  1138. if (openness != opennessDefault)
  1139. {
  1140. const String name (getUniqueName());
  1141. if (name.isNotEmpty())
  1142. {
  1143. XmlElement* e;
  1144. if (isOpen())
  1145. {
  1146. e = new XmlElement (T("OPEN"));
  1147. for (int i = 0; i < subItems.size(); ++i)
  1148. e->addChildElement (subItems.getUnchecked(i)->createXmlOpenness());
  1149. }
  1150. else
  1151. {
  1152. e = new XmlElement (T("CLOSED"));
  1153. }
  1154. e->setAttribute (T("id"), name);
  1155. return e;
  1156. }
  1157. else
  1158. {
  1159. // trying to save the openness for an element that has no name - this won't
  1160. // work because it needs the names to identify what to open.
  1161. jassertfalse
  1162. }
  1163. }
  1164. return 0;
  1165. }
  1166. END_JUCE_NAMESPACE