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.

453 lines
16KB

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