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.

959 lines
31KB

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