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.

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