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.

1195 lines
45KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "../../jucer_Headers.h"
  18. #include "../../Application/jucer_AppearanceSettings.h"
  19. #include "../../Application/jucer_Application.h"
  20. #include "jucer_JucerDocumentEditor.h"
  21. #include "jucer_TestComponent.h"
  22. #include "../jucer_ObjectTypes.h"
  23. #include "jucer_ComponentLayoutPanel.h"
  24. #include "jucer_PaintRoutinePanel.h"
  25. #include "jucer_ResourceEditorPanel.h"
  26. #include "../properties/jucer_ComponentTextProperty.h"
  27. #include "../properties/jucer_ComponentChoiceProperty.h"
  28. #include "../ui/jucer_JucerCommandIDs.h"
  29. //==============================================================================
  30. class ExtraMethodsList : public PropertyComponent,
  31. public ListBoxModel,
  32. public ChangeListener
  33. {
  34. public:
  35. ExtraMethodsList (JucerDocument& doc)
  36. : PropertyComponent ("extra callbacks", 250),
  37. document (doc)
  38. {
  39. addAndMakeVisible (listBox = new ListBox (String::empty, this));
  40. listBox->setRowHeight (22);
  41. document.addChangeListener (this);
  42. }
  43. ~ExtraMethodsList()
  44. {
  45. document.removeChangeListener (this);
  46. }
  47. int getNumRows()
  48. {
  49. return methods.size();
  50. }
  51. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected)
  52. {
  53. if (row < 0 || row >= getNumRows())
  54. return;
  55. if (rowIsSelected)
  56. g.fillAll (findColour (TextEditor::highlightColourId));
  57. g.setColour (Colours::black);
  58. g.setFont (height * 0.6f);
  59. g.drawText (returnValues [row] + " " + baseClasses [row] + "::" + methods [row],
  60. 30, 0, width - 32, height,
  61. Justification::centredLeft, true);
  62. getLookAndFeel().drawTickBox (g, *this, 6, 2, 18, 18, document.isOptionalMethodEnabled (methods [row]), true, false, false);
  63. }
  64. void listBoxItemClicked (int row, const MouseEvent& e)
  65. {
  66. if (row < 0 || row >= getNumRows())
  67. return;
  68. if (e.x < 30)
  69. document.setOptionalMethodEnabled (methods [row],
  70. ! document.isOptionalMethodEnabled (methods [row]));
  71. }
  72. void paint (Graphics& g)
  73. {
  74. g.fillAll (Colours::white);
  75. }
  76. void resized()
  77. {
  78. listBox->setBounds (getLocalBounds());
  79. }
  80. void refresh()
  81. {
  82. baseClasses.clear();
  83. returnValues.clear();
  84. methods.clear();
  85. initialContents.clear();
  86. document.getOptionalMethods (baseClasses, returnValues, methods, initialContents);
  87. listBox->updateContent();
  88. listBox->repaint();
  89. }
  90. void changeListenerCallback (ChangeBroadcaster*)
  91. {
  92. refresh();
  93. }
  94. private:
  95. JucerDocument& document;
  96. ScopedPointer<ListBox> listBox;
  97. StringArray baseClasses, returnValues, methods, initialContents;
  98. };
  99. //==============================================================================
  100. class ClassPropertiesPanel : public Component,
  101. private ChangeListener
  102. {
  103. public:
  104. ClassPropertiesPanel (JucerDocument& doc)
  105. : document (doc)
  106. {
  107. addAndMakeVisible (&panel1);
  108. addAndMakeVisible (&panel2);
  109. Array <PropertyComponent*> props;
  110. props.add (new ComponentClassNameProperty (doc));
  111. props.add (new TemplateFileProperty (doc));
  112. props.add (new ComponentCompNameProperty (doc));
  113. props.add (new ComponentParentClassesProperty (doc));
  114. props.add (new ComponentConstructorParamsProperty (doc));
  115. props.add (new ComponentInitialisersProperty (doc));
  116. props.add (new ComponentInitialSizeProperty (doc, true));
  117. props.add (new ComponentInitialSizeProperty (doc, false));
  118. props.add (new FixedSizeProperty (doc));
  119. panel1.addSection ("General class settings", props);
  120. Array <PropertyComponent*> props2;
  121. props2.add (new ExtraMethodsList (doc));
  122. panel2.addSection ("Extra callback methods to generate", props2);
  123. doc.addExtraClassProperties (panel1);
  124. doc.addChangeListener (this);
  125. }
  126. ~ClassPropertiesPanel()
  127. {
  128. document.removeChangeListener (this);
  129. }
  130. void resized()
  131. {
  132. int pw = jmin (getWidth() / 2 - 20, 350);
  133. panel1.setBounds (10, 6, pw, getHeight() - 12);
  134. panel2.setBounds (panel1.getRight() + 20, panel1.getY(), pw, panel1.getHeight());
  135. }
  136. void changeListenerCallback (ChangeBroadcaster*)
  137. {
  138. panel1.refreshAll();
  139. panel2.refreshAll();
  140. }
  141. private:
  142. JucerDocument& document;
  143. PropertyPanel panel1, panel2;
  144. //==============================================================================
  145. class ComponentClassNameProperty : public ComponentTextProperty <Component>
  146. {
  147. public:
  148. ComponentClassNameProperty (JucerDocument& doc)
  149. : ComponentTextProperty <Component> ("Class name", 128, false, 0, doc)
  150. {}
  151. void setText (const String& newText) { document.setClassName (newText); }
  152. String getText() const { return document.getClassName(); }
  153. };
  154. //==============================================================================
  155. class ComponentCompNameProperty : public ComponentTextProperty <Component>
  156. {
  157. public:
  158. ComponentCompNameProperty (JucerDocument& doc)
  159. : ComponentTextProperty <Component> ("Component name", 200, false, 0, doc)
  160. {}
  161. void setText (const String& newText) { document.setComponentName (newText); }
  162. String getText() const { return document.getComponentName(); }
  163. };
  164. //==============================================================================
  165. class ComponentParentClassesProperty : public ComponentTextProperty <Component>
  166. {
  167. public:
  168. ComponentParentClassesProperty (JucerDocument& doc)
  169. : ComponentTextProperty <Component> ("Parent classes", 512, false, 0, doc)
  170. {}
  171. void setText (const String& newText) { document.setParentClasses (newText); }
  172. String getText() const { return document.getParentClassString(); }
  173. };
  174. //==============================================================================
  175. class ComponentConstructorParamsProperty : public ComponentTextProperty <Component>
  176. {
  177. public:
  178. ComponentConstructorParamsProperty (JucerDocument& doc)
  179. : ComponentTextProperty <Component> ("Constructor params", 2048, false, 0, doc)
  180. {}
  181. void setText (const String& newText) { document.setConstructorParams (newText); }
  182. String getText() const { return document.getConstructorParams(); }
  183. };
  184. //==============================================================================
  185. class ComponentInitialisersProperty : public ComponentTextProperty <Component>
  186. {
  187. public:
  188. ComponentInitialisersProperty (JucerDocument& doc)
  189. : ComponentTextProperty <Component> ("Member intialisers", 2048, true, 0, doc)
  190. {
  191. preferredHeight = 24 * 3;
  192. }
  193. void setText (const String& newText) { document.setVariableInitialisers (newText); }
  194. String getText() const { return document.getVariableInitialisers(); }
  195. };
  196. //==============================================================================
  197. class ComponentInitialSizeProperty : public ComponentTextProperty <Component>
  198. {
  199. public:
  200. ComponentInitialSizeProperty (JucerDocument& doc, const bool isWidth_)
  201. : ComponentTextProperty <Component> (isWidth_ ? "Initial width"
  202. : "Initial height",
  203. 10, false, 0, doc),
  204. isWidth (isWidth_)
  205. {}
  206. void setText (const String& newText)
  207. {
  208. if (isWidth)
  209. document.setInitialSize (newText.getIntValue(), document.getInitialHeight());
  210. else
  211. document.setInitialSize (document.getInitialWidth(), newText.getIntValue());
  212. }
  213. String getText() const
  214. {
  215. return String (isWidth ? document.getInitialWidth()
  216. : document.getInitialHeight());
  217. }
  218. private:
  219. const bool isWidth;
  220. };
  221. //==============================================================================
  222. class FixedSizeProperty : public ComponentChoiceProperty <Component>
  223. {
  224. public:
  225. FixedSizeProperty (JucerDocument& doc)
  226. : ComponentChoiceProperty <Component> ("Fixed size", 0, doc)
  227. {
  228. choices.add ("Resize component to fit workspace");
  229. choices.add ("Keep component size fixed");
  230. }
  231. void setIndex (int newIndex) { document.setFixedSize (newIndex != 0); }
  232. int getIndex() const { return document.isFixedSize() ? 1 : 0; }
  233. };
  234. //==============================================================================
  235. class TemplateFileProperty : public ComponentTextProperty <Component>
  236. {
  237. public:
  238. TemplateFileProperty (JucerDocument& doc)
  239. : ComponentTextProperty <Component> ("Template file", 2048, false, 0, doc)
  240. {}
  241. void setText (const String& newText) { document.setTemplateFile (newText); }
  242. String getText() const { return document.getTemplateFile(); }
  243. };
  244. };
  245. static const Colour tabColour (Colour (0xff888888));
  246. //==============================================================================
  247. JucerDocumentEditor::JucerDocumentEditor (JucerDocument* const doc)
  248. : document (doc),
  249. tabbedComponent (TabbedButtonBar::TabsAtTop),
  250. compLayoutPanel (0),
  251. lastViewportX (0),
  252. lastViewportY (0),
  253. currentZoomLevel (1.0)
  254. {
  255. setOpaque (true);
  256. if (document != nullptr)
  257. {
  258. setSize (document->getInitialWidth(),
  259. document->getInitialHeight());
  260. addAndMakeVisible (&tabbedComponent);
  261. tabbedComponent.setOutline (0);
  262. tabbedComponent.addTab ("Class", tabColour, new ClassPropertiesPanel (*document), true);
  263. if (document->getComponentLayout() != nullptr)
  264. tabbedComponent.addTab ("Subcomponents", tabColour,
  265. compLayoutPanel = new ComponentLayoutPanel (*document, *document->getComponentLayout()), true);
  266. tabbedComponent.addTab ("Resources", tabColour, new ResourceEditorPanel (*document), true);
  267. SourceCodeEditor* codeEditor = new SourceCodeEditor (&document->getCppDocument(),
  268. new CppCodeEditorComponent (document->getCppFile(),
  269. document->getCppDocument().getCodeDocument()));
  270. tabbedComponent.addTab ("Code", tabColour, codeEditor, true);
  271. updateTabs();
  272. tabbedComponent.setCurrentTabIndex (1);
  273. document->addChangeListener (this);
  274. resized();
  275. refreshPropertiesPanel();
  276. changeListenerCallback (nullptr);
  277. }
  278. }
  279. JucerDocumentEditor::~JucerDocumentEditor()
  280. {
  281. tabbedComponent.clearTabs();
  282. }
  283. void JucerDocumentEditor::refreshPropertiesPanel() const
  284. {
  285. for (int i = tabbedComponent.getNumTabs(); --i >= 0;)
  286. {
  287. if (ComponentLayoutPanel* layoutPanel = dynamic_cast <ComponentLayoutPanel*> (tabbedComponent.getTabContentComponent (i)))
  288. {
  289. if (layoutPanel->isVisible())
  290. layoutPanel->updatePropertiesList();
  291. }
  292. else
  293. {
  294. if (PaintRoutinePanel* pr = dynamic_cast <PaintRoutinePanel*> (tabbedComponent.getTabContentComponent (i)))
  295. if (pr->isVisible())
  296. pr->updatePropertiesList();
  297. }
  298. }
  299. }
  300. void JucerDocumentEditor::updateTabs()
  301. {
  302. const StringArray paintRoutineNames (document->getPaintRoutineNames());
  303. for (int i = tabbedComponent.getNumTabs(); --i >= 0;)
  304. {
  305. if (dynamic_cast <PaintRoutinePanel*> (tabbedComponent.getTabContentComponent (i)) != 0
  306. && ! paintRoutineNames.contains (tabbedComponent.getTabNames() [i]))
  307. {
  308. tabbedComponent.removeTab (i);
  309. }
  310. }
  311. for (int i = 0; i < document->getNumPaintRoutines(); ++i)
  312. {
  313. if (! tabbedComponent.getTabNames().contains (paintRoutineNames [i]))
  314. {
  315. int index, numPaintRoutinesSeen = 0;
  316. for (index = 1; index < tabbedComponent.getNumTabs(); ++index)
  317. {
  318. if (dynamic_cast <PaintRoutinePanel*> (tabbedComponent.getTabContentComponent (index)) != nullptr)
  319. {
  320. if (++numPaintRoutinesSeen == i)
  321. {
  322. ++index;
  323. break;
  324. }
  325. }
  326. }
  327. if (numPaintRoutinesSeen == 0)
  328. index = document->getComponentLayout() != nullptr ? 2 : 1;
  329. tabbedComponent.addTab (paintRoutineNames[i], tabColour,
  330. new PaintRoutinePanel (*document,
  331. *document->getPaintRoutine (i),
  332. this), true, index);
  333. }
  334. }
  335. }
  336. //==============================================================================
  337. void JucerDocumentEditor::paint (Graphics& g)
  338. {
  339. IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
  340. }
  341. void JucerDocumentEditor::resized()
  342. {
  343. tabbedComponent.setBounds (getLocalBounds().reduced (4, 2));
  344. }
  345. void JucerDocumentEditor::changeListenerCallback (ChangeBroadcaster*)
  346. {
  347. setName (document->getClassName());
  348. updateTabs();
  349. }
  350. //==============================================================================
  351. ApplicationCommandTarget* JucerDocumentEditor::getNextCommandTarget()
  352. {
  353. return findFirstTargetParentComponent();
  354. }
  355. ComponentLayout* JucerDocumentEditor::getCurrentLayout() const
  356. {
  357. if (ComponentLayoutPanel* panel = dynamic_cast <ComponentLayoutPanel*> (tabbedComponent.getCurrentContentComponent()))
  358. return &(panel->getLayout());
  359. return nullptr;
  360. }
  361. PaintRoutine* JucerDocumentEditor::getCurrentPaintRoutine() const
  362. {
  363. if (PaintRoutinePanel* panel = dynamic_cast <PaintRoutinePanel*> (tabbedComponent.getCurrentContentComponent()))
  364. return &(panel->getPaintRoutine());
  365. return nullptr;
  366. }
  367. void JucerDocumentEditor::showLayout()
  368. {
  369. if (getCurrentLayout() == nullptr)
  370. {
  371. for (int i = 0; i < tabbedComponent.getNumTabs(); ++i)
  372. {
  373. if (dynamic_cast <ComponentLayoutPanel*> (tabbedComponent.getTabContentComponent (i)) != nullptr)
  374. {
  375. tabbedComponent.setCurrentTabIndex (i);
  376. break;
  377. }
  378. }
  379. }
  380. }
  381. void JucerDocumentEditor::showGraphics (PaintRoutine* routine)
  382. {
  383. if (getCurrentPaintRoutine() != routine || routine == 0)
  384. {
  385. for (int i = 0; i < tabbedComponent.getNumTabs(); ++i)
  386. {
  387. if (PaintRoutinePanel* pr = dynamic_cast <PaintRoutinePanel*> (tabbedComponent.getTabContentComponent (i)))
  388. {
  389. if (routine == &(pr->getPaintRoutine()) || routine == nullptr)
  390. {
  391. tabbedComponent.setCurrentTabIndex (i);
  392. break;
  393. }
  394. }
  395. }
  396. }
  397. }
  398. //==============================================================================
  399. void JucerDocumentEditor::setViewportToLastPos (Viewport* vp, EditingPanelBase& editor)
  400. {
  401. vp->setViewPosition (lastViewportX, lastViewportY);
  402. editor.setZoom (currentZoomLevel);
  403. }
  404. void JucerDocumentEditor::storeLastViewportPos (Viewport* vp, EditingPanelBase& editor)
  405. {
  406. lastViewportX = vp->getViewPositionX();
  407. lastViewportY = vp->getViewPositionY();
  408. currentZoomLevel = editor.getZoom();
  409. }
  410. void JucerDocumentEditor::setZoom (double scale)
  411. {
  412. scale = jlimit (1.0 / 4.0, 32.0, scale);
  413. if (EditingPanelBase* panel = dynamic_cast <EditingPanelBase*> (tabbedComponent.getCurrentContentComponent()))
  414. panel->setZoom (scale);
  415. }
  416. double JucerDocumentEditor::getZoom() const
  417. {
  418. if (EditingPanelBase* panel = dynamic_cast <EditingPanelBase*> (tabbedComponent.getCurrentContentComponent()))
  419. return panel->getZoom();
  420. return 1.0;
  421. }
  422. static double snapToIntegerZoom (double zoom)
  423. {
  424. if (zoom >= 1.0)
  425. return (double) (int) (zoom + 0.5);
  426. return 1.0 / (int) (1.0 / zoom + 0.5);
  427. }
  428. void JucerDocumentEditor::addElement (const int index)
  429. {
  430. if (PaintRoutinePanel* const panel = dynamic_cast <PaintRoutinePanel*> (tabbedComponent.getCurrentContentComponent()))
  431. {
  432. PaintRoutine* const currentPaintRoutine = & (panel->getPaintRoutine());
  433. const Rectangle<int> area (panel->getComponentArea());
  434. document->beginTransaction();
  435. PaintElement* e = ObjectTypes::createNewElement (index, currentPaintRoutine);
  436. e->setInitialBounds (area.getWidth(), area.getHeight());
  437. e = currentPaintRoutine->addNewElement (e, -1, true);
  438. if (e != nullptr)
  439. {
  440. const int randomness = jmin (80, area.getWidth() / 2, area.getHeight() / 2);
  441. int x = area.getX() + area.getWidth() / 2 + Random::getSystemRandom().nextInt (randomness) - randomness / 2;
  442. int y = area.getY() + area.getHeight() / 2 + Random::getSystemRandom().nextInt (randomness) - randomness / 2;
  443. x = document->snapPosition (x);
  444. y = document->snapPosition (y);
  445. panel->xyToTargetXY (x, y);
  446. Rectangle<int> r (e->getCurrentBounds (area));
  447. r.setPosition (x, y);
  448. e->setCurrentBounds (r, area, true);
  449. currentPaintRoutine->getSelectedElements().selectOnly (e);
  450. }
  451. document->beginTransaction();
  452. }
  453. }
  454. void JucerDocumentEditor::addComponent (const int index)
  455. {
  456. showLayout();
  457. if (ComponentLayoutPanel* const panel = dynamic_cast <ComponentLayoutPanel*> (tabbedComponent.getCurrentContentComponent()))
  458. {
  459. const Rectangle<int> area (panel->getComponentArea());
  460. document->beginTransaction ("Add new " + ObjectTypes::componentTypeHandlers [index]->getTypeName());
  461. const int randomness = jmin (80, area.getWidth() / 2, area.getHeight() / 2);
  462. int x = area.getWidth() / 2 + Random::getSystemRandom().nextInt (randomness) - randomness / 2;
  463. int y = area.getHeight() / 2 + Random::getSystemRandom().nextInt (randomness) - randomness / 2;
  464. x = document->snapPosition (x);
  465. y = document->snapPosition (y);
  466. panel->xyToTargetXY (x, y);
  467. if (Component* newOne = panel->getLayout().addNewComponent (ObjectTypes::componentTypeHandlers [index], x, y))
  468. panel->getLayout().getSelectedSet().selectOnly (newOne);
  469. document->beginTransaction();
  470. }
  471. }
  472. //==============================================================================
  473. bool JucerDocumentEditor::isSomethingSelected() const
  474. {
  475. if (ComponentLayout* layout = getCurrentLayout())
  476. return layout->getSelectedSet().getNumSelected() > 0;
  477. if (PaintRoutine* routine = getCurrentPaintRoutine())
  478. return routine->getSelectedElements().getNumSelected() > 0;
  479. return false;
  480. }
  481. //==============================================================================
  482. void JucerDocumentEditor::getAllCommands (Array <CommandID>& commands)
  483. {
  484. const CommandID ids[] =
  485. {
  486. JucerCommandIDs::test,
  487. JucerCommandIDs::toFront,
  488. JucerCommandIDs::toBack,
  489. JucerCommandIDs::group,
  490. JucerCommandIDs::ungroup,
  491. JucerCommandIDs::bringBackLostItems,
  492. JucerCommandIDs::enableSnapToGrid,
  493. JucerCommandIDs::showGrid,
  494. JucerCommandIDs::editCompLayout,
  495. JucerCommandIDs::editCompGraphics,
  496. JucerCommandIDs::zoomIn,
  497. JucerCommandIDs::zoomOut,
  498. JucerCommandIDs::zoomNormal,
  499. JucerCommandIDs::spaceBarDrag,
  500. JucerCommandIDs::compOverlay0,
  501. JucerCommandIDs::compOverlay33,
  502. JucerCommandIDs::compOverlay66,
  503. JucerCommandIDs::compOverlay100,
  504. StandardApplicationCommandIDs::undo,
  505. StandardApplicationCommandIDs::redo,
  506. StandardApplicationCommandIDs::cut,
  507. StandardApplicationCommandIDs::copy,
  508. StandardApplicationCommandIDs::paste,
  509. StandardApplicationCommandIDs::del,
  510. StandardApplicationCommandIDs::selectAll,
  511. StandardApplicationCommandIDs::deselectAll
  512. };
  513. commands.addArray (ids, numElementsInArray (ids));
  514. for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
  515. commands.add (JucerCommandIDs::newComponentBase + i);
  516. for (int i = 0; i < ObjectTypes::numElementTypes; ++i)
  517. commands.add (JucerCommandIDs::newElementBase + i);
  518. }
  519. void JucerDocumentEditor::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  520. {
  521. ComponentLayout* const currentLayout = getCurrentLayout();
  522. PaintRoutine* const currentPaintRoutine = getCurrentPaintRoutine();
  523. const int cmd = ModifierKeys::commandModifier;
  524. const int shift = ModifierKeys::shiftModifier;
  525. if (commandID >= JucerCommandIDs::newComponentBase
  526. && commandID < JucerCommandIDs::newComponentBase + ObjectTypes::numComponentTypes)
  527. {
  528. const int index = commandID - JucerCommandIDs::newComponentBase;
  529. result.setInfo ("New " + ObjectTypes::componentTypeHandlers [index]->getTypeName(),
  530. "Creates a new " + ObjectTypes::componentTypeHandlers [index]->getTypeName(),
  531. CommandCategories::editing, 0);
  532. return;
  533. }
  534. if (commandID >= JucerCommandIDs::newElementBase
  535. && commandID < JucerCommandIDs::newElementBase + ObjectTypes::numElementTypes)
  536. {
  537. const int index = commandID - JucerCommandIDs::newElementBase;
  538. result.setInfo (String ("New ") + ObjectTypes::elementTypeNames [index],
  539. String ("Adds a new ") + ObjectTypes::elementTypeNames [index],
  540. CommandCategories::editing, 0);
  541. result.setActive (currentPaintRoutine != nullptr);
  542. return;
  543. }
  544. switch (commandID)
  545. {
  546. case JucerCommandIDs::toFront:
  547. result.setInfo (TRANS("Bring to front"), TRANS("Brings the currently selected component to the front."), CommandCategories::editing, 0);
  548. result.setActive (isSomethingSelected());
  549. result.defaultKeypresses.add (KeyPress ('f', cmd, 0));
  550. break;
  551. case JucerCommandIDs::toBack:
  552. result.setInfo (TRANS("Send to back"), TRANS("Sends the currently selected component to the back."), CommandCategories::editing, 0);
  553. result.setActive (isSomethingSelected());
  554. result.defaultKeypresses.add (KeyPress ('b', cmd, 0));
  555. break;
  556. case JucerCommandIDs::group:
  557. result.setInfo (TRANS("Group selected items"), TRANS("Turns the currently selected elements into a single group object."), CommandCategories::editing, 0);
  558. result.setActive (currentPaintRoutine != nullptr && currentPaintRoutine->getSelectedElements().getNumSelected() > 1);
  559. result.defaultKeypresses.add (KeyPress ('k', cmd, 0));
  560. break;
  561. case JucerCommandIDs::ungroup:
  562. result.setInfo (TRANS("Ungroup selected items"), TRANS("Turns the currently selected elements into a single group object."), CommandCategories::editing, 0);
  563. result.setActive (currentPaintRoutine != nullptr
  564. && currentPaintRoutine->getSelectedElements().getNumSelected() == 1
  565. && currentPaintRoutine->getSelectedElements().getSelectedItem (0)->getTypeName() == "Group");
  566. result.defaultKeypresses.add (KeyPress ('k', cmd | shift, 0));
  567. break;
  568. case JucerCommandIDs::test:
  569. result.setInfo (TRANS("Test component..."), TRANS("Runs the current component interactively."), CommandCategories::view, 0);
  570. result.defaultKeypresses.add (KeyPress ('t', cmd, 0));
  571. break;
  572. case JucerCommandIDs::enableSnapToGrid:
  573. result.setInfo (TRANS("Enable snap-to-grid"), TRANS("Toggles whether components' positions are aligned to a grid."), CommandCategories::view, 0);
  574. result.setTicked (document != nullptr && document->isSnapActive (false));
  575. result.defaultKeypresses.add (KeyPress ('g', cmd, 0));
  576. break;
  577. case JucerCommandIDs::showGrid:
  578. result.setInfo (TRANS("Show snap-to-grid"), TRANS("Toggles whether the snapping grid is displayed on-screen."), CommandCategories::view, 0);
  579. result.setTicked (document != nullptr && document->isSnapShown());
  580. result.defaultKeypresses.add (KeyPress ('g', cmd | shift, 0));
  581. break;
  582. case JucerCommandIDs::editCompLayout:
  583. result.setInfo (TRANS("Edit sub-component layout"), TRANS("Switches to the sub-component editor view."), CommandCategories::view, 0);
  584. result.setTicked (currentLayout != nullptr);
  585. result.defaultKeypresses.add (KeyPress ('n', cmd, 0));
  586. break;
  587. case JucerCommandIDs::editCompGraphics:
  588. result.setInfo (TRANS("Edit background graphics"), TRANS("Switches to the background graphics editor view."), CommandCategories::view, 0);
  589. result.setTicked (currentPaintRoutine != nullptr);
  590. result.defaultKeypresses.add (KeyPress ('m', cmd, 0));
  591. break;
  592. case JucerCommandIDs::bringBackLostItems:
  593. result.setInfo (TRANS("Retrieve offscreen items"), TRANS("Moves any items that are lost beyond the edges of the screen back to the centre."), CommandCategories::editing, 0);
  594. result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
  595. result.defaultKeypresses.add (KeyPress ('m', cmd, 0));
  596. break;
  597. case JucerCommandIDs::zoomIn:
  598. result.setInfo (TRANS("Zoom in"), TRANS("Zooms in on the current component."), CommandCategories::editing, 0);
  599. result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
  600. result.defaultKeypresses.add (KeyPress (']', cmd, 0));
  601. break;
  602. case JucerCommandIDs::zoomOut:
  603. result.setInfo (TRANS("Zoom out"), TRANS("Zooms out on the current component."), CommandCategories::editing, 0);
  604. result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
  605. result.defaultKeypresses.add (KeyPress ('[', cmd, 0));
  606. break;
  607. case JucerCommandIDs::zoomNormal:
  608. result.setInfo (TRANS("Zoom to 100%"), TRANS("Restores the zoom level to normal."), CommandCategories::editing, 0);
  609. result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
  610. result.defaultKeypresses.add (KeyPress ('1', cmd, 0));
  611. break;
  612. case JucerCommandIDs::spaceBarDrag:
  613. result.setInfo (TRANS("Scroll while dragging mouse"), TRANS("When held down, this key lets you scroll around by dragging with the mouse."),
  614. CommandCategories::view, ApplicationCommandInfo::wantsKeyUpDownCallbacks);
  615. result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
  616. result.defaultKeypresses.add (KeyPress (KeyPress::spaceKey, 0, 0));
  617. break;
  618. case JucerCommandIDs::compOverlay0:
  619. case JucerCommandIDs::compOverlay33:
  620. case JucerCommandIDs::compOverlay66:
  621. case JucerCommandIDs::compOverlay100:
  622. {
  623. int amount = 0, num = 0;
  624. if (commandID == JucerCommandIDs::compOverlay33)
  625. {
  626. amount = 33;
  627. num = 1;
  628. }
  629. else if (commandID == JucerCommandIDs::compOverlay66)
  630. {
  631. amount = 66;
  632. num = 2;
  633. }
  634. else if (commandID == JucerCommandIDs::compOverlay100)
  635. {
  636. amount = 100;
  637. num = 3;
  638. }
  639. result.defaultKeypresses.add (KeyPress ('2' + num, cmd, 0));
  640. int currentAmount = 0;
  641. if (document != nullptr && document->getComponentOverlayOpacity() > 0.9f)
  642. currentAmount = 100;
  643. else if (document != nullptr && document->getComponentOverlayOpacity() > 0.6f)
  644. currentAmount = 66;
  645. else if (document != nullptr && document->getComponentOverlayOpacity() > 0.3f)
  646. currentAmount = 33;
  647. result.setInfo (commandID == JucerCommandIDs::compOverlay0
  648. ? TRANS("No component overlay")
  649. : TRANS("Overlay with opacity of 123%").replace ("123", String (amount)),
  650. TRANS("Changes the opacity of the components that are shown over the top of the graphics editor."),
  651. CommandCategories::view, 0);
  652. result.setActive (currentPaintRoutine != nullptr && document->getComponentLayout() != nullptr);
  653. result.setTicked (amount == currentAmount);
  654. }
  655. break;
  656. case StandardApplicationCommandIDs::undo:
  657. result.setInfo (TRANS ("Undo"), TRANS ("Undo"), "Editing", 0);
  658. result.setActive (document != nullptr && document->getUndoManager().canUndo());
  659. result.defaultKeypresses.add (KeyPress ('z', cmd, 0));
  660. break;
  661. case StandardApplicationCommandIDs::redo:
  662. result.setInfo (TRANS ("Redo"), TRANS ("Redo"), "Editing", 0);
  663. result.setActive (document != nullptr && document->getUndoManager().canRedo());
  664. result.defaultKeypresses.add (KeyPress ('z', cmd | shift, 0));
  665. break;
  666. case StandardApplicationCommandIDs::cut:
  667. result.setInfo (TRANS ("Cut"), String::empty, "Editing", 0);
  668. result.setActive (isSomethingSelected());
  669. result.defaultKeypresses.add (KeyPress ('x', cmd, 0));
  670. break;
  671. case StandardApplicationCommandIDs::copy:
  672. result.setInfo (TRANS ("Copy"), String::empty, "Editing", 0);
  673. result.setActive (isSomethingSelected());
  674. result.defaultKeypresses.add (KeyPress ('c', cmd, 0));
  675. break;
  676. case StandardApplicationCommandIDs::paste:
  677. {
  678. result.setInfo (TRANS ("Paste"), String::empty, "Editing", 0);
  679. result.defaultKeypresses.add (KeyPress ('v', cmd, 0));
  680. bool canPaste = false;
  681. ScopedPointer<XmlElement> doc (XmlDocument::parse (SystemClipboard::getTextFromClipboard()));
  682. if (doc != nullptr)
  683. {
  684. if (doc->hasTagName (ComponentLayout::clipboardXmlTag))
  685. canPaste = (currentLayout != nullptr);
  686. else if (doc->hasTagName (PaintRoutine::clipboardXmlTag))
  687. canPaste = (currentPaintRoutine != nullptr);
  688. }
  689. result.setActive (canPaste);
  690. }
  691. break;
  692. case StandardApplicationCommandIDs::del:
  693. result.setInfo (TRANS ("Delete"), String::empty, "Editing", 0);
  694. result.setActive (isSomethingSelected());
  695. break;
  696. case StandardApplicationCommandIDs::selectAll:
  697. result.setInfo (TRANS ("Select All"), String::empty, "Editing", 0);
  698. result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
  699. result.defaultKeypresses.add (KeyPress ('a', cmd, 0));
  700. break;
  701. case StandardApplicationCommandIDs::deselectAll:
  702. result.setInfo (TRANS ("Deselect All"), String::empty, "Editing", 0);
  703. result.setActive (currentPaintRoutine != nullptr || currentLayout != nullptr);
  704. result.defaultKeypresses.add (KeyPress ('d', cmd, 0));
  705. break;
  706. default:
  707. break;
  708. }
  709. }
  710. bool JucerDocumentEditor::perform (const InvocationInfo& info)
  711. {
  712. ComponentLayout* const currentLayout = getCurrentLayout();
  713. PaintRoutine* const currentPaintRoutine = getCurrentPaintRoutine();
  714. document->beginTransaction();
  715. if (info.commandID >= JucerCommandIDs::newComponentBase
  716. && info.commandID < JucerCommandIDs::newComponentBase + ObjectTypes::numComponentTypes)
  717. {
  718. addComponent (info.commandID - JucerCommandIDs::newComponentBase);
  719. return true;
  720. }
  721. if (info.commandID >= JucerCommandIDs::newElementBase
  722. && info.commandID < JucerCommandIDs::newElementBase + ObjectTypes::numElementTypes)
  723. {
  724. addElement (info.commandID - JucerCommandIDs::newElementBase);
  725. return true;
  726. }
  727. switch (info.commandID)
  728. {
  729. case StandardApplicationCommandIDs::undo:
  730. document->getUndoManager().undo();
  731. document->dispatchPendingMessages();
  732. break;
  733. case StandardApplicationCommandIDs::redo:
  734. document->getUndoManager().redo();
  735. document->dispatchPendingMessages();
  736. break;
  737. case JucerCommandIDs::test:
  738. TestComponent::showInDialogBox (*document);
  739. break;
  740. case JucerCommandIDs::enableSnapToGrid:
  741. document->setSnappingGrid (document->getSnappingGridSize(),
  742. ! document->isSnapActive (false),
  743. document->isSnapShown());
  744. break;
  745. case JucerCommandIDs::showGrid:
  746. document->setSnappingGrid (document->getSnappingGridSize(),
  747. document->isSnapActive (false),
  748. ! document->isSnapShown());
  749. break;
  750. case JucerCommandIDs::editCompLayout:
  751. showLayout();
  752. break;
  753. case JucerCommandIDs::editCompGraphics:
  754. showGraphics (0);
  755. break;
  756. case JucerCommandIDs::zoomIn: setZoom (snapToIntegerZoom (getZoom() * 2.0)); break;
  757. case JucerCommandIDs::zoomOut: setZoom (snapToIntegerZoom (getZoom() / 2.0)); break;
  758. case JucerCommandIDs::zoomNormal: setZoom (1.0); break;
  759. case JucerCommandIDs::spaceBarDrag:
  760. if (EditingPanelBase* panel = dynamic_cast <EditingPanelBase*> (tabbedComponent.getCurrentContentComponent()))
  761. panel->dragKeyHeldDown (info.isKeyDown);
  762. break;
  763. case JucerCommandIDs::compOverlay0:
  764. case JucerCommandIDs::compOverlay33:
  765. case JucerCommandIDs::compOverlay66:
  766. case JucerCommandIDs::compOverlay100:
  767. {
  768. int amount = 0;
  769. if (info.commandID == JucerCommandIDs::compOverlay33)
  770. amount = 33;
  771. else if (info.commandID == JucerCommandIDs::compOverlay66)
  772. amount = 66;
  773. else if (info.commandID == JucerCommandIDs::compOverlay100)
  774. amount = 100;
  775. document->setComponentOverlayOpacity (amount * 0.01f);
  776. }
  777. break;
  778. case JucerCommandIDs::bringBackLostItems:
  779. if (EditingPanelBase* panel = dynamic_cast <EditingPanelBase*> (tabbedComponent.getCurrentContentComponent()))
  780. {
  781. int w = panel->getComponentArea().getWidth();
  782. int h = panel->getComponentArea().getHeight();
  783. if (currentPaintRoutine != nullptr)
  784. currentPaintRoutine->bringLostItemsBackOnScreen (panel->getComponentArea());
  785. else if (currentLayout != nullptr)
  786. currentLayout->bringLostItemsBackOnScreen (w, h);
  787. }
  788. break;
  789. case JucerCommandIDs::toFront:
  790. if (currentLayout != nullptr)
  791. currentLayout->selectedToFront();
  792. else if (currentPaintRoutine != nullptr)
  793. currentPaintRoutine->selectedToFront();
  794. break;
  795. case JucerCommandIDs::toBack:
  796. if (currentLayout != nullptr)
  797. currentLayout->selectedToBack();
  798. else if (currentPaintRoutine != nullptr)
  799. currentPaintRoutine->selectedToBack();
  800. break;
  801. case JucerCommandIDs::group:
  802. if (currentPaintRoutine != nullptr)
  803. currentPaintRoutine->groupSelected();
  804. break;
  805. case JucerCommandIDs::ungroup:
  806. if (currentPaintRoutine != nullptr)
  807. currentPaintRoutine->ungroupSelected();
  808. break;
  809. case StandardApplicationCommandIDs::cut:
  810. if (currentLayout != nullptr)
  811. {
  812. currentLayout->copySelectedToClipboard();
  813. currentLayout->deleteSelected();
  814. }
  815. else if (currentPaintRoutine != nullptr)
  816. {
  817. currentPaintRoutine->copySelectedToClipboard();
  818. currentPaintRoutine->deleteSelected();
  819. }
  820. break;
  821. case StandardApplicationCommandIDs::copy:
  822. if (currentLayout != nullptr)
  823. currentLayout->copySelectedToClipboard();
  824. else if (currentPaintRoutine != nullptr)
  825. currentPaintRoutine->copySelectedToClipboard();
  826. break;
  827. case StandardApplicationCommandIDs::paste:
  828. {
  829. ScopedPointer<XmlElement> doc (XmlDocument::parse (SystemClipboard::getTextFromClipboard()));
  830. if (doc != nullptr)
  831. {
  832. if (doc->hasTagName (ComponentLayout::clipboardXmlTag))
  833. {
  834. if (currentLayout != nullptr)
  835. currentLayout->paste();
  836. }
  837. else if (doc->hasTagName (PaintRoutine::clipboardXmlTag))
  838. {
  839. if (currentPaintRoutine != nullptr)
  840. currentPaintRoutine->paste();
  841. }
  842. }
  843. }
  844. break;
  845. case StandardApplicationCommandIDs::del:
  846. if (currentLayout != nullptr)
  847. currentLayout->deleteSelected();
  848. else if (currentPaintRoutine != nullptr)
  849. currentPaintRoutine->deleteSelected();
  850. break;
  851. case StandardApplicationCommandIDs::selectAll:
  852. if (currentLayout != nullptr)
  853. currentLayout->selectAll();
  854. else if (currentPaintRoutine != nullptr)
  855. currentPaintRoutine->selectAll();
  856. break;
  857. case StandardApplicationCommandIDs::deselectAll:
  858. if (currentLayout != nullptr)
  859. {
  860. currentLayout->getSelectedSet().deselectAll();
  861. }
  862. else if (currentPaintRoutine != nullptr)
  863. {
  864. currentPaintRoutine->getSelectedElements().deselectAll();
  865. currentPaintRoutine->getSelectedPoints().deselectAll();
  866. }
  867. break;
  868. default:
  869. return false;
  870. }
  871. document->beginTransaction();
  872. return true;
  873. }
  874. bool JucerDocumentEditor::keyPressed (const KeyPress& key)
  875. {
  876. if (key.isKeyCode (KeyPress::deleteKey) || key.isKeyCode (KeyPress::backspaceKey))
  877. {
  878. commandManager->invokeDirectly (StandardApplicationCommandIDs::del, true);
  879. return true;
  880. }
  881. return false;
  882. }
  883. JucerDocumentEditor* JucerDocumentEditor::getActiveDocumentHolder()
  884. {
  885. ApplicationCommandInfo info (0);
  886. ApplicationCommandTarget* target = commandManager->getTargetForCommand (JucerCommandIDs::editCompLayout, info);
  887. return dynamic_cast <JucerDocumentEditor*> (target);
  888. }
  889. Image JucerDocumentEditor::createComponentLayerSnapshot() const
  890. {
  891. if (compLayoutPanel != nullptr)
  892. return compLayoutPanel->createComponentSnapshot();
  893. return Image();
  894. }
  895. const int gridSnapMenuItemBase = 0x8723620;
  896. const int snapSizes[] = { 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32 };
  897. void createGUIEditorMenu (PopupMenu& menu)
  898. {
  899. menu.addCommandItem (commandManager, JucerCommandIDs::editCompLayout);
  900. menu.addCommandItem (commandManager, JucerCommandIDs::editCompGraphics);
  901. menu.addSeparator();
  902. PopupMenu newComps;
  903. for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
  904. newComps.addCommandItem (commandManager, JucerCommandIDs::newComponentBase + i);
  905. menu.addSubMenu ("Add new component", newComps);
  906. PopupMenu newElements;
  907. for (int i = 0; i < ObjectTypes::numElementTypes; ++i)
  908. newElements.addCommandItem (commandManager, JucerCommandIDs::newElementBase + i);
  909. menu.addSubMenu ("Add new graphic element", newElements);
  910. menu.addSeparator();
  911. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
  912. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
  913. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
  914. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  915. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::selectAll);
  916. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::deselectAll);
  917. menu.addSeparator();
  918. menu.addCommandItem (commandManager, JucerCommandIDs::toFront);
  919. menu.addCommandItem (commandManager, JucerCommandIDs::toBack);
  920. menu.addSeparator();
  921. menu.addCommandItem (commandManager, JucerCommandIDs::group);
  922. menu.addCommandItem (commandManager, JucerCommandIDs::ungroup);
  923. menu.addSeparator();
  924. menu.addCommandItem (commandManager, JucerCommandIDs::bringBackLostItems);
  925. menu.addSeparator();
  926. menu.addCommandItem (commandManager, JucerCommandIDs::showGrid);
  927. menu.addCommandItem (commandManager, JucerCommandIDs::enableSnapToGrid);
  928. JucerDocumentEditor* holder = JucerDocumentEditor::getActiveDocumentHolder();
  929. {
  930. const int currentSnapSize = holder != nullptr ? holder->getDocument()->getSnappingGridSize() : -1;
  931. PopupMenu m;
  932. for (int i = 0; i < numElementsInArray (snapSizes); ++i)
  933. m.addItem (gridSnapMenuItemBase + i, String (snapSizes[i]) + " pixels",
  934. true, snapSizes[i] == currentSnapSize);
  935. menu.addSubMenu ("Grid size", m, currentSnapSize >= 0);
  936. }
  937. menu.addSeparator();
  938. menu.addCommandItem (commandManager, JucerCommandIDs::zoomIn);
  939. menu.addCommandItem (commandManager, JucerCommandIDs::zoomOut);
  940. menu.addCommandItem (commandManager, JucerCommandIDs::zoomNormal);
  941. menu.addSeparator();
  942. {
  943. PopupMenu overlays;
  944. overlays.addCommandItem (commandManager, JucerCommandIDs::compOverlay0);
  945. overlays.addCommandItem (commandManager, JucerCommandIDs::compOverlay33);
  946. overlays.addCommandItem (commandManager, JucerCommandIDs::compOverlay66);
  947. overlays.addCommandItem (commandManager, JucerCommandIDs::compOverlay100);
  948. menu.addSubMenu ("Component Overlay", overlays, holder != nullptr);
  949. }
  950. }
  951. void handleGUIEditorMenuCommand (int menuItemID)
  952. {
  953. if (JucerDocumentEditor* ed = JucerDocumentEditor::getActiveDocumentHolder())
  954. {
  955. int gridIndex = menuItemID - gridSnapMenuItemBase;
  956. if (isPositiveAndBelow (gridIndex, numElementsInArray (snapSizes)))
  957. {
  958. JucerDocument& doc = *ed->getDocument();
  959. doc.setSnappingGrid (snapSizes [gridIndex],
  960. doc.isSnapActive (false),
  961. doc.isSnapShown());
  962. }
  963. }
  964. }
  965. void registerGUIEditorCommands()
  966. {
  967. JucerDocumentEditor dh (nullptr);
  968. commandManager->registerAllCommandsForTarget (&dh);
  969. }