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.

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