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.

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