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.

1347 lines
47KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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_Headers.h"
  19. #include "jucer_ComponentEditor.h"
  20. const float snapDistance = 8.0f;
  21. static const Colour resizableBorderColour (0x7066aaff);
  22. static const Colour alignmentMarkerColour (0x77ff0000);
  23. //==============================================================================
  24. class SizeGuideComponent : public Component,
  25. public ComponentListener
  26. {
  27. public:
  28. enum Type { left, right, top, bottom };
  29. //==============================================================================
  30. SizeGuideComponent (ComponentDocument& document_, const ValueTree& state_, Component* component_,
  31. Component& parentForOverlays, Type type_)
  32. : document (document_), state (state_), component (component_), type (type_),
  33. font (10.0f)
  34. {
  35. component->addComponentListener (this);
  36. setAlwaysOnTop (true);
  37. parentForOverlays.addAndMakeVisible (this);
  38. updatePosition();
  39. }
  40. ~SizeGuideComponent()
  41. {
  42. if (component != 0)
  43. component->removeComponentListener (this);
  44. }
  45. //==============================================================================
  46. void paint (Graphics& g)
  47. {
  48. const float dashes[] = { 4.0f, 3.0f };
  49. g.setColour (resizableBorderColour);
  50. g.drawDashedLine (lineEnd1.getX() + 0.5f, lineEnd1.getY() + 0.5f,
  51. lineEnd2.getX() + 0.5f, lineEnd2.getY() + 0.5f,
  52. dashes, 2, 1.0f);
  53. g.setFont (font);
  54. g.setColour (Colours::white);
  55. for (int y = -1; y <= 1; ++y)
  56. for (int x = -1; x <= 1; ++x)
  57. g.drawText (getName(), textArea.getX() + x, textArea.getY() + y, textArea.getWidth(), textArea.getHeight(), Justification::centred, 1);
  58. g.setColour (Colours::black);
  59. g.drawText (getName(), textArea.getX(), textArea.getY(), textArea.getWidth(), textArea.getHeight(), Justification::centred, 1);
  60. }
  61. void componentMovedOrResized (Component&, bool, bool)
  62. {
  63. updatePosition();
  64. }
  65. void componentBeingDeleted (Component&)
  66. {
  67. setVisible (false);
  68. component = 0;
  69. }
  70. //==============================================================================
  71. void updatePosition()
  72. {
  73. RectangleCoordinates coords (document.getCoordsFor (state));
  74. Coordinate coord (false);
  75. bool isHorizontal = false;
  76. switch (type)
  77. {
  78. case left: coord = coords.left; isHorizontal = true; break;
  79. case right: coord = coords.right; isHorizontal = true; break;
  80. case top: coord = coords.top; break;
  81. case bottom: coord = coords.bottom; break;
  82. default: jassertfalse; break;
  83. }
  84. setName (coord.toString());
  85. int textW = (int) font.getStringWidth (getName());
  86. int textH = (int) font.getHeight();
  87. Point<int> p1, p2;
  88. switch (type)
  89. {
  90. case left:
  91. p1 = Point<int> (component->getX(), 0);
  92. p2 = Point<int> (component->getX(), component->getY());
  93. textArea.setBounds (p1.getX() - textW - 2, 4, textW, textH);
  94. break;
  95. case right:
  96. p1 = Point<int> (component->getRight(), 0);
  97. p2 = Point<int> (component->getRight(), component->getY());
  98. textArea.setBounds (p1.getX() + 2, 4, textW, textH);
  99. break;
  100. case top:
  101. p1 = Point<int> (0, component->getY());
  102. p2 = Point<int> (component->getX(), component->getY());
  103. textArea.setBounds (4, p1.getY() - textH - 2, textW, textH);
  104. break;
  105. case bottom:
  106. p1 = Point<int> (0, component->getBottom());
  107. p2 = Point<int> (component->getX(), component->getBottom());
  108. textArea.setBounds (4, p1.getY() + 2, textW, textH);
  109. break;
  110. default:
  111. jassertfalse;
  112. break;
  113. }
  114. Rectangle<int> bounds (Rectangle<int> (p1, p2).expanded (2, 2).getUnion (textArea));
  115. bounds.setPosition (component->getParentComponent()->relativePositionToOtherComponent (getParentComponent(), bounds.getPosition()));
  116. setBounds (bounds);
  117. lineEnd1 = component->getParentComponent()->relativePositionToOtherComponent (this, p1);
  118. lineEnd2 = component->getParentComponent()->relativePositionToOtherComponent (this, p2);
  119. textArea.setPosition (component->getParentComponent()->relativePositionToOtherComponent (this, textArea.getPosition()));
  120. repaint();
  121. }
  122. private:
  123. ComponentDocument& document;
  124. ValueTree state;
  125. Component* component;
  126. Type type;
  127. Font font;
  128. Point<int> lineEnd1, lineEnd2;
  129. Rectangle<int> textArea;
  130. };
  131. //==============================================================================
  132. class ComponentEditor::Canvas : public Component,
  133. public ValueTree::Listener,
  134. public Timer
  135. {
  136. public:
  137. //==============================================================================
  138. Canvas (ComponentEditor& editor_)
  139. : editor (editor_), border (14), resizerThickness (4),
  140. dragStartWidth (0), dragStartHeight (0)
  141. {
  142. setOpaque (true);
  143. addAndMakeVisible (componentHolder = new Component());
  144. addAndMakeVisible (overlay = new OverlayComponent (*this));
  145. setSize (500, 500);
  146. getDocument().getRoot().addListener (this);
  147. updateComponents();
  148. }
  149. ~Canvas()
  150. {
  151. dragger = 0;
  152. getDocument().getRoot().removeListener (this);
  153. componentHolder->deleteAllChildren();
  154. deleteAllChildren();
  155. }
  156. //==============================================================================
  157. ComponentEditor& getEditor() { return editor; }
  158. ComponentDocument& getDocument() { return editor.getDocument(); }
  159. ComponentDocument::SelectedItems& getSelection() { return selection; }
  160. Component* getComponentHolder() const { return componentHolder; }
  161. void timerCallback()
  162. {
  163. stopTimer();
  164. if (! Component::isMouseButtonDownAnywhere())
  165. getDocument().beginNewTransaction();
  166. }
  167. //==============================================================================
  168. void paint (Graphics& g)
  169. {
  170. g.fillAll (Colours::white);
  171. g.setColour (Colour::greyLevel (0.9f));
  172. g.drawRect (getContentArea().expanded (resizerThickness, resizerThickness), resizerThickness);
  173. g.setFont (border.getBottom() - 5.0f);
  174. g.setColour (Colours::grey);
  175. g.drawText (String (componentHolder->getWidth()) + " x " + String (componentHolder->getHeight()),
  176. 0, 0, jmax (getWidth() - border.getRight(), jmin (60, getWidth())), getHeight(), Justification::bottomRight, false);
  177. g.setFont (border.getTop() - 5.0f);
  178. g.setColour (Colours::darkgrey);
  179. g.drawHorizontalLine (border.getTop() - 1, 2.0f, (float) getWidth() - border.getRight());
  180. g.drawVerticalLine (border.getLeft() - 1, 2.0f, (float) getHeight() - border.getBottom());
  181. drawXAxis (g, Rectangle<int> (border.getLeft(), 0, componentHolder->getWidth(), border.getTop()));
  182. drawYAxis (g, Rectangle<int> (0, border.getTop(), border.getLeft(), componentHolder->getHeight()));
  183. }
  184. void drawXAxis (Graphics& g, const Rectangle<int>& r)
  185. {
  186. TickIterator ticks (0, r.getWidth(), 1.0, 10, 50);
  187. float pos, tickLength;
  188. String label;
  189. while (ticks.getNextTick (pos, tickLength, label))
  190. {
  191. if (pos > 0)
  192. {
  193. g.drawVerticalLine (r.getX() + (int) pos, r.getBottom() - tickLength * r.getHeight(), (float) r.getBottom());
  194. g.drawSingleLineText (label, r.getX() + (int) pos + 2, (int) r.getBottom() - 6);
  195. }
  196. }
  197. }
  198. void drawYAxis (Graphics& g, const Rectangle<int>& r)
  199. {
  200. TickIterator ticks (0, r.getHeight(), 1.0, 10, 80);
  201. float pos, tickLength;
  202. String label;
  203. while (ticks.getNextTick (pos, tickLength, label))
  204. {
  205. if (pos > 0)
  206. {
  207. g.drawHorizontalLine (r.getY() + (int) pos, r.getRight() - tickLength * r.getWidth(), (float) r.getRight());
  208. g.drawTextAsPath (label, AffineTransform::rotation (float_Pi / -2.0f)
  209. .translated (r.getRight() - 6.0f, r.getY() + pos - 2.0f));
  210. }
  211. }
  212. }
  213. const Rectangle<int> getContentArea() const
  214. {
  215. return border.subtractedFrom (getLocalBounds());
  216. }
  217. //==============================================================================
  218. void resized()
  219. {
  220. componentHolder->setBounds (getContentArea());
  221. overlay->setBounds (componentHolder->getBounds());
  222. updateComponents();
  223. }
  224. void updateComponents()
  225. {
  226. ComponentDocument& doc = getDocument();
  227. setSize ((int) doc.getCanvasWidth().getValue() + border.getLeftAndRight(),
  228. (int) doc.getCanvasHeight().getValue() + border.getTopAndBottom());
  229. int i;
  230. for (i = componentHolder->getNumChildComponents(); --i >= 0;)
  231. {
  232. Component* c = componentHolder->getChildComponent (i);
  233. if (! doc.containsComponent (c))
  234. {
  235. selection.deselect (c->getComponentUID());
  236. delete c;
  237. }
  238. }
  239. const int num = doc.getNumComponents();
  240. for (i = 0; i < num; ++i)
  241. {
  242. const ValueTree v (doc.getComponent (i));
  243. Component* c = getComponentForState (v);
  244. if (c == 0)
  245. {
  246. c = doc.createComponent (i);
  247. componentHolder->addAndMakeVisible (c);
  248. }
  249. doc.updateComponent (c);
  250. }
  251. startTimer (500);
  252. }
  253. void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const var::identifier& property)
  254. {
  255. updateComponents();
  256. }
  257. void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged)
  258. {
  259. updateComponents();
  260. }
  261. void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged)
  262. {
  263. }
  264. //==============================================================================
  265. void getSelectedItemProperties (Array <PropertyComponent*>& props)
  266. {
  267. //xxx needs to handle multiple selections..
  268. if (selection.getNumSelected() == 1)
  269. {
  270. Component* c = getComponentForUID (selection.getSelectedItem (0));
  271. jassert (c != 0);
  272. getDocument().getComponentProperties (props, c);
  273. }
  274. }
  275. //==============================================================================
  276. void mouseMove (const MouseEvent& e)
  277. {
  278. updateDragZone (e.getPosition());
  279. }
  280. void mouseDown (const MouseEvent& e)
  281. {
  282. updateDragZone (e.getPosition());
  283. dragStartWidth = getDocument().getCanvasWidth().getValue();
  284. dragStartHeight = getDocument().getCanvasHeight().getValue();
  285. showSizeGuides();
  286. }
  287. void mouseDrag (const MouseEvent& e)
  288. {
  289. if (dragZone.isDraggingRightEdge())
  290. getDocument().getCanvasWidth() = jmax (1, dragStartWidth + e.getDistanceFromDragStartX());
  291. if (dragZone.isDraggingBottomEdge())
  292. getDocument().getCanvasHeight() = jmax (1, dragStartHeight + e.getDistanceFromDragStartY());
  293. }
  294. void mouseUp (const MouseEvent& e)
  295. {
  296. hideSizeGuides();
  297. updateDragZone (e.getPosition());
  298. }
  299. void updateDragZone (const Point<int>& p)
  300. {
  301. ResizableBorderComponent::Zone newZone
  302. = ResizableBorderComponent::Zone::fromPositionOnBorder (getContentArea().expanded (resizerThickness, resizerThickness),
  303. BorderSize (0, 0, resizerThickness, resizerThickness), p);
  304. if (dragZone != newZone)
  305. {
  306. dragZone = newZone;
  307. setMouseCursor (newZone.getMouseCursor());
  308. }
  309. }
  310. void showSizeGuides() { overlay->showSizeGuides(); }
  311. void hideSizeGuides() { overlay->hideSizeGuides(); }
  312. //==============================================================================
  313. class DragOperation
  314. {
  315. public:
  316. DragOperation (Canvas& canvas_,
  317. const Array<Component*>& items,
  318. const Array<Component*>& itemsToSnapTo,
  319. const MouseEvent& e,
  320. const ResizableBorderComponent::Zone& zone_)
  321. : canvas (canvas_),
  322. zone (zone_)
  323. {
  324. int i;
  325. for (i = 0; i < items.size(); ++i)
  326. {
  327. jassert (items.getUnchecked(i) != 0);
  328. const ValueTree v (getDocument().getComponentState (items.getUnchecked(i)));
  329. draggedComponents.add (v);
  330. const Rectangle<float> floatPos (getComponentPosition (v));
  331. if (zone.isDraggingWholeObject() || zone.isDraggingLeftEdge())
  332. verticalSnapPositions.add (SnapLine (floatPos.getX(), floatPos.getY(), floatPos.getBottom()));
  333. if (zone.isDraggingWholeObject() || (zone.isDraggingLeftEdge() && zone.isDraggingRightEdge()))
  334. verticalSnapPositions.add (SnapLine (floatPos.getCentreX(), floatPos.getY(), floatPos.getBottom()));
  335. if (zone.isDraggingWholeObject() || zone.isDraggingRightEdge())
  336. verticalSnapPositions.add (SnapLine (floatPos.getRight(), floatPos.getY(), floatPos.getBottom()));
  337. if (zone.isDraggingWholeObject() || zone.isDraggingTopEdge())
  338. horizontalSnapPositions.add (SnapLine (floatPos.getY(), floatPos.getX(), floatPos.getRight()));
  339. if (zone.isDraggingWholeObject() || (zone.isDraggingTopEdge() && zone.isDraggingBottomEdge()))
  340. horizontalSnapPositions.add (SnapLine (floatPos.getCentreY(), floatPos.getX(), floatPos.getRight()));
  341. if (zone.isDraggingWholeObject() || zone.isDraggingBottomEdge())
  342. horizontalSnapPositions.add (SnapLine (floatPos.getBottom(), floatPos.getX(), floatPos.getRight()));
  343. }
  344. if (isDraggingLeftRight())
  345. {
  346. verticalSnapTargets.add (SnapLine (0, 0, 10000.0f));
  347. verticalSnapTargets.add (SnapLine (getDocument().getCanvasWidth().getValue(), 0, 10000.0f));
  348. if (zone.isDraggingWholeObject() || (zone.isDraggingLeftEdge() && zone.isDraggingRightEdge()))
  349. verticalSnapTargets.add (SnapLine ((float) getDocument().getCanvasWidth().getValue() / 2.0f, 0, 10000.0f));
  350. }
  351. if (isDraggingUpDown())
  352. {
  353. horizontalSnapTargets.add (SnapLine (0, 0, 10000.0f));
  354. horizontalSnapTargets.add (SnapLine (getDocument().getCanvasHeight().getValue(), 0, 10000.0f));
  355. if (zone.isDraggingWholeObject() || (zone.isDraggingTopEdge() && zone.isDraggingBottomEdge()))
  356. horizontalSnapTargets.add (SnapLine ((float) getDocument().getCanvasHeight().getValue() / 2.0f, 0, 10000.0f));
  357. }
  358. for (i = 0; i < itemsToSnapTo.size(); ++i)
  359. {
  360. jassert (itemsToSnapTo.getUnchecked(i) != 0);
  361. const ValueTree v (getDocument().getComponentState (itemsToSnapTo.getUnchecked(i)));
  362. const Rectangle<float> floatPos (getComponentPosition (v));
  363. if (isDraggingLeftRight())
  364. {
  365. verticalSnapTargets.add (SnapLine (floatPos.getX(), floatPos.getY(), floatPos.getBottom()));
  366. verticalSnapTargets.add (SnapLine (floatPos.getRight(), floatPos.getY(), floatPos.getBottom()));
  367. }
  368. if (zone.isDraggingWholeObject() || (zone.isDraggingLeftEdge() && zone.isDraggingRightEdge()))
  369. verticalSnapTargets.add (SnapLine (floatPos.getCentreX(), floatPos.getY(), floatPos.getBottom()));
  370. if (isDraggingUpDown())
  371. {
  372. horizontalSnapTargets.add (SnapLine (floatPos.getY(), floatPos.getX(), floatPos.getRight()));
  373. horizontalSnapTargets.add (SnapLine (floatPos.getBottom(), floatPos.getX(), floatPos.getRight()));
  374. }
  375. if (zone.isDraggingWholeObject() || (zone.isDraggingTopEdge() && zone.isDraggingBottomEdge()))
  376. horizontalSnapTargets.add (SnapLine (floatPos.getCentreY(), floatPos.getX(), floatPos.getRight()));
  377. }
  378. mergeSnapLines (verticalSnapTargets);
  379. mergeSnapLines (horizontalSnapTargets);
  380. getDocument().beginNewTransaction();
  381. }
  382. ~DragOperation()
  383. {
  384. getDocument().beginNewTransaction();
  385. }
  386. //==============================================================================
  387. struct SnapLine
  388. {
  389. SnapLine() : position (0), start (0), end (0) {}
  390. SnapLine (const float position_, const float start_, const float end_)
  391. : position (position_), start (start_), end (end_)
  392. {}
  393. float position, start, end;
  394. };
  395. //==============================================================================
  396. class AlignmentHintComponent : public Component
  397. {
  398. public:
  399. AlignmentHintComponent (const SnapLine& line_, bool isVertical_, Component* parent)
  400. : line (line_), isVertical (isVertical_)
  401. {
  402. const int extraEndLength = 5;
  403. setAlwaysOnTop (true);
  404. if (isVertical)
  405. setBounds (roundToInt (line.position), roundToInt (line.start) - extraEndLength, 1, roundToInt (line.end - line.start) + extraEndLength * 2);
  406. else
  407. setBounds (roundToInt (line.start) - extraEndLength, roundToInt (line.position), roundToInt (line.end - line.start) + extraEndLength * 2, 1);
  408. parent->addAndMakeVisible (this);
  409. }
  410. void paint (Graphics& g)
  411. {
  412. g.fillAll (alignmentMarkerColour);
  413. }
  414. private:
  415. const SnapLine line;
  416. const bool isVertical;
  417. AlignmentHintComponent (const AlignmentHintComponent&);
  418. AlignmentHintComponent& operator= (const AlignmentHintComponent&);
  419. };
  420. //==============================================================================
  421. void drag (const MouseEvent& e)
  422. {
  423. getDocument().getUndoManager()->undoCurrentTransactionOnly();
  424. Point<int> distance (e.getOffsetFromDragStart());
  425. if (! isDraggingLeftRight())
  426. distance = Point<int> (0, distance.getY());
  427. if (! isDraggingUpDown())
  428. distance = Point<int> (distance.getX(), 0);
  429. snapGuides.clear();
  430. performSnap (verticalSnapTargets, getVerticalSnapPositions (distance), true, distance);
  431. performSnap (horizontalSnapTargets, getHorizontalSnapPositions (distance), false, distance);
  432. for (int n = 50;;)
  433. {
  434. // Need to repeatedly apply the new positions until they all settle down, in case some of
  435. // the coords are relative to each other..
  436. bool anyUpdated = false;
  437. for (int i = 0; i < draggedComponents.size(); ++i)
  438. if (dragItem (draggedComponents.getReference(i), distance, originalPositions.getReference(i)))
  439. anyUpdated = true;
  440. if (! anyUpdated)
  441. break;
  442. if (--n == 0)
  443. {
  444. jassertfalse;
  445. break;
  446. }
  447. }
  448. }
  449. bool dragItem (ValueTree& v, const Point<int>& distance, const Rectangle<int>& originalPos)
  450. {
  451. const Rectangle<int> newBounds (zone.resizeRectangleBy (originalPos, distance));
  452. RectangleCoordinates pr (getDocument().getCoordsFor (v));
  453. ScopedPointer<Coordinate::MarkerResolver> markers (getDocument().createMarkerResolver (v));
  454. pr.moveToAbsolute (newBounds, *markers);
  455. return getDocument().setCoordsFor (v, pr);
  456. }
  457. //==============================================================================
  458. private:
  459. Canvas& canvas;
  460. Array <ValueTree> draggedComponents;
  461. Array <Rectangle<int> > originalPositions;
  462. Array <SnapLine> verticalSnapPositions, horizontalSnapPositions;
  463. Array <SnapLine> verticalSnapTargets, horizontalSnapTargets;
  464. const ResizableBorderComponent::Zone zone;
  465. OwnedArray<Component> snapGuides;
  466. ComponentDocument& getDocument() throw() { return canvas.getDocument(); }
  467. const Rectangle<float> getComponentPosition (const ValueTree& state)
  468. {
  469. RectangleCoordinates relativePos (getDocument().getCoordsFor (state));
  470. ScopedPointer<Coordinate::MarkerResolver> markers (getDocument().createMarkerResolver (state));
  471. const Rectangle<int> intPos (relativePos.resolve (*markers));
  472. originalPositions.add (intPos);
  473. return Rectangle<float> ((float) intPos.getX(), (float) intPos.getY(),
  474. (float) intPos.getWidth(), (float) intPos.getHeight());
  475. }
  476. static void mergeSnapLines (Array <SnapLine>& lines)
  477. {
  478. for (int i = lines.size(); --i > 0;)
  479. {
  480. SnapLine& s1 = lines.getReference(i);
  481. for (int j = i; --j >= 0;)
  482. {
  483. SnapLine& s2 = lines.getReference(j);
  484. if (s1.position == s2.position)
  485. {
  486. s2.start = jmin (s1.start, s2.start);
  487. s2.end = jmax (s1.end, s2.end);
  488. lines.remove (i);
  489. }
  490. }
  491. }
  492. }
  493. void performSnap (const Array<SnapLine>& targets, const Array<SnapLine>& sources, bool isVertical, Point<int>& distance)
  494. {
  495. if (targets.size() == 0 || sources.size() == 0)
  496. return;
  497. float best = std::numeric_limits<float>::max();
  498. float absBest = fabsf (best);
  499. Array <SnapLine> lines;
  500. for (int i = 0; i < targets.size(); ++i)
  501. {
  502. const SnapLine& target = targets.getReference(i);
  503. for (int j = 0; j < sources.size(); ++j)
  504. {
  505. const SnapLine& source = sources.getReference(j);
  506. const float diff = target.position - source.position;
  507. const float absDiff = fabsf (diff);
  508. if (absDiff <= absBest)
  509. {
  510. if (absDiff < absBest)
  511. lines.clearQuick();
  512. lines.add (SnapLine (target.position, jmin (target.start, source.start), jmax (target.end, source.end)));
  513. best = diff;
  514. absBest = absDiff;
  515. }
  516. }
  517. }
  518. jassert (absBest < std::numeric_limits<float>::max());
  519. if (absBest < snapDistance)
  520. {
  521. distance += isVertical ? Point<int> (roundToInt (best), 0) : Point<int> (0, roundToInt (best));
  522. for (int i = lines.size(); --i >= 0;)
  523. if (lines.getReference(i).position != 0)
  524. snapGuides.add (new AlignmentHintComponent (lines.getReference(i), isVertical, canvas.overlay));
  525. }
  526. }
  527. const Array<SnapLine> getVerticalSnapPositions (const Point<int>& distance) const
  528. {
  529. Array<SnapLine> p (verticalSnapPositions);
  530. for (int i = p.size(); --i >= 0;)
  531. {
  532. SnapLine& s = p.getReference(i);
  533. s.position += distance.getX();
  534. s.start += distance.getY();
  535. s.end += distance.getY();
  536. }
  537. return p;
  538. }
  539. const Array<SnapLine> getHorizontalSnapPositions (const Point<int>& distance) const
  540. {
  541. Array<SnapLine> p (horizontalSnapPositions);
  542. for (int i = p.size(); --i >= 0;)
  543. {
  544. SnapLine& s = p.getReference(i);
  545. s.position += distance.getY();
  546. s.start += distance.getX();
  547. s.end += distance.getX();
  548. }
  549. return p;
  550. }
  551. bool isDraggingLeftRight() const { return zone.isDraggingWholeObject() || zone.isDraggingLeftEdge() || zone.isDraggingRightEdge(); }
  552. bool isDraggingUpDown() const { return zone.isDraggingWholeObject() || zone.isDraggingTopEdge() || zone.isDraggingBottomEdge(); }
  553. DragOperation (const DragOperation&);
  554. DragOperation& operator= (const DragOperation&);
  555. };
  556. //==============================================================================
  557. void beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone)
  558. {
  559. dragger = new DragOperation (*this, getSelectedComps(), getUnselectedComps(), e, zone);
  560. }
  561. void continueDrag (const MouseEvent& e)
  562. {
  563. if (dragger != 0)
  564. dragger->drag (e);
  565. }
  566. void endDrag (const MouseEvent& e)
  567. {
  568. if (dragger != 0)
  569. {
  570. dragger->drag (e);
  571. dragger = 0;
  572. }
  573. }
  574. private:
  575. ComponentEditor& editor;
  576. const BorderSize border;
  577. const int resizerThickness;
  578. ScopedPointer <DragOperation> dragger;
  579. ResizableBorderComponent::Zone dragZone;
  580. int dragStartWidth, dragStartHeight;
  581. //==============================================================================
  582. class ComponentResizeFrame : public Component,
  583. public ComponentListener
  584. {
  585. public:
  586. ComponentResizeFrame (Canvas& canvas_,
  587. Component* componentToAttachTo)
  588. : canvas (canvas_),
  589. component (componentToAttachTo),
  590. borderThickness (4)
  591. {
  592. componentMovedOrResized (*componentToAttachTo, true, true);
  593. componentToAttachTo->addComponentListener (this);
  594. }
  595. ~ComponentResizeFrame()
  596. {
  597. if (component != 0)
  598. component->removeComponentListener (this);
  599. }
  600. void paint (Graphics& g)
  601. {
  602. g.setColour (resizableBorderColour);
  603. g.drawRect (0, 0, getWidth(), getHeight(), borderThickness);
  604. }
  605. void mouseEnter (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  606. void mouseExit (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  607. void mouseMove (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  608. void mouseDown (const MouseEvent& e)
  609. {
  610. jassert (component != 0);
  611. if (component != 0)
  612. {
  613. updateDragZone (e.getPosition());
  614. canvas.beginDrag (e, dragZone);
  615. canvas.showSizeGuides();
  616. }
  617. }
  618. void mouseDrag (const MouseEvent& e)
  619. {
  620. if (component != 0)
  621. canvas.continueDrag (e);
  622. }
  623. void mouseUp (const MouseEvent& e)
  624. {
  625. canvas.hideSizeGuides();
  626. if (component != 0)
  627. canvas.endDrag (e);
  628. updateDragZone (e.getPosition());
  629. }
  630. void componentMovedOrResized (Component&, bool wasMoved, bool wasResized)
  631. {
  632. if (component != 0)
  633. setBounds (component->getBounds().expanded (borderThickness, borderThickness));
  634. }
  635. bool hitTest (int x, int y)
  636. {
  637. return ! getCentreArea().contains (x, y);
  638. }
  639. uint32 getTargetComponentUID() const { return component == 0 ? 0 : component->getComponentUID(); }
  640. void showSizeGuides()
  641. {
  642. if (sizeGuides.size() == 0)
  643. {
  644. const ValueTree v (canvas.getDocument().getComponentState (component));
  645. sizeGuides.add (new SizeGuideComponent (canvas.getDocument(), v, component, canvas, SizeGuideComponent::left));
  646. sizeGuides.add (new SizeGuideComponent (canvas.getDocument(), v, component, canvas, SizeGuideComponent::right));
  647. sizeGuides.add (new SizeGuideComponent (canvas.getDocument(), v, component, canvas, SizeGuideComponent::top));
  648. sizeGuides.add (new SizeGuideComponent (canvas.getDocument(), v, component, canvas, SizeGuideComponent::bottom));
  649. }
  650. }
  651. void hideSizeGuides()
  652. {
  653. sizeGuides.clear();
  654. }
  655. private:
  656. Canvas& canvas;
  657. Component::SafePointer<Component> component;
  658. ResizableBorderComponent::Zone dragZone;
  659. const int borderThickness;
  660. OwnedArray <SizeGuideComponent> sizeGuides;
  661. const Rectangle<int> getCentreArea() const
  662. {
  663. return getLocalBounds().reduced (borderThickness, borderThickness);
  664. }
  665. void updateDragZone (const Point<int>& p)
  666. {
  667. ResizableBorderComponent::Zone newZone
  668. = ResizableBorderComponent::Zone::fromPositionOnBorder (getLocalBounds(),
  669. BorderSize (borderThickness), p);
  670. if (dragZone != newZone)
  671. {
  672. dragZone = newZone;
  673. setMouseCursor (newZone.getMouseCursor());
  674. }
  675. }
  676. };
  677. //==============================================================================
  678. class OverlayComponent : public Component,
  679. public LassoSource <ComponentDocument::SelectedItems::ItemType>,
  680. public ChangeListener
  681. {
  682. public:
  683. OverlayComponent (Canvas& canvas_)
  684. : canvas (canvas_)
  685. {
  686. setAlwaysOnTop (true);
  687. setWantsKeyboardFocus (true);
  688. canvas.getSelection().addChangeListener (this);
  689. }
  690. ~OverlayComponent()
  691. {
  692. canvas.getSelection().removeChangeListener (this);
  693. lasso = 0;
  694. deleteAllChildren();
  695. }
  696. //==============================================================================
  697. void mouseDown (const MouseEvent& e)
  698. {
  699. lasso = 0;
  700. mouseDownCompUID = 0;
  701. isDraggingClickedComp = false;
  702. if (e.mods.isPopupMenu())
  703. {
  704. PopupMenu m;
  705. canvas.getDocument().addNewComponentMenuItems (m);
  706. const int r = m.show();
  707. canvas.getDocument().performNewComponentMenuItem (r);
  708. }
  709. else
  710. {
  711. Component* underMouse = 0;
  712. for (int i = canvas.getComponentHolder()->getNumChildComponents(); --i >= 0;)
  713. {
  714. Component* const c = canvas.getComponentHolder()->getChildComponent(i);
  715. if (c->getBounds().contains (e.getPosition()))
  716. {
  717. underMouse = c;
  718. break;
  719. }
  720. }
  721. if (underMouse == 0 || e.mods.isAltDown())
  722. {
  723. addAndMakeVisible (lasso = new LassoComponent <ComponentDocument::SelectedItems::ItemType>());
  724. lasso->beginLasso (e, this);
  725. }
  726. else
  727. {
  728. mouseDownCompUID = underMouse->getComponentUID();
  729. mouseDownResult = canvas.getSelection().addToSelectionOnMouseDown (mouseDownCompUID, e.mods);
  730. updateSelectedComponentResizeFrames();
  731. hideSizeGuides();
  732. showSizeGuides();
  733. }
  734. }
  735. }
  736. void mouseDrag (const MouseEvent& e)
  737. {
  738. if (lasso != 0)
  739. {
  740. lasso->dragLasso (e);
  741. }
  742. else if (mouseDownCompUID != 0 && (! e.mouseWasClicked()) && (! e.mods.isPopupMenu()))
  743. {
  744. if (! isDraggingClickedComp)
  745. {
  746. isDraggingClickedComp = true;
  747. canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, true, mouseDownResult);
  748. canvas.beginDrag (e, ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre));
  749. }
  750. canvas.continueDrag (e);
  751. showSizeGuides();
  752. }
  753. }
  754. void mouseUp (const MouseEvent& e)
  755. {
  756. hideSizeGuides();
  757. if (lasso != 0)
  758. {
  759. lasso->endLasso();
  760. lasso = 0;
  761. if (e.mouseWasClicked())
  762. canvas.getSelection().deselectAll();
  763. }
  764. else if (! e.mods.isPopupMenu())
  765. {
  766. if (! isDraggingClickedComp)
  767. canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, ! e.mouseWasClicked(), mouseDownResult);
  768. }
  769. canvas.endDrag (e);
  770. }
  771. void findLassoItemsInArea (Array <ComponentDocument::SelectedItems::ItemType>& itemsFound, int x, int y, int width, int height)
  772. {
  773. const Rectangle<int> lassoArea (x, y, width, height);
  774. for (int i = canvas.getComponentHolder()->getNumChildComponents(); --i >= 0;)
  775. {
  776. Component* c = canvas.getComponentHolder()->getChildComponent(i);
  777. if (c->getBounds().intersects (lassoArea))
  778. itemsFound.add (c->getComponentUID());
  779. }
  780. }
  781. ComponentDocument::SelectedItems& getLassoSelection() { return canvas.getSelection(); }
  782. void changeListenerCallback (void*)
  783. {
  784. updateSelectedComponentResizeFrames();
  785. }
  786. void modifierKeysChanged (const ModifierKeys&)
  787. {
  788. Desktop::getInstance().getMainMouseSource().triggerFakeMove();
  789. }
  790. void showSizeGuides()
  791. {
  792. for (int i = getNumChildComponents(); --i >= 0;)
  793. {
  794. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  795. if (resizer != 0)
  796. resizer->showSizeGuides();
  797. }
  798. }
  799. void hideSizeGuides()
  800. {
  801. for (int i = getNumChildComponents(); --i >= 0;)
  802. {
  803. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  804. if (resizer != 0)
  805. resizer->hideSizeGuides();
  806. }
  807. }
  808. private:
  809. //==============================================================================
  810. Canvas& canvas;
  811. ScopedPointer <LassoComponent <ComponentDocument::SelectedItems::ItemType> > lasso;
  812. bool mouseDownResult, isDraggingClickedComp;
  813. uint32 mouseDownCompUID;
  814. ComponentResizeFrame* getSelectorFrameFor (Component* c) const
  815. {
  816. for (int i = getNumChildComponents(); --i >= 0;)
  817. {
  818. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  819. if (resizer != 0 && resizer->getTargetComponentUID() == c->getComponentUID())
  820. return resizer;
  821. }
  822. return 0;
  823. }
  824. void updateSelectedComponentResizeFrames()
  825. {
  826. ComponentDocument::SelectedItems& selection = canvas.getSelection();
  827. int i;
  828. for (i = getNumChildComponents(); --i >= 0;)
  829. {
  830. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  831. if (resizer != 0 && ! selection.isSelected (resizer->getTargetComponentUID()))
  832. delete resizer;
  833. }
  834. for (i = canvas.getComponentHolder()->getNumChildComponents(); --i >= 0;)
  835. {
  836. Component* c = canvas.getComponentHolder()->getChildComponent(i);
  837. if (c != this && selection.isSelected (c->getComponentUID()) && getSelectorFrameFor (c) == 0)
  838. addAndMakeVisible (new ComponentResizeFrame (canvas, c));
  839. }
  840. }
  841. };
  842. //==============================================================================
  843. Component* componentHolder;
  844. OverlayComponent* overlay;
  845. ComponentDocument::SelectedItems selection;
  846. Component* getComponentForUID (const uint32 uid) const
  847. {
  848. for (int i = componentHolder->getNumChildComponents(); --i >= 0;)
  849. if (componentHolder->getChildComponent (i)->getComponentUID() == uid)
  850. return componentHolder->getChildComponent (i);
  851. return 0;
  852. }
  853. Component* getComponentForState (const ValueTree& state)
  854. {
  855. ComponentDocument& doc = getDocument();
  856. for (int i = componentHolder->getNumChildComponents(); --i >= 0;)
  857. {
  858. Component* const c = componentHolder->getChildComponent (i);
  859. if (doc.isStateForComponent (state, c))
  860. return c;
  861. }
  862. return 0;
  863. }
  864. const Array<Component*> getSelectedComps() const
  865. {
  866. Array<Component*> comps;
  867. for (int i = 0; i < selection.getNumSelected(); ++i)
  868. {
  869. Component* c = getComponentForUID (selection.getSelectedItem (i));
  870. jassert (c != 0);
  871. if (c != 0)
  872. comps.add (c);
  873. }
  874. return comps;
  875. }
  876. const Array<Component*> getUnselectedComps() const
  877. {
  878. Array<Component*> comps;
  879. for (int i = componentHolder->getNumChildComponents(); --i >= 0;)
  880. if (! selection.isSelected (componentHolder->getChildComponent(i)->getComponentUID()))
  881. comps.add (componentHolder->getChildComponent(i));
  882. return comps;
  883. }
  884. };
  885. //==============================================================================
  886. class ComponentEditor::ClassInfoHolder : public Component
  887. {
  888. public:
  889. ClassInfoHolder (ComponentEditor& editor_)
  890. : editor (editor_)
  891. {
  892. addAndMakeVisible (panel = new PropertyPanelWithTooltips());
  893. Array <PropertyComponent*> props;
  894. editor.getDocument().createClassProperties (props);
  895. panel->getPanel()->addSection ("Component Properties", props, true);
  896. }
  897. ~ClassInfoHolder()
  898. {
  899. deleteAllChildren();
  900. }
  901. void resized()
  902. {
  903. panel->setBounds (getLocalBounds());
  904. }
  905. private:
  906. ComponentEditor& editor;
  907. PropertyPanelWithTooltips* panel;
  908. };
  909. //==============================================================================
  910. class ComponentEditor::LayoutEditorHolder : public Component
  911. {
  912. public:
  913. LayoutEditorHolder (ComponentEditor& editor_)
  914. : editor (editor_), infoPanel (0)
  915. {
  916. addAndMakeVisible (viewport = new Viewport());
  917. }
  918. ~LayoutEditorHolder()
  919. {
  920. deleteAndZero (infoPanel);
  921. deleteAllChildren();
  922. }
  923. void createCanvas()
  924. {
  925. viewport->setViewedComponent (new Canvas (editor));
  926. addAndMakeVisible (infoPanel = new InfoPanel (editor));
  927. }
  928. void resized()
  929. {
  930. const int infoPanelWidth = 200;
  931. viewport->setBounds (0, 0, getWidth() - infoPanelWidth, getHeight());
  932. if (infoPanel != 0)
  933. infoPanel->setBounds (getWidth() - infoPanelWidth, 0, infoPanelWidth, getHeight());
  934. }
  935. Viewport* getViewport() const { return viewport; }
  936. private:
  937. class InfoPanel : public Component,
  938. public ChangeListener
  939. {
  940. public:
  941. InfoPanel (ComponentEditor& editor_)
  942. : editor (editor_)
  943. {
  944. setOpaque (true);
  945. addAndMakeVisible (props = new PropertyPanel());
  946. editor.getCanvas()->getSelection().addChangeListener (this);
  947. }
  948. ~InfoPanel()
  949. {
  950. editor.getCanvas()->getSelection().removeChangeListener (this);
  951. props->clear();
  952. deleteAllChildren();
  953. }
  954. void changeListenerCallback (void*)
  955. {
  956. Array <PropertyComponent*> newComps;
  957. editor.getCanvas()->getSelectedItemProperties (newComps);
  958. props->clear();
  959. props->addProperties (newComps);
  960. }
  961. void paint (Graphics& g)
  962. {
  963. g.fillAll (Colour::greyLevel (0.92f));
  964. }
  965. void resized()
  966. {
  967. props->setSize (getWidth(), getHeight());
  968. }
  969. private:
  970. ComponentEditor& editor;
  971. PropertyPanel* props;
  972. };
  973. ComponentEditor& editor;
  974. Viewport* viewport;
  975. InfoPanel* infoPanel;
  976. };
  977. //==============================================================================
  978. class ComponentEditor::BackgroundEditorHolder : public Component
  979. {
  980. public:
  981. BackgroundEditorHolder (ComponentEditor& editor_)
  982. : editor (editor_)
  983. {
  984. }
  985. ~BackgroundEditorHolder()
  986. {
  987. }
  988. private:
  989. ComponentEditor& editor;
  990. };
  991. //==============================================================================
  992. class ComponentEditor::CodeEditorHolder : public Component
  993. {
  994. public:
  995. CodeEditorHolder (ComponentEditor& editor_)
  996. : editor (editor_)
  997. {
  998. }
  999. ~CodeEditorHolder()
  1000. {
  1001. }
  1002. private:
  1003. ComponentEditor& editor;
  1004. };
  1005. //==============================================================================
  1006. ComponentEditor::ComponentEditor (OpenDocumentManager::Document* document,
  1007. Project* project_, ComponentDocument* componentDocument_)
  1008. : DocumentEditorComponent (document),
  1009. project (project_),
  1010. componentDocument (componentDocument_),
  1011. classInfoHolder (0),
  1012. layoutEditorHolder (0),
  1013. backgroundEditorHolder (0),
  1014. codeEditorHolder (0)
  1015. {
  1016. setOpaque (true);
  1017. if (componentDocument != 0)
  1018. {
  1019. classInfoHolder = new ClassInfoHolder (*this);
  1020. layoutEditorHolder = new LayoutEditorHolder (*this);
  1021. backgroundEditorHolder = new BackgroundEditorHolder (*this);
  1022. codeEditorHolder = new CodeEditorHolder (*this);
  1023. layoutEditorHolder->createCanvas();
  1024. }
  1025. addAndMakeVisible (tabs = new TabbedComponent (TabbedButtonBar::TabsAtRight));
  1026. tabs->setTabBarDepth (22);
  1027. tabs->addTab ("Class Settings", Colour::greyLevel (0.88f), classInfoHolder, true);
  1028. tabs->addTab ("Components", Colours::white, layoutEditorHolder, true);
  1029. tabs->addTab ("Background", Colours::white, backgroundEditorHolder, true);
  1030. tabs->addTab ("Source Code", Colours::white, codeEditorHolder, true);
  1031. tabs->setCurrentTabIndex (1);
  1032. }
  1033. ComponentEditor::~ComponentEditor()
  1034. {
  1035. deleteAllChildren();
  1036. }
  1037. void ComponentEditor::paint (Graphics& g)
  1038. {
  1039. g.fillAll (Colours::white);
  1040. }
  1041. void ComponentEditor::resized()
  1042. {
  1043. tabs->setBounds (getLocalBounds());
  1044. }
  1045. ComponentEditor::Canvas* ComponentEditor::getCanvas() const
  1046. {
  1047. return dynamic_cast <Canvas*> (getViewport()->getViewedComponent());
  1048. }
  1049. Viewport* ComponentEditor::getViewport() const
  1050. {
  1051. return layoutEditorHolder->getViewport();
  1052. }
  1053. void ComponentEditor::getAllCommands (Array <CommandID>& commands)
  1054. {
  1055. DocumentEditorComponent::getAllCommands (commands);
  1056. const CommandID ids[] = { CommandIDs::undo,
  1057. CommandIDs::redo };
  1058. commands.addArray (ids, numElementsInArray (ids));
  1059. }
  1060. void ComponentEditor::getCommandInfo (CommandID commandID, ApplicationCommandInfo& result)
  1061. {
  1062. result.setActive (document != 0);
  1063. switch (commandID)
  1064. {
  1065. case CommandIDs::undo:
  1066. result.setInfo ("Undo", "Undoes the last change",
  1067. CommandCategories::general, 0);
  1068. result.defaultKeypresses.add (KeyPress ('z', ModifierKeys::commandModifier, 0));
  1069. break;
  1070. case CommandIDs::redo:
  1071. result.setInfo ("Redo", "Redoes the last change",
  1072. CommandCategories::general, 0);
  1073. result.defaultKeypresses.add (KeyPress ('z', ModifierKeys::shiftModifier | ModifierKeys::commandModifier, 0));
  1074. result.defaultKeypresses.add (KeyPress ('y', ModifierKeys::commandModifier, 0));
  1075. break;
  1076. default:
  1077. DocumentEditorComponent::getCommandInfo (commandID, result);
  1078. break;
  1079. }
  1080. }
  1081. bool ComponentEditor::perform (const InvocationInfo& info)
  1082. {
  1083. switch (info.commandID)
  1084. {
  1085. case CommandIDs::undo:
  1086. getDocument().getUndoManager()->undo();
  1087. return true;
  1088. case CommandIDs::redo:
  1089. getDocument().getUndoManager()->redo();
  1090. return true;
  1091. default:
  1092. break;
  1093. }
  1094. return DocumentEditorComponent::perform (info);
  1095. }