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.

1141 lines
38KB

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