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.

577 lines
20KB

  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. #ifndef __JUCER_EDITORPANEL_H_8E192A99__
  19. #define __JUCER_EDITORPANEL_H_8E192A99__
  20. #include "../../utility/jucer_TickIterator.h"
  21. #include "jucer_EditorCanvas.h"
  22. //==============================================================================
  23. class EditorPanelBase : public Component
  24. {
  25. public:
  26. EditorPanelBase()
  27. : rulerX (true), rulerY (false), markersVisible (true), snappingEnabled (true), canvas (0)
  28. {
  29. setOpaque (true);
  30. background = ImageCache::getFromMemory (BinaryData::brushed_aluminium_png, BinaryData::brushed_aluminium_pngSize);
  31. addAndMakeVisible (&toolbar);
  32. toolbar.setStyle (Toolbar::textOnly);
  33. addAndMakeVisible (&viewport);
  34. addAndMakeVisible (&rulerX);
  35. addAndMakeVisible (&rulerY);
  36. addChildComponent (&tree);
  37. tree.setRootItemVisible (true);
  38. tree.setMultiSelectEnabled (true);
  39. tree.setDefaultOpenness (true);
  40. tree.setColour (TreeView::backgroundColourId, Colour::greyLevel (0.92f));
  41. tree.setIndentSize (15);
  42. }
  43. ~EditorPanelBase()
  44. {
  45. jassert (infoPanel == 0); // remember to call shutdown()
  46. }
  47. void initialise (EditorCanvasBase* canvas_, ToolbarItemFactory& toolbarFactory, TreeViewItem* treeRootItem)
  48. {
  49. canvas = canvas_;
  50. toolbar.addDefaultItems (toolbarFactory);
  51. viewport.setViewedComponent (canvas);
  52. addAndMakeVisible (infoPanel = new InfoPanel (this));
  53. tree.setRootItem (treeRootItem);
  54. resized();
  55. }
  56. void shutdown()
  57. {
  58. tree.deleteRootItem();
  59. infoPanel = 0;
  60. }
  61. //==============================================================================
  62. void showOrHideProperties()
  63. {
  64. infoPanel->setVisible (! infoPanel->isVisible());
  65. resized();
  66. }
  67. bool arePropertiesVisible() const { return infoPanel->isVisible(); }
  68. void showOrHideTree()
  69. {
  70. tree.setVisible (! tree.isVisible());
  71. resized();
  72. }
  73. bool isTreeVisible() const { return tree.isVisible(); }
  74. void showOrHideMarkers()
  75. {
  76. markersVisible = ! markersVisible;
  77. commandManager->commandStatusChanged();
  78. }
  79. bool areMarkersVisible() const { return markersVisible; }
  80. void toggleSnapping()
  81. {
  82. snappingEnabled = ! snappingEnabled;
  83. commandManager->commandStatusChanged();
  84. }
  85. bool isSnappingEnabled() const { return snappingEnabled; }
  86. //==============================================================================
  87. virtual SelectedItemSet<String>& getSelection() = 0;
  88. virtual void getSelectedItemProperties (Array<PropertyComponent*>& newComps) = 0;
  89. void paint (Graphics& g)
  90. {
  91. g.setTiledImageFill (background, 0, 0, 1.0f);
  92. g.fillAll();
  93. }
  94. void resized()
  95. {
  96. const int toolbarHeight = 22;
  97. toolbar.setBounds (0, 0, getWidth(), toolbarHeight);
  98. int contentL = 0, contentR = getWidth();
  99. if (infoPanel != 0 && infoPanel->isVisible())
  100. {
  101. contentR -= 200;
  102. infoPanel->setBounds (contentR, toolbar.getBottom(), getWidth() - contentR, getHeight() - toolbar.getBottom());
  103. }
  104. if (tree.isVisible())
  105. {
  106. contentL = 200;
  107. tree.setBounds (0, toolbar.getBottom(), contentL, getHeight() - toolbar.getBottom());
  108. }
  109. const int rulerThickness = 16;
  110. viewport.setBounds (contentL + rulerThickness, toolbar.getBottom() + rulerThickness,
  111. contentR - contentL - rulerThickness,
  112. getHeight() - toolbar.getBottom() - rulerThickness);
  113. rulerX.setBounds (viewport.getX(), viewport.getY() - rulerThickness, viewport.getWidth(), rulerThickness);
  114. rulerY.setBounds (viewport.getX() - rulerThickness, viewport.getY(), rulerThickness, viewport.getHeight());
  115. updateRulers();
  116. }
  117. void updateRulers()
  118. {
  119. if (canvas != 0)
  120. {
  121. rulerX.update (canvas->getScale(), canvas->getComponentHolder());
  122. rulerY.update (canvas->getScale(), canvas->getComponentHolder());
  123. }
  124. updateMarkers();
  125. }
  126. void updateMarkers()
  127. {
  128. if (canvas != 0)
  129. {
  130. const int vw = viewport.getMaximumVisibleWidth();
  131. const int vh = viewport.getMaximumVisibleHeight();
  132. rulerX.updateMarkers (canvas->getMarkerList (true), canvas, vw, vh);
  133. rulerY.updateMarkers (canvas->getMarkerList (false), canvas, vw, vh);
  134. }
  135. }
  136. private:
  137. //==============================================================================
  138. class InfoPanel : public Component,
  139. public ChangeListener
  140. {
  141. public:
  142. InfoPanel (EditorPanelBase* owner_)
  143. : owner (owner_)
  144. {
  145. setOpaque (true);
  146. addAndMakeVisible (props = new PropertyPanel());
  147. owner->getSelection().addChangeListener (this);
  148. }
  149. ~InfoPanel()
  150. {
  151. owner->getSelection().removeChangeListener (this);
  152. props->clear();
  153. deleteAllChildren();
  154. }
  155. void changeListenerCallback (void*)
  156. {
  157. Array <PropertyComponent*> newComps;
  158. owner->getSelectedItemProperties (newComps);
  159. props->clear();
  160. props->addProperties (newComps);
  161. }
  162. void paint (Graphics& g)
  163. {
  164. g.fillAll (Colour::greyLevel (0.92f));
  165. }
  166. void resized()
  167. {
  168. props->setSize (getWidth(), getHeight());
  169. }
  170. private:
  171. EditorPanelBase* owner;
  172. PropertyPanel* props;
  173. };
  174. //==============================================================================
  175. class RulerComponent : public Component
  176. {
  177. public:
  178. RulerComponent (const bool isX_)
  179. : isX (isX_), range (0.0, 100.0), canvas (0)
  180. {
  181. }
  182. ~RulerComponent()
  183. {
  184. }
  185. void update (const EditorCanvasBase::Scale& scale, Component* contentHolder)
  186. {
  187. const Point<int> origin (contentHolder->relativePositionToOtherComponent (this, scale.origin));
  188. const double start = isX ? origin.getX() : origin.getY();
  189. const Range<double> newRange (-start * scale.scale,
  190. ((isX ? getWidth() : getHeight()) - start) * scale.scale);
  191. if (range != newRange)
  192. {
  193. range = newRange;
  194. repaint();
  195. }
  196. }
  197. void updateMarkers (MarkerListBase& markerList, EditorCanvasBase* canvas_, const int viewportWidth, const int viewportHeight)
  198. {
  199. canvas = canvas_;
  200. const int num = markerList.size();
  201. Array<ValueTree> requiredMarkers;
  202. requiredMarkers.ensureStorageAllocated (num);
  203. int i;
  204. for (i = 0; i < num; ++i)
  205. requiredMarkers.add (markerList.getMarker (i));
  206. for (i = markers.size(); --i >= 0;)
  207. {
  208. MarkerComponent* marker = markers.getUnchecked (i);
  209. const int index = requiredMarkers.indexOf (marker->marker);
  210. if (index >= 0)
  211. {
  212. marker->updatePosition (viewportWidth, viewportHeight);
  213. requiredMarkers.removeValue (marker->marker);
  214. }
  215. else
  216. {
  217. if (marker->isMouseButtonDown())
  218. marker->setBounds (-1, -1, 1, 1);
  219. else
  220. markers.remove (i);
  221. }
  222. }
  223. for (i = requiredMarkers.size(); --i >= 0;)
  224. {
  225. MarkerComponent* marker = new MarkerComponent (*this, canvas, requiredMarkers.getReference(i),
  226. isX, isX ? getHeight() : getWidth());
  227. markers.add (marker);
  228. getParentComponent()->addAndMakeVisible (marker);
  229. marker->updatePosition (viewportWidth, viewportHeight);
  230. }
  231. }
  232. void paint (Graphics& g)
  233. {
  234. g.setFont (10.0f);
  235. g.setColour (Colour::greyLevel (0.9f));
  236. TickIterator ticks (range.getStart(), range.getEnd(), range.getLength() / (isX ? getWidth() : getHeight()),
  237. 10, isX ? 50 : 80);
  238. float pos, tickLength;
  239. String label;
  240. while (ticks.getNextTick (pos, tickLength, label))
  241. {
  242. if (pos > 0)
  243. {
  244. if (isX)
  245. {
  246. g.drawVerticalLine ((int) pos, getHeight() - tickLength * getHeight(), (float) getHeight());
  247. g.drawSingleLineText (label, (int) pos + 2, getHeight() - 6);
  248. }
  249. else
  250. {
  251. g.drawHorizontalLine ((int) pos, getWidth() - tickLength * getWidth(), (float) getWidth());
  252. g.drawTextAsPath (label, AffineTransform::rotation (float_Pi / -2.0f)
  253. .translated (getWidth() - 6.0f, pos - 2.0f));
  254. }
  255. }
  256. }
  257. }
  258. void mouseDoubleClick (const MouseEvent& e)
  259. {
  260. if (isX)
  261. canvas->getMarkerList (true).createMarker (canvas->getMarkerList (true).getNonexistentMarkerName ("Marker"),
  262. xToPosition (e.x));
  263. else
  264. canvas->getMarkerList (false).createMarker (canvas->getMarkerList (false).getNonexistentMarkerName ("Marker"),
  265. xToPosition (e.y));
  266. }
  267. double xToPosition (const int x) const
  268. {
  269. return range.getStart() + x * range.getLength() / (isX ? getWidth() : getHeight());
  270. }
  271. int positionToX (const double position) const
  272. {
  273. const float proportion = (float) ((position - range.getStart()) / range.getLength());
  274. return isX ? proportionOfWidth (proportion) : proportionOfHeight (proportion);
  275. }
  276. //==============================================================================
  277. class MarkerComponent : public Component
  278. {
  279. public:
  280. MarkerComponent (RulerComponent& ruler_, EditorCanvasBase* const canvas_,
  281. const ValueTree& marker_, bool isX_, int headSize_)
  282. : ruler (ruler_), canvas (canvas_), marker (marker_), isX (isX_), headSize (headSize_ - 2),
  283. isDragging (false)
  284. {
  285. }
  286. ~MarkerComponent()
  287. {
  288. }
  289. void paint (Graphics& g)
  290. {
  291. g.setColour (Colours::lightblue.withAlpha (isMouseOverOrDragging() ? 0.9f : 0.5f));
  292. g.fillPath (path);
  293. }
  294. void updatePosition (const int viewportWidth, const int viewportHeight)
  295. {
  296. RelativeCoordinate coord (getMarkerList().getCoordinate (marker));
  297. const double pos = coord.resolve (&getMarkerList());
  298. if (! ruler.range.contains (pos))
  299. {
  300. setVisible (false);
  301. }
  302. else
  303. {
  304. setVisible (true);
  305. Point<int> anchorPoint;
  306. if (isX)
  307. anchorPoint.setXY (ruler.positionToX (pos), ruler.getHeight());
  308. else
  309. anchorPoint.setXY (ruler.getWidth(), ruler.positionToX (pos));
  310. Component* const parent = getParentComponent();
  311. anchorPoint = ruler.relativePositionToOtherComponent (parent, anchorPoint);
  312. const int width = 8;
  313. if (isX)
  314. setBounds (anchorPoint.getX() - width, anchorPoint.getY() - headSize, width * 2, viewportHeight + headSize);
  315. else
  316. setBounds (anchorPoint.getX() - headSize, anchorPoint.getY() - width, viewportWidth + headSize, width * 2);
  317. }
  318. labelText = "name: " + getMarkerList().getName (marker) + "\nposition: " + coord.toString();
  319. updateLabel();
  320. }
  321. void updateLabel()
  322. {
  323. if (isMouseOverOrDragging() && isVisible() && (getWidth() > 1 || getHeight() > 1))
  324. label.update (getParentComponent(), labelText, Colours::darkgreen,
  325. isX ? getBounds().getCentreX() : getX() + headSize,
  326. isX ? getY() + headSize : getBounds().getCentreY(), true, true);
  327. else
  328. label.remove();
  329. }
  330. bool hitTest (int x, int y)
  331. {
  332. return (isX ? y : x) < headSize;
  333. }
  334. void resized()
  335. {
  336. const float lineThickness = 1.0f;
  337. path.clear();
  338. if (isX)
  339. {
  340. const float centre = getWidth() / 2 + 0.5f;
  341. path.addLineSegment (Line<float> (centre, 2.0f, centre, getHeight() + 1.0f), lineThickness);
  342. path.addTriangle (1.0f, 0.0f, centre * 2.0f - 1.0f, 0.0f, centre, headSize + 1.0f);
  343. }
  344. else
  345. {
  346. const float centre = getHeight() / 2 + 0.5f;
  347. path.addLineSegment (Line<float> (2.0f, centre, getWidth() + 1.0f, centre), lineThickness);
  348. path.addTriangle (0.0f, centre * 2.0f - 1.0f, 0.0f, 1.0f, headSize + 1.0f, centre);
  349. }
  350. updateLabel();
  351. }
  352. void mouseDown (const MouseEvent& e)
  353. {
  354. mouseDownPos = e.getMouseDownPosition();
  355. toFront (false);
  356. updateLabel();
  357. canvas->getSelection().selectOnly (getMarkerList().getId (marker));
  358. if (e.mods.isPopupMenu())
  359. {
  360. isDragging = false;
  361. }
  362. else
  363. {
  364. isDragging = true;
  365. canvas->getUndoManager().beginNewTransaction();
  366. }
  367. }
  368. void mouseDrag (const MouseEvent& e)
  369. {
  370. if (isDragging)
  371. {
  372. autoScrollForMouseEvent (e.getEventRelativeTo (canvas), isX, ! isX);
  373. canvas->getUndoManager().undoCurrentTransactionOnly();
  374. Rectangle<int> axis;
  375. if (isX)
  376. axis.setBounds (0, 0, getParentWidth(), headSize);
  377. else
  378. axis.setBounds (0, 0, headSize, getParentHeight());
  379. if (axis.expanded (isX ? 500 : 30, isX ? 30 : 500).contains (e.x, e.y))
  380. {
  381. RelativeCoordinate coord (getMarkerList().getCoordinate (marker));
  382. MouseEvent rulerEvent (e.getEventRelativeTo (&ruler));
  383. int rulerPos = isX ? (rulerEvent.x + getWidth() / 2 - mouseDownPos.getX())
  384. : (rulerEvent.y + getHeight() / 2 - mouseDownPos.getY());
  385. coord.moveToAbsolute (canvas->limitMarkerPosition (ruler.xToPosition (rulerPos)), &getMarkerList());
  386. getMarkerList().setCoordinate (marker, coord);
  387. }
  388. else
  389. {
  390. getMarkerList().deleteMarker (marker);
  391. }
  392. }
  393. }
  394. void mouseUp (const MouseEvent& e)
  395. {
  396. canvas->getUndoManager().beginNewTransaction();
  397. updateLabel();
  398. }
  399. void mouseEnter (const MouseEvent& e)
  400. {
  401. updateLabel();
  402. repaint();
  403. }
  404. void mouseExit (const MouseEvent& e)
  405. {
  406. updateLabel();
  407. repaint();
  408. }
  409. MarkerListBase& getMarkerList() { return canvas->getMarkerList (isX); }
  410. ValueTree marker;
  411. const bool isX;
  412. private:
  413. RulerComponent& ruler;
  414. EditorCanvasBase* canvas;
  415. const int headSize;
  416. Path path;
  417. bool isDragging;
  418. FloatingLabelComponent label;
  419. String labelText;
  420. Point<int> mouseDownPos;
  421. };
  422. Range<double> range;
  423. private:
  424. const bool isX;
  425. OwnedArray <MarkerComponent> markers;
  426. EditorCanvasBase* canvas;
  427. };
  428. //==============================================================================
  429. class CanvasViewport : public Viewport
  430. {
  431. public:
  432. CanvasViewport()
  433. : canvas (0)
  434. {
  435. setOpaque (true);
  436. }
  437. ~CanvasViewport()
  438. {
  439. }
  440. void paint (Graphics& g)
  441. {
  442. if (canvas == 0)
  443. canvas = dynamic_cast <EditorCanvasBase*> (getViewedComponent());
  444. if (canvas != 0)
  445. canvas->fillBackground (g);
  446. }
  447. void paintOverChildren (Graphics& g)
  448. {
  449. drawRecessedShadows (g, getMaximumVisibleWidth(), getMaximumVisibleHeight(), 14);
  450. }
  451. void visibleAreaChanged (int, int , int, int)
  452. {
  453. EditorPanelBase* p = dynamic_cast <EditorPanelBase*> (getParentComponent());
  454. if (p != 0)
  455. p->updateRulers();
  456. }
  457. private:
  458. EditorCanvasBase* canvas;
  459. };
  460. //==============================================================================
  461. Toolbar toolbar;
  462. CanvasViewport viewport;
  463. RulerComponent rulerX, rulerY;
  464. ScopedPointer<InfoPanel> infoPanel;
  465. TreeView tree;
  466. EditorCanvasBase* canvas;
  467. bool markersVisible, snappingEnabled;
  468. Image background;
  469. };
  470. #endif // __JUCER_EDITORPANEL_H_8E192A99__