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.

1059 lines
34KB

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