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.

775 lines
27KB

  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 LabelHandler : public ComponentTypeHandler
  21. {
  22. public:
  23. LabelHandler()
  24. : ComponentTypeHandler ("Label", "juce::Label", typeid (Label), 150, 24)
  25. {
  26. registerColour (juce::Label::backgroundColourId, "background", "bkgCol");
  27. registerColour (juce::Label::textColourId, "text", "textCol");
  28. registerColour (juce::Label::outlineColourId, "outline", "outlineCol");
  29. registerColour (juce::TextEditor::textColourId, "editor text", "edTextCol");
  30. registerColour (juce::TextEditor::backgroundColourId, "editor bkg", "edBkgCol");
  31. registerColour (juce::TextEditor::highlightColourId, "highlight", "hiliteCol");
  32. }
  33. Component* createNewComponent (JucerDocument*) override
  34. {
  35. return new Label ("new label", "label text");
  36. }
  37. XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
  38. {
  39. Label* const l = dynamic_cast<Label*> (comp);
  40. XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
  41. e->setAttribute ("labelText", l->getText());
  42. e->setAttribute ("editableSingleClick", l->isEditableOnSingleClick());
  43. e->setAttribute ("editableDoubleClick", l->isEditableOnDoubleClick());
  44. e->setAttribute ("focusDiscardsChanges", l->doesLossOfFocusDiscardChanges());
  45. e->setAttribute ("fontname", l->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont()).toString());
  46. e->setAttribute ("fontsize", roundToInt (l->getFont().getHeight() * 100.0) / 100.0);
  47. e->setAttribute ("kerning", roundToInt (l->getFont().getExtraKerningFactor() * 1000.0) / 1000.0);
  48. e->setAttribute ("bold", l->getFont().isBold());
  49. e->setAttribute ("italic", l->getFont().isItalic());
  50. e->setAttribute ("justification", l->getJustificationType().getFlags());
  51. if (l->getFont().getTypefaceStyle() != "Regular")
  52. {
  53. e->setAttribute ("typefaceStyle", l->getFont().getTypefaceStyle());
  54. }
  55. return e;
  56. }
  57. bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
  58. {
  59. Label* const l = dynamic_cast<Label*> (comp);
  60. if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
  61. return false;
  62. Label defaultLabel;
  63. Font font;
  64. font.setHeight ((float) xml.getDoubleAttribute ("fontsize", 15.0));
  65. font.setBold (xml.getBoolAttribute ("bold", false));
  66. font.setItalic (xml.getBoolAttribute ("italic", false));
  67. font.setExtraKerningFactor ((float) xml.getDoubleAttribute ("kerning", 0.0));
  68. auto fontStyle = xml.getStringAttribute ("typefaceStyle");
  69. if (! fontStyle.isEmpty())
  70. font.setTypefaceStyle (fontStyle);
  71. l->setFont (font);
  72. l->getProperties().set ("typefaceName", xml.getStringAttribute ("fontname", FontPropertyComponent::getDefaultFont()));
  73. updateLabelFont (l);
  74. l->setJustificationType (Justification (xml.getIntAttribute ("justification", Justification::centred)));
  75. l->setText (xml.getStringAttribute ("labelText", "Label Text"), dontSendNotification);
  76. l->setEditable (xml.getBoolAttribute ("editableSingleClick", defaultLabel.isEditableOnSingleClick()),
  77. xml.getBoolAttribute ("editableDoubleClick", defaultLabel.isEditableOnDoubleClick()),
  78. xml.getBoolAttribute ("focusDiscardsChanges", defaultLabel.doesLossOfFocusDiscardChanges()));
  79. return true;
  80. }
  81. static void updateLabelFont (Label* label)
  82. {
  83. Font f (label->getFont());
  84. f = FontPropertyComponent::applyNameToFont (label->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont()), f);
  85. label->setFont (f);
  86. }
  87. String getCreationParameters (GeneratedCode& code, Component* component) override
  88. {
  89. Label* const l = dynamic_cast<Label*> (component);
  90. return quotedString (component->getName(), false)
  91. + ",\n"
  92. + quotedString (l->getText(), code.shouldUseTransMacro());
  93. }
  94. void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
  95. {
  96. ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
  97. Label* const l = dynamic_cast<Label*> (component);
  98. String s;
  99. s << memberVariableName << "->setFont ("
  100. << FontPropertyComponent::getCompleteFontCode (l->getFont(), l->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont()))
  101. << ");\n"
  102. << memberVariableName << "->setJustificationType ("
  103. << CodeHelpers::justificationToCode (l->getJustificationType())
  104. << ");\n"
  105. << memberVariableName << "->setEditable ("
  106. << CodeHelpers::boolLiteral (l->isEditableOnSingleClick()) << ", "
  107. << CodeHelpers::boolLiteral (l->isEditableOnDoubleClick()) << ", "
  108. << CodeHelpers::boolLiteral (l->doesLossOfFocusDiscardChanges()) << ");\n"
  109. << getColourIntialisationCode (component, memberVariableName);
  110. if (needsCallback (component))
  111. s << memberVariableName << "->addListener (this);\n";
  112. s << '\n';
  113. code.constructorCode += s;
  114. }
  115. void fillInGeneratedCode (Component* component, GeneratedCode& code) override
  116. {
  117. ComponentTypeHandler::fillInGeneratedCode (component, code);
  118. if (needsCallback (component))
  119. {
  120. String& callback = code.getCallbackCode ("public juce::Label::Listener",
  121. "void",
  122. "labelTextChanged (juce::Label* labelThatHasChanged)",
  123. true);
  124. if (callback.trim().isNotEmpty())
  125. callback << "else ";
  126. const String memberVariableName (code.document->getComponentLayout()->getComponentMemberVariableName (component));
  127. const String userCodeComment ("UserLabelCode_" + memberVariableName);
  128. callback
  129. << "if (labelThatHasChanged == " << memberVariableName << ".get())\n"
  130. << "{\n //[" << userCodeComment << "] -- add your label text handling code here..\n //[/" << userCodeComment << "]\n}\n";
  131. }
  132. }
  133. void getEditableProperties (Component* component, JucerDocument& document,
  134. Array<PropertyComponent*>& props, bool multipleSelected) override
  135. {
  136. ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
  137. if (multipleSelected)
  138. return;
  139. if (auto* const l = dynamic_cast<Label*> (component))
  140. {
  141. props.add (new LabelTextProperty (l, document));
  142. props.add (new LabelJustificationProperty (l, document));
  143. props.add (new FontNameProperty (l, document));
  144. props.add (new FontStyleProperty (l, document));
  145. props.add (new FontSizeProperty (l, document));
  146. props.add (new FontKerningProperty (l, document));
  147. props.add (new LabelEditableProperty (l, document));
  148. if (l->isEditableOnDoubleClick() || l->isEditableOnSingleClick())
  149. props.add (new LabelLossOfFocusProperty (l, document));
  150. }
  151. addColourProperties (component, document, props);
  152. }
  153. static bool needsCallback (Component* label)
  154. {
  155. return ((Label*) label)->isEditableOnSingleClick()
  156. || ((Label*) label)->isEditableOnDoubleClick(); // xxx should be configurable
  157. }
  158. private:
  159. //==============================================================================
  160. class LabelTextProperty : public ComponentTextProperty <Label>
  161. {
  162. public:
  163. LabelTextProperty (Label* comp, JucerDocument& doc)
  164. : ComponentTextProperty <Label> ("text", 10000, true, comp, doc)
  165. {}
  166. void setText (const String& newText) override
  167. {
  168. document.perform (new LabelTextChangeAction (component, *document.getComponentLayout(), newText),
  169. "Change Label text");
  170. }
  171. String getText() const override
  172. {
  173. return component->getText();
  174. }
  175. private:
  176. class LabelTextChangeAction : public ComponentUndoableAction <Label>
  177. {
  178. public:
  179. LabelTextChangeAction (Label* const comp, ComponentLayout& l, const String& newState_)
  180. : ComponentUndoableAction <Label> (comp, l),
  181. newState (newState_)
  182. {
  183. oldState = comp->getText();
  184. }
  185. bool perform()
  186. {
  187. showCorrectTab();
  188. getComponent()->setText (newState, dontSendNotification);
  189. changed();
  190. return true;
  191. }
  192. bool undo()
  193. {
  194. showCorrectTab();
  195. getComponent()->setText (oldState, dontSendNotification);
  196. changed();
  197. return true;
  198. }
  199. String newState, oldState;
  200. };
  201. };
  202. //==============================================================================
  203. class LabelEditableProperty : public ComponentChoiceProperty <Label>
  204. {
  205. public:
  206. LabelEditableProperty (Label* comp, JucerDocument& doc)
  207. : ComponentChoiceProperty <Label> ("editing", comp, doc)
  208. {
  209. choices.add ("read-only");
  210. choices.add ("edit on single-click");
  211. choices.add ("edit on double-click");
  212. }
  213. void setIndex (int newIndex)
  214. {
  215. document.perform (new LabelEditableChangeAction (component, *document.getComponentLayout(), newIndex),
  216. "Change Label editability");
  217. }
  218. int getIndex() const
  219. {
  220. return component->isEditableOnSingleClick()
  221. ? 1
  222. : (component->isEditableOnDoubleClick() ? 2 : 0);
  223. }
  224. private:
  225. class LabelEditableChangeAction : public ComponentUndoableAction <Label>
  226. {
  227. public:
  228. LabelEditableChangeAction (Label* const comp, ComponentLayout& l, const int newState_)
  229. : ComponentUndoableAction <Label> (comp, l),
  230. newState (newState_)
  231. {
  232. oldState = comp->isEditableOnSingleClick()
  233. ? 1
  234. : (comp->isEditableOnDoubleClick() ? 2 : 0);
  235. }
  236. bool perform()
  237. {
  238. showCorrectTab();
  239. getComponent()->setEditable (newState == 1, newState >= 1, getComponent()->doesLossOfFocusDiscardChanges());
  240. changed();
  241. layout.getSelectedSet().changed();
  242. return true;
  243. }
  244. bool undo()
  245. {
  246. showCorrectTab();
  247. getComponent()->setEditable (oldState == 1, oldState >= 1, getComponent()->doesLossOfFocusDiscardChanges());
  248. changed();
  249. layout.getSelectedSet().changed();
  250. return true;
  251. }
  252. int newState, oldState;
  253. };
  254. };
  255. //==============================================================================
  256. class LabelLossOfFocusProperty : public ComponentChoiceProperty <Label>
  257. {
  258. public:
  259. LabelLossOfFocusProperty (Label* comp, JucerDocument& doc)
  260. : ComponentChoiceProperty <Label> ("focus", comp, doc)
  261. {
  262. choices.add ("loss of focus discards changes");
  263. choices.add ("loss of focus commits changes");
  264. }
  265. void setIndex (int newIndex)
  266. {
  267. document.perform (new LabelFocusLossChangeAction (component, *document.getComponentLayout(), newIndex == 0),
  268. "Change Label focus behaviour");
  269. }
  270. int getIndex() const
  271. {
  272. return component->doesLossOfFocusDiscardChanges() ? 0 : 1;
  273. }
  274. private:
  275. class LabelFocusLossChangeAction : public ComponentUndoableAction <Label>
  276. {
  277. public:
  278. LabelFocusLossChangeAction (Label* const comp, ComponentLayout& l, const bool newState_)
  279. : ComponentUndoableAction <Label> (comp, l),
  280. newState (newState_)
  281. {
  282. oldState = comp->doesLossOfFocusDiscardChanges();
  283. }
  284. bool perform()
  285. {
  286. showCorrectTab();
  287. getComponent()->setEditable (getComponent()->isEditableOnSingleClick(),
  288. getComponent()->isEditableOnDoubleClick(),
  289. newState);
  290. changed();
  291. return true;
  292. }
  293. bool undo()
  294. {
  295. showCorrectTab();
  296. getComponent()->setEditable (getComponent()->isEditableOnSingleClick(),
  297. getComponent()->isEditableOnDoubleClick(),
  298. oldState);
  299. changed();
  300. return true;
  301. }
  302. bool newState, oldState;
  303. };
  304. };
  305. //==============================================================================
  306. class LabelJustificationProperty : public JustificationProperty,
  307. private ChangeListener
  308. {
  309. public:
  310. LabelJustificationProperty (Label* const label_, JucerDocument& doc)
  311. : JustificationProperty ("layout", false),
  312. label (label_),
  313. document (doc)
  314. {
  315. document.addChangeListener (this);
  316. }
  317. ~LabelJustificationProperty() override
  318. {
  319. document.removeChangeListener (this);
  320. }
  321. void setJustification (Justification newJustification) override
  322. {
  323. document.perform (new LabelJustifyChangeAction (label, *document.getComponentLayout(), newJustification),
  324. "Change Label justification");
  325. }
  326. Justification getJustification() const override
  327. {
  328. return label->getJustificationType();
  329. }
  330. private:
  331. void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
  332. Label* const label;
  333. JucerDocument& document;
  334. class LabelJustifyChangeAction : public ComponentUndoableAction <Label>
  335. {
  336. public:
  337. LabelJustifyChangeAction (Label* const comp, ComponentLayout& l, Justification newState_)
  338. : ComponentUndoableAction <Label> (comp, l),
  339. newState (newState_),
  340. oldState (comp->getJustificationType())
  341. {
  342. }
  343. bool perform()
  344. {
  345. showCorrectTab();
  346. getComponent()->setJustificationType (newState);
  347. changed();
  348. return true;
  349. }
  350. bool undo()
  351. {
  352. showCorrectTab();
  353. getComponent()->setJustificationType (oldState);
  354. changed();
  355. return true;
  356. }
  357. Justification newState, oldState;
  358. };
  359. };
  360. //==============================================================================
  361. class FontNameProperty : public FontPropertyComponent,
  362. private ChangeListener
  363. {
  364. public:
  365. FontNameProperty (Label* const label_, JucerDocument& doc)
  366. : FontPropertyComponent ("font"),
  367. label (label_),
  368. document (doc)
  369. {
  370. document.addChangeListener (this);
  371. }
  372. ~FontNameProperty() override
  373. {
  374. document.removeChangeListener (this);
  375. }
  376. void setTypefaceName (const String& newFontName) override
  377. {
  378. document.perform (new FontNameChangeAction (label, *document.getComponentLayout(), newFontName),
  379. "Change Label typeface");
  380. }
  381. String getTypefaceName() const override
  382. {
  383. return label->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont());
  384. }
  385. private:
  386. void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
  387. Label* const label;
  388. JucerDocument& document;
  389. class FontNameChangeAction : public ComponentUndoableAction <Label>
  390. {
  391. public:
  392. FontNameChangeAction (Label* const comp, ComponentLayout& l, const String& newState_)
  393. : ComponentUndoableAction <Label> (comp, l),
  394. newState (newState_)
  395. {
  396. oldState = comp->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont());
  397. }
  398. bool perform()
  399. {
  400. showCorrectTab();
  401. getComponent()->getProperties().set ("typefaceName", newState);
  402. LabelHandler::updateLabelFont (getComponent());
  403. changed();
  404. return true;
  405. }
  406. bool undo()
  407. {
  408. showCorrectTab();
  409. getComponent()->getProperties().set ("typefaceName", oldState);
  410. LabelHandler::updateLabelFont (getComponent());
  411. changed();
  412. return true;
  413. }
  414. String newState, oldState;
  415. };
  416. };
  417. //==============================================================================
  418. class FontSizeProperty : public SliderPropertyComponent,
  419. private ChangeListener
  420. {
  421. public:
  422. FontSizeProperty (Label* const label_, JucerDocument& doc)
  423. : SliderPropertyComponent ("size", 1.0, 250.0, 0.1, 0.3),
  424. label (label_),
  425. document (doc)
  426. {
  427. document.addChangeListener (this);
  428. }
  429. ~FontSizeProperty() override
  430. {
  431. document.removeChangeListener (this);
  432. }
  433. void setValue (double newValue) override
  434. {
  435. document.getUndoManager().undoCurrentTransactionOnly();
  436. document.perform (new FontSizeChangeAction (label, *document.getComponentLayout(), (float) newValue),
  437. "Change Label font size");
  438. }
  439. double getValue() const override
  440. {
  441. return label->getFont().getHeight();
  442. }
  443. private:
  444. void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
  445. Label* const label;
  446. JucerDocument& document;
  447. class FontSizeChangeAction : public ComponentUndoableAction <Label>
  448. {
  449. public:
  450. FontSizeChangeAction (Label* const comp, ComponentLayout& l, const float newState_)
  451. : ComponentUndoableAction <Label> (comp, l),
  452. newState (newState_)
  453. {
  454. oldState = comp->getFont().getHeight();
  455. }
  456. bool perform()
  457. {
  458. showCorrectTab();
  459. Font f (getComponent()->getFont());
  460. f.setHeight ((float) newState);
  461. getComponent()->setFont (f);
  462. changed();
  463. return true;
  464. }
  465. bool undo()
  466. {
  467. showCorrectTab();
  468. Font f (getComponent()->getFont());
  469. f.setHeight ((float) oldState);
  470. getComponent()->setFont (f);
  471. changed();
  472. return true;
  473. }
  474. float newState, oldState;
  475. };
  476. };
  477. //==============================================================================
  478. class FontStyleProperty : public ChoicePropertyComponent,
  479. private ChangeListener
  480. {
  481. public:
  482. FontStyleProperty (Label* const label_, JucerDocument& doc)
  483. : ChoicePropertyComponent ("style"),
  484. label (label_),
  485. document (doc)
  486. {
  487. document.addChangeListener (this);
  488. updateStylesList (label->getFont());
  489. }
  490. ~FontStyleProperty() override
  491. {
  492. document.removeChangeListener (this);
  493. }
  494. void updateStylesList (const Font& newFont)
  495. {
  496. if (getNumChildComponents() > 0)
  497. {
  498. if (auto cb = dynamic_cast<ComboBox*> (getChildComponent (0)))
  499. cb->clear();
  500. getChildComponent (0)->setVisible (false);
  501. removeAllChildren();
  502. }
  503. choices.clear();
  504. choices.add ("Regular");
  505. choices.add ("Bold");
  506. choices.add ("Italic");
  507. choices.add ("Bold Italic");
  508. choices.mergeArray (newFont.getAvailableStyles());
  509. refresh();
  510. }
  511. void setIndex (int newIndex) override
  512. {
  513. Font f (label->getFont());
  514. if (f.getAvailableStyles().contains (choices[newIndex]))
  515. {
  516. f.setBold (false);
  517. f.setItalic (false);
  518. f.setTypefaceStyle (choices[newIndex]);
  519. }
  520. else
  521. {
  522. f.setTypefaceStyle ("Regular");
  523. f.setBold (newIndex == 1 || newIndex == 3);
  524. f.setItalic (newIndex == 2 || newIndex == 3);
  525. }
  526. document.perform (new FontStyleChangeAction (label, *document.getComponentLayout(), f),
  527. "Change Label font style");
  528. }
  529. int getIndex() const override
  530. {
  531. auto f = label->getFont();
  532. const auto typefaceIndex = choices.indexOf (f.getTypefaceStyle());
  533. if (typefaceIndex == -1)
  534. {
  535. if (f.isBold() && f.isItalic())
  536. return 3;
  537. else if (f.isBold())
  538. return 1;
  539. else if (f.isItalic())
  540. return 2;
  541. return 0;
  542. }
  543. return typefaceIndex;
  544. }
  545. private:
  546. void changeListenerCallback (ChangeBroadcaster*) override
  547. {
  548. updateStylesList (label->getFont());
  549. }
  550. Label* const label;
  551. JucerDocument& document;
  552. class FontStyleChangeAction : public ComponentUndoableAction <Label>
  553. {
  554. public:
  555. FontStyleChangeAction (Label* const comp, ComponentLayout& l, const Font& newState_)
  556. : ComponentUndoableAction <Label> (comp, l),
  557. newState (newState_)
  558. {
  559. oldState = comp->getFont();
  560. }
  561. bool perform()
  562. {
  563. showCorrectTab();
  564. getComponent()->setFont (newState);
  565. changed();
  566. return true;
  567. }
  568. bool undo()
  569. {
  570. showCorrectTab();
  571. getComponent()->setFont (oldState);
  572. changed();
  573. return true;
  574. }
  575. Font newState, oldState;
  576. };
  577. };
  578. //==============================================================================
  579. class FontKerningProperty : public SliderPropertyComponent,
  580. private ChangeListener
  581. {
  582. public:
  583. FontKerningProperty (Label* const label_, JucerDocument& doc)
  584. : SliderPropertyComponent ("kerning", -0.5, 0.5, 0.001),
  585. label (label_),
  586. document (doc)
  587. {
  588. document.addChangeListener (this);
  589. }
  590. ~FontKerningProperty() override
  591. {
  592. document.removeChangeListener (this);
  593. }
  594. void setValue (double newValue) override
  595. {
  596. document.getUndoManager().undoCurrentTransactionOnly();
  597. document.perform (new FontKerningChangeAction (label, *document.getComponentLayout(), (float) newValue),
  598. "Change Label font kerning");
  599. }
  600. double getValue() const override
  601. {
  602. return label->getFont().getExtraKerningFactor();
  603. }
  604. private:
  605. void changeListenerCallback (ChangeBroadcaster*) override
  606. {
  607. refresh();
  608. }
  609. Label* const label;
  610. JucerDocument& document;
  611. class FontKerningChangeAction : public ComponentUndoableAction <Label>
  612. {
  613. public:
  614. FontKerningChangeAction (Label* const comp, ComponentLayout& l, const float newState_)
  615. : ComponentUndoableAction <Label> (comp, l),
  616. newState (newState_)
  617. {
  618. oldState = comp->getFont().getExtraKerningFactor();
  619. }
  620. bool perform()
  621. {
  622. showCorrectTab();
  623. Font f (getComponent()->getFont());
  624. f.setExtraKerningFactor ((float) newState);
  625. getComponent()->setFont (f);
  626. changed();
  627. return true;
  628. }
  629. bool undo()
  630. {
  631. showCorrectTab();
  632. Font f (getComponent()->getFont());
  633. f.setExtraKerningFactor ((float) oldState);
  634. getComponent()->setFont (f);
  635. changed();
  636. return true;
  637. }
  638. float newState, oldState;
  639. };
  640. };
  641. };