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.

1112 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, 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, roundToInt (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::OverlayComponent : public Component,
  316. public LassoSource <SelectedItems::ItemType>,
  317. public ChangeListener,
  318. public ValueTree::Listener
  319. {
  320. public:
  321. OverlayComponent (ComponentEditorCanvas& canvas_)
  322. : canvas (canvas_)
  323. {
  324. setWantsKeyboardFocus (true);
  325. canvas.getSelection().addChangeListener (this);
  326. markerRootX = getDocument().getMarkerListX().getGroup();
  327. markerRootY = getDocument().getMarkerListY().getGroup();
  328. markerRootX.addListener (this);
  329. markerRootY.addListener (this);
  330. }
  331. ~OverlayComponent()
  332. {
  333. markerRootX.removeListener (this);
  334. markerRootY.removeListener (this);
  335. canvas.getSelection().removeChangeListener (this);
  336. lasso = 0;
  337. deleteAllChildren();
  338. }
  339. //==============================================================================
  340. void mouseDown (const MouseEvent& e)
  341. {
  342. lasso = 0;
  343. mouseDownCompUID = String::empty;
  344. isDraggingClickedComp = false;
  345. Component* underMouse = canvas.getComponentHolder()->findComponentAt (e.getEventRelativeTo (canvas.getComponentHolder()).getPosition());
  346. if (e.mods.isPopupMenu())
  347. {
  348. if (underMouse != 0)
  349. {
  350. if (! canvas.getSelection().isSelected (ComponentDocument::getJucerIDFor (underMouse)))
  351. canvas.getSelection().selectOnly (ComponentDocument::getJucerIDFor (underMouse));
  352. }
  353. PopupMenu m;
  354. if (underMouse != 0)
  355. {
  356. m.addCommandItem (commandManager, CommandIDs::toFront);
  357. m.addCommandItem (commandManager, CommandIDs::toBack);
  358. m.addSeparator();
  359. m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  360. const int r = m.show();
  361. (void) r;
  362. }
  363. else
  364. {
  365. getDocument().addNewComponentMenuItems (m);
  366. const int r = m.show();
  367. getDocument().performNewComponentMenuItem (r);
  368. }
  369. }
  370. else
  371. {
  372. if (underMouse == 0 || e.mods.isAltDown())
  373. {
  374. canvas.deselectNonComponents();
  375. addAndMakeVisible (lasso = new LassoComponent <SelectedItems::ItemType>());
  376. lasso->beginLasso (e, this);
  377. }
  378. else
  379. {
  380. mouseDownCompUID = ComponentDocument::getJucerIDFor (underMouse);
  381. canvas.deselectNonComponents();
  382. mouseDownResult = canvas.getSelection().addToSelectionOnMouseDown (mouseDownCompUID, e.mods);
  383. updateResizeFrames();
  384. hideSizeGuides();
  385. showSizeGuides();
  386. }
  387. }
  388. }
  389. void mouseDrag (const MouseEvent& e)
  390. {
  391. if (lasso != 0)
  392. {
  393. lasso->dragLasso (e);
  394. }
  395. else if (mouseDownCompUID.isNotEmpty() && (! e.mouseWasClicked()) && (! e.mods.isPopupMenu()))
  396. {
  397. if (! isDraggingClickedComp)
  398. {
  399. isDraggingClickedComp = true;
  400. canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, true, mouseDownResult);
  401. canvas.beginDrag (e, ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre));
  402. }
  403. canvas.continueDrag (e);
  404. showSizeGuides();
  405. }
  406. }
  407. void mouseUp (const MouseEvent& e)
  408. {
  409. hideSizeGuides();
  410. if (lasso != 0)
  411. {
  412. lasso->endLasso();
  413. lasso = 0;
  414. if (e.mouseWasClicked())
  415. canvas.getSelection().deselectAll();
  416. }
  417. else if (! e.mods.isPopupMenu())
  418. {
  419. if (! isDraggingClickedComp)
  420. canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, ! e.mouseWasClicked(), mouseDownResult);
  421. }
  422. canvas.endDrag (e);
  423. }
  424. void mouseDoubleClick (const MouseEvent& e)
  425. {
  426. const BorderSize& border = canvas.border;
  427. const Rectangle<int> xAxis (border.getLeft(), 0, getWidth() - border.getLeftAndRight(), border.getTop());
  428. const Rectangle<int> yAxis (0, border.getTop(), border.getLeft(), getHeight() - border.getTopAndBottom());
  429. if (xAxis.contains (e.x, e.y))
  430. {
  431. getDocument().getMarkerListX().createMarker ("Marker", e.x - xAxis.getX());
  432. }
  433. else if (yAxis.contains (e.x, e.y))
  434. {
  435. getDocument().getMarkerListY().createMarker ("Marker", e.y - yAxis.getY());
  436. }
  437. }
  438. void findLassoItemsInArea (Array <SelectedItems::ItemType>& itemsFound, int x, int y, int width, int height)
  439. {
  440. canvas.getComponentHolder()->findLassoItemsInArea (itemsFound, Rectangle<int> (x, y, width, height)
  441. + relativePositionToOtherComponent (canvas.getComponentHolder(), Point<int>()));
  442. }
  443. SelectedItems& getLassoSelection() { return canvas.getSelection(); }
  444. void resized()
  445. {
  446. updateMarkers();
  447. }
  448. void changeListenerCallback (void*)
  449. {
  450. updateResizeFrames();
  451. }
  452. void modifierKeysChanged (const ModifierKeys&)
  453. {
  454. Desktop::getInstance().getMainMouseSource().triggerFakeMove();
  455. }
  456. void showSizeGuides()
  457. {
  458. for (int i = getNumChildComponents(); --i >= 0;)
  459. {
  460. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  461. if (resizer != 0)
  462. resizer->showSizeGuides();
  463. }
  464. }
  465. void hideSizeGuides()
  466. {
  467. for (int i = getNumChildComponents(); --i >= 0;)
  468. {
  469. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  470. if (resizer != 0)
  471. resizer->hideSizeGuides();
  472. }
  473. }
  474. void valueTreePropertyChanged (ValueTree&, const var::identifier&) { updateMarkers(); }
  475. void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged) { updateMarkers(); }
  476. void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) {}
  477. private:
  478. //==============================================================================
  479. ComponentEditorCanvas& canvas;
  480. ValueTree markerRootX, markerRootY;
  481. ScopedPointer <LassoComponent <SelectedItems::ItemType> > lasso;
  482. bool mouseDownResult, isDraggingClickedComp;
  483. SelectedItems::ItemType mouseDownCompUID;
  484. ComponentDocument& getDocument() { return canvas.getDocument(); }
  485. void updateResizeFrames()
  486. {
  487. SelectedItems& selection = canvas.getSelection();
  488. StringArray requiredIds (canvas.getSelectedIds());
  489. int i;
  490. for (i = getNumChildComponents(); --i >= 0;)
  491. {
  492. ComponentResizeFrame* resizer = dynamic_cast <ComponentResizeFrame*> (getChildComponent(i));
  493. if (resizer != 0)
  494. {
  495. if (selection.isSelected (resizer->getTargetComponentID()))
  496. requiredIds.removeString (resizer->getTargetComponentID());
  497. else
  498. delete resizer;
  499. }
  500. }
  501. for (i = requiredIds.size(); --i >= 0;)
  502. {
  503. Component* c = canvas.getComponentHolder()->findComponentWithID (requiredIds[i]);
  504. if (c != 0)
  505. {
  506. ComponentResizeFrame* frame = new ComponentResizeFrame (canvas, c);
  507. addAndMakeVisible (frame);
  508. frame->updatePosition();
  509. }
  510. }
  511. }
  512. void updateMarkers (bool isX)
  513. {
  514. ComponentDocument& doc = getDocument();
  515. ComponentDocument::MarkerList& markerList = doc.getMarkerList (isX);
  516. Array<ValueTree> requiredMarkers;
  517. int i;
  518. for (i = doc.getMarkerList (isX).size(); --i >= 0;)
  519. requiredMarkers.add (markerList.getMarker (i));
  520. for (i = getNumChildComponents(); --i >= 0;)
  521. {
  522. MarkerComponent* marker = dynamic_cast <MarkerComponent*> (getChildComponent(i));
  523. if (marker != 0 && marker->isX == isX)
  524. {
  525. if (requiredMarkers.contains (marker->marker))
  526. {
  527. marker->setVisible (true);
  528. marker->updatePosition();
  529. requiredMarkers.removeValue (marker->marker);
  530. }
  531. else
  532. {
  533. if (marker->isMouseButtonDown())
  534. marker->setBounds (-1, -1, 1, 1);
  535. else
  536. delete marker;
  537. }
  538. }
  539. }
  540. for (i = requiredMarkers.size(); --i >= 0;)
  541. {
  542. MarkerComponent* marker = new MarkerComponent (canvas, requiredMarkers.getReference(i),
  543. isX, isX ? canvas.border.getTop()
  544. : canvas.border.getLeft());
  545. addAndMakeVisible (marker);
  546. marker->updatePosition();
  547. }
  548. }
  549. void updateMarkers()
  550. {
  551. updateMarkers (true);
  552. updateMarkers (false);
  553. }
  554. };
  555. //==============================================================================
  556. class ComponentEditorCanvas::WholeComponentResizer : public Component
  557. {
  558. public:
  559. WholeComponentResizer (ComponentEditorCanvas& canvas_)
  560. : canvas (canvas_), dragStartWidth (0), dragStartHeight (0), resizerThickness (4)
  561. {
  562. }
  563. ~WholeComponentResizer()
  564. {
  565. }
  566. void paint (Graphics& g)
  567. {
  568. const Rectangle<int> content (getContentArea());
  569. g.setColour (Colour::greyLevel (0.7f).withAlpha (0.4f));
  570. g.drawRect (content.expanded (resizerThickness, resizerThickness), resizerThickness);
  571. const int bottomGap = getHeight() - content.getBottom();
  572. g.setFont (bottomGap - 5.0f);
  573. g.setColour (Colours::grey);
  574. g.drawText (String (content.getWidth()) + " x " + String (content.getHeight()),
  575. 0, 0, jmax (content.getRight(), jmin (60, getWidth())), getHeight(), Justification::bottomRight, false);
  576. }
  577. void mouseMove (const MouseEvent& e)
  578. {
  579. updateDragZone (e.getPosition());
  580. }
  581. void mouseDown (const MouseEvent& e)
  582. {
  583. updateDragZone (e.getPosition());
  584. dragStartWidth = getDocument().getCanvasWidth().getValue();
  585. dragStartHeight = getDocument().getCanvasHeight().getValue();
  586. canvas.showSizeGuides();
  587. }
  588. void mouseDrag (const MouseEvent& e)
  589. {
  590. if (dragZone.isDraggingRightEdge())
  591. getDocument().getCanvasWidth() = jmax (1, dragStartWidth + e.getDistanceFromDragStartX());
  592. if (dragZone.isDraggingBottomEdge())
  593. getDocument().getCanvasHeight() = jmax (1, dragStartHeight + e.getDistanceFromDragStartY());
  594. }
  595. void mouseUp (const MouseEvent& e)
  596. {
  597. canvas.hideSizeGuides();
  598. updateDragZone (e.getPosition());
  599. }
  600. void updateDragZone (const Point<int>& p)
  601. {
  602. ResizableBorderComponent::Zone newZone
  603. = ResizableBorderComponent::Zone::fromPositionOnBorder (getContentArea().expanded (resizerThickness, resizerThickness),
  604. BorderSize (0, 0, resizerThickness, resizerThickness), p);
  605. if (dragZone != newZone)
  606. {
  607. dragZone = newZone;
  608. setMouseCursor (newZone.getMouseCursor());
  609. }
  610. }
  611. bool hitTest (int x, int y)
  612. {
  613. const Rectangle<int> content (getContentArea());
  614. return (x >= content.getRight() || y >= content.getBottom())
  615. && (! content.contains (x, y))
  616. && content.expanded (resizerThickness, resizerThickness).contains (x, y);
  617. }
  618. private:
  619. ComponentEditorCanvas& canvas;
  620. ResizableBorderComponent::Zone dragZone;
  621. int dragStartWidth, dragStartHeight;
  622. const int resizerThickness;
  623. ComponentDocument& getDocument() { return canvas.getDocument(); }
  624. const Rectangle<int> getContentArea() const { return canvas.getContentArea(); }
  625. };
  626. //==============================================================================
  627. ComponentEditorCanvas::ComponentEditorCanvas (ComponentEditor& editor_)
  628. : editor (editor_), border (14)
  629. {
  630. setOpaque (true);
  631. addAndMakeVisible (componentHolder = new ComponentHolder());
  632. addAndMakeVisible (overlay = new OverlayComponent (*this));
  633. overlay->addAndMakeVisible (resizeFrame = new WholeComponentResizer (*this));
  634. setSize (500, 500);
  635. getDocument().getRoot().addListener (this);
  636. updateComponents();
  637. }
  638. ComponentEditorCanvas::~ComponentEditorCanvas()
  639. {
  640. dragger = 0;
  641. getDocument().getRoot().removeListener (this);
  642. componentHolder->deleteAllChildren();
  643. deleteAllChildren();
  644. }
  645. //==============================================================================
  646. ComponentEditor& ComponentEditorCanvas::getEditor() { return editor; }
  647. ComponentDocument& ComponentEditorCanvas::getDocument() { return editor.getDocument(); }
  648. ComponentEditorCanvas::SelectedItems& ComponentEditorCanvas::getSelection() { return selection; }
  649. ComponentEditorCanvas::ComponentHolder* ComponentEditorCanvas::getComponentHolder() const { return componentHolder; }
  650. void ComponentEditorCanvas::timerCallback()
  651. {
  652. stopTimer();
  653. if (! Component::isMouseButtonDownAnywhere())
  654. getDocument().beginNewTransaction();
  655. }
  656. //==============================================================================
  657. void ComponentEditorCanvas::paint (Graphics& g)
  658. {
  659. g.fillAll (Colours::white);
  660. g.setFont (border.getTop() - 5.0f);
  661. g.setColour (Colours::darkgrey);
  662. g.drawHorizontalLine (border.getTop() - 1, 2.0f, (float) getWidth() - border.getRight());
  663. g.drawVerticalLine (border.getLeft() - 1, 2.0f, (float) getHeight() - border.getBottom());
  664. drawXAxis (g, Rectangle<int> (border.getLeft(), 0, componentHolder->getWidth(), border.getTop()));
  665. drawYAxis (g, Rectangle<int> (0, border.getTop(), border.getLeft(), componentHolder->getHeight()));
  666. }
  667. void ComponentEditorCanvas::drawXAxis (Graphics& g, const Rectangle<int>& r)
  668. {
  669. TickIterator ticks (0, r.getWidth(), 1.0, 10, 50);
  670. float pos, tickLength;
  671. String label;
  672. while (ticks.getNextTick (pos, tickLength, label))
  673. {
  674. if (pos > 0)
  675. {
  676. g.drawVerticalLine (r.getX() + (int) pos, r.getBottom() - tickLength * r.getHeight(), (float) r.getBottom());
  677. g.drawSingleLineText (label, r.getX() + (int) pos + 2, (int) r.getBottom() - 6);
  678. }
  679. }
  680. }
  681. void ComponentEditorCanvas::drawYAxis (Graphics& g, const Rectangle<int>& r)
  682. {
  683. TickIterator ticks (0, r.getHeight(), 1.0, 10, 80);
  684. float pos, tickLength;
  685. String label;
  686. while (ticks.getNextTick (pos, tickLength, label))
  687. {
  688. if (pos > 0)
  689. {
  690. g.drawHorizontalLine (r.getY() + (int) pos, r.getRight() - tickLength * r.getWidth(), (float) r.getRight());
  691. g.drawTextAsPath (label, AffineTransform::rotation (float_Pi / -2.0f)
  692. .translated (r.getRight() - 6.0f, r.getY() + pos - 2.0f));
  693. }
  694. }
  695. }
  696. const Rectangle<int> ComponentEditorCanvas::getContentArea() const
  697. {
  698. return border.subtractedFrom (getLocalBounds());
  699. }
  700. //==============================================================================
  701. void ComponentEditorCanvas::resized()
  702. {
  703. componentHolder->setBounds (getContentArea());
  704. overlay->setBounds (getLocalBounds());
  705. resizeFrame->setBounds (getLocalBounds());
  706. updateComponents();
  707. }
  708. void ComponentEditorCanvas::updateComponents()
  709. {
  710. setSize ((int) getDocument().getCanvasWidth().getValue() + border.getLeftAndRight(),
  711. (int) getDocument().getCanvasHeight().getValue() + border.getTopAndBottom());
  712. componentHolder->updateComponents (getDocument(), selection);
  713. startTimer (500);
  714. }
  715. //==============================================================================
  716. const StringArray ComponentEditorCanvas::getSelectedIds() const
  717. {
  718. StringArray ids;
  719. const int num = selection.getNumSelected();
  720. for (int i = 0; i < num; ++i)
  721. ids.add (selection.getSelectedItem(i));
  722. return ids;
  723. }
  724. void ComponentEditorCanvas::getSelectedItemProperties (Array <PropertyComponent*>& props)
  725. {
  726. getDocument().createItemProperties (props, getSelectedIds());
  727. }
  728. void ComponentEditorCanvas::deleteSelection()
  729. {
  730. getDocument().beginNewTransaction();
  731. for (int i = selection.getNumSelected(); --i >= 0;)
  732. {
  733. Component* c = componentHolder->findComponentWithID (selection.getSelectedItem (0));
  734. if (c != 0)
  735. getDocument().removeComponent (getDocument().getComponentState (c));
  736. }
  737. selection.deselectAll();
  738. getDocument().beginNewTransaction();
  739. }
  740. void ComponentEditorCanvas::deselectNonComponents()
  741. {
  742. for (int i = getSelection().getNumSelected(); --i >= 0;)
  743. if (! getDocument().getComponentWithID (getSelection().getSelectedItem (i)).isValid())
  744. getSelection().deselect (getSelection().getSelectedItem (i));
  745. }
  746. void ComponentEditorCanvas::selectionToFront()
  747. {
  748. getDocument().beginNewTransaction();
  749. int index = 0;
  750. for (int i = getDocument().getNumComponents(); --i >= 0;)
  751. {
  752. const ValueTree comp (getDocument().getComponent (index));
  753. Component* c = componentHolder->getComponentForState (getDocument(), comp);
  754. if (c != 0 && selection.isSelected (ComponentDocument::getJucerIDFor (c)))
  755. {
  756. ValueTree parent (comp.getParent());
  757. parent.moveChild (parent.indexOf (comp), -1, getDocument().getUndoManager());
  758. }
  759. else
  760. {
  761. ++index;
  762. }
  763. }
  764. getDocument().beginNewTransaction();
  765. }
  766. void ComponentEditorCanvas::selectionToBack()
  767. {
  768. getDocument().beginNewTransaction();
  769. int index = getDocument().getNumComponents() - 1;
  770. for (int i = getDocument().getNumComponents(); --i >= 0;)
  771. {
  772. const ValueTree comp (getDocument().getComponent (index));
  773. Component* c = componentHolder->getComponentForState (getDocument(), comp);
  774. if (c != 0 && selection.isSelected (ComponentDocument::getJucerIDFor (c)))
  775. {
  776. ValueTree parent (comp.getParent());
  777. parent.moveChild (parent.indexOf (comp), 0, getDocument().getUndoManager());
  778. }
  779. else
  780. {
  781. --index;
  782. }
  783. }
  784. getDocument().beginNewTransaction();
  785. }
  786. //==============================================================================
  787. void ComponentEditorCanvas::showSizeGuides() { overlay->showSizeGuides(); }
  788. void ComponentEditorCanvas::hideSizeGuides() { overlay->hideSizeGuides(); }
  789. //==============================================================================
  790. const Array<Component*> ComponentEditorCanvas::getSelectedComps() const
  791. {
  792. Array<Component*> comps;
  793. for (int i = 0; i < selection.getNumSelected(); ++i)
  794. {
  795. Component* c = componentHolder->findComponentWithID (selection.getSelectedItem (i));
  796. jassert (c != 0);
  797. if (c != 0)
  798. comps.add (c);
  799. }
  800. return comps;
  801. }
  802. const Array<Component*> ComponentEditorCanvas::getUnselectedComps() const
  803. {
  804. Array<Component*> comps;
  805. for (int i = componentHolder->getNumChildComponents(); --i >= 0;)
  806. if (! selection.isSelected (ComponentDocument::getJucerIDFor (componentHolder->getChildComponent(i))))
  807. comps.add (componentHolder->getChildComponent(i));
  808. return comps;
  809. }
  810. //==============================================================================
  811. void ComponentEditorCanvas::beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone)
  812. {
  813. dragger = new DragOperation (*this, getSelectedComps(), getUnselectedComps(), e, overlay, zone);
  814. }
  815. void ComponentEditorCanvas::continueDrag (const MouseEvent& e)
  816. {
  817. if (dragger != 0)
  818. dragger->drag (e);
  819. }
  820. void ComponentEditorCanvas::endDrag (const MouseEvent& e)
  821. {
  822. if (dragger != 0)
  823. {
  824. dragger->drag (e);
  825. dragger = 0;
  826. }
  827. }
  828. //==============================================================================
  829. ComponentEditorCanvas::ComponentHolder::ComponentHolder()
  830. {
  831. }
  832. ComponentEditorCanvas::ComponentHolder::~ComponentHolder()
  833. {
  834. }
  835. void ComponentEditorCanvas::ComponentHolder::updateComponents (ComponentDocument& doc, SelectedItems& selection)
  836. {
  837. int i;
  838. for (i = getNumChildComponents(); --i >= 0;)
  839. {
  840. Component* c = getChildComponent (i);
  841. if (! doc.containsComponent (c))
  842. {
  843. selection.deselect (ComponentDocument::getJucerIDFor (c));
  844. delete c;
  845. }
  846. }
  847. Array <Component*> componentsInOrder;
  848. const int num = doc.getNumComponents();
  849. for (i = 0; i < num; ++i)
  850. {
  851. const ValueTree v (doc.getComponent (i));
  852. Component* c = getComponentForState (doc, v);
  853. if (c == 0)
  854. {
  855. c = doc.createComponent (i);
  856. addAndMakeVisible (c);
  857. }
  858. doc.updateComponent (c);
  859. componentsInOrder.add (c);
  860. }
  861. // Make sure the z-order is correct..
  862. if (num > 0)
  863. {
  864. componentsInOrder.getLast()->toFront (false);
  865. for (i = num - 1; --i >= 0;)
  866. componentsInOrder.getUnchecked(i)->toBehind (componentsInOrder.getUnchecked (i + 1));
  867. }
  868. }
  869. Component* ComponentEditorCanvas::ComponentHolder::getComponentForState (ComponentDocument& doc, const ValueTree& state) const
  870. {
  871. for (int i = getNumChildComponents(); --i >= 0;)
  872. {
  873. Component* const c = getChildComponent (i);
  874. if (doc.isStateForComponent (state, c))
  875. return c;
  876. }
  877. return 0;
  878. }
  879. Component* ComponentEditorCanvas::ComponentHolder::findComponentWithID (const String& uid) const
  880. {
  881. for (int i = getNumChildComponents(); --i >= 0;)
  882. {
  883. Component* const c = getChildComponent(i);
  884. if (ComponentDocument::getJucerIDFor (c) == uid)
  885. return c;
  886. }
  887. return 0;
  888. }
  889. Component* ComponentEditorCanvas::ComponentHolder::findComponentAt (const Point<int>& pos) const
  890. {
  891. for (int i = getNumChildComponents(); --i >= 0;)
  892. {
  893. Component* const c = getChildComponent(i);
  894. if (c->getBounds().contains (pos))
  895. return c;
  896. }
  897. return 0;
  898. }
  899. void ComponentEditorCanvas::ComponentHolder::findLassoItemsInArea (Array <SelectedItems::ItemType>& itemsFound, const Rectangle<int>& lassoArea)
  900. {
  901. for (int i = getNumChildComponents(); --i >= 0;)
  902. {
  903. Component* c = getChildComponent(i);
  904. if (c->getBounds().intersects (lassoArea))
  905. itemsFound.add (ComponentDocument::getJucerIDFor (c));
  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. }