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.

645 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_Coordinate.h"
  19. //==============================================================================
  20. Coordinate::Coordinate()
  21. : value (0)
  22. {
  23. }
  24. Coordinate::Coordinate (const double absoluteDistanceFromOrigin, const bool horizontal_)
  25. : anchor1 (getOriginAnchorName (horizontal_)),
  26. value (absoluteDistanceFromOrigin)
  27. {
  28. }
  29. Coordinate::Coordinate (const double absoluteDistance, const String& source)
  30. : anchor1 (source.trim()),
  31. value (absoluteDistance)
  32. {
  33. jassert (anchor1.isNotEmpty());
  34. }
  35. Coordinate::Coordinate (const double relativeProportion, const String& pos1, const String& pos2)
  36. : anchor1 (pos1.trim()),
  37. anchor2 (pos2.trim()),
  38. value (relativeProportion)
  39. {
  40. jassert (anchor1.isNotEmpty());
  41. jassert (anchor2.isNotEmpty());
  42. }
  43. Coordinate::~Coordinate()
  44. {
  45. }
  46. //==============================================================================
  47. const String Coordinate::Strings::parent ("parent");
  48. const String Coordinate::Strings::left ("left");
  49. const String Coordinate::Strings::right ("right");
  50. const String Coordinate::Strings::top ("top");
  51. const String Coordinate::Strings::bottom ("bottom");
  52. const String Coordinate::Strings::originX ("parent.left");
  53. const String Coordinate::Strings::originY ("parent.top");
  54. const String Coordinate::Strings::extentX ("parent.right");
  55. const String Coordinate::Strings::extentY ("parent.bottom");
  56. const String Coordinate::getObjectName (const String& fullName)
  57. {
  58. return fullName.upToFirstOccurrenceOf (".", false, false);
  59. }
  60. const String Coordinate::getEdgeName (const String& fullName)
  61. {
  62. return fullName.fromFirstOccurrenceOf (".", false, false);
  63. }
  64. const Coordinate Coordinate::getAnchorCoordinate1() const
  65. {
  66. return Coordinate (0.0, anchor1);
  67. }
  68. const Coordinate Coordinate::getAnchorCoordinate2() const
  69. {
  70. return Coordinate (0.0, anchor2);
  71. }
  72. bool Coordinate::isOrigin (const String& name)
  73. {
  74. return name.isEmpty()
  75. || name == Strings::originX
  76. || name == Strings::originY;
  77. }
  78. const String Coordinate::getOriginAnchorName (const bool isHorizontal) const throw()
  79. {
  80. return isHorizontal ? Strings::originX : Strings::originY;
  81. }
  82. const String Coordinate::getExtentAnchorName (const bool isHorizontal) const throw()
  83. {
  84. return isHorizontal ? Strings::extentX : Strings::extentY;
  85. }
  86. //==============================================================================
  87. Coordinate::RecursiveCoordinateException::RecursiveCoordinateException()
  88. : std::runtime_error ("Coordinate::RecursiveCoordinateException")
  89. {
  90. }
  91. const Coordinate Coordinate::lookUpName (const String& name, const NamedCoordinateFinder& nameSource) const
  92. {
  93. return nameSource.findNamedCoordinate (getObjectName (name), getEdgeName (name));
  94. }
  95. double Coordinate::resolveAnchor (const String& anchorName, const NamedCoordinateFinder& nameSource, int recursionCounter) const
  96. {
  97. if (isOrigin (anchorName))
  98. return 0.0;
  99. return lookUpName (anchorName, nameSource).resolve (nameSource, recursionCounter + 1);
  100. }
  101. double Coordinate::resolve (const NamedCoordinateFinder& nameSource, int recursionCounter) const
  102. {
  103. if (recursionCounter > 100)
  104. {
  105. jassertfalse
  106. throw RecursiveCoordinateException();
  107. }
  108. const double pos1 = resolveAnchor (anchor1, nameSource, recursionCounter);
  109. return isProportional() ? pos1 + (resolveAnchor (anchor2, nameSource, recursionCounter) - pos1) * value
  110. : pos1 + value;
  111. }
  112. double Coordinate::resolve (const NamedCoordinateFinder& nameSource) const
  113. {
  114. try
  115. {
  116. return resolve (nameSource, 0);
  117. }
  118. catch (RecursiveCoordinateException&)
  119. {}
  120. return 0.0;
  121. }
  122. void Coordinate::moveToAbsolute (double newPos, const NamedCoordinateFinder& nameSource)
  123. {
  124. try
  125. {
  126. const double pos1 = resolveAnchor (anchor1, nameSource, 0);
  127. if (isProportional())
  128. {
  129. const double size = resolveAnchor (anchor2, nameSource, 0) - pos1;
  130. if (size != 0)
  131. value = (newPos - pos1) / size;
  132. }
  133. else
  134. {
  135. value = newPos - pos1;
  136. }
  137. }
  138. catch (RecursiveCoordinateException&)
  139. {}
  140. }
  141. void Coordinate::toggleProportionality (const NamedCoordinateFinder& nameSource, bool isHorizontal)
  142. {
  143. const double oldValue = resolve (nameSource);
  144. anchor1 = getOriginAnchorName (isHorizontal);
  145. anchor2 = isProportional() ? String::empty
  146. : getExtentAnchorName (isHorizontal);
  147. moveToAbsolute (oldValue, nameSource);
  148. }
  149. //==============================================================================
  150. bool Coordinate::references (const String& coordName, const NamedCoordinateFinder& nameSource) const
  151. {
  152. if (isOrigin (anchor1) && ! isProportional())
  153. return isOrigin (coordName);
  154. return anchor1 == coordName
  155. || anchor2 == coordName
  156. || lookUpName (anchor1, nameSource).references (coordName, nameSource)
  157. || (isProportional() && lookUpName (anchor2, nameSource).references (coordName, nameSource));
  158. }
  159. //==============================================================================
  160. namespace CoordParserHelpers
  161. {
  162. static void skipWhitespace (const String& s, int& i)
  163. {
  164. while (CharacterFunctions::isWhitespace (s[i]))
  165. ++i;
  166. }
  167. static const String readAnchorName (const String& s, int& i)
  168. {
  169. skipWhitespace (s, i);
  170. if (CharacterFunctions::isLetter (s[i]) || s[i] == '_')
  171. {
  172. int start = i;
  173. while (CharacterFunctions::isLetterOrDigit (s[i]) || s[i] == '_' || s[i] == '.')
  174. ++i;
  175. return s.substring (start, i);
  176. }
  177. return String::empty;
  178. }
  179. static double readNumber (const String& s, int& i)
  180. {
  181. skipWhitespace (s, i);
  182. int start = i;
  183. if (CharacterFunctions::isDigit (s[i]) || s[i] == '.' || s[i] == '-')
  184. ++i;
  185. while (CharacterFunctions::isDigit (s[i]) || s[i] == '.')
  186. ++i;
  187. if ((s[i] == 'e' || s[i] == 'E')
  188. && (CharacterFunctions::isDigit (s[i + 1])
  189. || s[i + 1] == '-'
  190. || s[i + 1] == '+'))
  191. {
  192. i += 2;
  193. while (CharacterFunctions::isDigit (s[i]))
  194. ++i;
  195. }
  196. const double value = s.substring (start, i).getDoubleValue();
  197. while (CharacterFunctions::isWhitespace (s[i]) || s[i] == ',')
  198. ++i;
  199. return value;
  200. }
  201. static const String limitedAccuracyString (const double n)
  202. {
  203. return String (n, 3).trimCharactersAtEnd ("0").trimCharactersAtEnd (".");
  204. }
  205. }
  206. Coordinate::Coordinate (const String& s, bool isHorizontal)
  207. : value (0)
  208. {
  209. int i = 0;
  210. anchor1 = CoordParserHelpers::readAnchorName (s, i);
  211. if (anchor1.isNotEmpty())
  212. {
  213. CoordParserHelpers::skipWhitespace (s, i);
  214. if (s[i] == '+')
  215. value = CoordParserHelpers::readNumber (s, ++i);
  216. else if (s[i] == '-')
  217. value = -CoordParserHelpers::readNumber (s, ++i);
  218. }
  219. else
  220. {
  221. anchor1 = getOriginAnchorName (isHorizontal);
  222. value = CoordParserHelpers::readNumber (s, i);
  223. CoordParserHelpers::skipWhitespace (s, i);
  224. if (s[i] == '%')
  225. {
  226. value /= 100.0;
  227. CoordParserHelpers::skipWhitespace (s, ++i);
  228. if (s[i] == '*')
  229. {
  230. anchor1 = CoordParserHelpers::readAnchorName (s, ++i);
  231. if (anchor1.isEmpty())
  232. anchor1 = getOriginAnchorName (isHorizontal);
  233. CoordParserHelpers::skipWhitespace (s, i);
  234. if (s[i] == '-' && s[i + 1] == '>')
  235. {
  236. i += 2;
  237. anchor2 = CoordParserHelpers::readAnchorName (s, i);
  238. }
  239. else
  240. {
  241. anchor2 = anchor1;
  242. anchor1 = getOriginAnchorName (isHorizontal);
  243. }
  244. }
  245. else
  246. {
  247. anchor1 = getOriginAnchorName (isHorizontal);
  248. anchor2 = getExtentAnchorName (isHorizontal);
  249. }
  250. }
  251. }
  252. }
  253. const String Coordinate::toString() const
  254. {
  255. if (isProportional())
  256. {
  257. const String percent (CoordParserHelpers::limitedAccuracyString (value * 100.0));
  258. if (isOrigin (anchor1))
  259. {
  260. if (anchor2 == "parent.right" || anchor2 == "parent.bottom")
  261. return percent + "%";
  262. else
  263. return percent + "% * " + anchor2;
  264. }
  265. else
  266. return percent + "% * " + anchor1 + " -> " + anchor2;
  267. }
  268. else
  269. {
  270. if (isOrigin (anchor1))
  271. return CoordParserHelpers::limitedAccuracyString (value);
  272. else if (value > 0)
  273. return anchor1 + " + " + CoordParserHelpers::limitedAccuracyString (value);
  274. else if (value < 0)
  275. return anchor1 + " - " + CoordParserHelpers::limitedAccuracyString (-value);
  276. else
  277. return anchor1;
  278. }
  279. }
  280. //==============================================================================
  281. const double Coordinate::getEditableNumber() const
  282. {
  283. return isProportional() ? value * 100.0 : value;
  284. }
  285. void Coordinate::setEditableNumber (const double newValue)
  286. {
  287. value = isProportional() ? newValue / 100.0 : newValue;
  288. }
  289. //==============================================================================
  290. void Coordinate::changeAnchor1 (const String& newAnchorName, const NamedCoordinateFinder& nameSource)
  291. {
  292. jassert (newAnchorName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_."));
  293. const double oldValue = resolve (nameSource);
  294. anchor1 = newAnchorName;
  295. moveToAbsolute (oldValue, nameSource);
  296. }
  297. void Coordinate::changeAnchor2 (const String& newAnchorName, const NamedCoordinateFinder& nameSource)
  298. {
  299. jassert (isProportional());
  300. jassert (newAnchorName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_."));
  301. const double oldValue = resolve (nameSource);
  302. anchor2 = newAnchorName;
  303. moveToAbsolute (oldValue, nameSource);
  304. }
  305. void Coordinate::renameAnchorIfUsed (const String& oldName, const String& newName, const NamedCoordinateFinder& nameSource)
  306. {
  307. jassert (oldName.isNotEmpty());
  308. jassert (newName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_"));
  309. if (newName.isEmpty())
  310. {
  311. if (getObjectName (anchor1) == oldName
  312. || getObjectName (anchor2) == oldName)
  313. {
  314. value = resolve (nameSource);
  315. anchor1 = String::empty;
  316. anchor2 = String::empty;
  317. }
  318. }
  319. else
  320. {
  321. if (getObjectName (anchor1) == oldName)
  322. anchor1 = newName + "." + getEdgeName (anchor1);
  323. if (getObjectName (anchor2) == oldName)
  324. anchor2 = newName + "." + getEdgeName (anchor2);
  325. }
  326. }
  327. //==============================================================================
  328. CoordinatePair::CoordinatePair()
  329. {
  330. }
  331. CoordinatePair::CoordinatePair (const Point<float>& absolutePoint)
  332. : x (absolutePoint.getX(), true), y (absolutePoint.getY(), false)
  333. {
  334. }
  335. CoordinatePair::CoordinatePair (const String& stringVersion)
  336. {
  337. StringArray tokens;
  338. tokens.addTokens (stringVersion, ",", String::empty);
  339. x = Coordinate (tokens [0], true);
  340. y = Coordinate (tokens [1], false);
  341. }
  342. const Point<float> CoordinatePair::resolve (const Coordinate::NamedCoordinateFinder& nameSource) const
  343. {
  344. return Point<float> ((float) x.resolve (nameSource),
  345. (float) y.resolve (nameSource));
  346. }
  347. void CoordinatePair::moveToAbsolute (const Point<float>& newPos, const Coordinate::NamedCoordinateFinder& nameSource)
  348. {
  349. x.moveToAbsolute (newPos.getX(), nameSource);
  350. y.moveToAbsolute (newPos.getY(), nameSource);
  351. }
  352. const String CoordinatePair::toString() const
  353. {
  354. return x.toString() + ", " + y.toString();
  355. }
  356. void CoordinatePair::renameAnchorIfUsed (const String& oldName, const String& newName, const Coordinate::NamedCoordinateFinder& nameSource)
  357. {
  358. x.renameAnchorIfUsed (oldName, newName, nameSource);
  359. y.renameAnchorIfUsed (oldName, newName, nameSource);
  360. }
  361. //==============================================================================
  362. RectangleCoordinates::RectangleCoordinates()
  363. {
  364. }
  365. RectangleCoordinates::RectangleCoordinates (const Rectangle<float>& rect, const String& componentName)
  366. : left (rect.getX(), true),
  367. right (rect.getWidth(), componentName + "." + Coordinate::Strings::left),
  368. top (rect.getY(), false),
  369. bottom (rect.getHeight(), componentName + "." + Coordinate::Strings::top)
  370. {
  371. }
  372. RectangleCoordinates::RectangleCoordinates (const String& stringVersion)
  373. {
  374. StringArray tokens;
  375. tokens.addTokens (stringVersion, ",", String::empty);
  376. left = Coordinate (tokens [0], true);
  377. top = Coordinate (tokens [1], false);
  378. right = Coordinate (tokens [2], true);
  379. bottom = Coordinate (tokens [3], false);
  380. }
  381. const Rectangle<int> RectangleCoordinates::resolve (const Coordinate::NamedCoordinateFinder& nameSource) const
  382. {
  383. const int l = roundToInt (left.resolve (nameSource));
  384. const int r = roundToInt (right.resolve (nameSource));
  385. const int t = roundToInt (top.resolve (nameSource));
  386. const int b = roundToInt (bottom.resolve (nameSource));
  387. return Rectangle<int> (l, t, r - l, b - t);
  388. }
  389. void RectangleCoordinates::moveToAbsolute (const Rectangle<float>& newPos, const Coordinate::NamedCoordinateFinder& nameSource)
  390. {
  391. left.moveToAbsolute (newPos.getX(), nameSource);
  392. right.moveToAbsolute (newPos.getRight(), nameSource);
  393. top.moveToAbsolute (newPos.getY(), nameSource);
  394. bottom.moveToAbsolute (newPos.getBottom(), nameSource);
  395. }
  396. const String RectangleCoordinates::toString() const
  397. {
  398. return left.toString() + ", " + top.toString() + ", " + right.toString() + ", " + bottom.toString();
  399. }
  400. void RectangleCoordinates::renameAnchorIfUsed (const String& oldName, const String& newName,
  401. const Coordinate::NamedCoordinateFinder& nameSource)
  402. {
  403. left.renameAnchorIfUsed (oldName, newName, nameSource);
  404. right.renameAnchorIfUsed (oldName, newName, nameSource);
  405. top.renameAnchorIfUsed (oldName, newName, nameSource);
  406. bottom.renameAnchorIfUsed (oldName, newName, nameSource);
  407. }
  408. //==============================================================================
  409. ComponentAutoLayoutManager::ComponentAutoLayoutManager (Component* parentComponent)
  410. : parent (parentComponent)
  411. {
  412. parent->addComponentListener (this);
  413. }
  414. ComponentAutoLayoutManager::~ComponentAutoLayoutManager()
  415. {
  416. parent->removeComponentListener (this);
  417. for (int i = components.size(); --i >= 0;)
  418. components.getUnchecked(i)->component->removeComponentListener (this);
  419. }
  420. void ComponentAutoLayoutManager::setMarker (const String& name, const Coordinate& coord)
  421. {
  422. for (int i = markers.size(); --i >= 0;)
  423. {
  424. MarkerPosition* m = markers.getUnchecked(i);
  425. if (m->markerName == name)
  426. {
  427. m->position = coord;
  428. applyLayout();
  429. return;
  430. }
  431. }
  432. markers.add (new MarkerPosition (name, coord));
  433. applyLayout();
  434. }
  435. void ComponentAutoLayoutManager::setComponentBounds (Component* comp, const String& name, const RectangleCoordinates& coords)
  436. {
  437. jassert (comp != 0);
  438. // All the components that this layout manages must be inside the parent component..
  439. jassert (parent->isParentOf (comp));
  440. for (int i = components.size(); --i >= 0;)
  441. {
  442. ComponentPosition* c = components.getUnchecked(i);
  443. if (c->component == comp)
  444. {
  445. c->name = name;
  446. c->coords = coords;
  447. triggerAsyncUpdate();
  448. return;
  449. }
  450. }
  451. components.add (new ComponentPosition (comp, name, coords));
  452. comp->addComponentListener (this);
  453. triggerAsyncUpdate();
  454. }
  455. void ComponentAutoLayoutManager::applyLayout()
  456. {
  457. for (int i = components.size(); --i >= 0;)
  458. {
  459. ComponentPosition* c = components.getUnchecked(i);
  460. // All the components that this layout manages must be inside the parent component..
  461. jassert (parent->isParentOf (c->component));
  462. c->component->setBounds (c->coords.resolve (*this));
  463. }
  464. }
  465. const Coordinate ComponentAutoLayoutManager::findNamedCoordinate (const String& objectName, const String& edge) const
  466. {
  467. if (objectName == Coordinate::Strings::parent)
  468. {
  469. if (edge == Coordinate::Strings::right) return Coordinate ((double) parent->getWidth(), true);
  470. if (edge == Coordinate::Strings::bottom) return Coordinate ((double) parent->getHeight(), false);
  471. }
  472. if (objectName.isNotEmpty() && edge.isNotEmpty())
  473. {
  474. for (int i = components.size(); --i >= 0;)
  475. {
  476. ComponentPosition* c = components.getUnchecked(i);
  477. if (c->name == objectName)
  478. {
  479. if (edge == Coordinate::Strings::left) return c->coords.left;
  480. if (edge == Coordinate::Strings::right) return c->coords.right;
  481. if (edge == Coordinate::Strings::top) return c->coords.top;
  482. if (edge == Coordinate::Strings::bottom) return c->coords.bottom;
  483. }
  484. }
  485. }
  486. for (int i = markers.size(); --i >= 0;)
  487. {
  488. MarkerPosition* m = markers.getUnchecked(i);
  489. if (m->markerName == objectName)
  490. return m->position;
  491. }
  492. return Coordinate();
  493. }
  494. void ComponentAutoLayoutManager::componentMovedOrResized (Component& component, bool wasMoved, bool wasResized)
  495. {
  496. triggerAsyncUpdate();
  497. if (parent == &component)
  498. handleUpdateNowIfNeeded();
  499. }
  500. void ComponentAutoLayoutManager::componentBeingDeleted (Component& component)
  501. {
  502. for (int i = components.size(); --i >= 0;)
  503. {
  504. ComponentPosition* c = components.getUnchecked(i);
  505. if (c->component == &component)
  506. {
  507. components.remove (i);
  508. break;
  509. }
  510. }
  511. }
  512. void ComponentAutoLayoutManager::handleAsyncUpdate()
  513. {
  514. applyLayout();
  515. }
  516. ComponentAutoLayoutManager::MarkerPosition::MarkerPosition (const String& name, const Coordinate& coord)
  517. : markerName (name), position (coord)
  518. {
  519. }
  520. ComponentAutoLayoutManager::ComponentPosition::ComponentPosition (Component* component_, const String& name_, const RectangleCoordinates& coords_)
  521. : component (component_), name (name_), coords (coords_)
  522. {
  523. }