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.

1110 lines
37KB

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