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.

452 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #pragma once
  18. #include "../ui/jucer_PaintRoutineEditor.h"
  19. #include "../ui/jucer_ComponentLayoutEditor.h"
  20. //==============================================================================
  21. /**
  22. Base class for a property that edits the x, y, w, or h of a PositionedRectangle.
  23. */
  24. class PositionPropertyBase : public PropertyComponent,
  25. protected ChangeListener,
  26. private ButtonListener
  27. {
  28. public:
  29. enum ComponentPositionDimension
  30. {
  31. componentX = 0,
  32. componentY = 1,
  33. componentWidth = 2,
  34. componentHeight = 3
  35. };
  36. PositionPropertyBase (Component* comp,
  37. const String& name,
  38. ComponentPositionDimension dimension_,
  39. const bool includeAnchorOptions_,
  40. const bool allowRelativeOptions_,
  41. ComponentLayout* layout_)
  42. : PropertyComponent (name),
  43. layout (layout_),
  44. button ("mode"),
  45. component (comp),
  46. dimension (dimension_),
  47. includeAnchorOptions (includeAnchorOptions_),
  48. allowRelativeOptions (allowRelativeOptions_)
  49. {
  50. addAndMakeVisible (button);
  51. button.addListener (this);
  52. button.setTriggeredOnMouseDown (true);
  53. button.setConnectedEdges (TextButton::ConnectedOnLeft | TextButton::ConnectedOnRight);
  54. addAndMakeVisible (textEditor = new PositionPropLabel (*this));
  55. }
  56. String getText() const
  57. {
  58. RelativePositionedRectangle rpr (getPosition());
  59. PositionedRectangle& p = rpr.rect;
  60. String s;
  61. switch (dimension)
  62. {
  63. case componentX:
  64. if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
  65. s << valueToString (p.getX() * 100.0) << '%';
  66. else
  67. s << valueToString (p.getX());
  68. break;
  69. case componentY:
  70. if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
  71. s << valueToString (p.getY() * 100.0) << '%';
  72. else
  73. s << valueToString (p.getY());
  74. break;
  75. case componentWidth:
  76. if (p.getWidthMode() == PositionedRectangle::proportionalSize)
  77. s << valueToString (p.getWidth() * 100.0) << '%';
  78. else
  79. s << valueToString (p.getWidth());
  80. break;
  81. case componentHeight:
  82. if (p.getHeightMode() == PositionedRectangle::proportionalSize)
  83. s << valueToString (p.getHeight() * 100.0) << '%';
  84. else
  85. s << valueToString (p.getHeight());
  86. break;
  87. default:
  88. jassertfalse;
  89. break;
  90. };
  91. return s;
  92. }
  93. static String valueToString (const double n)
  94. {
  95. return String (roundToInt (n * 1000.0) / 1000.0);
  96. }
  97. void setText (const String& newText)
  98. {
  99. RelativePositionedRectangle rpr (getPosition());
  100. PositionedRectangle p (rpr.rect);
  101. const double value = newText.getDoubleValue();
  102. switch (dimension)
  103. {
  104. case componentX:
  105. if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
  106. p.setX (value / 100.0);
  107. else
  108. p.setX (value);
  109. break;
  110. case componentY:
  111. if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
  112. p.setY (value / 100.0);
  113. else
  114. p.setY (value);
  115. break;
  116. case componentWidth:
  117. if (p.getWidthMode() == PositionedRectangle::proportionalSize)
  118. p.setWidth (value / 100.0);
  119. else
  120. p.setWidth (value);
  121. break;
  122. case componentHeight:
  123. if (p.getHeightMode() == PositionedRectangle::proportionalSize)
  124. p.setHeight (value / 100.0);
  125. else
  126. p.setHeight (value);
  127. break;
  128. default:
  129. jassertfalse;
  130. break;
  131. };
  132. if (p != rpr.rect)
  133. {
  134. rpr.rect = p;
  135. setPosition (rpr);
  136. }
  137. }
  138. void changeListenerCallback (ChangeBroadcaster*)
  139. {
  140. refresh();
  141. }
  142. bool showMenu (ComponentLayout* compLayout)
  143. {
  144. RelativePositionedRectangle rpr (getPosition());
  145. PositionedRectangle p (rpr.rect);
  146. PositionedRectangle::AnchorPoint xAnchor = p.getAnchorPointX();
  147. PositionedRectangle::AnchorPoint yAnchor = p.getAnchorPointY();
  148. PositionedRectangle::PositionMode xMode = p.getPositionModeX();
  149. PositionedRectangle::PositionMode yMode = p.getPositionModeY();
  150. PositionedRectangle::SizeMode sizeW = p.getWidthMode();
  151. PositionedRectangle::SizeMode sizeH = p.getHeightMode();
  152. String relCompName ("parent");
  153. if (Component* const relComp = compLayout != nullptr ? compLayout->getComponentRelativePosTarget (component, (int) dimension)
  154. : nullptr)
  155. relCompName = compLayout->getComponentMemberVariableName (relComp);
  156. jassert (relCompName.isNotEmpty());
  157. PopupMenu m;
  158. if (dimension == componentX || dimension == componentY)
  159. {
  160. const PositionedRectangle::PositionMode posMode = (dimension == componentX) ? xMode : yMode;
  161. m.addItem (10, ((dimension == componentX) ? "Absolute distance from left of "
  162. : "Absolute distance from top of ") + relCompName,
  163. true, posMode == PositionedRectangle::absoluteFromParentTopLeft);
  164. m.addItem (11, ((dimension == componentX) ? "Absolute distance from right of "
  165. : "Absolute distance from bottom of ") + relCompName,
  166. true, posMode == PositionedRectangle::absoluteFromParentBottomRight);
  167. m.addItem (12, "Absolute distance from centre of " + relCompName,
  168. true, posMode == PositionedRectangle::absoluteFromParentCentre);
  169. m.addItem (13, ((dimension == componentX) ? "Percentage of width of "
  170. : "Percentage of height of ") + relCompName,
  171. true, posMode == PositionedRectangle::proportionOfParentSize);
  172. m.addSeparator();
  173. if (includeAnchorOptions)
  174. {
  175. const PositionedRectangle::AnchorPoint anchor = (dimension == componentX) ? xAnchor : yAnchor;
  176. m.addItem (14, (dimension == componentX) ? "Anchored at left of component"
  177. : "Anchored at top of component",
  178. true, anchor == PositionedRectangle::anchorAtLeftOrTop);
  179. m.addItem (15, "Anchored at centre of component", true, anchor == PositionedRectangle::anchorAtCentre);
  180. m.addItem (16, (dimension == componentX) ? "Anchored at right of component"
  181. : "Anchored at bottom of component",
  182. true, anchor == PositionedRectangle::anchorAtRightOrBottom);
  183. }
  184. }
  185. else
  186. {
  187. const PositionedRectangle::SizeMode sizeMode = (dimension == componentWidth) ? sizeW : sizeH;
  188. m.addItem (20, (dimension == componentWidth) ? "Absolute width"
  189. : "Absolute height",
  190. true, sizeMode == PositionedRectangle::absoluteSize);
  191. m.addItem (21, ((dimension == componentWidth) ? "Percentage of width of "
  192. : "Percentage of height of ") + relCompName,
  193. true, sizeMode == PositionedRectangle::proportionalSize);
  194. m.addItem (22, ((dimension == componentWidth) ? "Subtracted from width of "
  195. : "Subtracted from height of ") + relCompName,
  196. true, sizeMode == PositionedRectangle::parentSizeMinusAbsolute);
  197. }
  198. if (allowRelativeOptions && compLayout != nullptr)
  199. {
  200. m.addSeparator();
  201. m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension));
  202. }
  203. WeakReference<Component> ref (this);
  204. const int menuResult = m.showAt (&button);
  205. if (menuResult == 0 || ref == nullptr)
  206. return false;
  207. switch (menuResult)
  208. {
  209. case 10:
  210. if (dimension == componentX)
  211. xMode = PositionedRectangle::absoluteFromParentTopLeft;
  212. else
  213. yMode = PositionedRectangle::absoluteFromParentTopLeft;
  214. break;
  215. case 11:
  216. if (dimension == componentX)
  217. xMode = PositionedRectangle::absoluteFromParentBottomRight;
  218. else
  219. yMode = PositionedRectangle::absoluteFromParentBottomRight;
  220. break;
  221. case 12:
  222. if (dimension == componentX)
  223. xMode = PositionedRectangle::absoluteFromParentCentre;
  224. else
  225. yMode = PositionedRectangle::absoluteFromParentCentre;
  226. break;
  227. case 13:
  228. if (dimension == componentX)
  229. xMode = PositionedRectangle::proportionOfParentSize;
  230. else
  231. yMode = PositionedRectangle::proportionOfParentSize;
  232. break;
  233. case 14:
  234. if (dimension == componentX)
  235. xAnchor = PositionedRectangle::anchorAtLeftOrTop;
  236. else
  237. yAnchor = PositionedRectangle::anchorAtLeftOrTop;
  238. break;
  239. case 15:
  240. if (dimension == componentX)
  241. xAnchor = PositionedRectangle::anchorAtCentre;
  242. else
  243. yAnchor = PositionedRectangle::anchorAtCentre;
  244. break;
  245. case 16:
  246. if (dimension == componentX)
  247. xAnchor = PositionedRectangle::anchorAtRightOrBottom;
  248. else
  249. yAnchor = PositionedRectangle::anchorAtRightOrBottom;
  250. break;
  251. case 20:
  252. if (dimension == componentWidth)
  253. sizeW = PositionedRectangle::absoluteSize;
  254. else
  255. sizeH = PositionedRectangle::absoluteSize;
  256. break;
  257. case 21:
  258. if (dimension == componentWidth)
  259. sizeW = PositionedRectangle::proportionalSize;
  260. else
  261. sizeH = PositionedRectangle::proportionalSize;
  262. break;
  263. case 22:
  264. if (dimension == componentWidth)
  265. sizeW = PositionedRectangle::parentSizeMinusAbsolute;
  266. else
  267. sizeH = PositionedRectangle::parentSizeMinusAbsolute;
  268. break;
  269. default:
  270. if (allowRelativeOptions && compLayout != nullptr)
  271. compLayout->processRelativeTargetMenuResult (component, (int) dimension, menuResult);
  272. break;
  273. }
  274. Rectangle<int> parentArea;
  275. if (component->findParentComponentOfClass<ComponentLayoutEditor>() != 0)
  276. parentArea.setSize (component->getParentWidth(), component->getParentHeight());
  277. else if (PaintRoutineEditor* pre = dynamic_cast<PaintRoutineEditor*> (component->getParentComponent()))
  278. parentArea = pre->getComponentArea();
  279. else
  280. jassertfalse;
  281. int x, xw, y, yh, w, h;
  282. rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);
  283. PositionedRectangle xyRect (p);
  284. PositionedRectangle whRect (p);
  285. xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
  286. Rectangle<int> (x, y, xw, yh));
  287. whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
  288. Rectangle<int> (x, y, w, h));
  289. p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
  290. Rectangle<int> (x, y, xw, yh));
  291. p.setX (xyRect.getX());
  292. p.setY (xyRect.getY());
  293. p.setWidth (whRect.getWidth());
  294. p.setHeight (whRect.getHeight());
  295. if (p != rpr.rect)
  296. {
  297. rpr.rect = p;
  298. setPosition (rpr);
  299. }
  300. return true;
  301. }
  302. void resized()
  303. {
  304. const Rectangle<int> r (getLookAndFeel().getPropertyComponentContentPosition (*this));
  305. button.changeWidthToFitText (r.getHeight());
  306. button.setTopRightPosition (r.getRight(), r.getY());
  307. textEditor->setBounds (r.getX(), r.getY(), button.getX() - r.getX(), r.getHeight());
  308. }
  309. void refresh()
  310. {
  311. textEditor->setText (getText(), dontSendNotification);
  312. }
  313. void buttonClicked (Button*)
  314. {
  315. if (showMenu (layout))
  316. refresh(); // (to clear the text editor if it's got focus)
  317. }
  318. void textWasEdited()
  319. {
  320. const String newText (textEditor->getText());
  321. if (getText() != newText)
  322. setText (newText);
  323. }
  324. //==============================================================================
  325. virtual void setPosition (const RelativePositionedRectangle& newPos) = 0;
  326. virtual RelativePositionedRectangle getPosition() const = 0;
  327. protected:
  328. class PositionPropLabel : public Label
  329. {
  330. PositionPropertyBase& owner;
  331. public:
  332. PositionPropLabel (PositionPropertyBase& owner_)
  333. : Label (String(), String()),
  334. owner (owner_)
  335. {
  336. setEditable (true, true, false);
  337. setColour (backgroundColourId, Colours::white);
  338. setColour (textColourId, Colours::black);
  339. setColour (outlineColourId, findColour (ComboBox::outlineColourId));
  340. setColour (TextEditor::textColourId, Colours::black);
  341. setColour (TextEditor::backgroundColourId, Colours::white);
  342. setColour (TextEditor::outlineColourId, findColour (ComboBox::outlineColourId));
  343. }
  344. TextEditor* createEditorComponent()
  345. {
  346. TextEditor* ed = Label::createEditorComponent();
  347. ed->setInputRestrictions (14, "0123456789.-%");
  348. return ed;
  349. }
  350. void textWasEdited()
  351. {
  352. owner.textWasEdited();
  353. }
  354. };
  355. ComponentLayout* layout;
  356. ScopedPointer<PositionPropLabel> textEditor;
  357. TextButton button;
  358. Component* component;
  359. ComponentPositionDimension dimension;
  360. const bool includeAnchorOptions, allowRelativeOptions;
  361. };