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.

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