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.

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