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.

1002 lines
32KB

  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 "../../utility/jucer_TickIterator.h"
  20. #include "jucer_EditorCanvas.h"
  21. #include "jucer_EditorPanel.h"
  22. //==============================================================================
  23. class EditorCanvasBase::ResizeFrame : public EditorCanvasBase::OverlayItemComponent
  24. {
  25. public:
  26. ResizeFrame (EditorCanvasBase* canvas_, const String& objectId_, const ValueTree& objectState_)
  27. : OverlayItemComponent (canvas_),
  28. objectState (objectState_),
  29. objectId (objectId_),
  30. borderThickness (4),
  31. isDragging (false)
  32. {
  33. jassert (objectState.isValid());
  34. }
  35. ~ResizeFrame()
  36. {
  37. }
  38. void paint (Graphics& g)
  39. {
  40. g.setColour (resizableBorderColour);
  41. g.drawRect (0, 0, getWidth(), getHeight(), borderThickness);
  42. }
  43. void mouseEnter (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  44. void mouseExit (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  45. void mouseMove (const MouseEvent& e) { updateDragZone (e.getPosition()); }
  46. void mouseDown (const MouseEvent& e)
  47. {
  48. updateDragZone (e.getPosition());
  49. if (e.mods.isPopupMenu())
  50. {
  51. isDragging = false;
  52. canvas->showPopupMenu (true);
  53. }
  54. else
  55. {
  56. isDragging = true;
  57. canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()).getEventRelativeTo (getParentComponent()), dragZone);
  58. canvas->showSizeGuides();
  59. }
  60. }
  61. void mouseDrag (const MouseEvent& e)
  62. {
  63. if (isDragging)
  64. {
  65. canvas->continueDrag (e.getEventRelativeTo (getParentComponent()));
  66. autoScrollForMouseEvent (e);
  67. }
  68. }
  69. void mouseUp (const MouseEvent& e)
  70. {
  71. if (isDragging)
  72. {
  73. canvas->hideSizeGuides();
  74. canvas->endDrag (e.getEventRelativeTo (getParentComponent()));
  75. updateDragZone (e.getPosition());
  76. }
  77. }
  78. void mouseDoubleClick (const MouseEvent& e)
  79. {
  80. canvas->objectDoubleClicked (e, objectState);
  81. }
  82. bool hitTest (int x, int y)
  83. {
  84. if (ModifierKeys::getCurrentModifiers().isAnyModifierKeyDown())
  85. return ! getCentreArea().contains (x, y);
  86. return true;
  87. }
  88. bool updatePosition()
  89. {
  90. if (! objectState.getParent().isValid())
  91. return false;
  92. const Rectangle<int> bounds (canvas->getObjectPosition (objectState));
  93. setBoundsInTargetSpace (bounds.expanded (borderThickness, borderThickness));
  94. int i;
  95. for (i = sizeGuides.size(); --i >= 0;)
  96. {
  97. sizeGuides.getUnchecked(i)->setVisible (isVisible());
  98. sizeGuides.getUnchecked(i)->updatePosition (bounds);
  99. }
  100. return true;
  101. }
  102. const String& getTargetObjectID() const { return objectId; }
  103. //==============================================================================
  104. class SizeGuideComponent : public OverlayItemComponent,
  105. public ComponentListener
  106. {
  107. public:
  108. enum Type { left, right, top, bottom };
  109. //==============================================================================
  110. SizeGuideComponent (EditorCanvasBase* canvas_, const ValueTree& state_, Type type_)
  111. : OverlayItemComponent (canvas_), state (state_), type (type_)
  112. {
  113. setAlwaysOnTop (true);
  114. canvas->addAndMakeVisible (this);
  115. setInterceptsMouseClicks (false, false);
  116. }
  117. //==============================================================================
  118. void paint (Graphics& g)
  119. {
  120. const float dashes[] = { 4.0f, 3.0f };
  121. g.setColour (resizableBorderColour);
  122. g.drawDashedLine (0.5f, 0.5f, getWidth() - 0.5f, getHeight() - 0.5f, dashes, 2, 1.0f);
  123. }
  124. //==============================================================================
  125. void updatePosition (const Rectangle<int>& bounds)
  126. {
  127. RelativeRectangle coords (canvas->getObjectCoords (state));
  128. RelativeCoordinate coord;
  129. Rectangle<int> r;
  130. switch (type)
  131. {
  132. case left: coord = coords.left; r.setBounds (bounds.getX(), 0, 1, bounds.getY()); break;
  133. case right: coord = coords.right; r.setBounds (bounds.getRight(), 0, 1, bounds.getY()); break;
  134. case top: coord = coords.top; r.setBounds (0, bounds.getY(), bounds.getX(), 1); break;
  135. case bottom: coord = coords.bottom; r.setBounds (0, bounds.getBottom(), bounds.getX(), 1); break;
  136. default: jassertfalse; break;
  137. }
  138. setBoundsInTargetSpace (r);
  139. label.update (getParentComponent(), coord.toString(), resizableBorderColour.withAlpha (0.9f), getX(), getY(), type != left, type != top);
  140. }
  141. private:
  142. ValueTree state;
  143. Type type;
  144. FloatingLabelComponent label;
  145. };
  146. void showSizeGuides()
  147. {
  148. if (sizeGuides.size() == 0 && canvas->hasSizeGuides())
  149. {
  150. sizeGuides.add (new SizeGuideComponent (canvas, objectState, SizeGuideComponent::left));
  151. sizeGuides.add (new SizeGuideComponent (canvas, objectState, SizeGuideComponent::right));
  152. sizeGuides.add (new SizeGuideComponent (canvas, objectState, SizeGuideComponent::top));
  153. sizeGuides.add (new SizeGuideComponent (canvas, objectState, SizeGuideComponent::bottom));
  154. }
  155. }
  156. void hideSizeGuides()
  157. {
  158. sizeGuides.clear();
  159. }
  160. private:
  161. ValueTree objectState;
  162. String objectId;
  163. ResizableBorderComponent::Zone dragZone;
  164. const int borderThickness;
  165. OwnedArray <SizeGuideComponent> sizeGuides;
  166. bool isDragging;
  167. const Rectangle<int> getCentreArea() const
  168. {
  169. return getLocalBounds().reduced (borderThickness, borderThickness);
  170. }
  171. void updateDragZone (const Point<int>& p)
  172. {
  173. ResizableBorderComponent::Zone newZone
  174. = ResizableBorderComponent::Zone::fromPositionOnBorder (getLocalBounds(),
  175. BorderSize (borderThickness), p);
  176. if (dragZone != newZone)
  177. {
  178. dragZone = newZone;
  179. setMouseCursor (newZone.getMouseCursor());
  180. }
  181. }
  182. };
  183. //==============================================================================
  184. class EditorCanvasBase::MarkerComponent : public EditorCanvasBase::OverlayItemComponent
  185. {
  186. public:
  187. MarkerComponent (EditorCanvasBase* canvas_, const ValueTree& marker_, bool isX_, int headSize_)
  188. : OverlayItemComponent (canvas_), marker (marker_), isX (isX_), headSize (headSize_ - 2),
  189. dragStartPos (0), isDragging (false)
  190. {
  191. }
  192. ~MarkerComponent()
  193. {
  194. }
  195. void paint (Graphics& g)
  196. {
  197. g.setColour (Colours::lightblue.withAlpha (isMouseOverOrDragging() ? 0.9f : 0.5f));
  198. g.fillPath (path);
  199. }
  200. void updatePosition()
  201. {
  202. RelativeCoordinate 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, -canvas->getOrigin().getY() - headSize,
  207. width * 2, getParentHeight()));
  208. else
  209. setBoundsInTargetSpace (Rectangle<int> (-canvas->getOrigin().getX() - headSize, pos - width,
  210. getParentWidth(), width * 2));
  211. labelText = "name: " + getMarkerList().getName (marker) + "\nposition: " + coord.toString();
  212. updateLabel();
  213. }
  214. void updateLabel()
  215. {
  216. if (isMouseOverOrDragging() && (getWidth() > 1 || getHeight() > 1))
  217. label.update (getParentComponent(), labelText, Colours::darkgreen,
  218. isX ? getBounds().getCentreX() : getX() + headSize,
  219. isX ? getY() + headSize : getBounds().getCentreY(), true, true);
  220. else
  221. label.remove();
  222. }
  223. bool hitTest (int x, int y)
  224. {
  225. return (isX ? y : x) < headSize;
  226. }
  227. void resized()
  228. {
  229. const float lineThickness = 1.0f;
  230. path.clear();
  231. if (isX)
  232. {
  233. const float centre = getWidth() / 2 + 0.5f;
  234. path.addLineSegment (centre, 2.0f, centre, getHeight() + 1.0f, lineThickness);
  235. path.addTriangle (1.0f, 0.0f, centre * 2.0f - 1.0f, 0.0f, centre, headSize + 1.0f);
  236. }
  237. else
  238. {
  239. const float centre = getHeight() / 2 + 0.5f;
  240. path.addLineSegment (2.0f, centre, getWidth() + 1.0f, centre, lineThickness);
  241. path.addTriangle (0.0f, centre * 2.0f - 1.0f, 0.0f, 1.0f, headSize + 1.0f, centre);
  242. }
  243. updateLabel();
  244. }
  245. void mouseDown (const MouseEvent& e)
  246. {
  247. mouseDownPos = e.getEventRelativeTo (getParentComponent()).getMouseDownPosition();
  248. toFront (false);
  249. updateLabel();
  250. canvas->getSelection().selectOnly (getMarkerList().getId (marker));
  251. if (e.mods.isPopupMenu())
  252. {
  253. isDragging = false;
  254. }
  255. else
  256. {
  257. isDragging = true;
  258. canvas->getUndoManager().beginNewTransaction();
  259. RelativeCoordinate coord (getMarkerList().getCoordinate (marker));
  260. dragStartPos = coord.resolve (&getMarkerList());
  261. }
  262. }
  263. void mouseDrag (const MouseEvent& e)
  264. {
  265. if (isDragging)
  266. {
  267. autoScrollForMouseEvent (e);
  268. const MouseEvent e2 (e.getEventRelativeTo (getParentComponent()));
  269. canvas->getUndoManager().undoCurrentTransactionOnly();
  270. Rectangle<int> axis;
  271. if (isX)
  272. axis.setBounds (0, 0, getParentWidth(), headSize);
  273. else
  274. axis.setBounds (0, 0, headSize, getParentHeight());
  275. if (axis.expanded (isX ? 500 : 30, isX ? 30 : 500).contains (e.x, e.y))
  276. {
  277. RelativeCoordinate coord (getMarkerList().getCoordinate (marker));
  278. // (can't use getDistanceFromDragStart() because it doesn't take into account auto-scrolling)
  279. coord.moveToAbsolute (canvas->limitMarkerPosition (dragStartPos + (isX ? e2.x - mouseDownPos.getX()
  280. : e2.y - mouseDownPos.getY())),
  281. &getMarkerList());
  282. getMarkerList().setCoordinate (marker, coord);
  283. }
  284. else
  285. {
  286. getMarkerList().deleteMarker (marker);
  287. }
  288. }
  289. }
  290. void mouseUp (const MouseEvent& e)
  291. {
  292. canvas->getUndoManager().beginNewTransaction();
  293. updateLabel();
  294. }
  295. void mouseEnter (const MouseEvent& e)
  296. {
  297. updateLabel();
  298. repaint();
  299. }
  300. void mouseExit (const MouseEvent& e)
  301. {
  302. updateLabel();
  303. repaint();
  304. }
  305. MarkerListBase& getMarkerList() { return canvas->getMarkerList (isX); }
  306. ValueTree marker;
  307. const bool isX;
  308. private:
  309. const int headSize;
  310. Path path;
  311. double dragStartPos;
  312. bool isDragging;
  313. FloatingLabelComponent label;
  314. String labelText;
  315. Point<int> mouseDownPos;
  316. };
  317. //==============================================================================
  318. class EditorCanvasBase::OverlayComponent : public Component,
  319. public LassoSource <SelectedItems::ItemType>,
  320. public ChangeListener
  321. {
  322. public:
  323. OverlayComponent (EditorCanvasBase* canvas_)
  324. : canvas (canvas_)
  325. {
  326. setWantsKeyboardFocus (true);
  327. getSelection().addChangeListener (this);
  328. }
  329. ~OverlayComponent()
  330. {
  331. getSelection().removeChangeListener (this);
  332. lasso = 0;
  333. resizers.clear();
  334. markersX.clear();
  335. markersY.clear();
  336. controlPoints.clear();
  337. deleteAllChildren();
  338. }
  339. //==============================================================================
  340. void mouseDown (const MouseEvent& e)
  341. {
  342. lasso = 0;
  343. mouseDownCompUID = SelectedItems::ItemType();
  344. isDraggingClickedComp = false;
  345. const MouseEvent e2 (e.getEventRelativeTo (canvas->getComponentHolder()));
  346. const SelectedItems::ItemType underMouse (canvas->findObjectIdAt (canvas->screenSpaceToObjectSpace (e2.getPosition())));
  347. if (e.mods.isPopupMenu())
  348. {
  349. if (underMouse.isNotEmpty() && ! getSelection().isSelected (underMouse))
  350. {
  351. canvas->enableResizingMode();
  352. getSelection().selectOnly (underMouse);
  353. }
  354. canvas->showPopupMenu (underMouse.isNotEmpty());
  355. }
  356. else
  357. {
  358. if (underMouse.isEmpty() || e.mods.isAltDown())
  359. {
  360. canvas->deselectNonDraggableObjects();
  361. addAndMakeVisible (lasso = new LassoComponent <SelectedItems::ItemType>());
  362. lasso->beginLasso (e, this);
  363. }
  364. else
  365. {
  366. mouseDownCompUID = underMouse;
  367. canvas->deselectNonDraggableObjects();
  368. canvas->enableResizingMode();
  369. mouseDownResult = getSelection().addToSelectionOnMouseDown (mouseDownCompUID, e.mods);
  370. updateResizeFrames();
  371. hideSizeGuides();
  372. showSizeGuides();
  373. }
  374. }
  375. }
  376. void mouseDrag (const MouseEvent& e)
  377. {
  378. if (lasso != 0)
  379. {
  380. lasso->dragLasso (e);
  381. }
  382. else
  383. {
  384. if ((! isDraggingClickedComp)
  385. && mouseDownCompUID.isNotEmpty()
  386. && (! e.mouseWasClicked())
  387. && (! e.mods.isPopupMenu())
  388. && e.getDistanceFromDragStart() > 7) // whenever this drag occurs, it's selecting the object
  389. // and beginning a drag, so allow for more wobble than
  390. // when when dragging an already-selected object
  391. {
  392. isDraggingClickedComp = true;
  393. canvas->enableResizingMode();
  394. getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, true, mouseDownResult);
  395. canvas->beginDrag (e.withNewPosition (e.getMouseDownPosition()), ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre));
  396. }
  397. if (isDraggingClickedComp)
  398. {
  399. canvas->continueDrag (e);
  400. showSizeGuides();
  401. }
  402. }
  403. autoScrollForMouseEvent (e);
  404. }
  405. void mouseUp (const MouseEvent& e)
  406. {
  407. hideSizeGuides();
  408. if (lasso != 0)
  409. {
  410. lasso->endLasso();
  411. lasso = 0;
  412. if (e.mouseWasClicked())
  413. getSelection().deselectAll();
  414. }
  415. else if (! e.mods.isPopupMenu())
  416. {
  417. if (! isDraggingClickedComp)
  418. getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, ! e.mouseWasClicked(), mouseDownResult);
  419. }
  420. canvas->endDrag (e);
  421. }
  422. void mouseDoubleClick (const MouseEvent& e)
  423. {
  424. const BorderSize& border = canvas->border;
  425. const Rectangle<int> xAxis (border.getLeft(), 0, getWidth() - border.getLeftAndRight(), border.getTop());
  426. const Rectangle<int> yAxis (0, border.getTop(), border.getLeft(), getHeight() - border.getTopAndBottom());
  427. if (xAxis.contains (e.x, e.y))
  428. {
  429. canvas->getMarkerList (true).createMarker (canvas->getMarkerList (true).getNonexistentMarkerName ("Marker"),
  430. e.x - xAxis.getX());
  431. }
  432. else if (yAxis.contains (e.x, e.y))
  433. {
  434. canvas->getMarkerList (false).createMarker (canvas->getMarkerList (false).getNonexistentMarkerName ("Marker"),
  435. e.y - yAxis.getY());
  436. }
  437. else
  438. {
  439. const MouseEvent e2 (e.getEventRelativeTo (canvas->getComponentHolder()));
  440. const SelectedItems::ItemType underMouse (canvas->findObjectIdAt (canvas->screenSpaceToObjectSpace (e2.getPosition())));
  441. if (underMouse.isNotEmpty())
  442. {
  443. const ValueTree state (canvas->getObjectState (underMouse));
  444. canvas->objectDoubleClicked (e2, state);
  445. }
  446. }
  447. }
  448. void findLassoItemsInArea (Array <SelectedItems::ItemType>& itemsFound, const Rectangle<int>& area)
  449. {
  450. const Rectangle<int> sourceArea (area + relativePositionToOtherComponent (canvas->getComponentHolder(), Point<int>()));
  451. canvas->findLassoItemsInArea (itemsFound, canvas->screenSpaceToObjectSpace (sourceArea));
  452. }
  453. SelectedItems& getSelection() { return canvas->getSelection(); }
  454. SelectedItems& getLassoSelection() { return getSelection(); }
  455. void changeListenerCallback (void*)
  456. {
  457. update();
  458. }
  459. void modifierKeysChanged (const ModifierKeys&)
  460. {
  461. Desktop::getInstance().getMainMouseSource().triggerFakeMove();
  462. }
  463. void showSizeGuides()
  464. {
  465. if (canvas->hasSizeGuides())
  466. {
  467. for (int i = getNumChildComponents(); --i >= 0;)
  468. {
  469. ResizeFrame* resizer = dynamic_cast <ResizeFrame*> (getChildComponent(i));
  470. if (resizer != 0)
  471. resizer->showSizeGuides();
  472. }
  473. }
  474. }
  475. void hideSizeGuides()
  476. {
  477. if (canvas->hasSizeGuides())
  478. {
  479. for (int i = getNumChildComponents(); --i >= 0;)
  480. {
  481. ResizeFrame* resizer = dynamic_cast <ResizeFrame*> (getChildComponent(i));
  482. if (resizer != 0)
  483. resizer->hideSizeGuides();
  484. }
  485. }
  486. }
  487. void update()
  488. {
  489. updateResizeFrames();
  490. updateControlPoints();
  491. updateMarkers();
  492. }
  493. private:
  494. //==============================================================================
  495. EditorCanvasBase* canvas;
  496. ScopedPointer <LassoComponent <SelectedItems::ItemType> > lasso;
  497. bool mouseDownResult, isDraggingClickedComp;
  498. SelectedItems::ItemType mouseDownCompUID;
  499. OwnedArray <ResizeFrame> resizers;
  500. OwnedArray <MarkerComponent> markersX, markersY;
  501. OwnedArray <OverlayItemComponent> controlPoints;
  502. void updateResizeFrames()
  503. {
  504. if (! canvas->isResizingMode())
  505. {
  506. resizers.clear();
  507. return;
  508. }
  509. SelectedItems& selection = getSelection();
  510. StringArray requiredIds;
  511. const int num = selection.getNumSelected();
  512. int i;
  513. for (i = 0; i < num; ++i)
  514. requiredIds.add (selection.getSelectedItem(i));
  515. for (i = resizers.size(); --i >= 0;)
  516. {
  517. ResizeFrame* resizer = resizers.getUnchecked(i);
  518. const int index = requiredIds.indexOf (resizer->getTargetObjectID());
  519. if (index >= 0)
  520. {
  521. if (resizer->updatePosition())
  522. {
  523. requiredIds.remove (index);
  524. }
  525. else
  526. {
  527. resizers.remove (i);
  528. canvas->getSelection().deselect (requiredIds[i]);
  529. }
  530. }
  531. else
  532. {
  533. resizers.remove (i);
  534. }
  535. }
  536. for (i = requiredIds.size(); --i >= 0;)
  537. {
  538. const ValueTree state (canvas->getObjectState (requiredIds[i]));
  539. if (state.isValid()) // (the id may be a marker)
  540. {
  541. ResizeFrame* frame = new ResizeFrame (canvas, requiredIds[i], state);
  542. resizers.add (frame);
  543. addAndMakeVisible (frame);
  544. frame->updatePosition();
  545. }
  546. }
  547. }
  548. void updateControlPoints()
  549. {
  550. if (! canvas->isControlPointMode())
  551. {
  552. controlPoints.clear();
  553. return;
  554. }
  555. canvas->updateControlPointComponents (this, controlPoints);
  556. }
  557. void updateMarkers (OwnedArray <MarkerComponent>& markers, const bool isX)
  558. {
  559. MarkerListBase& markerList = canvas->getMarkerList (isX);
  560. const int num = markerList.size();
  561. Array<ValueTree> requiredMarkers;
  562. requiredMarkers.ensureStorageAllocated (num);
  563. int i;
  564. for (i = 0; i < num; ++i)
  565. requiredMarkers.add (markerList.getMarker (i));
  566. for (i = markers.size(); --i >= 0;)
  567. {
  568. MarkerComponent* marker = markers.getUnchecked (i);
  569. const int index = requiredMarkers.indexOf (marker->marker);
  570. if (index >= 0)
  571. {
  572. marker->updatePosition();
  573. requiredMarkers.removeValue (marker->marker);
  574. }
  575. else
  576. {
  577. if (marker->isMouseButtonDown())
  578. marker->setBounds (-1, -1, 1, 1);
  579. else
  580. markers.remove (i);
  581. }
  582. }
  583. for (i = requiredMarkers.size(); --i >= 0;)
  584. {
  585. MarkerComponent* marker = new MarkerComponent (canvas, requiredMarkers.getReference(i),
  586. isX, isX ? canvas->border.getTop()
  587. : canvas->border.getLeft());
  588. markers.add (marker);
  589. addAndMakeVisible (marker);
  590. marker->updatePosition();
  591. }
  592. }
  593. void updateMarkers()
  594. {
  595. updateMarkers (markersX, true);
  596. updateMarkers (markersY, false);
  597. }
  598. };
  599. //==============================================================================
  600. class EditorCanvasBase::DocumentResizeFrame : public Component
  601. {
  602. public:
  603. DocumentResizeFrame (EditorCanvasBase* canvas_)
  604. : canvas (canvas_), resizerThickness (4)
  605. {
  606. }
  607. ~DocumentResizeFrame()
  608. {
  609. }
  610. void paint (Graphics& g)
  611. {
  612. const Rectangle<int> content (getContentArea());
  613. g.setColour (Colour::greyLevel (0.1f).withAlpha (0.3f));
  614. g.drawRect (content.expanded (1, 1), 1);
  615. const int bottomGap = getHeight() - content.getBottom();
  616. g.setFont (bottomGap - 5.0f);
  617. g.setColour (Colour::greyLevel (0.9f));
  618. g.drawText (String (content.getWidth()) + " x " + String (content.getHeight()),
  619. 0, 0, jmax (content.getRight(), jmin (60, getWidth())), getHeight(), Justification::bottomRight, false);
  620. }
  621. void mouseMove (const MouseEvent& e)
  622. {
  623. updateDragZone (e.getPosition());
  624. }
  625. void mouseDown (const MouseEvent& e)
  626. {
  627. updateDragZone (e.getPosition());
  628. dragStartBounds = canvas->getCanvasBounds();
  629. canvas->showSizeGuides();
  630. }
  631. void mouseDrag (const MouseEvent& e)
  632. {
  633. Rectangle<int> newBounds (dragStartBounds);
  634. if (dragZone.isDraggingRightEdge())
  635. newBounds.setWidth (jmax (1, newBounds.getWidth() + e.getDistanceFromDragStartX()));
  636. if (dragZone.isDraggingBottomEdge())
  637. newBounds.setHeight (jmax (1, newBounds.getHeight() + e.getDistanceFromDragStartY()));
  638. canvas->setCanvasBounds (newBounds);
  639. }
  640. void mouseUp (const MouseEvent& e)
  641. {
  642. canvas->hideSizeGuides();
  643. updateDragZone (e.getPosition());
  644. }
  645. void updateDragZone (const Point<int>& p)
  646. {
  647. ResizableBorderComponent::Zone newZone
  648. = ResizableBorderComponent::Zone::fromPositionOnBorder (getContentArea().expanded (resizerThickness, resizerThickness),
  649. BorderSize (0, 0, resizerThickness, resizerThickness), p);
  650. if (dragZone != newZone)
  651. {
  652. dragZone = newZone;
  653. setMouseCursor (newZone.getMouseCursor());
  654. }
  655. }
  656. bool hitTest (int x, int y)
  657. {
  658. if (! canvas->canResizeCanvas())
  659. return false;
  660. const Rectangle<int> content (getContentArea());
  661. return (x >= content.getRight() || y >= content.getBottom())
  662. && (! content.contains (x, y))
  663. && content.expanded (resizerThickness, resizerThickness).contains (x, y);
  664. }
  665. private:
  666. EditorCanvasBase* canvas;
  667. ResizableBorderComponent::Zone dragZone;
  668. Rectangle<int> dragStartBounds;
  669. const int resizerThickness;
  670. const Rectangle<int> getContentArea() const { return canvas->getContentArea(); }
  671. };
  672. //==============================================================================
  673. EditorCanvasBase::EditorCanvasBase()
  674. : border (14),
  675. scaleFactor (1.0)
  676. {
  677. //setOpaque (true);
  678. }
  679. EditorCanvasBase::~EditorCanvasBase()
  680. {
  681. jassert (overlay == 0);
  682. }
  683. void EditorCanvasBase::initialise()
  684. {
  685. addAndMakeVisible (componentHolder = createComponentHolder());
  686. addAndMakeVisible (overlay = new OverlayComponent (this));
  687. overlay->addAndMakeVisible (resizeFrame = new DocumentResizeFrame (this));
  688. handleAsyncUpdate();
  689. }
  690. void EditorCanvasBase::shutdown()
  691. {
  692. dragger = 0;
  693. deleteAndZero (overlay);
  694. deleteAllChildren();
  695. }
  696. EditorPanelBase* EditorCanvasBase::getPanel() const
  697. {
  698. return findParentComponentOfClass ((EditorPanelBase*) 0);
  699. }
  700. const Point<int> EditorCanvasBase::screenSpaceToObjectSpace (const Point<int>& p) const
  701. {
  702. return p - origin;
  703. }
  704. const Point<int> EditorCanvasBase::objectSpaceToScreenSpace (const Point<int>& p) const
  705. {
  706. return p + origin;
  707. }
  708. const Rectangle<int> EditorCanvasBase::screenSpaceToObjectSpace (const Rectangle<int>& r) const
  709. {
  710. return r - origin;
  711. }
  712. const Rectangle<int> EditorCanvasBase::objectSpaceToScreenSpace (const Rectangle<int>& r) const
  713. {
  714. return r + origin;
  715. }
  716. void EditorCanvasBase::enableResizingMode()
  717. {
  718. enableControlPointMode (ValueTree::invalid);
  719. }
  720. void EditorCanvasBase::enableControlPointMode (const ValueTree& objectToEdit)
  721. {
  722. if (controlPointEditingTarget != objectToEdit)
  723. {
  724. controlPointEditingTarget = objectToEdit;
  725. getSelection().deselectAll();
  726. overlay->update();
  727. }
  728. }
  729. //==============================================================================
  730. void EditorCanvasBase::paint (Graphics& g)
  731. {
  732. g.setFont (border.getTop() - 5.0f);
  733. g.setColour (Colour::greyLevel (0.9f));
  734. //g.drawHorizontalLine (border.getTop() - 1, 2.0f, (float) getWidth() - border.getRight());
  735. //g.drawVerticalLine (border.getLeft() - 1, 2.0f, (float) getHeight() - border.getBottom());
  736. drawXAxis (g, Rectangle<int> (border.getLeft(), 0, componentHolder->getWidth(), border.getTop()));
  737. drawYAxis (g, Rectangle<int> (0, border.getTop(), border.getLeft(), componentHolder->getHeight()));
  738. }
  739. void EditorCanvasBase::drawXAxis (Graphics& g, const Rectangle<int>& r)
  740. {
  741. TickIterator ticks (-origin.getX(), r.getWidth(), 1.0, 10, 50);
  742. float pos, tickLength;
  743. String label;
  744. while (ticks.getNextTick (pos, tickLength, label))
  745. {
  746. if (pos > 0)
  747. {
  748. g.drawVerticalLine (r.getX() + (int) pos, r.getBottom() - tickLength * r.getHeight(), (float) r.getBottom());
  749. g.drawSingleLineText (label, r.getX() + (int) pos + 2, (int) r.getBottom() - 6);
  750. }
  751. }
  752. }
  753. void EditorCanvasBase::drawYAxis (Graphics& g, const Rectangle<int>& r)
  754. {
  755. TickIterator ticks (-origin.getY(), r.getHeight(), 1.0, 10, 80);
  756. float pos, tickLength;
  757. String label;
  758. while (ticks.getNextTick (pos, tickLength, label))
  759. {
  760. if (pos > 0)
  761. {
  762. g.drawHorizontalLine (r.getY() + (int) pos, r.getRight() - tickLength * r.getWidth(), (float) r.getRight());
  763. g.drawTextAsPath (label, AffineTransform::rotation (float_Pi / -2.0f)
  764. .translated (r.getRight() - 6.0f, r.getY() + pos - 2.0f));
  765. }
  766. }
  767. }
  768. const Rectangle<int> EditorCanvasBase::getContentArea() const
  769. {
  770. return border.subtractedFrom (getLocalBounds());
  771. }
  772. //==============================================================================
  773. void EditorCanvasBase::handleAsyncUpdate()
  774. {
  775. documentChanged();
  776. const Rectangle<int> canvasBounds (getCanvasBounds());
  777. const Point<int> newOrigin (jmax (0, -canvasBounds.getX()), jmax (0, -canvasBounds.getY()));
  778. if (origin != newOrigin)
  779. {
  780. repaint();
  781. const Point<int> oldOrigin (origin);
  782. origin = newOrigin;
  783. setBounds (jmin (0, getX() + oldOrigin.getX() - origin.getX()),
  784. jmin (0, getY() + oldOrigin.getY() - origin.getY()),
  785. jmax (canvasBounds.getWidth(), canvasBounds.getRight()) + border.getLeftAndRight(),
  786. jmax (canvasBounds.getHeight(), canvasBounds.getBottom()) + border.getTopAndBottom());
  787. }
  788. else
  789. {
  790. setSize (jmax (canvasBounds.getWidth(), canvasBounds.getRight()) + border.getLeftAndRight(),
  791. jmax (canvasBounds.getHeight(), canvasBounds.getBottom()) + border.getTopAndBottom());
  792. }
  793. overlay->update();
  794. }
  795. void EditorCanvasBase::resized()
  796. {
  797. componentHolder->setBounds (getContentArea());
  798. overlay->setBounds (getLocalBounds());
  799. resizeFrame->setBounds (getLocalBounds());
  800. overlay->update();
  801. }
  802. //==============================================================================
  803. void EditorCanvasBase::showSizeGuides() { overlay->showSizeGuides(); }
  804. void EditorCanvasBase::hideSizeGuides() { overlay->hideSizeGuides(); }
  805. //==============================================================================
  806. void EditorCanvasBase::beginDrag (const MouseEvent& e, const ResizableBorderComponent::Zone& zone)
  807. {
  808. dragger = createDragOperation (e, overlay, zone);
  809. }
  810. void EditorCanvasBase::continueDrag (const MouseEvent& e)
  811. {
  812. if (dragger != 0)
  813. dragger->drag (e, e.getPosition() - origin);
  814. }
  815. void EditorCanvasBase::endDrag (const MouseEvent& e)
  816. {
  817. if (dragger != 0)
  818. {
  819. dragger->drag (e, e.getPosition() - origin);
  820. dragger = 0;
  821. getUndoManager().beginNewTransaction();
  822. }
  823. }
  824. //==============================================================================
  825. EditorCanvasBase::OverlayItemComponent::OverlayItemComponent (EditorCanvasBase* canvas_)
  826. : canvas (canvas_)
  827. {
  828. }
  829. EditorCanvasBase::OverlayItemComponent::~OverlayItemComponent()
  830. {
  831. }
  832. void EditorCanvasBase::OverlayItemComponent::setBoundsInTargetSpace (const Rectangle<int>& r)
  833. {
  834. setBounds (canvas->objectSpaceToScreenSpace (r)
  835. + canvas->getComponentHolder()->relativePositionToOtherComponent (getParentComponent(), Point<int>()));
  836. }