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.

700 lines
23KB

  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. addAndMakeVisible (&tooltipBar);
  37. addChildComponent (&tree);
  38. tree.setRootItemVisible (true);
  39. tree.setMultiSelectEnabled (true);
  40. tree.setDefaultOpenness (true);
  41. tree.setColour (TreeView::backgroundColourId, Colour::greyLevel (0.92f));
  42. tree.setIndentSize (15);
  43. }
  44. ~EditorPanelBase()
  45. {
  46. jassert (infoPanel == 0); // remember to call shutdown()
  47. }
  48. void initialise (EditorCanvasBase* canvas_, ToolbarItemFactory& toolbarFactory, TreeViewItem* treeRootItem)
  49. {
  50. canvas = canvas_;
  51. toolbar.addDefaultItems (toolbarFactory);
  52. viewport.setViewedComponent (canvas);
  53. addAndMakeVisible (infoPanel = new InfoPanel (this));
  54. tree.setRootItem (treeRootItem);
  55. resized();
  56. }
  57. void shutdown()
  58. {
  59. tree.deleteRootItem();
  60. infoPanel = 0;
  61. }
  62. //==============================================================================
  63. void showOrHideProperties()
  64. {
  65. infoPanel->setVisible (! infoPanel->isVisible());
  66. resized();
  67. }
  68. bool arePropertiesVisible() const { return infoPanel->isVisible(); }
  69. void showOrHideTree()
  70. {
  71. tree.setVisible (! tree.isVisible());
  72. resized();
  73. }
  74. bool isTreeVisible() const { return tree.isVisible(); }
  75. void showOrHideMarkers()
  76. {
  77. markersVisible = ! markersVisible;
  78. commandManager->commandStatusChanged();
  79. }
  80. bool areMarkersVisible() const { return markersVisible; }
  81. void toggleSnapping()
  82. {
  83. snappingEnabled = ! snappingEnabled;
  84. commandManager->commandStatusChanged();
  85. }
  86. bool isSnappingEnabled() const { return snappingEnabled; }
  87. //==============================================================================
  88. virtual SelectedItemSet<String>& getSelection() = 0;
  89. virtual void getSelectedItemProperties (Array<PropertyComponent*>& newComps) = 0;
  90. static int getRulerThickness() throw() { return 16; }
  91. void paint (Graphics& g)
  92. {
  93. g.setTiledImageFill (background, 0, 0, 1.0f);
  94. g.fillAll();
  95. }
  96. void resized()
  97. {
  98. Rectangle<int> area (getLocalBounds());
  99. toolbar.setBounds (area.removeFromTop (22));
  100. if (infoPanel != 0 && infoPanel->isVisible())
  101. {
  102. Rectangle<int> panel (area.removeFromRight (200));
  103. tooltipBar.setBounds (panel.removeFromBottom (30));
  104. infoPanel->setBounds (panel);
  105. }
  106. else
  107. {
  108. tooltipBar.setBounds (area.removeFromBottom (18));
  109. }
  110. if (tree.isVisible())
  111. tree.setBounds (area.removeFromLeft (200));
  112. Rectangle<int> ry (area.removeFromLeft (getRulerThickness()));
  113. ry.removeFromTop (getRulerThickness());
  114. rulerY.setBounds (ry);
  115. rulerX.setBounds (area.removeFromTop (getRulerThickness()));
  116. viewport.setBounds (area);
  117. updateRulers();
  118. }
  119. void updateRulers()
  120. {
  121. if (canvas != 0)
  122. {
  123. rulerX.update (canvas->getScale(), canvas->getComponentHolder());
  124. rulerY.update (canvas->getScale(), canvas->getComponentHolder());
  125. }
  126. updateMarkers();
  127. }
  128. void updateMarkers()
  129. {
  130. if (canvas != 0)
  131. {
  132. const int vw = viewport.getMaximumVisibleWidth();
  133. const int vh = viewport.getMaximumVisibleHeight();
  134. rulerX.updateMarkers (canvas->getMarkerList (true), canvas, vw, vh);
  135. rulerY.updateMarkers (canvas->getMarkerList (false), canvas, vw, vh);
  136. }
  137. }
  138. private:
  139. //==============================================================================
  140. class InfoPanel : public Component,
  141. public ChangeListener
  142. {
  143. public:
  144. InfoPanel (EditorPanelBase* owner_)
  145. : owner (owner_)
  146. {
  147. setOpaque (true);
  148. addAndMakeVisible (props = new PropertyPanel());
  149. owner->getSelection().addChangeListener (this);
  150. }
  151. ~InfoPanel()
  152. {
  153. owner->getSelection().removeChangeListener (this);
  154. props->clear();
  155. deleteAllChildren();
  156. }
  157. void changeListenerCallback (void*)
  158. {
  159. Array <PropertyComponent*> newComps;
  160. owner->getSelectedItemProperties (newComps);
  161. props->clear();
  162. props->addProperties (newComps);
  163. }
  164. void paint (Graphics& g)
  165. {
  166. g.fillAll (Colour::greyLevel (0.92f));
  167. }
  168. void resized()
  169. {
  170. props->setSize (getWidth(), getHeight());
  171. }
  172. private:
  173. EditorPanelBase* owner;
  174. PropertyPanel* props;
  175. };
  176. //==============================================================================
  177. class RulerComponent : public Component
  178. {
  179. public:
  180. RulerComponent (const bool isX_)
  181. : isX (isX_), range (0.0, 100.0), canvas (0)
  182. {
  183. }
  184. ~RulerComponent()
  185. {
  186. }
  187. void update (const EditorCanvasBase::Scale& scale, Component* contentHolder)
  188. {
  189. const Point<int> origin (contentHolder->relativePositionToOtherComponent (this, scale.origin));
  190. const double start = isX ? origin.getX() : origin.getY();
  191. const Range<double> newRange (-start * scale.scale,
  192. ((isX ? getWidth() : getHeight()) - start) * scale.scale);
  193. if (range != newRange)
  194. {
  195. range = newRange;
  196. repaint();
  197. }
  198. }
  199. void updateMarkers (MarkerListBase& markerList, EditorCanvasBase* canvas_,
  200. const int viewportWidth, const int viewportHeight)
  201. {
  202. canvas = canvas_;
  203. const int num = markerList.size();
  204. Array<ValueTree> requiredMarkers;
  205. requiredMarkers.ensureStorageAllocated (num);
  206. int i;
  207. for (i = 0; i < num; ++i)
  208. requiredMarkers.add (markerList.getMarker (i));
  209. for (i = markers.size(); --i >= 0;)
  210. {
  211. MarkerComponent* marker = markers.getUnchecked (i);
  212. const int index = requiredMarkers.indexOf (marker->marker);
  213. if (index >= 0)
  214. {
  215. marker->updatePosition (viewportWidth, viewportHeight);
  216. requiredMarkers.removeValue (marker->marker);
  217. }
  218. else
  219. {
  220. if (marker->isMouseButtonDown())
  221. marker->setBounds (-1, -1, 1, 1);
  222. else
  223. markers.remove (i);
  224. }
  225. }
  226. for (i = requiredMarkers.size(); --i >= 0;)
  227. {
  228. MarkerComponent* marker = new MarkerComponent (*this, canvas, requiredMarkers.getReference(i),
  229. markerList.isSpecialMarker (requiredMarkers.getReference(i)), isX);
  230. markers.add (marker);
  231. getParentComponent()->addAndMakeVisible (marker);
  232. marker->updatePosition (viewportWidth, viewportHeight);
  233. }
  234. }
  235. void paint (Graphics& g)
  236. {
  237. g.setFont (10.0f);
  238. g.setColour (Colour::greyLevel (0.9f));
  239. TickIterator ticks (range.getStart(), range.getEnd(), range.getLength() / (isX ? getWidth() : getHeight()),
  240. 10, isX ? 50 : 80);
  241. float pos, tickLength;
  242. String label;
  243. while (ticks.getNextTick (pos, tickLength, label))
  244. {
  245. if (pos > 0)
  246. {
  247. if (isX)
  248. {
  249. g.drawVerticalLine ((int) pos, getHeight() - tickLength * getHeight(), (float) getHeight());
  250. g.drawSingleLineText (label, (int) pos + 2, getHeight() - 6);
  251. }
  252. else
  253. {
  254. g.drawHorizontalLine ((int) pos, getWidth() - tickLength * getWidth(), (float) getWidth());
  255. g.drawTextAsPath (label, AffineTransform::rotation (float_Pi / -2.0f)
  256. .translated (getWidth() - 6.0f, pos - 2.0f));
  257. }
  258. }
  259. }
  260. }
  261. void mouseDoubleClick (const MouseEvent& e)
  262. {
  263. if (isX)
  264. canvas->getMarkerList (true).createMarker (canvas->getMarkerList (true).getNonexistentMarkerName ("Marker"),
  265. xToPosition (e.x));
  266. else
  267. canvas->getMarkerList (false).createMarker (canvas->getMarkerList (false).getNonexistentMarkerName ("Marker"),
  268. xToPosition (e.y));
  269. }
  270. double xToPosition (const int x) const
  271. {
  272. return range.getStart() + x * range.getLength() / (isX ? getWidth() : getHeight());
  273. }
  274. int positionToX (const double position) const
  275. {
  276. const float proportion = (float) ((position - range.getStart()) / range.getLength());
  277. return isX ? proportionOfWidth (proportion) : proportionOfHeight (proportion);
  278. }
  279. //==============================================================================
  280. class MarkerComponent : public Component,
  281. public ChangeListener
  282. {
  283. public:
  284. MarkerComponent (RulerComponent& ruler_, EditorCanvasBase* const canvas_,
  285. const ValueTree& marker_, bool isSpecial_, bool isX_)
  286. : ruler (ruler_), canvas (canvas_), marker (marker_), isX (isX_), headSize (getRulerThickness() - 2),
  287. isDragging (false), isSpecial (isSpecial_), isSelected (false)
  288. {
  289. updateSelectionState();
  290. canvas->getSelection().addChangeListener (this);
  291. }
  292. ~MarkerComponent()
  293. {
  294. canvas->getSelection().removeChangeListener (this);
  295. }
  296. void paint (Graphics& g)
  297. {
  298. if (isSelected || isMouseOverOrDragging())
  299. {
  300. g.setColour (Colours::white.withAlpha (0.5f));
  301. g.strokePath (path, PathStrokeType (isSelected ? 2.5f : 1.5f));
  302. }
  303. Colour c (isSpecial ? Colours::darkgreen : Colours::darkgrey);
  304. if (isSelected)
  305. c = c.overlaidWith (Colours::red.withAlpha (0.5f));
  306. g.setColour (c.withAlpha (isMouseOverOrDragging() ? 0.95f : 0.6f));
  307. g.fillPath (path);
  308. }
  309. void updatePosition (const int viewportWidth, const int viewportHeight)
  310. {
  311. RelativeCoordinate coord (getMarkerList().getCoordinate (marker));
  312. const double pos = coord.resolve (&getMarkerList());
  313. if (! ruler.range.contains (pos))
  314. {
  315. setVisible (false);
  316. }
  317. else
  318. {
  319. setVisible (true);
  320. Point<int> anchorPoint;
  321. if (isX)
  322. anchorPoint.setXY (ruler.positionToX (pos), ruler.getHeight());
  323. else
  324. anchorPoint.setXY (ruler.getWidth(), ruler.positionToX (pos));
  325. Component* const parent = getParentComponent();
  326. anchorPoint = ruler.relativePositionToOtherComponent (parent, anchorPoint);
  327. const int width = 8;
  328. if (isX)
  329. setBounds (anchorPoint.getX() - width, anchorPoint.getY() - headSize, width * 2, viewportHeight + headSize);
  330. else
  331. setBounds (anchorPoint.getX() - headSize, anchorPoint.getY() - width, viewportWidth + headSize, width * 2);
  332. }
  333. labelText = "name: " + getMarkerList().getName (marker) + "\nposition: " + coord.toString();
  334. updateLabel();
  335. }
  336. void updateLabel()
  337. {
  338. if (isMouseOverOrDragging() && isVisible() && (getWidth() > 1 || getHeight() > 1))
  339. label.update (getParentComponent(), labelText, Colours::darkgreen,
  340. isX ? getBounds().getCentreX() : getX() + headSize,
  341. isX ? getY() + headSize : getBounds().getCentreY(), true, true);
  342. else
  343. label.remove();
  344. }
  345. bool hitTest (int x, int y)
  346. {
  347. return (isX ? y : x) < headSize;
  348. }
  349. void resized()
  350. {
  351. const float lineThickness = 1.0f;
  352. path.clear();
  353. if (isX)
  354. {
  355. float w = getWidth() / 2.0f;
  356. const float centre = w + 0.5f;
  357. w -= 2.0f;
  358. path.startNewSubPath (centre - w, 1.0f);
  359. path.lineTo (centre + w, 1.0f);
  360. path.lineTo (centre + lineThickness / 2.0f, headSize);
  361. path.lineTo (centre + lineThickness / 2.0f, (float) getHeight());
  362. path.lineTo (centre - lineThickness / 2.0f, (float) getHeight());
  363. path.lineTo (centre - lineThickness / 2.0f, headSize);
  364. path.closeSubPath();
  365. }
  366. else
  367. {
  368. float w = getHeight() / 2.0f;
  369. const float centre = w + 0.5f;
  370. w -= 2.0f;
  371. path.startNewSubPath (1.0f, centre + w);
  372. path.lineTo (1.0f, centre - w);
  373. path.lineTo (headSize, centre - lineThickness / 2.0f);
  374. path.lineTo ((float) getWidth(), centre - lineThickness / 2.0f);
  375. path.lineTo ((float) getWidth(), centre + lineThickness / 2.0f);
  376. path.lineTo (headSize, centre + lineThickness / 2.0f);
  377. path.closeSubPath();
  378. }
  379. updateLabel();
  380. }
  381. void mouseDown (const MouseEvent& e)
  382. {
  383. mouseDownPos = e.getMouseDownPosition();
  384. toFront (false);
  385. updateLabel();
  386. canvas->getSelection().selectOnly (getMarkerList().getId (marker));
  387. if (e.mods.isPopupMenu())
  388. {
  389. isDragging = false;
  390. }
  391. else
  392. {
  393. isDragging = true;
  394. canvas->getUndoManager().beginNewTransaction();
  395. }
  396. }
  397. void mouseDrag (const MouseEvent& e)
  398. {
  399. if (isDragging)
  400. {
  401. autoScrollForMouseEvent (e.getEventRelativeTo (canvas), isX, ! isX);
  402. canvas->getUndoManager().undoCurrentTransactionOnly();
  403. Rectangle<int> axis;
  404. if (isX)
  405. axis.setBounds (0, 0, getParentWidth(), headSize);
  406. else
  407. axis.setBounds (0, 0, headSize, getParentHeight());
  408. if (axis.expanded (isX ? 500 : 30, isX ? 30 : 500).contains (e.x, e.y))
  409. {
  410. RelativeCoordinate coord (getMarkerList().getCoordinate (marker));
  411. MouseEvent rulerEvent (e.getEventRelativeTo (&ruler));
  412. int rulerPos = isX ? (rulerEvent.x + getWidth() / 2 - mouseDownPos.getX())
  413. : (rulerEvent.y + getHeight() / 2 - mouseDownPos.getY());
  414. coord.moveToAbsolute (canvas->limitMarkerPosition (ruler.xToPosition (rulerPos)), &getMarkerList());
  415. getMarkerList().setCoordinate (marker, coord);
  416. canvas->handleUpdateNowIfNeeded();
  417. }
  418. else
  419. {
  420. getMarkerList().deleteMarker (marker);
  421. }
  422. }
  423. }
  424. void mouseUp (const MouseEvent& e)
  425. {
  426. canvas->getUndoManager().beginNewTransaction();
  427. updateLabel();
  428. }
  429. void mouseEnter (const MouseEvent& e)
  430. {
  431. updateLabel();
  432. repaint();
  433. }
  434. void mouseExit (const MouseEvent& e)
  435. {
  436. updateLabel();
  437. repaint();
  438. }
  439. void updateSelectionState()
  440. {
  441. bool nowSelected = canvas->getSelection().isSelected (getMarkerList().getId (marker));
  442. if (isSelected != nowSelected)
  443. {
  444. isSelected = nowSelected;
  445. repaint();
  446. }
  447. }
  448. void changeListenerCallback (void*)
  449. {
  450. updateSelectionState();
  451. }
  452. MarkerListBase& getMarkerList() { return canvas->getMarkerList (isX); }
  453. ValueTree marker;
  454. const bool isX;
  455. private:
  456. RulerComponent& ruler;
  457. EditorCanvasBase* canvas;
  458. const int headSize;
  459. Path path;
  460. bool isSpecial, isDragging, isSelected;
  461. FloatingLabelComponent label;
  462. String labelText;
  463. Point<int> mouseDownPos;
  464. };
  465. Range<double> range;
  466. private:
  467. const bool isX;
  468. OwnedArray <MarkerComponent> markers;
  469. EditorCanvasBase* canvas;
  470. };
  471. //==============================================================================
  472. class CanvasViewport : public Viewport
  473. {
  474. public:
  475. CanvasViewport()
  476. : canvas (0)
  477. {
  478. setOpaque (true);
  479. }
  480. ~CanvasViewport()
  481. {
  482. }
  483. void paint (Graphics& g)
  484. {
  485. if (canvas == 0)
  486. canvas = dynamic_cast <EditorCanvasBase*> (getViewedComponent());
  487. if (canvas != 0)
  488. canvas->fillBackground (g);
  489. }
  490. void paintOverChildren (Graphics& g)
  491. {
  492. drawRecessedShadows (g, getMaximumVisibleWidth(), getMaximumVisibleHeight(), 14);
  493. }
  494. void visibleAreaChanged (int, int , int, int)
  495. {
  496. EditorPanelBase* p = dynamic_cast <EditorPanelBase*> (getParentComponent());
  497. if (p != 0)
  498. p->updateRulers();
  499. }
  500. private:
  501. EditorCanvasBase* canvas;
  502. };
  503. //==============================================================================
  504. class TooltipBar : public Component,
  505. public Timer
  506. {
  507. public:
  508. TooltipBar()
  509. : lastComp (0)
  510. {
  511. label.setColour (Label::textColourId, Colour::greyLevel (0.15f));
  512. label.setColour (Label::backgroundColourId, Colour::greyLevel (0.75f));
  513. label.setFont (Font (13.0f));
  514. label.setJustificationType (Justification::centredLeft);
  515. addAndMakeVisible (&label);
  516. }
  517. ~TooltipBar()
  518. {
  519. }
  520. void timerCallback()
  521. {
  522. Component* const newComp = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse();
  523. if (newComp != lastComp)
  524. {
  525. lastComp = newComp;
  526. label.setText (findTip (newComp), false);
  527. }
  528. }
  529. void resized()
  530. {
  531. label.setBounds (getLocalBounds());
  532. }
  533. void visibilityChanged()
  534. {
  535. if (isVisible())
  536. startTimer (150);
  537. else
  538. stopTimer();
  539. }
  540. private:
  541. Label label;
  542. Component* lastComp;
  543. const String findTip (Component* c)
  544. {
  545. while (c != 0 && c != this)
  546. {
  547. TooltipClient* const tc = dynamic_cast <TooltipClient*> (c);
  548. if (tc != 0)
  549. {
  550. const String tip (tc->getTooltip());
  551. if (tip.isNotEmpty())
  552. return tip;
  553. }
  554. c = c->getParentComponent();
  555. }
  556. return String::empty;
  557. }
  558. TooltipBar (const TooltipBar&);
  559. TooltipBar& operator= (const TooltipBar&);
  560. };
  561. //==============================================================================
  562. Toolbar toolbar;
  563. CanvasViewport viewport;
  564. RulerComponent rulerX, rulerY;
  565. ScopedPointer<InfoPanel> infoPanel;
  566. TreeView tree;
  567. TooltipBar tooltipBar;
  568. EditorCanvasBase* canvas;
  569. bool markersVisible, snappingEnabled;
  570. Image background;
  571. };
  572. #endif // __JUCER_EDITORPANEL_H_8E192A99__