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.

1123 lines
37KB

  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_Headers.h"
  19. #include "jucer_ComponentEditorCanvas.h"
  20. #include "jucer_ComponentEditor.h"
  21. const float snapDistance = 8.0f;
  22. static const Colour alignmentMarkerColour (0x77ff0000);
  23. static const Colour resizableBorderColour (0x7066aaff);
  24. #include "jucer_ComponentDragOperation.h"
  25. //==============================================================================
  26. class ComponentEditorCanvas::ComponentResizeFrame : public ComponentEditorCanvas::OverlayItemComponent,
  27. public ComponentListener
  28. {
  29. public:
  30. ComponentResizeFrame (ComponentEditorCanvas& canvas_, Component* componentToAttachTo)
  31. : OverlayItemComponent (canvas_),
  32. component (componentToAttachTo),
  33. borderThickness (4)
  34. {
  35. component->addComponentListener (this);
  36. }
  37. ~ComponentResizeFrame()
  38. {
  39. if (component != 0)
  40. component->removeComponentListener (this);
  41. }
  42. void paint (Graphics& g)
  43. {
  44. g.setColour (resizableBorderColour);
  45. g.drawRect (0, 0, getWidth(), getHeight(), borderThickness);
  46. }
  47. void componentMovedOrResized (Component&, bool, bool) { updatePosition(); }
  48. void mouseEnter (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  49. void mouseExit (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  50. void mouseMove (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  51. void mouseDown (const MouseEvent& e)
  52. {
  53. jassert (component != 0);
  54. if (component != 0)
  55. {
  56. updateDragZone (e.getPosition());
  57. canvas.beginDrag (e.getEventRelativeTo (getParentComponent()), dragZone);
  58. canvas.showSizeGuides();
  59. }
  60. }
  61. void mouseDrag (const MouseEvent& e)
  62. {
  63. if (component != 0)
  64. {
  65. canvas.continueDrag (e.getEventRelativeTo (getParentComponent()));
  66. autoScrollForMouseEvent (e);
  67. }
  68. }
  69. void mouseUp (const MouseEvent& e)
  70. {
  71. canvas.hideSizeGuides();
  72. if (component != 0)
  73. canvas.endDrag (e.getEventRelativeTo (getParentComponent()));
  74. updateDragZone (e.getPosition());
  75. }
  76. bool hitTest (int x, int y)
  77. {
  78. return ! getCentreArea().contains (x, y);
  79. }
  80. void updatePosition()
  81. {
  82. if (component != 0)
  83. setBoundsInTargetSpace (component->getBounds().expanded (borderThickness, borderThickness));
  84. }
  85. const String getTargetComponentID() const { return component == 0 ? String::empty : ComponentDocument::getJucerIDFor (component); }
  86. //==============================================================================
  87. class SizeGuideComponent : public OverlayItemComponent,
  88. public ComponentListener
  89. {
  90. public:
  91. enum Type { left, right, top, bottom };
  92. //==============================================================================
  93. SizeGuideComponent (ComponentEditorCanvas& canvas_, const ValueTree& state_, Component* component_, Type type_)
  94. : OverlayItemComponent (canvas_), state (state_), component (component_), type (type_)
  95. {
  96. component->addComponentListener (this);
  97. setAlwaysOnTop (true);
  98. canvas.addAndMakeVisible (this);
  99. setInterceptsMouseClicks (false, false);
  100. updatePosition();
  101. }
  102. ~SizeGuideComponent()
  103. {
  104. if (component != 0)
  105. component->removeComponentListener (this);
  106. }
  107. //==============================================================================
  108. void paint (Graphics& g)
  109. {
  110. const float dashes[] = { 4.0f, 3.0f };
  111. g.setColour (resizableBorderColour);
  112. g.drawDashedLine (0.5f, 0.5f, getWidth() - 0.5f, getHeight() - 0.5f, dashes, 2, 1.0f);
  113. }
  114. void componentMovedOrResized (Component&, bool, bool) { updatePosition(); }
  115. void componentBeingDeleted (Component&)
  116. {
  117. setVisible (false);
  118. component = 0;
  119. }
  120. //==============================================================================
  121. void updatePosition()
  122. {
  123. if (component != 0)
  124. {
  125. RectangleCoordinates coords (getDocument().getCoordsFor (state));
  126. Coordinate coord (false);
  127. Rectangle<int> r;
  128. switch (type)
  129. {
  130. case left: coord = coords.left; r.setBounds (component->getX(), 0, 1, component->getY()); break;
  131. case right: coord = coords.right; r.setBounds (component->getRight(), 0, 1, component->getY()); break;
  132. case top: coord = coords.top; r.setBounds (0, component->getY(), component->getX(), 1); break;
  133. case bottom: coord = coords.bottom; r.setBounds (0, component->getBottom(), component->getX(), 1); break;
  134. default: jassertfalse; break;
  135. }
  136. setBoundsInTargetSpace (r);
  137. label.update (getParentComponent(), coord.toString(), resizableBorderColour.withAlpha (0.9f), getX(), getY(), type != left, type != top);
  138. }
  139. }
  140. private:
  141. ValueTree state;
  142. Component* component;
  143. Type type;
  144. FloatingLabelComponent label;
  145. Point<int> lineEnd1, lineEnd2;
  146. };
  147. void showSizeGuides()
  148. {
  149. if (sizeGuides.size() == 0)
  150. {
  151. const ValueTree v (getDocument().getComponentState (component));
  152. sizeGuides.add (new SizeGuideComponent (canvas, v, component, SizeGuideComponent::left));
  153. sizeGuides.add (new SizeGuideComponent (canvas, v, component, SizeGuideComponent::right));
  154. sizeGuides.add (new SizeGuideComponent (canvas, v, component, SizeGuideComponent::top));
  155. sizeGuides.add (new SizeGuideComponent (canvas, v, component, SizeGuideComponent::bottom));
  156. }
  157. }
  158. void hideSizeGuides()
  159. {
  160. sizeGuides.clear();
  161. }
  162. private:
  163. Component::SafePointer<Component> component;
  164. ResizableBorderComponent::Zone dragZone;
  165. const int borderThickness;
  166. OwnedArray <SizeGuideComponent> sizeGuides;
  167. const Rectangle<int> getCentreArea() const
  168. {
  169. return getLocalBounds().reduced (borderThickness, borderThickness);
  170. }
  171. void updateDragZone (const Point<int>& p)
  172. {
  173. ResizableBorderComponent::Zone newZone
  174. = ResizableBorderComponent::Zone::fromPositionOnBorder (getLocalBounds(),
  175. BorderSize (borderThickness), p);
  176. if (dragZone != newZone)
  177. {
  178. dragZone = newZone;
  179. setMouseCursor (newZone.getMouseCursor());
  180. }
  181. }
  182. };
  183. //==============================================================================
  184. class ComponentEditorCanvas::MarkerComponent : public ComponentEditorCanvas::OverlayItemComponent,
  185. public ValueTree::Listener
  186. {
  187. public:
  188. MarkerComponent (ComponentEditorCanvas& canvas_, const ValueTree& marker_, bool isX_, int headSize_)
  189. : OverlayItemComponent (canvas_), marker (marker_), isX (isX_), headSize (headSize_ - 2),
  190. dragStartPos (0), isDragging (false)
  191. {
  192. marker.addListener (this);
  193. }
  194. ~MarkerComponent()
  195. {
  196. marker.removeListener (this);
  197. }
  198. void paint (Graphics& g)
  199. {
  200. g.setColour (Colours::darkgreen.withAlpha (isMouseOverOrDragging() ? 0.8f : 0.4f));
  201. g.fillPath (path);
  202. }
  203. void updatePosition()
  204. {
  205. Coordinate coord (getMarkerList().getCoordinate (marker));
  206. const int pos = roundToInt (coord.resolve (getMarkerList()));
  207. const int width = 8;
  208. if (isX)
  209. setBoundsInTargetSpace (Rectangle<int> (pos - width, -headSize, width * 2, getParentHeight()));
  210. else
  211. setBoundsInTargetSpace (Rectangle<int> (-headSize, pos - width, getParentWidth(), width * 2));
  212. labelText = "name: " + getMarkerList().getName (marker) + "\nposition: " + coord.toString();
  213. updateLabel();
  214. }
  215. void updateLabel()
  216. {
  217. if (isMouseOverOrDragging() && (getWidth() > 1 || getHeight() > 1))
  218. label.update (getParentComponent(), labelText, Colours::darkgreen,
  219. isX ? getBounds().getCentreX() : getX() + headSize,
  220. isX ? getY() + headSize : getBounds().getCentreY(), true, true);
  221. else
  222. label.remove();
  223. }
  224. bool hitTest (int x, int y)
  225. {
  226. return (isX ? y : x) < headSize;
  227. }
  228. void resized()
  229. {
  230. const float lineThickness = 1.0f;
  231. path.clear();
  232. if (isX)
  233. {
  234. const float centre = getWidth() / 2 + 0.5f;
  235. path.addLineSegment (centre, 2.0f, centre, getHeight() + 1.0f, lineThickness);
  236. path.addTriangle (1.0f, 0.0f, centre * 2.0f - 1.0f, 0.0f, centre, headSize + 1.0f);
  237. }
  238. else
  239. {
  240. const float centre = getHeight() / 2 + 0.5f;
  241. path.addLineSegment (2.0f, centre, getWidth() + 1.0f, centre, lineThickness);
  242. path.addTriangle (0.0f, centre * 2.0f - 1.0f, 0.0f, 1.0f, headSize + 1.0f, centre);
  243. }
  244. updateLabel();
  245. }
  246. void mouseDown (const MouseEvent& e)
  247. {
  248. mouseDownPos = e.getEventRelativeTo (getParentComponent()).getMouseDownPosition();
  249. toFront (false);
  250. updateLabel();
  251. canvas.getSelection().selectOnly (marker [ComponentDocument::idProperty]);
  252. if (e.mods.isPopupMenu())
  253. {
  254. isDragging = false;
  255. }
  256. else
  257. {
  258. isDragging = true;
  259. getDocument().beginNewTransaction();
  260. Coordinate coord (getMarkerList().getCoordinate (marker));
  261. dragStartPos = coord.resolve (getMarkerList());
  262. }
  263. }
  264. void mouseDrag (const MouseEvent& e)
  265. {
  266. if (isDragging)
  267. {
  268. autoScrollForMouseEvent (e);
  269. const MouseEvent e2 (e.getEventRelativeTo (getParentComponent()));
  270. ComponentDocument& doc = getDocument();
  271. doc.getUndoManager()->undoCurrentTransactionOnly();
  272. Rectangle<int> axis;
  273. if (isX)
  274. axis.setBounds (0, 0, getParentWidth(), headSize);
  275. else
  276. axis.setBounds (0, 0, headSize, getParentHeight());
  277. if (axis.expanded (30, 30).contains (e.x, e.y))
  278. {
  279. Coordinate coord (getMarkerList().getCoordinate (marker));
  280. // (can't use getDistanceFromDragStart() because it doesn't take into account auto-scrolling)
  281. coord.moveToAbsolute (jmax (0, roundToInt (dragStartPos + (isX ? e2.x - mouseDownPos.getX()
  282. : e2.y - mouseDownPos.getY()))),
  283. getMarkerList());
  284. getMarkerList().setCoordinate (marker, coord);
  285. }
  286. else
  287. {
  288. getMarkerList().deleteMarker (marker);
  289. }
  290. }
  291. }
  292. void mouseUp (const MouseEvent& e)
  293. {
  294. getDocument().beginNewTransaction();
  295. updateLabel();
  296. }
  297. void mouseEnter (const MouseEvent& e)
  298. {
  299. updateLabel();
  300. repaint();
  301. }
  302. void mouseExit (const MouseEvent& e)
  303. {
  304. updateLabel();
  305. repaint();
  306. }
  307. ComponentDocument::MarkerList& getMarkerList() { return getDocument().getMarkerList (isX); }
  308. void valueTreePropertyChanged (ValueTree&, const var::identifier&) { updatePosition(); }
  309. void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged) {}
  310. void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) {}
  311. ValueTree marker;
  312. const bool isX;
  313. private:
  314. const int headSize;
  315. Path path;
  316. double dragStartPos;
  317. bool isDragging;
  318. FloatingLabelComponent label;
  319. String labelText;
  320. Point<int> mouseDownPos;
  321. };
  322. //==============================================================================
  323. class ComponentEditorCanvas::OverlayComponent : public Component,
  324. public LassoSource <SelectedItems::ItemType>,
  325. public ChangeListener,
  326. public ValueTree::Listener
  327. {
  328. public:
  329. OverlayComponent (ComponentEditorCanvas& canvas_)
  330. : canvas (canvas_)
  331. {
  332. setWantsKeyboardFocus (true);
  333. canvas.getSelection().addChangeListener (this);
  334. markerRootX = getDocument().getMarkerListX().getGroup();
  335. markerRootY = getDocument().getMarkerListY().getGroup();
  336. markerRootX.addListener (this);
  337. markerRootY.addListener (this);
  338. }
  339. ~OverlayComponent()
  340. {
  341. markerRootX.removeListener (this);
  342. markerRootY.removeListener (this);
  343. canvas.getSelection().removeChangeListener (this);
  344. lasso = 0;
  345. deleteAllChildren();
  346. }
  347. //==============================================================================
  348. void mouseDown (const MouseEvent& e)
  349. {
  350. lasso = 0;
  351. mouseDownCompUID = String::empty;
  352. isDraggingClickedComp = false;
  353. Component* underMouse = canvas.getComponentHolder()->findComponentAt (e.getEventRelativeTo (canvas.getComponentHolder()).getPosition());
  354. if (e.mods.isPopupMenu())
  355. {
  356. if (underMouse != 0)
  357. {
  358. if (! canvas.getSelection().isSelected (ComponentDocument::getJucerIDFor (underMouse)))
  359. canvas.getSelection().selectOnly (ComponentDocument::getJucerIDFor (underMouse));
  360. }
  361. PopupMenu m;
  362. if (underMouse != 0)
  363. {
  364. m.addCommandItem (commandManager, CommandIDs::toFront);
  365. m.addCommandItem (commandManager, CommandIDs::toBack);
  366. m.addSeparator();
  367. m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  368. const int r = m.show();
  369. (void) r;
  370. }
  371. else
  372. {
  373. getDocument().addNewComponentMenuItems (m);
  374. const int r = m.show();
  375. getDocument().performNewComponentMenuItem (r);
  376. }
  377. }
  378. else
  379. {
  380. if (underMouse == 0 || e.mods.isAltDown())
  381. {
  382. canvas.deselectNonComponents();
  383. addAndMakeVisible (lasso = new LassoComponent <SelectedItems::ItemType>());
  384. lasso->beginLasso (e, this);
  385. }
  386. else
  387. {
  388. mouseDownCompUID = ComponentDocument::getJucerIDFor (underMouse);
  389. canvas.deselectNonComponents();
  390. mouseDownResult = canvas.getSelection().addToSelectionOnMouseDown (mouseDownCompUID, e.mods);
  391. updateResizeFrames();
  392. hideSizeGuides();
  393. showSizeGuides();
  394. }
  395. }
  396. }
  397. void mouseDrag (const MouseEvent& e)
  398. {
  399. if (lasso != 0)
  400. {
  401. lasso->dragLasso (e);
  402. }
  403. else if (mouseDownCompUID.isNotEmpty() && (! e.mouseWasClicked()) && (! e.mods.isPopupMenu()))
  404. {
  405. if (! isDraggingClickedComp)
  406. {
  407. isDraggingClickedComp = true;
  408. canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, true, mouseDownResult);
  409. canvas.beginDrag (e, ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre));
  410. }
  411. canvas.continueDrag (e);
  412. showSizeGuides();
  413. }
  414. autoScrollForMouseEvent (e);
  415. }
  416. void mouseUp (const MouseEvent& e)
  417. {
  418. hideSizeGuides();
  419. if (lasso != 0)
  420. {
  421. lasso->endLasso();
  422. lasso = 0;
  423. if (e.mouseWasClicked())
  424. canvas.getSelection().deselectAll();
  425. }
  426. else if (! e.mods.isPopupMenu())
  427. {
  428. if (! isDraggingClickedComp)
  429. canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, ! e.mouseWasClicked(), mouseDownResult);
  430. }
  431. canvas.endDrag (e);
  432. }
  433. void mouseDoubleClick (const MouseEvent& e)
  434. {
  435. const BorderSize& border = canvas.border;
  436. const Rectangle<int> xAxis (border.getLeft(), 0, getWidth() - border.getLeftAndRight(), border.getTop());
  437. const Rectangle<int> yAxis (0, border.getTop(), border.getLeft(), getHeight() - border.getTopAndBottom());
  438. if (xAxis.contains (e.x, e.y))
  439. {
  440. getDocument().getMarkerListX().createMarker ("Marker", e.x - xAxis.getX());
  441. }
  442. else if (yAxis.contains (e.x, e.y))
  443. {
  444. getDocument().getMarkerListY().createMarker ("Marker", e.y - yAxis.getY());
  445. }
  446. }
  447. void findLassoItemsInArea (Array <SelectedItems::ItemType>& itemsFound, const Rectangle<int>& area)
  448. {
  449. canvas.getComponentHolder()
  450. ->findLassoItemsInArea (itemsFound, area + relativePositionToOtherComponent (canvas.getComponentHolder(), Point<int>()));
  451. }
  452. SelectedItems& getLassoSelection() { return canvas.getSelection(); }
  453. void resized()
  454. {
  455. updateMarkers();
  456. }
  457. void changeListenerCallback (void*)
  458. {
  459. updateResizeFrames();
  460. }
  461. void modifierKeysChanged (const ModifierKeys&)
  462. {
  463. Desktop::getInstance().getMainMouseSource().triggerFakeMove();
  464. }
  465. void showSizeGuides()
  466. {
  467. for (int i = getNumChildComponents(); --i >= 0;)
  468. {
  469. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  470. if (resizer != 0)
  471. resizer->showSizeGuides();
  472. }
  473. }
  474. void hideSizeGuides()
  475. {
  476. for (int i = getNumChildComponents(); --i >= 0;)
  477. {
  478. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  479. if (resizer != 0)
  480. resizer->hideSizeGuides();
  481. }
  482. }
  483. void valueTreePropertyChanged (ValueTree&, const var::identifier&) { updateMarkers(); }
  484. void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged) { updateMarkers(); }
  485. void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) {}
  486. private:
  487. //==============================================================================
  488. ComponentEditorCanvas& canvas;
  489. ValueTree markerRootX, markerRootY;
  490. ScopedPointer <LassoComponent <SelectedItems::ItemType> > lasso;
  491. bool mouseDownResult, isDraggingClickedComp;
  492. SelectedItems::ItemType mouseDownCompUID;
  493. ComponentDocument& getDocument() { return canvas.getDocument(); }
  494. void updateResizeFrames()
  495. {
  496. SelectedItems& selection = canvas.getSelection();
  497. StringArray requiredIds (canvas.getSelectedIds());
  498. int i;
  499. for (i = getNumChildComponents(); --i >= 0;)
  500. {
  501. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  502. if (resizer != 0)
  503. {
  504. if (selection.isSelected (resizer->getTargetComponentID()))
  505. requiredIds.removeString (resizer->getTargetComponentID());
  506. else
  507. delete resizer;
  508. }
  509. }
  510. for (i = requiredIds.size(); --i >= 0;)
  511. {
  512. Component* c = canvas.getComponentHolder()->findComponentWithID (requiredIds[i]);
  513. if (c != 0)
  514. {
  515. ComponentResizeFrame* frame = new ComponentResizeFrame (canvas, c);
  516. addAndMakeVisible (frame);
  517. frame->updatePosition();
  518. }
  519. }
  520. }
  521. void updateMarkers (bool isX)
  522. {
  523. ComponentDocument& doc = getDocument();
  524. ComponentDocument::MarkerList& markerList = doc.getMarkerList (isX);
  525. Array<ValueTree> requiredMarkers;
  526. int i;
  527. for (i = doc.getMarkerList (isX).size(); --i >= 0;)
  528. requiredMarkers.add (markerList.getMarker (i));
  529. for (i = getNumChildComponents(); --i >= 0;)
  530. {
  531. MarkerComponent* marker = dynamic_cast <MarkerComponent*> (getChildComponent(i));
  532. if (marker != 0 && marker->isX == isX)
  533. {
  534. if (requiredMarkers.contains (marker->marker))
  535. {
  536. marker->setVisible (true);
  537. marker->updatePosition();
  538. requiredMarkers.removeValue (marker->marker);
  539. }
  540. else
  541. {
  542. if (marker->isMouseButtonDown())
  543. marker->setBounds (-1, -1, 1, 1);
  544. else
  545. delete marker;
  546. }
  547. }
  548. }
  549. for (i = requiredMarkers.size(); --i >= 0;)
  550. {
  551. MarkerComponent* marker = new MarkerComponent (canvas, requiredMarkers.getReference(i),
  552. isX, isX ? canvas.border.getTop()
  553. : canvas.border.getLeft());
  554. addAndMakeVisible (marker);
  555. marker->updatePosition();
  556. }
  557. }
  558. void updateMarkers()
  559. {
  560. updateMarkers (true);
  561. updateMarkers (false);
  562. }
  563. };
  564. //==============================================================================
  565. class ComponentEditorCanvas::DocumentResizerFrame : public Component
  566. {
  567. public:
  568. DocumentResizerFrame (ComponentEditorCanvas& canvas_)
  569. : canvas (canvas_), dragStartWidth (0), dragStartHeight (0), resizerThickness (4)
  570. {
  571. }
  572. ~DocumentResizerFrame()
  573. {
  574. }
  575. void paint (Graphics& g)
  576. {
  577. const Rectangle<int> content (getContentArea());
  578. g.setColour (Colour::greyLevel (0.7f).withAlpha (0.4f));
  579. g.drawRect (content.expanded (resizerThickness, resizerThickness), resizerThickness);
  580. const int bottomGap = getHeight() - content.getBottom();
  581. g.setFont (bottomGap - 5.0f);
  582. g.setColour (Colours::grey);
  583. g.drawText (String (content.getWidth()) + " x " + String (content.getHeight()),
  584. 0, 0, jmax (content.getRight(), jmin (60, getWidth())), getHeight(), Justification::bottomRight, false);
  585. }
  586. void mouseMove (const MouseEvent& e)
  587. {
  588. updateDragZone (e.getPosition());
  589. }
  590. void mouseDown (const MouseEvent& e)
  591. {
  592. updateDragZone (e.getPosition());
  593. dragStartWidth = getDocument().getCanvasWidth().getValue();
  594. dragStartHeight = getDocument().getCanvasHeight().getValue();
  595. canvas.showSizeGuides();
  596. }
  597. void mouseDrag (const MouseEvent& e)
  598. {
  599. if (dragZone.isDraggingRightEdge())
  600. getDocument().getCanvasWidth() = jmax (1, dragStartWidth + e.getDistanceFromDragStartX());
  601. if (dragZone.isDraggingBottomEdge())
  602. getDocument().getCanvasHeight() = jmax (1, dragStartHeight + e.getDistanceFromDragStartY());
  603. }
  604. void mouseUp (const MouseEvent& e)
  605. {
  606. canvas.hideSizeGuides();
  607. updateDragZone (e.getPosition());
  608. }
  609. void updateDragZone (const Point<int>& p)
  610. {
  611. ResizableBorderComponent::Zone newZone
  612. = ResizableBorderComponent::Zone::fromPositionOnBorder (getContentArea().expanded (resizerThickness, resizerThickness),
  613. BorderSize (0, 0, resizerThickness, resizerThickness), p);
  614. if (dragZone != newZone)
  615. {
  616. dragZone = newZone;
  617. setMouseCursor (newZone.getMouseCursor());
  618. }
  619. }
  620. bool hitTest (int x, int y)
  621. {
  622. const Rectangle<int> content (getContentArea());
  623. return (x >= content.getRight() || y >= content.getBottom())
  624. && (! content.contains (x, y))
  625. && content.expanded (resizerThickness, resizerThickness).contains (x, y);
  626. }
  627. private:
  628. ComponentEditorCanvas& canvas;
  629. ResizableBorderComponent::Zone dragZone;
  630. int dragStartWidth, dragStartHeight;
  631. const int resizerThickness;
  632. ComponentDocument& getDocument() { return canvas.getDocument(); }
  633. const Rectangle<int> getContentArea() const { return canvas.getContentArea(); }
  634. };
  635. //==============================================================================
  636. ComponentEditorCanvas::ComponentEditorCanvas (ComponentEditor& editor_)
  637. : editor (editor_), border (14)
  638. {
  639. setOpaque (true);
  640. addAndMakeVisible (componentHolder = new ComponentHolder());
  641. addAndMakeVisible (overlay = new OverlayComponent (*this));
  642. overlay->addAndMakeVisible (resizeFrame = new DocumentResizerFrame (*this));
  643. setSize (500, 500);
  644. getDocument().getRoot().addListener (this);
  645. updateComponents();
  646. }
  647. ComponentEditorCanvas::~ComponentEditorCanvas()
  648. {
  649. dragger = 0;
  650. getDocument().getRoot().removeListener (this);
  651. //deleteAndZero (componentHolder);
  652. deleteAllChildren();
  653. }
  654. //==============================================================================
  655. ComponentEditor& ComponentEditorCanvas::getEditor() { return editor; }
  656. ComponentDocument& ComponentEditorCanvas::getDocument() { return editor.getDocument(); }
  657. ComponentEditorCanvas::SelectedItems& ComponentEditorCanvas::getSelection() { return selection; }
  658. ComponentEditorCanvas::ComponentHolder* ComponentEditorCanvas::getComponentHolder() const { return componentHolder; }
  659. void ComponentEditorCanvas::timerCallback()
  660. {
  661. stopTimer();
  662. if (! Component::isMouseButtonDownAnywhere())
  663. getDocument().beginNewTransaction();
  664. }
  665. //==============================================================================
  666. void ComponentEditorCanvas::paint (Graphics& g)
  667. {
  668. g.fillAll (Colours::white);
  669. g.setFont (border.getTop() - 5.0f);
  670. g.setColour (Colours::darkgrey);
  671. g.drawHorizontalLine (border.getTop() - 1, 2.0f, (float) getWidth() - border.getRight());
  672. g.drawVerticalLine (border.getLeft() - 1, 2.0f, (float) getHeight() - border.getBottom());
  673. drawXAxis (g, Rectangle<int> (border.getLeft(), 0, componentHolder->getWidth(), border.getTop()));
  674. drawYAxis (g, Rectangle<int> (0, border.getTop(), border.getLeft(), componentHolder->getHeight()));
  675. }
  676. void ComponentEditorCanvas::drawXAxis (Graphics& g, const Rectangle<int>& r)
  677. {
  678. TickIterator ticks (0, r.getWidth(), 1.0, 10, 50);
  679. float pos, tickLength;
  680. String label;
  681. while (ticks.getNextTick (pos, tickLength, label))
  682. {
  683. if (pos > 0)
  684. {
  685. g.drawVerticalLine (r.getX() + (int) pos, r.getBottom() - tickLength * r.getHeight(), (float) r.getBottom());
  686. g.drawSingleLineText (label, r.getX() + (int) pos + 2, (int) r.getBottom() - 6);
  687. }
  688. }
  689. }
  690. void ComponentEditorCanvas::drawYAxis (Graphics& g, const Rectangle<int>& r)
  691. {
  692. TickIterator ticks (0, r.getHeight(), 1.0, 10, 80);
  693. float pos, tickLength;
  694. String label;
  695. while (ticks.getNextTick (pos, tickLength, label))
  696. {
  697. if (pos > 0)
  698. {
  699. g.drawHorizontalLine (r.getY() + (int) pos, r.getRight() - tickLength * r.getWidth(), (float) r.getRight());
  700. g.drawTextAsPath (label, AffineTransform::rotation (float_Pi / -2.0f)
  701. .translated (r.getRight() - 6.0f, r.getY() + pos - 2.0f));
  702. }
  703. }
  704. }
  705. const Rectangle<int> ComponentEditorCanvas::getContentArea() const
  706. {
  707. return border.subtractedFrom (getLocalBounds());
  708. }
  709. //==============================================================================
  710. void ComponentEditorCanvas::resized()
  711. {
  712. componentHolder->setBounds (getContentArea());
  713. overlay->setBounds (getLocalBounds());
  714. resizeFrame->setBounds (getLocalBounds());
  715. updateComponents();
  716. }
  717. void ComponentEditorCanvas::updateComponents()
  718. {
  719. setSize ((int) getDocument().getCanvasWidth().getValue() + border.getLeftAndRight(),
  720. (int) getDocument().getCanvasHeight().getValue() + border.getTopAndBottom());
  721. componentHolder->updateComponents (getDocument(), selection);
  722. startTimer (500);
  723. }
  724. //==============================================================================
  725. const StringArray ComponentEditorCanvas::getSelectedIds() const
  726. {
  727. StringArray ids;
  728. const int num = selection.getNumSelected();
  729. for (int i = 0; i < num; ++i)
  730. ids.add (selection.getSelectedItem(i));
  731. return ids;
  732. }
  733. void ComponentEditorCanvas::getSelectedItemProperties (Array <PropertyComponent*>& props)
  734. {
  735. getDocument().createItemProperties (props, getSelectedIds());
  736. }
  737. void ComponentEditorCanvas::deleteSelection()
  738. {
  739. getDocument().beginNewTransaction();
  740. for (int i = selection.getNumSelected(); --i >= 0;)
  741. {
  742. Component* c = componentHolder->findComponentWithID (selection.getSelectedItem (0));
  743. if (c != 0)
  744. getDocument().removeComponent (getDocument().getComponentState (c));
  745. }
  746. selection.deselectAll();
  747. getDocument().beginNewTransaction();
  748. }
  749. void ComponentEditorCanvas::deselectNonComponents()
  750. {
  751. for (int i = getSelection().getNumSelected(); --i >= 0;)
  752. if (! getDocument().getComponentWithID (getSelection().getSelectedItem (i)).isValid())
  753. getSelection().deselect (getSelection().getSelectedItem (i));
  754. }
  755. void ComponentEditorCanvas::selectionToFront()
  756. {
  757. getDocument().beginNewTransaction();
  758. int index = 0;
  759. for (int i = getDocument().getNumComponents(); --i >= 0;)
  760. {
  761. const ValueTree comp (getDocument().getComponent (index));
  762. Component* c = componentHolder->getComponentForState (getDocument(), comp);
  763. if (c != 0 && selection.isSelected (ComponentDocument::getJucerIDFor (c)))
  764. {
  765. ValueTree parent (comp.getParent());
  766. parent.moveChild (parent.indexOf (comp), -1, getDocument().getUndoManager());
  767. }
  768. else
  769. {
  770. ++index;
  771. }
  772. }
  773. getDocument().beginNewTransaction();
  774. }
  775. void ComponentEditorCanvas::selectionToBack()
  776. {
  777. getDocument().beginNewTransaction();
  778. int index = getDocument().getNumComponents() - 1;
  779. for (int i = getDocument().getNumComponents(); --i >= 0;)
  780. {
  781. const ValueTree comp (getDocument().getComponent (index));
  782. Component* c = componentHolder->getComponentForState (getDocument(), comp);
  783. if (c != 0 && selection.isSelected (ComponentDocument::getJucerIDFor (c)))
  784. {
  785. ValueTree parent (comp.getParent());
  786. parent.moveChild (parent.indexOf (comp), 0, getDocument().getUndoManager());
  787. }
  788. else
  789. {
  790. --index;
  791. }
  792. }
  793. getDocument().beginNewTransaction();
  794. }
  795. //==============================================================================
  796. void ComponentEditorCanvas::showSizeGuides() { overlay->showSizeGuides(); }
  797. void ComponentEditorCanvas::hideSizeGuides() { overlay->hideSizeGuides(); }
  798. //==============================================================================
  799. const Array<Component*> ComponentEditorCanvas::getSelectedComps() const
  800. {
  801. Array<Component*> comps;
  802. for (int i = 0; i < selection.getNumSelected(); ++i)
  803. {
  804. Component* c = componentHolder->findComponentWithID (selection.getSelectedItem (i));
  805. jassert (c != 0);
  806. if (c != 0)
  807. comps.add (c);
  808. }
  809. return comps;
  810. }
  811. const Array<Component*> ComponentEditorCanvas::getUnselectedComps() const
  812. {
  813. Array<Component*> comps;
  814. for (int i = componentHolder->getNumChildComponents(); --i >= 0;)
  815. if (! selection.isSelected (ComponentDocument::getJucerIDFor (componentHolder->getChildComponent(i))))
  816. comps.add (componentHolder->getChildComponent(i));
  817. return comps;
  818. }
  819. //==============================================================================
  820. void ComponentEditorCanvas::beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone)
  821. {
  822. dragger = new DragOperation (*this, getSelectedComps(), getUnselectedComps(), e, overlay, zone);
  823. }
  824. void ComponentEditorCanvas::continueDrag (const MouseEvent& e)
  825. {
  826. if (dragger != 0)
  827. dragger->drag (e);
  828. }
  829. void ComponentEditorCanvas::endDrag (const MouseEvent& e)
  830. {
  831. if (dragger != 0)
  832. {
  833. dragger->drag (e);
  834. dragger = 0;
  835. }
  836. }
  837. //==============================================================================
  838. ComponentEditorCanvas::ComponentHolder::ComponentHolder()
  839. {
  840. }
  841. ComponentEditorCanvas::ComponentHolder::~ComponentHolder()
  842. {
  843. deleteAllChildren();
  844. }
  845. void ComponentEditorCanvas::ComponentHolder::updateComponents (ComponentDocument& doc, SelectedItems& selection)
  846. {
  847. int i;
  848. for (i = getNumChildComponents(); --i >= 0;)
  849. {
  850. Component* c = getChildComponent (i);
  851. if (! doc.containsComponent (c))
  852. {
  853. selection.deselect (ComponentDocument::getJucerIDFor (c));
  854. delete c;
  855. }
  856. }
  857. Array <Component*> componentsInOrder;
  858. const int num = doc.getNumComponents();
  859. for (i = 0; i < num; ++i)
  860. {
  861. const ValueTree v (doc.getComponent (i));
  862. Component* c = getComponentForState (doc, v);
  863. if (c == 0)
  864. {
  865. c = doc.createComponent (i);
  866. addAndMakeVisible (c);
  867. }
  868. doc.updateComponent (c);
  869. componentsInOrder.add (c);
  870. }
  871. // Make sure the z-order is correct..
  872. if (num > 0)
  873. {
  874. componentsInOrder.getLast()->toFront (false);
  875. for (i = num - 1; --i >= 0;)
  876. componentsInOrder.getUnchecked(i)->toBehind (componentsInOrder.getUnchecked (i + 1));
  877. }
  878. }
  879. Component* ComponentEditorCanvas::ComponentHolder::getComponentForState (ComponentDocument& doc, const ValueTree& state) const
  880. {
  881. for (int i = getNumChildComponents(); --i >= 0;)
  882. {
  883. Component* const c = getChildComponent (i);
  884. if (doc.isStateForComponent (state, c))
  885. return c;
  886. }
  887. return 0;
  888. }
  889. Component* ComponentEditorCanvas::ComponentHolder::findComponentWithID (const String& uid) const
  890. {
  891. for (int i = getNumChildComponents(); --i >= 0;)
  892. {
  893. Component* const c = getChildComponent(i);
  894. if (ComponentDocument::getJucerIDFor (c) == uid)
  895. return c;
  896. }
  897. return 0;
  898. }
  899. Component* ComponentEditorCanvas::ComponentHolder::findComponentAt (const Point<int>& pos) const
  900. {
  901. for (int i = getNumChildComponents(); --i >= 0;)
  902. {
  903. Component* const c = getChildComponent(i);
  904. if (c->getBounds().contains (pos))
  905. return c;
  906. }
  907. return 0;
  908. }
  909. void ComponentEditorCanvas::ComponentHolder::findLassoItemsInArea (Array <SelectedItems::ItemType>& itemsFound, const Rectangle<int>& lassoArea)
  910. {
  911. for (int i = getNumChildComponents(); --i >= 0;)
  912. {
  913. Component* c = getChildComponent(i);
  914. if (c->getBounds().intersects (lassoArea))
  915. itemsFound.add (ComponentDocument::getJucerIDFor (c));
  916. }
  917. }
  918. //==============================================================================
  919. ComponentEditorCanvas::OverlayItemComponent::OverlayItemComponent (ComponentEditorCanvas& canvas_)
  920. : canvas (canvas_)
  921. {
  922. }
  923. void ComponentEditorCanvas::OverlayItemComponent::setBoundsInTargetSpace (const Rectangle<int>& r)
  924. {
  925. setBounds (r + canvas.getComponentHolder()->relativePositionToOtherComponent (getParentComponent(), Point<int>()));
  926. }