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.

1196 lines
41KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. #pragma once
  19. //==============================================================================
  20. class TabbedComponentHandler : public ComponentTypeHandler
  21. {
  22. public:
  23. TabbedComponentHandler()
  24. : ComponentTypeHandler ("Tabbed Component", "juce::TabbedComponent", typeid (TabbedComponent), 200, 150)
  25. {}
  26. Component* createNewComponent (JucerDocument*) override
  27. {
  28. TabbedComponent* const t = new TabbedComponent (TabbedButtonBar::TabsAtTop);
  29. t->setName ("new tabbed component");
  30. for (int i = 3; --i >= 0;)
  31. addNewTab (t);
  32. return t;
  33. }
  34. XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
  35. {
  36. TabbedComponent* const t = dynamic_cast<TabbedComponent*> (comp);
  37. XmlElement* const e = ComponentTypeHandler::createXmlFor (comp, layout);
  38. if (t->getOrientation() == TabbedButtonBar::TabsAtTop) e->setAttribute ("orientation", "top");
  39. else if (t->getOrientation() == TabbedButtonBar::TabsAtBottom) e->setAttribute ("orientation", "bottom");
  40. else if (t->getOrientation() == TabbedButtonBar::TabsAtLeft) e->setAttribute ("orientation", "left");
  41. else if (t->getOrientation() == TabbedButtonBar::TabsAtRight) e->setAttribute ("orientation", "right");
  42. e->setAttribute ("tabBarDepth", t->getTabBarDepth());
  43. e->setAttribute ("initialTab", t->getCurrentTabIndex());
  44. for (int i = 0; i < t->getNumTabs(); ++i)
  45. e->addChildElement (getTabState (t, i).release());
  46. return e;
  47. }
  48. bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
  49. {
  50. if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
  51. return false;
  52. TabbedComponent* const t = dynamic_cast<TabbedComponent*> (comp);
  53. if (xml.getStringAttribute ("orientation") == "top") t->setOrientation (TabbedButtonBar::TabsAtTop);
  54. else if (xml.getStringAttribute ("orientation") == "bottom") t->setOrientation (TabbedButtonBar::TabsAtBottom);
  55. else if (xml.getStringAttribute ("orientation") == "left") t->setOrientation (TabbedButtonBar::TabsAtLeft);
  56. else if (xml.getStringAttribute ("orientation") == "right") t->setOrientation (TabbedButtonBar::TabsAtRight);
  57. TabbedComponent defaultTabComp (TabbedButtonBar::TabsAtTop);
  58. t->setTabBarDepth (xml.getIntAttribute ("tabBarDepth", defaultTabComp.getTabBarDepth()));
  59. t->clearTabs();
  60. for (auto* e : xml.getChildIterator())
  61. {
  62. addNewTab (t);
  63. restoreTabState (t, t->getNumTabs() - 1, *e);
  64. }
  65. t->setCurrentTabIndex (xml.getIntAttribute ("initialTab", 0));
  66. return true;
  67. }
  68. void getEditableProperties (Component* component, JucerDocument& doc,
  69. Array<PropertyComponent*>& props, bool multipleSelected) override
  70. {
  71. ComponentTypeHandler::getEditableProperties (component, doc, props, multipleSelected);
  72. if (multipleSelected)
  73. return;
  74. if (auto* t = dynamic_cast<TabbedComponent*> (component))
  75. {
  76. props.add (new TabOrientationProperty (t, doc));
  77. props.add (new TabDepthProperty (t, doc));
  78. if (t->getNumTabs() > 0)
  79. props.add (new TabInitialTabProperty (t, doc));
  80. props.add (new TabAddTabProperty (t, doc));
  81. if (t->getNumTabs() > 0)
  82. props.add (new TabRemoveTabProperty (t, doc));
  83. }
  84. }
  85. void addPropertiesToPropertyPanel (Component* comp, JucerDocument& doc,
  86. PropertyPanel& panel, bool multipleSelected) override
  87. {
  88. ComponentTypeHandler::addPropertiesToPropertyPanel (comp, doc, panel, multipleSelected);
  89. TabbedComponent* const t = dynamic_cast<TabbedComponent*> (comp);
  90. for (int i = 0; i < t->getNumTabs(); ++i)
  91. {
  92. Array<PropertyComponent*> properties;
  93. properties.add (new TabNameProperty (t, doc, i));
  94. properties.add (new TabColourProperty (t, doc, i));
  95. properties.add (new TabContentTypeProperty (t, doc, i));
  96. if (isTabUsingJucerComp (t, i))
  97. properties.add (new TabJucerFileProperty (t, doc, i));
  98. else
  99. properties.add (new TabContentClassProperty (t, doc, i));
  100. properties.add (new TabContentConstructorParamsProperty (t, doc, i));
  101. properties.add (new TabMoveProperty (t, doc, i, t->getNumTabs()));
  102. panel.addSection ("Tab " + String (i), properties);
  103. }
  104. }
  105. String getCreationParameters (GeneratedCode&, Component* comp) override
  106. {
  107. TabbedComponent* const t = dynamic_cast<TabbedComponent*> (comp);
  108. switch (t->getOrientation())
  109. {
  110. case TabbedButtonBar::TabsAtTop: return "juce::TabbedButtonBar::TabsAtTop";
  111. case TabbedButtonBar::TabsAtBottom: return "juce::TabbedButtonBar::TabsAtBottom";
  112. case TabbedButtonBar::TabsAtLeft: return "juce::TabbedButtonBar::TabsAtLeft";
  113. case TabbedButtonBar::TabsAtRight: return "juce::TabbedButtonBar::TabsAtRight";
  114. default: jassertfalse; break;
  115. }
  116. return {};
  117. }
  118. void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
  119. {
  120. TabbedComponent* const t = dynamic_cast<TabbedComponent*> (component);
  121. ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
  122. code.constructorCode
  123. << memberVariableName << "->setTabBarDepth (" << t->getTabBarDepth() << ");\n";
  124. for (int i = 0; i < t->getNumTabs(); ++i)
  125. {
  126. String contentClassName;
  127. if (isTabUsingJucerComp (t, i))
  128. {
  129. File jucerCpp = code.document->getCppFile().getSiblingFile (getTabJucerFile (t, i));
  130. std::unique_ptr<JucerDocument> doc (JucerDocument::createForCppFile (nullptr, jucerCpp));
  131. if (doc != nullptr)
  132. {
  133. code.includeFilesCPP.add (jucerCpp.withFileExtension (".h"));
  134. contentClassName = doc->getClassName();
  135. }
  136. }
  137. else
  138. {
  139. contentClassName = getTabClassName (t, i);
  140. }
  141. code.constructorCode
  142. << memberVariableName
  143. << "->addTab ("
  144. << quotedString (t->getTabNames() [i], code.shouldUseTransMacro())
  145. << ", "
  146. << CodeHelpers::colourToCode (t->getTabBackgroundColour (i));
  147. if (contentClassName.isNotEmpty())
  148. {
  149. code.constructorCode << ", new " << contentClassName;
  150. if (getTabConstructorParams (t, i).trim().isNotEmpty())
  151. code.constructorCode << " ";
  152. code.constructorCode << "(" << getTabConstructorParams (t, i).trim() << "), true);\n";
  153. }
  154. else
  155. {
  156. code.constructorCode << ", 0, false);\n";
  157. }
  158. }
  159. code.constructorCode
  160. << memberVariableName << "->setCurrentTabIndex (" << t->getCurrentTabIndex() << ");\n";
  161. code.constructorCode << "\n";
  162. }
  163. //==============================================================================
  164. static void addNewTab (TabbedComponent* tc, const int insertIndex = -1)
  165. {
  166. tc->addTab ("Tab " + String (tc->getNumTabs()), Colours::lightgrey,
  167. new TabDemoContentComp(), true, insertIndex);
  168. }
  169. //==============================================================================
  170. static std::unique_ptr<XmlElement> getTabState (TabbedComponent* tc, int tabIndex)
  171. {
  172. auto xml = std::make_unique<XmlElement> ("TAB");
  173. xml->setAttribute ("name", tc->getTabNames() [tabIndex]);
  174. xml->setAttribute ("colour", tc->getTabBackgroundColour (tabIndex).toString());
  175. if (TabDemoContentComp* const tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex)))
  176. {
  177. xml->setAttribute ("useJucerComp", tdc->isUsingJucerComp);
  178. xml->setAttribute ("contentClassName", tdc->contentClassName);
  179. xml->setAttribute ("constructorParams", tdc->constructorParams);
  180. xml->setAttribute ("jucerComponentFile", tdc->jucerComponentFile);
  181. }
  182. return xml;
  183. }
  184. static void restoreTabState (TabbedComponent* tc, int tabIndex, const XmlElement& xml)
  185. {
  186. tc->setTabName (tabIndex, xml.getStringAttribute ("name", "Tab"));
  187. tc->setTabBackgroundColour (tabIndex, Colour::fromString (xml.getStringAttribute ("colour", Colours::lightgrey.toString())));
  188. if (TabDemoContentComp* const tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex)))
  189. {
  190. tdc->isUsingJucerComp = xml.getBoolAttribute ("useJucerComp", false);
  191. tdc->contentClassName = xml.getStringAttribute ("contentClassName");
  192. tdc->constructorParams = xml.getStringAttribute ("constructorParams");
  193. tdc->jucerComponentFile = xml.getStringAttribute ("jucerComponentFile");
  194. tdc->updateContent();
  195. }
  196. }
  197. //==============================================================================
  198. static bool isTabUsingJucerComp (TabbedComponent* tc, int tabIndex)
  199. {
  200. auto tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  201. jassert (tdc != nullptr);
  202. return tdc != nullptr && tdc->isUsingJucerComp;
  203. }
  204. static void setTabUsingJucerComp (TabbedComponent* tc, int tabIndex, const bool b)
  205. {
  206. auto tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  207. jassert (tdc != nullptr);
  208. if (tdc != nullptr)
  209. {
  210. tdc->isUsingJucerComp = b;
  211. tdc->updateContent();
  212. }
  213. }
  214. static String getTabClassName (TabbedComponent* tc, int tabIndex)
  215. {
  216. auto tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  217. jassert (tdc != nullptr);
  218. return tdc != nullptr ? tdc->contentClassName : String();
  219. }
  220. static void setTabClassName (TabbedComponent* tc, int tabIndex, const String& newName)
  221. {
  222. TabDemoContentComp* const tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  223. jassert (tdc != nullptr);
  224. if (tdc != nullptr)
  225. {
  226. tdc->contentClassName = newName;
  227. tdc->updateContent();
  228. }
  229. }
  230. static String getTabConstructorParams (TabbedComponent* tc, int tabIndex)
  231. {
  232. auto tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  233. jassert (tdc != nullptr);
  234. return tdc != nullptr ? tdc->constructorParams : String();
  235. }
  236. static void setTabConstructorParams (TabbedComponent* tc, int tabIndex, const String& newParams)
  237. {
  238. auto tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  239. jassert (tdc != nullptr);
  240. if (tdc != nullptr)
  241. {
  242. tdc->constructorParams = newParams;
  243. tdc->updateContent();
  244. }
  245. }
  246. static String getTabJucerFile (TabbedComponent* tc, int tabIndex)
  247. {
  248. auto tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  249. jassert (tdc != nullptr);
  250. return tdc != nullptr ? tdc->jucerComponentFile : String();
  251. }
  252. static void setTabJucerFile (TabbedComponent* tc, int tabIndex, const String& newFile)
  253. {
  254. auto tdc = dynamic_cast<TabDemoContentComp*> (tc->getTabContentComponent (tabIndex));
  255. jassert (tdc != nullptr);
  256. if (tdc != nullptr)
  257. {
  258. tdc->jucerComponentFile = newFile;
  259. tdc->updateContent();
  260. }
  261. }
  262. private:
  263. //==============================================================================
  264. class TabDemoContentComp : public Component
  265. {
  266. public:
  267. TabDemoContentComp()
  268. : isUsingJucerComp (false)
  269. {
  270. setSize (2048, 2048);
  271. }
  272. void paint (Graphics& g) override
  273. {
  274. if (jucerComp == nullptr)
  275. g.fillCheckerBoard (getLocalBounds().toFloat(), 50.0f, 50.0f,
  276. Colour::greyLevel (0.9f).withAlpha (0.4f),
  277. Colour::greyLevel (0.8f).withAlpha (0.4f));
  278. }
  279. void resized() override
  280. {
  281. if (jucerComp != nullptr)
  282. {
  283. jucerComp->setBounds (getLocalBounds());
  284. setOpaque (jucerComp->isOpaque());
  285. }
  286. }
  287. void updateContent()
  288. {
  289. if (isUsingJucerComp)
  290. {
  291. if (jucerComp == nullptr
  292. || jucerComp->getOwnerDocument() == nullptr
  293. || jucerComp->getFilename() != jucerComponentFile)
  294. {
  295. jucerComp.reset();
  296. jucerComp.reset (new TestComponent (ComponentTypeHandler::findParentDocument (this), nullptr, false));
  297. jucerComp->setFilename (jucerComponentFile);
  298. jucerComp->setToInitialSize();
  299. addAndMakeVisible (jucerComp.get());
  300. }
  301. }
  302. else
  303. {
  304. jucerComp.reset();
  305. }
  306. resized();
  307. }
  308. void parentHierarchyChanged() override
  309. {
  310. updateContent();
  311. }
  312. bool isUsingJucerComp;
  313. String contentClassName, constructorParams;
  314. String jucerComponentFile;
  315. std::unique_ptr<TestComponent> jucerComp;
  316. };
  317. //==============================================================================
  318. class TabOrientationProperty : public ComponentChoiceProperty<TabbedComponent>
  319. {
  320. public:
  321. TabOrientationProperty (TabbedComponent* comp, JucerDocument& doc)
  322. : ComponentChoiceProperty<TabbedComponent> ("tab position", comp, doc)
  323. {
  324. choices.add ("Tabs at top");
  325. choices.add ("Tabs at bottom");
  326. choices.add ("Tabs at left");
  327. choices.add ("Tabs at right");
  328. }
  329. void setIndex (int newIndex)
  330. {
  331. const TabbedButtonBar::Orientation orientations[] = { TabbedButtonBar::TabsAtTop,
  332. TabbedButtonBar::TabsAtBottom,
  333. TabbedButtonBar::TabsAtLeft,
  334. TabbedButtonBar::TabsAtRight };
  335. document.perform (new TabOrienationChangeAction (component, *document.getComponentLayout(), orientations [newIndex]),
  336. "Change TabComponent orientation");
  337. }
  338. int getIndex() const
  339. {
  340. switch (component->getOrientation())
  341. {
  342. case TabbedButtonBar::TabsAtTop: return 0;
  343. case TabbedButtonBar::TabsAtBottom: return 1;
  344. case TabbedButtonBar::TabsAtLeft: return 2;
  345. case TabbedButtonBar::TabsAtRight: return 3;
  346. default: jassertfalse; break;
  347. }
  348. return 0;
  349. }
  350. private:
  351. class TabOrienationChangeAction : public ComponentUndoableAction<TabbedComponent>
  352. {
  353. public:
  354. TabOrienationChangeAction (TabbedComponent* const comp, ComponentLayout& l, const TabbedButtonBar::Orientation newState_)
  355. : ComponentUndoableAction<TabbedComponent> (comp, l),
  356. newState (newState_)
  357. {
  358. oldState = comp->getOrientation();
  359. }
  360. bool perform()
  361. {
  362. showCorrectTab();
  363. getComponent()->setOrientation (newState);
  364. changed();
  365. return true;
  366. }
  367. bool undo()
  368. {
  369. showCorrectTab();
  370. getComponent()->setOrientation (oldState);
  371. changed();
  372. return true;
  373. }
  374. TabbedButtonBar::Orientation newState, oldState;
  375. };
  376. };
  377. //==============================================================================
  378. class TabInitialTabProperty : public ComponentChoiceProperty<TabbedComponent>
  379. {
  380. public:
  381. TabInitialTabProperty (TabbedComponent* comp, JucerDocument& doc)
  382. : ComponentChoiceProperty<TabbedComponent> ("initial tab", comp, doc)
  383. {
  384. for (int i = 0; i < comp->getNumTabs(); ++i)
  385. choices.add ("Tab " + String (i) + ": \"" + comp->getTabNames() [i] + "\"");
  386. }
  387. void setIndex (int newIndex)
  388. {
  389. document.perform (new InitialTabChangeAction (component, *document.getComponentLayout(), newIndex),
  390. "Change initial tab");
  391. }
  392. int getIndex() const
  393. {
  394. return component->getCurrentTabIndex();
  395. }
  396. private:
  397. class InitialTabChangeAction : public ComponentUndoableAction<TabbedComponent>
  398. {
  399. public:
  400. InitialTabChangeAction (TabbedComponent* const comp, ComponentLayout& l, const int newValue_)
  401. : ComponentUndoableAction<TabbedComponent> (comp, l),
  402. newValue (newValue_)
  403. {
  404. oldValue = comp->getCurrentTabIndex();
  405. }
  406. bool perform()
  407. {
  408. showCorrectTab();
  409. getComponent()->setCurrentTabIndex (newValue);
  410. changed();
  411. return true;
  412. }
  413. bool undo()
  414. {
  415. showCorrectTab();
  416. getComponent()->setCurrentTabIndex (oldValue);
  417. changed();
  418. return true;
  419. }
  420. private:
  421. int newValue, oldValue;
  422. };
  423. };
  424. //==============================================================================
  425. class TabDepthProperty : public SliderPropertyComponent,
  426. private ChangeListener
  427. {
  428. public:
  429. TabDepthProperty (TabbedComponent* comp, JucerDocument& doc)
  430. : SliderPropertyComponent ("tab depth", 10.0, 80.0, 1.0, 1.0),
  431. component (comp),
  432. document (doc)
  433. {
  434. document.addChangeListener (this);
  435. }
  436. ~TabDepthProperty() override
  437. {
  438. document.removeChangeListener (this);
  439. }
  440. void setValue (double newValue) override
  441. {
  442. document.getUndoManager().undoCurrentTransactionOnly();
  443. document.perform (new TabDepthChangeAction (component, *document.getComponentLayout(), roundToInt (newValue)),
  444. "Change TabComponent tab depth");
  445. }
  446. double getValue() const override
  447. {
  448. return component->getTabBarDepth();
  449. }
  450. TabbedComponent* const component;
  451. JucerDocument& document;
  452. private:
  453. void changeListenerCallback (ChangeBroadcaster*) override
  454. {
  455. refresh();
  456. }
  457. class TabDepthChangeAction : public ComponentUndoableAction<TabbedComponent>
  458. {
  459. public:
  460. TabDepthChangeAction (TabbedComponent* const comp, ComponentLayout& l, const int newState_)
  461. : ComponentUndoableAction<TabbedComponent> (comp, l),
  462. newState (newState_)
  463. {
  464. oldState = comp->getTabBarDepth();
  465. }
  466. bool perform()
  467. {
  468. showCorrectTab();
  469. getComponent()->setTabBarDepth (newState);
  470. changed();
  471. return true;
  472. }
  473. bool undo()
  474. {
  475. showCorrectTab();
  476. getComponent()->setTabBarDepth (oldState);
  477. changed();
  478. return true;
  479. }
  480. int newState, oldState;
  481. };
  482. };
  483. //==============================================================================
  484. class TabAddTabProperty : public ButtonPropertyComponent
  485. {
  486. public:
  487. TabAddTabProperty (TabbedComponent* comp, JucerDocument& doc)
  488. : ButtonPropertyComponent ("add tab", false),
  489. component (comp),
  490. document (doc)
  491. {
  492. }
  493. void buttonClicked()
  494. {
  495. document.perform (new AddTabAction (component, *document.getComponentLayout()),
  496. "Add a new tab");
  497. }
  498. String getButtonText() const
  499. {
  500. return "Create a new tab";
  501. }
  502. TabbedComponent* const component;
  503. JucerDocument& document;
  504. private:
  505. class AddTabAction : public ComponentUndoableAction<TabbedComponent>
  506. {
  507. public:
  508. AddTabAction (TabbedComponent* const comp, ComponentLayout& l)
  509. : ComponentUndoableAction<TabbedComponent> (comp, l)
  510. {
  511. }
  512. bool perform()
  513. {
  514. showCorrectTab();
  515. addNewTab (getComponent());
  516. layout.getDocument()->refreshAllPropertyComps();
  517. changed();
  518. return true;
  519. }
  520. bool undo()
  521. {
  522. showCorrectTab();
  523. getComponent()->removeTab (getComponent()->getNumTabs() - 1);
  524. layout.getDocument()->refreshAllPropertyComps();
  525. changed();
  526. return true;
  527. }
  528. };
  529. };
  530. //==============================================================================
  531. class TabRemoveTabProperty : public ButtonPropertyComponent
  532. {
  533. public:
  534. TabRemoveTabProperty (TabbedComponent* comp, JucerDocument& doc)
  535. : ButtonPropertyComponent ("remove tab", true),
  536. component (comp),
  537. document (doc)
  538. {
  539. }
  540. void buttonClicked()
  541. {
  542. const StringArray names (component->getTabNames());
  543. PopupMenu m;
  544. for (int i = 0; i < component->getNumTabs(); ++i)
  545. m.addItem (i + 1, "Delete tab " + String (i)
  546. + ": \"" + names[i] + "\"");
  547. PopupMenu::Options options{};
  548. m.showMenuAsync (PopupMenu::Options().withTargetComponent (this), [this] (int r)
  549. {
  550. if (r > 0)
  551. document.perform (new RemoveTabAction (component, *document.getComponentLayout(), r - 1),
  552. "Remove a tab");
  553. });
  554. }
  555. String getButtonText() const
  556. {
  557. return "Delete a tab...";
  558. }
  559. TabbedComponent* const component;
  560. JucerDocument& document;
  561. private:
  562. class RemoveTabAction : public ComponentUndoableAction<TabbedComponent>
  563. {
  564. public:
  565. RemoveTabAction (TabbedComponent* const comp, ComponentLayout& l, int indexToRemove_)
  566. : ComponentUndoableAction<TabbedComponent> (comp, l),
  567. indexToRemove (indexToRemove_)
  568. {
  569. previousState = getTabState (comp, indexToRemove);
  570. }
  571. bool perform()
  572. {
  573. showCorrectTab();
  574. getComponent()->removeTab (indexToRemove);
  575. layout.getDocument()->refreshAllPropertyComps();
  576. changed();
  577. return true;
  578. }
  579. bool undo()
  580. {
  581. showCorrectTab();
  582. addNewTab (getComponent(), indexToRemove);
  583. restoreTabState (getComponent(), indexToRemove, *previousState);
  584. layout.getDocument()->refreshAllPropertyComps();
  585. changed();
  586. return true;
  587. }
  588. private:
  589. int indexToRemove;
  590. std::unique_ptr<XmlElement> previousState;
  591. };
  592. };
  593. //==============================================================================
  594. class TabNameProperty : public ComponentTextProperty<TabbedComponent>
  595. {
  596. public:
  597. TabNameProperty (TabbedComponent* comp, JucerDocument& doc, const int tabIndex_)
  598. : ComponentTextProperty<TabbedComponent> ("name", 200, false, comp, doc),
  599. tabIndex (tabIndex_)
  600. {
  601. }
  602. void setText (const String& newText) override
  603. {
  604. document.perform (new TabNameChangeAction (component, *document.getComponentLayout(), tabIndex, newText),
  605. "Change tab name");
  606. }
  607. String getText() const override
  608. {
  609. return component->getTabNames() [tabIndex];
  610. }
  611. private:
  612. int tabIndex;
  613. class TabNameChangeAction : public ComponentUndoableAction<TabbedComponent>
  614. {
  615. public:
  616. TabNameChangeAction (TabbedComponent* const comp, ComponentLayout& l, const int tabIndex_, const String& newValue_)
  617. : ComponentUndoableAction<TabbedComponent> (comp, l),
  618. tabIndex (tabIndex_),
  619. newValue (newValue_)
  620. {
  621. oldValue = comp->getTabNames() [tabIndex];
  622. }
  623. bool perform()
  624. {
  625. showCorrectTab();
  626. getComponent()->setTabName (tabIndex, newValue);
  627. changed();
  628. return true;
  629. }
  630. bool undo()
  631. {
  632. showCorrectTab();
  633. getComponent()->setTabName (tabIndex, oldValue);
  634. changed();
  635. return true;
  636. }
  637. private:
  638. const int tabIndex;
  639. String newValue, oldValue;
  640. };
  641. };
  642. //==============================================================================
  643. class TabColourProperty : public JucerColourPropertyComponent,
  644. private ChangeListener
  645. {
  646. public:
  647. TabColourProperty (TabbedComponent* comp, JucerDocument& doc, const int tabIndex_)
  648. : JucerColourPropertyComponent ("colour", false),
  649. component (comp),
  650. document (doc),
  651. tabIndex (tabIndex_)
  652. {
  653. document.addChangeListener (this);
  654. }
  655. ~TabColourProperty() override
  656. {
  657. document.removeChangeListener (this);
  658. }
  659. void setColour (Colour newColour) override
  660. {
  661. document.getUndoManager().undoCurrentTransactionOnly();
  662. document.perform (new TabColourChangeAction (component, *document.getComponentLayout(), tabIndex, newColour),
  663. "Change tab colour");
  664. }
  665. Colour getColour() const override
  666. {
  667. return component->getTabBackgroundColour (tabIndex);
  668. }
  669. void resetToDefault() override
  670. {
  671. jassertfalse; // shouldn't get called
  672. }
  673. void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
  674. private:
  675. TabbedComponent* component;
  676. JucerDocument& document;
  677. int tabIndex;
  678. class TabColourChangeAction : public ComponentUndoableAction<TabbedComponent>
  679. {
  680. public:
  681. TabColourChangeAction (TabbedComponent* comp, ComponentLayout& l,
  682. int tabIndex_, Colour newValue_)
  683. : ComponentUndoableAction<TabbedComponent> (comp, l),
  684. tabIndex (tabIndex_),
  685. newValue (newValue_)
  686. {
  687. oldValue = comp->getTabBackgroundColour (tabIndex);
  688. }
  689. bool perform()
  690. {
  691. showCorrectTab();
  692. getComponent()->setTabBackgroundColour (tabIndex, newValue);
  693. changed();
  694. return true;
  695. }
  696. bool undo()
  697. {
  698. showCorrectTab();
  699. getComponent()->setTabBackgroundColour (tabIndex, oldValue);
  700. changed();
  701. return true;
  702. }
  703. private:
  704. const int tabIndex;
  705. Colour newValue, oldValue;
  706. };
  707. };
  708. //==============================================================================
  709. class TabContentTypeProperty : public ComponentChoiceProperty<TabbedComponent>
  710. {
  711. public:
  712. TabContentTypeProperty (TabbedComponent* comp, JucerDocument& doc, const int tabIndex_)
  713. : ComponentChoiceProperty<TabbedComponent> ("content type", comp, doc),
  714. tabIndex (tabIndex_)
  715. {
  716. choices.add ("Jucer content component");
  717. choices.add ("Named content component");
  718. }
  719. void setIndex (int newIndex)
  720. {
  721. document.perform (new TabContentTypeChangeAction (component, *document.getComponentLayout(), tabIndex, newIndex == 0),
  722. "Change tab content type");
  723. }
  724. int getIndex() const
  725. {
  726. return isTabUsingJucerComp (component, tabIndex) ? 0 : 1;
  727. }
  728. private:
  729. int tabIndex;
  730. class TabContentTypeChangeAction : public ComponentUndoableAction<TabbedComponent>
  731. {
  732. public:
  733. TabContentTypeChangeAction (TabbedComponent* const comp, ComponentLayout& l, const int tabIndex_, const bool newValue_)
  734. : ComponentUndoableAction<TabbedComponent> (comp, l),
  735. tabIndex (tabIndex_),
  736. newValue (newValue_)
  737. {
  738. oldValue = isTabUsingJucerComp (comp, tabIndex);
  739. }
  740. bool perform()
  741. {
  742. showCorrectTab();
  743. setTabUsingJucerComp (getComponent(), tabIndex, newValue);
  744. layout.getDocument()->refreshAllPropertyComps();
  745. changed();
  746. return true;
  747. }
  748. bool undo()
  749. {
  750. showCorrectTab();
  751. setTabUsingJucerComp (getComponent(), tabIndex, oldValue);
  752. layout.getDocument()->refreshAllPropertyComps();
  753. changed();
  754. return true;
  755. }
  756. private:
  757. int tabIndex;
  758. bool newValue, oldValue;
  759. };
  760. };
  761. //==============================================================================
  762. class TabJucerFileProperty : public FilePropertyComponent,
  763. private ChangeListener
  764. {
  765. public:
  766. TabJucerFileProperty (TabbedComponent* const comp, JucerDocument& doc, const int tabIndex_)
  767. : FilePropertyComponent ("jucer file", false, true),
  768. component (comp),
  769. document (doc),
  770. tabIndex (tabIndex_)
  771. {
  772. document.addChangeListener (this);
  773. }
  774. ~TabJucerFileProperty() override
  775. {
  776. document.removeChangeListener (this);
  777. }
  778. //==============================================================================
  779. void setFile (const File& newFile) override
  780. {
  781. document.perform (new JucerCompFileChangeAction (component, *document.getComponentLayout(), tabIndex,
  782. newFile.getRelativePathFrom (document.getCppFile().getParentDirectory())
  783. .replaceCharacter ('\\', '/')),
  784. "Change tab component file");
  785. }
  786. File getFile() const override
  787. {
  788. return document.getCppFile().getSiblingFile (getTabJucerFile (component, tabIndex));
  789. }
  790. private:
  791. void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
  792. TabbedComponent* const component;
  793. JucerDocument& document;
  794. int tabIndex;
  795. class JucerCompFileChangeAction : public ComponentUndoableAction<TabbedComponent>
  796. {
  797. public:
  798. JucerCompFileChangeAction (TabbedComponent* const comp, ComponentLayout& l, const int tabIndex_, const String& newState_)
  799. : ComponentUndoableAction<TabbedComponent> (comp, l),
  800. tabIndex (tabIndex_),
  801. newState (newState_)
  802. {
  803. oldState = getTabJucerFile (comp, tabIndex);
  804. }
  805. bool perform()
  806. {
  807. showCorrectTab();
  808. setTabJucerFile (getComponent(), tabIndex, newState);
  809. changed();
  810. return true;
  811. }
  812. bool undo()
  813. {
  814. showCorrectTab();
  815. setTabJucerFile (getComponent(), tabIndex, oldState);
  816. changed();
  817. return true;
  818. }
  819. int tabIndex;
  820. String newState, oldState;
  821. };
  822. };
  823. //==============================================================================
  824. class TabContentClassProperty : public ComponentTextProperty<TabbedComponent>
  825. {
  826. public:
  827. TabContentClassProperty (TabbedComponent* comp, JucerDocument& doc, const int tabIndex_)
  828. : ComponentTextProperty<TabbedComponent> ("content class", 256, false, comp, doc),
  829. tabIndex (tabIndex_)
  830. {
  831. }
  832. void setText (const String& newText) override
  833. {
  834. document.perform (new TabClassNameChangeAction (component, *document.getComponentLayout(), tabIndex, newText),
  835. "Change TabbedComponent content class");
  836. }
  837. String getText() const override
  838. {
  839. return getTabClassName (component, tabIndex);
  840. }
  841. private:
  842. int tabIndex;
  843. class TabClassNameChangeAction : public ComponentUndoableAction<TabbedComponent>
  844. {
  845. public:
  846. TabClassNameChangeAction (TabbedComponent* const comp, ComponentLayout& l, const int tabIndex_, const String& newValue_)
  847. : ComponentUndoableAction<TabbedComponent> (comp, l),
  848. tabIndex (tabIndex_),
  849. newValue (newValue_)
  850. {
  851. oldValue = getTabClassName (comp, tabIndex);
  852. }
  853. bool perform()
  854. {
  855. showCorrectTab();
  856. setTabClassName (getComponent(), tabIndex, newValue);
  857. changed();
  858. layout.getDocument()->refreshAllPropertyComps();
  859. return true;
  860. }
  861. bool undo()
  862. {
  863. showCorrectTab();
  864. setTabClassName (getComponent(), tabIndex, oldValue);
  865. changed();
  866. layout.getDocument()->refreshAllPropertyComps();
  867. return true;
  868. }
  869. int tabIndex;
  870. String newValue, oldValue;
  871. };
  872. };
  873. //==============================================================================
  874. class TabContentConstructorParamsProperty : public ComponentTextProperty<TabbedComponent>
  875. {
  876. public:
  877. TabContentConstructorParamsProperty (TabbedComponent* comp, JucerDocument& doc, const int tabIndex_)
  878. : ComponentTextProperty<TabbedComponent> ("constructor params", 512, false, comp, doc),
  879. tabIndex (tabIndex_)
  880. {
  881. }
  882. void setText (const String& newText) override
  883. {
  884. document.perform (new TabConstructorParamChangeAction (component, *document.getComponentLayout(), tabIndex, newText),
  885. "Change TabbedComponent content constructor param");
  886. }
  887. String getText() const override
  888. {
  889. return getTabConstructorParams (component, tabIndex);
  890. }
  891. private:
  892. int tabIndex;
  893. class TabConstructorParamChangeAction : public ComponentUndoableAction<TabbedComponent>
  894. {
  895. public:
  896. TabConstructorParamChangeAction (TabbedComponent* const comp, ComponentLayout& l, const int tabIndex_, const String& newValue_)
  897. : ComponentUndoableAction<TabbedComponent> (comp, l),
  898. tabIndex (tabIndex_),
  899. newValue (newValue_)
  900. {
  901. oldValue = getTabConstructorParams (comp, tabIndex);
  902. }
  903. bool perform()
  904. {
  905. showCorrectTab();
  906. setTabConstructorParams (getComponent(), tabIndex, newValue);
  907. changed();
  908. layout.getDocument()->refreshAllPropertyComps();
  909. return true;
  910. }
  911. bool undo()
  912. {
  913. showCorrectTab();
  914. setTabConstructorParams (getComponent(), tabIndex, oldValue);
  915. changed();
  916. layout.getDocument()->refreshAllPropertyComps();
  917. return true;
  918. }
  919. int tabIndex;
  920. String newValue, oldValue;
  921. };
  922. };
  923. //==============================================================================
  924. class TabMoveProperty : public ButtonPropertyComponent
  925. {
  926. public:
  927. TabMoveProperty (TabbedComponent* comp, JucerDocument& doc,
  928. const int tabIndex_, const int totalNumTabs_)
  929. : ButtonPropertyComponent ("move tab", false),
  930. component (comp),
  931. document (doc),
  932. tabIndex (tabIndex_),
  933. totalNumTabs (totalNumTabs_)
  934. {
  935. }
  936. void buttonClicked()
  937. {
  938. PopupMenu m;
  939. m.addItem (1, "Move this tab up", tabIndex > 0);
  940. m.addItem (2, "Move this tab down", tabIndex < totalNumTabs - 1);
  941. PopupMenu::Options options{};
  942. m.showMenuAsync (PopupMenu::Options().withTargetComponent (this), [this] (int r)
  943. {
  944. if (r != 0)
  945. document.perform (new MoveTabAction (component, *document.getComponentLayout(), tabIndex, tabIndex + (r == 2 ? 1 : -1)),
  946. "Move a tab");
  947. });
  948. }
  949. String getButtonText() const
  950. {
  951. return "Move this tab...";
  952. }
  953. TabbedComponent* const component;
  954. JucerDocument& document;
  955. const int tabIndex, totalNumTabs;
  956. private:
  957. class MoveTabAction : public ComponentUndoableAction<TabbedComponent>
  958. {
  959. public:
  960. MoveTabAction (TabbedComponent* const comp, ComponentLayout& l,
  961. const int oldIndex_, const int newIndex_)
  962. : ComponentUndoableAction<TabbedComponent> (comp, l),
  963. oldIndex (oldIndex_),
  964. newIndex (newIndex_)
  965. {
  966. }
  967. void move (int from, int to)
  968. {
  969. showCorrectTab();
  970. auto state = getTabState (getComponent(), from);
  971. getComponent()->removeTab (from);
  972. addNewTab (getComponent(), to);
  973. restoreTabState (getComponent(), to, *state);
  974. layout.getDocument()->refreshAllPropertyComps();
  975. changed();
  976. }
  977. bool perform()
  978. {
  979. move (oldIndex, newIndex);
  980. return true;
  981. }
  982. bool undo()
  983. {
  984. move (newIndex, oldIndex);
  985. return true;
  986. }
  987. private:
  988. const int oldIndex, newIndex;
  989. };
  990. };
  991. };