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.

1210 lines
45KB

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