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.

juce_TreeView.cpp 53KB

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