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.

836 lines
30KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "../Application/jucer_Headers.h"
  19. #include "../Application/jucer_Application.h"
  20. #include "../Utility/Helpers/jucer_NewFileWizard.h"
  21. #include "jucer_JucerDocument.h"
  22. #include "jucer_ObjectTypes.h"
  23. #include "UI/jucer_JucerDocumentEditor.h"
  24. #include "UI/jucer_TestComponent.h"
  25. #include "jucer_UtilityFunctions.h"
  26. #include "Documents/jucer_ComponentDocument.h"
  27. #include "Documents/jucer_ButtonDocument.h"
  28. const char* const defaultClassName = "NewComponent";
  29. const char* const defaultParentClasses = "public juce::Component";
  30. //==============================================================================
  31. JucerDocument::JucerDocument (SourceCodeDocument* c)
  32. : cpp (c),
  33. className (defaultClassName),
  34. parentClasses (defaultParentClasses)
  35. {
  36. jassert (cpp != nullptr);
  37. resources.setDocument (this);
  38. ProjucerApplication::getCommandManager().commandStatusChanged();
  39. cpp->getCodeDocument().addListener (this);
  40. }
  41. JucerDocument::~JucerDocument()
  42. {
  43. cpp->getCodeDocument().removeListener (this);
  44. ProjucerApplication::getCommandManager().commandStatusChanged();
  45. }
  46. //==============================================================================
  47. void JucerDocument::changed()
  48. {
  49. sendChangeMessage();
  50. ProjucerApplication::getCommandManager().commandStatusChanged();
  51. startTimer (800);
  52. }
  53. struct UserDocChangeTimer : public Timer
  54. {
  55. explicit UserDocChangeTimer (JucerDocument& d) : doc (d) {}
  56. void timerCallback() override { doc.reloadFromDocument(); }
  57. JucerDocument& doc;
  58. };
  59. void JucerDocument::userEditedCpp()
  60. {
  61. if (userDocChangeTimer == nullptr)
  62. userDocChangeTimer.reset (new UserDocChangeTimer (*this));
  63. userDocChangeTimer->startTimer (500);
  64. }
  65. void JucerDocument::beginTransaction()
  66. {
  67. getUndoManager().beginNewTransaction();
  68. }
  69. void JucerDocument::beginTransaction (const String& name)
  70. {
  71. getUndoManager().beginNewTransaction (name);
  72. }
  73. void JucerDocument::timerCallback()
  74. {
  75. if (! Component::isMouseButtonDownAnywhere())
  76. {
  77. stopTimer();
  78. beginTransaction();
  79. flushChangesToDocuments (nullptr, false);
  80. }
  81. }
  82. void JucerDocument::codeDocumentTextInserted (const String&, int) { userEditedCpp(); }
  83. void JucerDocument::codeDocumentTextDeleted (int, int) { userEditedCpp(); }
  84. bool JucerDocument::perform (UndoableAction* const action, const String& actionName)
  85. {
  86. return undoManager.perform (action, actionName);
  87. }
  88. void JucerDocument::refreshAllPropertyComps()
  89. {
  90. if (ComponentLayout* l = getComponentLayout())
  91. l->getSelectedSet().changed();
  92. for (int i = getNumPaintRoutines(); --i >= 0;)
  93. {
  94. getPaintRoutine (i)->getSelectedElements().changed();
  95. getPaintRoutine (i)->getSelectedPoints().changed();
  96. }
  97. }
  98. //==============================================================================
  99. void JucerDocument::setClassName (const String& newName)
  100. {
  101. if (newName != className
  102. && build_tools::makeValidIdentifier (newName, false, false, true).isNotEmpty())
  103. {
  104. className = build_tools::makeValidIdentifier (newName, false, false, true);
  105. changed();
  106. }
  107. }
  108. void JucerDocument::setComponentName (const String& newName)
  109. {
  110. if (newName != componentName)
  111. {
  112. componentName = newName;
  113. changed();
  114. }
  115. }
  116. void JucerDocument::setParentClasses (const String& classes)
  117. {
  118. if (classes != parentClasses)
  119. {
  120. StringArray parentClassLines (getCleanedStringArray (StringArray::fromTokens (classes, ",", StringRef())));
  121. for (int i = parentClassLines.size(); --i >= 0;)
  122. {
  123. String s (parentClassLines[i]);
  124. String type;
  125. if (s.startsWith ("public ")
  126. || s.startsWith ("protected ")
  127. || s.startsWith ("private "))
  128. {
  129. type = s.upToFirstOccurrenceOf (" ", true, false);
  130. s = s.fromFirstOccurrenceOf (" ", false, false);
  131. if (s.trim().isEmpty())
  132. type = s = String();
  133. }
  134. s = type + build_tools::makeValidIdentifier (s.trim(), false, false, true, true);
  135. parentClassLines.set (i, s);
  136. }
  137. parentClasses = parentClassLines.joinIntoString (", ");
  138. changed();
  139. }
  140. }
  141. void JucerDocument::setConstructorParams (const String& newParams)
  142. {
  143. if (constructorParams != newParams)
  144. {
  145. constructorParams = newParams;
  146. changed();
  147. }
  148. }
  149. void JucerDocument::setVariableInitialisers (const String& newInitlialisers)
  150. {
  151. if (variableInitialisers != newInitlialisers)
  152. {
  153. variableInitialisers = newInitlialisers;
  154. changed();
  155. }
  156. }
  157. void JucerDocument::setFixedSize (const bool isFixed)
  158. {
  159. if (fixedSize != isFixed)
  160. {
  161. fixedSize = isFixed;
  162. changed();
  163. }
  164. }
  165. void JucerDocument::setInitialSize (int w, int h)
  166. {
  167. w = jmax (1, w);
  168. h = jmax (1, h);
  169. if (initialWidth != w || initialHeight != h)
  170. {
  171. initialWidth = w;
  172. initialHeight = h;
  173. changed();
  174. }
  175. }
  176. //==============================================================================
  177. bool JucerDocument::isSnapActive (const bool disableIfCtrlKeyDown) const noexcept
  178. {
  179. return snapActive != (disableIfCtrlKeyDown && ModifierKeys::currentModifiers.isCtrlDown());
  180. }
  181. int JucerDocument::snapPosition (int pos) const noexcept
  182. {
  183. if (isSnapActive (true))
  184. {
  185. jassert (snapGridPixels > 0);
  186. pos = ((pos + snapGridPixels * 1024 + snapGridPixels / 2) / snapGridPixels - 1024) * snapGridPixels;
  187. }
  188. return pos;
  189. }
  190. void JucerDocument::setSnappingGrid (const int numPixels, const bool active, const bool shown)
  191. {
  192. if (numPixels != snapGridPixels
  193. || active != snapActive
  194. || shown != snapShown)
  195. {
  196. snapGridPixels = numPixels;
  197. snapActive = active;
  198. snapShown = shown;
  199. changed();
  200. }
  201. }
  202. void JucerDocument::setComponentOverlayOpacity (const float alpha)
  203. {
  204. if (! approximatelyEqual (alpha, componentOverlayOpacity))
  205. {
  206. componentOverlayOpacity = alpha;
  207. changed();
  208. }
  209. }
  210. //==============================================================================
  211. void JucerDocument::addMethod (const String& base, const String& returnVal, const String& method, const String& initialContent,
  212. StringArray& baseClasses, StringArray& returnValues, StringArray& methods, StringArray& initialContents)
  213. {
  214. baseClasses.add (base);
  215. returnValues.add (returnVal);
  216. methods.add (method);
  217. initialContents.add (initialContent);
  218. }
  219. void JucerDocument::getOptionalMethods (StringArray& baseClasses,
  220. StringArray& returnValues,
  221. StringArray& methods,
  222. StringArray& initialContents) const
  223. {
  224. addMethod ("juce::Component", "void", "visibilityChanged()", "", baseClasses, returnValues, methods, initialContents);
  225. addMethod ("juce::Component", "void", "moved()", "", baseClasses, returnValues, methods, initialContents);
  226. addMethod ("juce::Component", "void", "parentHierarchyChanged()", "", baseClasses, returnValues, methods, initialContents);
  227. addMethod ("juce::Component", "void", "parentSizeChanged()", "", baseClasses, returnValues, methods, initialContents);
  228. addMethod ("juce::Component", "void", "lookAndFeelChanged()", "", baseClasses, returnValues, methods, initialContents);
  229. addMethod ("juce::Component", "bool", "hitTest (int x, int y)", "return true;", baseClasses, returnValues, methods, initialContents);
  230. addMethod ("juce::Component", "void", "broughtToFront()", "", baseClasses, returnValues, methods, initialContents);
  231. addMethod ("juce::Component", "void", "filesDropped (const juce::StringArray& filenames, int mouseX, int mouseY)", "", baseClasses, returnValues, methods, initialContents);
  232. addMethod ("juce::Component", "void", "handleCommandMessage (int commandId)", "", baseClasses, returnValues, methods, initialContents);
  233. addMethod ("juce::Component", "void", "childrenChanged()", "", baseClasses, returnValues, methods, initialContents);
  234. addMethod ("juce::Component", "void", "enablementChanged()", "", baseClasses, returnValues, methods, initialContents);
  235. addMethod ("juce::Component", "void", "mouseMove (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  236. addMethod ("juce::Component", "void", "mouseEnter (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  237. addMethod ("juce::Component", "void", "mouseExit (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  238. addMethod ("juce::Component", "void", "mouseDown (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  239. addMethod ("juce::Component", "void", "mouseDrag (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  240. addMethod ("juce::Component", "void", "mouseUp (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  241. addMethod ("juce::Component", "void", "mouseDoubleClick (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  242. addMethod ("juce::Component", "void", "mouseWheelMove (const juce::MouseEvent& e, const juce::MouseWheelDetails& wheel)", "", baseClasses, returnValues, methods, initialContents);
  243. addMethod ("juce::Component", "bool", "keyPressed (const juce::KeyPress& key)", "return false; // Return true if your handler uses this key event, or false to allow it to be passed-on.", baseClasses, returnValues, methods, initialContents);
  244. addMethod ("juce::Component", "bool", "keyStateChanged (bool isKeyDown)", "return false; // Return true if your handler uses this key event, or false to allow it to be passed-on.", baseClasses, returnValues, methods, initialContents);
  245. addMethod ("juce::Component", "void", "modifierKeysChanged (const juce::ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents);
  246. addMethod ("juce::Component", "void", "focusGained (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  247. addMethod ("juce::Component", "void", "focusLost (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  248. addMethod ("juce::Component", "void", "focusOfChildComponentChanged (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  249. addMethod ("juce::Component", "void", "modifierKeysChanged (const juce::ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents);
  250. addMethod ("juce::Component", "void", "inputAttemptWhenModal()", "", baseClasses, returnValues, methods, initialContents);
  251. }
  252. void JucerDocument::setOptionalMethodEnabled (const String& methodSignature, const bool enable)
  253. {
  254. if (enable)
  255. activeExtraMethods.addIfNotAlreadyThere (methodSignature);
  256. else
  257. activeExtraMethods.removeString (methodSignature);
  258. changed();
  259. }
  260. bool JucerDocument::isOptionalMethodEnabled (const String& sig) const noexcept
  261. {
  262. return activeExtraMethods.contains (sig)
  263. || activeExtraMethods.contains (sig.replace ("juce::", {}));
  264. }
  265. void JucerDocument::addExtraClassProperties (PropertyPanel&)
  266. {
  267. }
  268. //==============================================================================
  269. const char* const JucerDocument::jucerCompXmlTag = "JUCER_COMPONENT";
  270. std::unique_ptr<XmlElement> JucerDocument::createXml() const
  271. {
  272. auto doc = std::make_unique<XmlElement> (jucerCompXmlTag);
  273. doc->setAttribute ("documentType", getTypeName());
  274. doc->setAttribute ("className", className);
  275. if (templateFile.trim().isNotEmpty())
  276. doc->setAttribute ("template", templateFile);
  277. doc->setAttribute ("componentName", componentName);
  278. doc->setAttribute ("parentClasses", parentClasses);
  279. doc->setAttribute ("constructorParams", constructorParams);
  280. doc->setAttribute ("variableInitialisers", variableInitialisers);
  281. doc->setAttribute ("snapPixels", snapGridPixels);
  282. doc->setAttribute ("snapActive", snapActive);
  283. doc->setAttribute ("snapShown", snapShown);
  284. doc->setAttribute ("overlayOpacity", String (componentOverlayOpacity, 3));
  285. doc->setAttribute ("fixedSize", fixedSize);
  286. doc->setAttribute ("initialWidth", initialWidth);
  287. doc->setAttribute ("initialHeight", initialHeight);
  288. if (activeExtraMethods.size() > 0)
  289. {
  290. XmlElement* extraMethods = new XmlElement ("METHODS");
  291. doc->addChildElement (extraMethods);
  292. for (int i = 0; i < activeExtraMethods.size(); ++i)
  293. {
  294. XmlElement* e = new XmlElement ("METHOD");
  295. extraMethods ->addChildElement (e);
  296. e->setAttribute ("name", activeExtraMethods[i]);
  297. }
  298. }
  299. return doc;
  300. }
  301. bool JucerDocument::loadFromXml (const XmlElement& xml)
  302. {
  303. if (xml.hasTagName (jucerCompXmlTag)
  304. && getTypeName().equalsIgnoreCase (xml.getStringAttribute ("documentType")))
  305. {
  306. className = xml.getStringAttribute ("className", defaultClassName);
  307. templateFile = xml.getStringAttribute ("template", String());
  308. componentName = xml.getStringAttribute ("componentName", String());
  309. parentClasses = xml.getStringAttribute ("parentClasses", defaultParentClasses);
  310. constructorParams = xml.getStringAttribute ("constructorParams", String());
  311. variableInitialisers = xml.getStringAttribute ("variableInitialisers", String());
  312. fixedSize = xml.getBoolAttribute ("fixedSize", false);
  313. initialWidth = xml.getIntAttribute ("initialWidth", 300);
  314. initialHeight = xml.getIntAttribute ("initialHeight", 200);
  315. snapGridPixels = xml.getIntAttribute ("snapPixels", snapGridPixels);
  316. snapActive = xml.getBoolAttribute ("snapActive", snapActive);
  317. snapShown = xml.getBoolAttribute ("snapShown", snapShown);
  318. componentOverlayOpacity = (float) xml.getDoubleAttribute ("overlayOpacity", 0.0);
  319. activeExtraMethods.clear();
  320. if (XmlElement* const methods = xml.getChildByName ("METHODS"))
  321. for (auto* e : methods->getChildWithTagNameIterator ("METHOD"))
  322. activeExtraMethods.addIfNotAlreadyThere (e->getStringAttribute ("name"));
  323. activeExtraMethods.trim();
  324. activeExtraMethods.removeEmptyStrings();
  325. changed();
  326. getUndoManager().clearUndoHistory();
  327. return true;
  328. }
  329. return false;
  330. }
  331. //==============================================================================
  332. void JucerDocument::fillInGeneratedCode (GeneratedCode& code) const
  333. {
  334. code.className = className;
  335. code.componentName = componentName;
  336. code.parentClasses = parentClasses;
  337. code.constructorParams = constructorParams;
  338. code.initialisers.addLines (variableInitialisers);
  339. if (! componentName.isEmpty())
  340. code.constructorCode << "setName (" + quotedString (componentName, false) + ");\n";
  341. // call these now, just to make sure they're the first two methods in the list.
  342. code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false)
  343. << "//[UserPrePaint] Add your own custom painting code here..\n//[/UserPrePaint]\n\n";
  344. code.getCallbackCode (String(), "void", "resized()", false)
  345. << "//[UserPreResize] Add your own custom resize code here..\n//[/UserPreResize]\n\n";
  346. if (ComponentLayout* l = getComponentLayout())
  347. l->fillInGeneratedCode (code);
  348. fillInPaintCode (code);
  349. std::unique_ptr<XmlElement> e (createXml());
  350. jassert (e != nullptr);
  351. code.jucerMetadata = e->toString (XmlElement::TextFormat().withoutHeader());
  352. resources.fillInGeneratedCode (code);
  353. code.constructorCode
  354. << "\n//[UserPreSize]\n"
  355. "//[/UserPreSize]\n";
  356. if (initialWidth > 0 || initialHeight > 0)
  357. code.constructorCode << "\nsetSize (" << initialWidth << ", " << initialHeight << ");\n";
  358. code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false)
  359. << "//[UserPaint] Add your own custom painting code here..\n//[/UserPaint]";
  360. code.getCallbackCode (String(), "void", "resized()", false)
  361. << "//[UserResized] Add your own custom resize handling here..\n//[/UserResized]";
  362. // add optional methods
  363. StringArray baseClasses, returnValues, methods, initialContents;
  364. getOptionalMethods (baseClasses, returnValues, methods, initialContents);
  365. for (int i = 0; i < methods.size(); ++i)
  366. {
  367. if (isOptionalMethodEnabled (methods[i]))
  368. {
  369. String baseClassToAdd (baseClasses[i]);
  370. if (baseClassToAdd == "juce::Component" || baseClassToAdd == "juce::Button")
  371. baseClassToAdd.clear();
  372. String& s = code.getCallbackCode (baseClassToAdd, returnValues[i], methods[i], false);
  373. if (! s.contains ("//["))
  374. {
  375. String userCommentTag ("UserCode_");
  376. userCommentTag += methods[i].upToFirstOccurrenceOf ("(", false, false).trim();
  377. s << "\n//[" << userCommentTag << "] -- Add your code here...\n"
  378. << initialContents[i];
  379. if (initialContents[i].isNotEmpty() && ! initialContents[i].endsWithChar ('\n'))
  380. s << '\n';
  381. s << "//[/" << userCommentTag << "]\n";
  382. }
  383. }
  384. }
  385. }
  386. void JucerDocument::fillInPaintCode (GeneratedCode& code) const
  387. {
  388. for (int i = 0; i < getNumPaintRoutines(); ++i)
  389. getPaintRoutine (i)
  390. ->fillInGeneratedCode (code, code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false));
  391. }
  392. void JucerDocument::setTemplateFile (const String& newFile)
  393. {
  394. if (templateFile != newFile)
  395. {
  396. templateFile = newFile;
  397. changed();
  398. }
  399. }
  400. //==============================================================================
  401. bool JucerDocument::findTemplateFiles (String& headerContent, String& cppContent) const
  402. {
  403. if (templateFile.isNotEmpty())
  404. {
  405. const File f (getCppFile().getSiblingFile (templateFile));
  406. const File templateCpp (f.withFileExtension (".cpp"));
  407. const File templateH (f.withFileExtension (".h"));
  408. headerContent = templateH.loadFileAsString();
  409. cppContent = templateCpp.loadFileAsString();
  410. if (headerContent.isNotEmpty() && cppContent.isNotEmpty())
  411. return true;
  412. }
  413. headerContent = BinaryData::jucer_ComponentTemplate_h;
  414. cppContent = BinaryData::jucer_ComponentTemplate_cpp;
  415. return true;
  416. }
  417. bool JucerDocument::flushChangesToDocuments (Project* project, bool isInitial)
  418. {
  419. String headerTemplate, cppTemplate;
  420. if (! findTemplateFiles (headerTemplate, cppTemplate))
  421. return false;
  422. GeneratedCode generated (this);
  423. fillInGeneratedCode (generated);
  424. const File headerFile (getHeaderFile());
  425. generated.includeFilesCPP.insert (0, headerFile);
  426. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  427. if (SourceCodeDocument* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (nullptr, headerFile)))
  428. {
  429. String existingHeader (header->getCodeDocument().getAllContent());
  430. String existingCpp (cpp->getCodeDocument().getAllContent());
  431. generated.applyToCode (headerTemplate, headerFile, existingHeader);
  432. generated.applyToCode (cppTemplate, headerFile.withFileExtension (".cpp"), existingCpp);
  433. if (isInitial)
  434. {
  435. jassert (project != nullptr);
  436. auto lineFeed = project->getProjectLineFeed();
  437. headerTemplate = replaceLineFeeds (headerTemplate, lineFeed);
  438. cppTemplate = replaceLineFeeds (cppTemplate, lineFeed);
  439. }
  440. else
  441. {
  442. headerTemplate = replaceLineFeeds (headerTemplate, getLineFeedForFile (existingHeader));
  443. cppTemplate = replaceLineFeeds (cppTemplate, getLineFeedForFile (existingCpp));
  444. }
  445. if (header->getCodeDocument().getAllContent() != headerTemplate)
  446. header->getCodeDocument().replaceAllContent (headerTemplate);
  447. if (cpp->getCodeDocument().getAllContent() != cppTemplate)
  448. cpp->getCodeDocument().replaceAllContent (cppTemplate);
  449. }
  450. userDocChangeTimer.reset();
  451. return true;
  452. }
  453. bool JucerDocument::reloadFromDocument()
  454. {
  455. const String cppContent (cpp->getCodeDocument().getAllContent());
  456. std::unique_ptr<XmlElement> newXML (pullMetaDataFromCppFile (cppContent));
  457. if (newXML == nullptr || ! newXML->hasTagName (jucerCompXmlTag))
  458. return false;
  459. if (currentXML != nullptr && currentXML->isEquivalentTo (newXML.get(), true))
  460. return true;
  461. currentXML = std::move (newXML);
  462. stopTimer();
  463. resources.loadFromCpp (getCppFile(), cppContent);
  464. bool result = loadFromXml (*currentXML);
  465. extractCustomPaintSnippetsFromCppFile (cppContent);
  466. return result;
  467. }
  468. void JucerDocument::refreshCustomCodeFromDocument()
  469. {
  470. const String cppContent (cpp->getCodeDocument().getAllContent());
  471. extractCustomPaintSnippetsFromCppFile (cppContent);
  472. }
  473. void JucerDocument::extractCustomPaintSnippetsFromCppFile (const String& cppContent)
  474. {
  475. StringArray customPaintSnippets;
  476. auto lines = StringArray::fromLines (cppContent);
  477. int last = 0;
  478. while (last >= 0)
  479. {
  480. const int start = indexOfLineStartingWith (lines, "//[UserPaintCustomArguments]", last);
  481. if (start < 0)
  482. break;
  483. const int end = indexOfLineStartingWith (lines, "//[/UserPaintCustomArguments]", start);
  484. if (end < 0)
  485. break;
  486. last = end + 1;
  487. String result;
  488. for (int i = start + 1; i < end; ++i)
  489. result << lines [i] << newLine;
  490. customPaintSnippets.add (CodeHelpers::unindent (result, 4));
  491. }
  492. applyCustomPaintSnippets (customPaintSnippets);
  493. }
  494. std::unique_ptr<XmlElement> JucerDocument::pullMetaDataFromCppFile (const String& cpp)
  495. {
  496. auto lines = StringArray::fromLines (cpp);
  497. auto startLine = indexOfLineStartingWith (lines, "BEGIN_JUCER_METADATA", 0);
  498. if (startLine > 0)
  499. {
  500. auto endLine = indexOfLineStartingWith (lines, "END_JUCER_METADATA", startLine);
  501. if (endLine > startLine)
  502. return parseXML (lines.joinIntoString ("\n", startLine + 1, endLine - startLine - 1));
  503. }
  504. return nullptr;
  505. }
  506. bool JucerDocument::isValidJucerCppFile (const File& f)
  507. {
  508. if (f.hasFileExtension (cppFileExtensions))
  509. {
  510. std::unique_ptr<XmlElement> xml (pullMetaDataFromCppFile (f.loadFileAsString()));
  511. if (xml != nullptr)
  512. return xml->hasTagName (jucerCompXmlTag);
  513. }
  514. return false;
  515. }
  516. static JucerDocument* createDocument (SourceCodeDocument* cpp)
  517. {
  518. auto& codeDoc = cpp->getCodeDocument();
  519. std::unique_ptr<XmlElement> xml (JucerDocument::pullMetaDataFromCppFile (codeDoc.getAllContent()));
  520. if (xml == nullptr || ! xml->hasTagName (JucerDocument::jucerCompXmlTag))
  521. return nullptr;
  522. const String docType (xml->getStringAttribute ("documentType"));
  523. std::unique_ptr<JucerDocument> newDoc;
  524. if (docType.equalsIgnoreCase ("Button"))
  525. newDoc.reset (new ButtonDocument (cpp));
  526. if (docType.equalsIgnoreCase ("Component") || docType.isEmpty())
  527. newDoc.reset (new ComponentDocument (cpp));
  528. if (newDoc != nullptr && newDoc->reloadFromDocument())
  529. return newDoc.release();
  530. return nullptr;
  531. }
  532. JucerDocument* JucerDocument::createForCppFile (Project* p, const File& file)
  533. {
  534. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  535. if (SourceCodeDocument* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file)))
  536. if (dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file.withFileExtension (".h"))) != nullptr)
  537. return createDocument (cpp);
  538. return nullptr;
  539. }
  540. //==============================================================================
  541. class JucerComponentDocument : public SourceCodeDocument
  542. {
  543. public:
  544. JucerComponentDocument (Project* p, const File& f)
  545. : SourceCodeDocument (p, f)
  546. {
  547. }
  548. void saveAsync (std::function<void (bool)> callback) override
  549. {
  550. SourceCodeDocument::saveAsync ([parent = WeakReference<JucerComponentDocument> { this }, callback] (bool saveResult)
  551. {
  552. if (parent == nullptr)
  553. return;
  554. if (! saveResult)
  555. {
  556. callback (false);
  557. return;
  558. }
  559. parent->saveHeaderAsync ([parent, callback] (bool headerSaveResult)
  560. {
  561. if (parent != nullptr)
  562. callback (headerSaveResult);
  563. });
  564. });
  565. }
  566. void saveHeaderAsync (std::function<void (bool)> callback)
  567. {
  568. auto& odm = ProjucerApplication::getApp().openDocumentManager;
  569. if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h")))
  570. {
  571. header->saveAsync ([parent = WeakReference<JucerComponentDocument> { this }, callback] (bool saveResult)
  572. {
  573. if (parent == nullptr)
  574. return;
  575. if (saveResult)
  576. ProjucerApplication::getApp()
  577. .openDocumentManager
  578. .closeFileWithoutSaving (parent->getFile().withFileExtension (".h"));
  579. callback (saveResult);
  580. });
  581. return;
  582. }
  583. callback (false);
  584. }
  585. std::unique_ptr<Component> createEditor() override
  586. {
  587. if (ProjucerApplication::getApp().isGUIEditorEnabled())
  588. {
  589. std::unique_ptr<JucerDocument> jucerDoc (JucerDocument::createForCppFile (getProject(), getFile()));
  590. if (jucerDoc != nullptr)
  591. return std::make_unique<JucerDocumentEditor> (jucerDoc.release());
  592. }
  593. return SourceCodeDocument::createEditor();
  594. }
  595. struct Type : public OpenDocumentManager::DocumentType
  596. {
  597. Type() {}
  598. bool canOpenFile (const File& f) override { return JucerDocument::isValidJucerCppFile (f); }
  599. Document* openFile (Project* p, const File& f) override { return new JucerComponentDocument (p, f); }
  600. };
  601. JUCE_DECLARE_WEAK_REFERENCEABLE (JucerComponentDocument)
  602. };
  603. OpenDocumentManager::DocumentType* createGUIDocumentType();
  604. OpenDocumentManager::DocumentType* createGUIDocumentType()
  605. {
  606. return new JucerComponentDocument::Type();
  607. }
  608. //==============================================================================
  609. struct NewGUIComponentWizard : public NewFileWizard::Type
  610. {
  611. NewGUIComponentWizard (Project& proj)
  612. : project (proj)
  613. {}
  614. String getName() override { return "GUI Component"; }
  615. void createNewFile (Project& p, Project::Item parent) override
  616. {
  617. jassert (&p == &project);
  618. askUserToChooseNewFile (String (defaultClassName) + ".h", "*.h;*.cpp", parent, [this, parent] (File newFile) mutable
  619. {
  620. if (newFile != File())
  621. {
  622. auto headerFile = newFile.withFileExtension (".h");
  623. auto cppFile = newFile.withFileExtension (".cpp");
  624. headerFile.replaceWithText (String());
  625. cppFile.replaceWithText (String());
  626. auto& odm = ProjucerApplication::getApp().openDocumentManager;
  627. if (auto* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, cppFile)))
  628. {
  629. if (auto* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, headerFile)))
  630. {
  631. std::unique_ptr<JucerDocument> jucerDoc (new ComponentDocument (cpp));
  632. if (jucerDoc != nullptr)
  633. {
  634. jucerDoc->setClassName (newFile.getFileNameWithoutExtension());
  635. jucerDoc->flushChangesToDocuments (&project, true);
  636. jucerDoc.reset();
  637. for (auto* doc : { cpp, header })
  638. {
  639. doc->saveAsync ([doc] (bool)
  640. {
  641. ProjucerApplication::getApp()
  642. .openDocumentManager
  643. .closeDocumentAsync (doc, OpenDocumentManager::SaveIfNeeded::yes, nullptr);
  644. });
  645. }
  646. parent.addFileRetainingSortOrder (headerFile, true);
  647. parent.addFileRetainingSortOrder (cppFile, true);
  648. }
  649. }
  650. }
  651. }
  652. });
  653. }
  654. Project& project;
  655. };
  656. NewFileWizard::Type* createGUIComponentWizard (Project&);
  657. NewFileWizard::Type* createGUIComponentWizard (Project& p)
  658. {
  659. return new NewGUIComponentWizard (p);
  660. }