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