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.

796 lines
29KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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 (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 (juce::FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  247. addMethod ("juce::Component", "void", "focusLost (juce::FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  248. addMethod ("juce::Component", "void", "focusOfChildComponentChanged (juce::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. }
  264. void JucerDocument::addExtraClassProperties (PropertyPanel&)
  265. {
  266. }
  267. //==============================================================================
  268. const char* const JucerDocument::jucerCompXmlTag = "JUCER_COMPONENT";
  269. std::unique_ptr<XmlElement> JucerDocument::createXml() const
  270. {
  271. auto doc = std::make_unique<XmlElement> (jucerCompXmlTag);
  272. doc->setAttribute ("documentType", getTypeName());
  273. doc->setAttribute ("className", className);
  274. if (templateFile.trim().isNotEmpty())
  275. doc->setAttribute ("template", templateFile);
  276. doc->setAttribute ("componentName", componentName);
  277. doc->setAttribute ("parentClasses", parentClasses);
  278. doc->setAttribute ("constructorParams", constructorParams);
  279. doc->setAttribute ("variableInitialisers", variableInitialisers);
  280. doc->setAttribute ("snapPixels", snapGridPixels);
  281. doc->setAttribute ("snapActive", snapActive);
  282. doc->setAttribute ("snapShown", snapShown);
  283. doc->setAttribute ("overlayOpacity", String (componentOverlayOpacity, 3));
  284. doc->setAttribute ("fixedSize", fixedSize);
  285. doc->setAttribute ("initialWidth", initialWidth);
  286. doc->setAttribute ("initialHeight", initialHeight);
  287. if (activeExtraMethods.size() > 0)
  288. {
  289. XmlElement* extraMethods = new XmlElement ("METHODS");
  290. doc->addChildElement (extraMethods);
  291. for (int i = 0; i < activeExtraMethods.size(); ++i)
  292. {
  293. XmlElement* e = new XmlElement ("METHOD");
  294. extraMethods ->addChildElement (e);
  295. e->setAttribute ("name", activeExtraMethods[i]);
  296. }
  297. }
  298. return doc;
  299. }
  300. bool JucerDocument::loadFromXml (const XmlElement& xml)
  301. {
  302. if (xml.hasTagName (jucerCompXmlTag)
  303. && getTypeName().equalsIgnoreCase (xml.getStringAttribute ("documentType")))
  304. {
  305. className = xml.getStringAttribute ("className", defaultClassName);
  306. templateFile = xml.getStringAttribute ("template", String());
  307. componentName = xml.getStringAttribute ("componentName", String());
  308. parentClasses = xml.getStringAttribute ("parentClasses", defaultParentClasses);
  309. constructorParams = xml.getStringAttribute ("constructorParams", String());
  310. variableInitialisers = xml.getStringAttribute ("variableInitialisers", String());
  311. fixedSize = xml.getBoolAttribute ("fixedSize", false);
  312. initialWidth = xml.getIntAttribute ("initialWidth", 300);
  313. initialHeight = xml.getIntAttribute ("initialHeight", 200);
  314. snapGridPixels = xml.getIntAttribute ("snapPixels", snapGridPixels);
  315. snapActive = xml.getBoolAttribute ("snapActive", snapActive);
  316. snapShown = xml.getBoolAttribute ("snapShown", snapShown);
  317. componentOverlayOpacity = (float) xml.getDoubleAttribute ("overlayOpacity", 0.0);
  318. activeExtraMethods.clear();
  319. if (XmlElement* const methods = xml.getChildByName ("METHODS"))
  320. forEachXmlChildElementWithTagName (*methods, e, "METHOD")
  321. activeExtraMethods.addIfNotAlreadyThere (e->getStringAttribute ("name"));
  322. activeExtraMethods.trim();
  323. activeExtraMethods.removeEmptyStrings();
  324. changed();
  325. getUndoManager().clearUndoHistory();
  326. return true;
  327. }
  328. return false;
  329. }
  330. //==============================================================================
  331. void JucerDocument::fillInGeneratedCode (GeneratedCode& code) const
  332. {
  333. code.className = className;
  334. code.componentName = componentName;
  335. code.parentClasses = parentClasses;
  336. code.constructorParams = constructorParams;
  337. code.initialisers.addLines (variableInitialisers);
  338. if (! componentName.isEmpty())
  339. code.constructorCode << "setName (" + quotedString (componentName, false) + ");\n";
  340. // call these now, just to make sure they're the first two methods in the list.
  341. code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false)
  342. << "//[UserPrePaint] Add your own custom painting code here..\n//[/UserPrePaint]\n\n";
  343. code.getCallbackCode (String(), "void", "resized()", false)
  344. << "//[UserPreResize] Add your own custom resize code here..\n//[/UserPreResize]\n\n";
  345. if (ComponentLayout* l = getComponentLayout())
  346. l->fillInGeneratedCode (code);
  347. fillInPaintCode (code);
  348. std::unique_ptr<XmlElement> e (createXml());
  349. jassert (e != nullptr);
  350. code.jucerMetadata = e->toString (XmlElement::TextFormat().withoutHeader());
  351. resources.fillInGeneratedCode (code);
  352. code.constructorCode
  353. << "\n//[UserPreSize]\n"
  354. "//[/UserPreSize]\n";
  355. if (initialWidth > 0 || initialHeight > 0)
  356. code.constructorCode << "\nsetSize (" << initialWidth << ", " << initialHeight << ");\n";
  357. code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false)
  358. << "//[UserPaint] Add your own custom painting code here..\n//[/UserPaint]";
  359. code.getCallbackCode (String(), "void", "resized()", false)
  360. << "//[UserResized] Add your own custom resize handling here..\n//[/UserResized]";
  361. // add optional methods
  362. StringArray baseClasses, returnValues, methods, initialContents;
  363. getOptionalMethods (baseClasses, returnValues, methods, initialContents);
  364. for (int i = 0; i < methods.size(); ++i)
  365. {
  366. if (isOptionalMethodEnabled (methods[i]))
  367. {
  368. String baseClassToAdd (baseClasses[i]);
  369. if (baseClassToAdd == "juce::Component" || baseClassToAdd == "juce::Button")
  370. baseClassToAdd.clear();
  371. String& s = code.getCallbackCode (baseClassToAdd, returnValues[i], methods[i], false);
  372. if (! s.contains ("//["))
  373. {
  374. String userCommentTag ("UserCode_");
  375. userCommentTag += methods[i].upToFirstOccurrenceOf ("(", false, false).trim();
  376. s << "\n//[" << userCommentTag << "] -- Add your code here...\n"
  377. << initialContents[i];
  378. if (initialContents[i].isNotEmpty() && ! initialContents[i].endsWithChar ('\n'))
  379. s << '\n';
  380. s << "//[/" << userCommentTag << "]\n";
  381. }
  382. }
  383. }
  384. }
  385. void JucerDocument::fillInPaintCode (GeneratedCode& code) const
  386. {
  387. for (int i = 0; i < getNumPaintRoutines(); ++i)
  388. getPaintRoutine (i)
  389. ->fillInGeneratedCode (code, code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false));
  390. }
  391. void JucerDocument::setTemplateFile (const String& newFile)
  392. {
  393. if (templateFile != newFile)
  394. {
  395. templateFile = newFile;
  396. changed();
  397. }
  398. }
  399. //==============================================================================
  400. bool JucerDocument::findTemplateFiles (String& headerContent, String& cppContent) const
  401. {
  402. if (templateFile.isNotEmpty())
  403. {
  404. const File f (getCppFile().getSiblingFile (templateFile));
  405. const File templateCpp (f.withFileExtension (".cpp"));
  406. const File templateH (f.withFileExtension (".h"));
  407. headerContent = templateH.loadFileAsString();
  408. cppContent = templateCpp.loadFileAsString();
  409. if (headerContent.isNotEmpty() && cppContent.isNotEmpty())
  410. return true;
  411. }
  412. headerContent = BinaryData::jucer_ComponentTemplate_h;
  413. cppContent = BinaryData::jucer_ComponentTemplate_cpp;
  414. return true;
  415. }
  416. bool JucerDocument::flushChangesToDocuments (Project* project, bool isInitial)
  417. {
  418. String headerTemplate, cppTemplate;
  419. if (! findTemplateFiles (headerTemplate, cppTemplate))
  420. return false;
  421. GeneratedCode generated (this);
  422. fillInGeneratedCode (generated);
  423. const File headerFile (getHeaderFile());
  424. generated.includeFilesCPP.insert (0, headerFile);
  425. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  426. if (SourceCodeDocument* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (nullptr, headerFile)))
  427. {
  428. String existingHeader (header->getCodeDocument().getAllContent());
  429. String existingCpp (cpp->getCodeDocument().getAllContent());
  430. generated.applyToCode (headerTemplate, headerFile, existingHeader);
  431. generated.applyToCode (cppTemplate, headerFile.withFileExtension (".cpp"), existingCpp);
  432. if (isInitial)
  433. {
  434. jassert (project != nullptr);
  435. auto lineFeed = project->getProjectLineFeed();
  436. headerTemplate = replaceLineFeeds (headerTemplate, lineFeed);
  437. cppTemplate = replaceLineFeeds (cppTemplate, lineFeed);
  438. }
  439. else
  440. {
  441. headerTemplate = replaceLineFeeds (headerTemplate, getLineFeedForFile (existingHeader));
  442. cppTemplate = replaceLineFeeds (cppTemplate, getLineFeedForFile (existingCpp));
  443. }
  444. if (header->getCodeDocument().getAllContent() != headerTemplate)
  445. header->getCodeDocument().replaceAllContent (headerTemplate);
  446. if (cpp->getCodeDocument().getAllContent() != cppTemplate)
  447. cpp->getCodeDocument().replaceAllContent (cppTemplate);
  448. }
  449. userDocChangeTimer.reset();
  450. return true;
  451. }
  452. bool JucerDocument::reloadFromDocument()
  453. {
  454. const String cppContent (cpp->getCodeDocument().getAllContent());
  455. std::unique_ptr<XmlElement> newXML (pullMetaDataFromCppFile (cppContent));
  456. if (newXML == nullptr || ! newXML->hasTagName (jucerCompXmlTag))
  457. return false;
  458. if (currentXML != nullptr && currentXML->isEquivalentTo (newXML.get(), true))
  459. return true;
  460. currentXML.reset (newXML.release());
  461. stopTimer();
  462. resources.loadFromCpp (getCppFile(), cppContent);
  463. bool result = loadFromXml (*currentXML);
  464. extractCustomPaintSnippetsFromCppFile (cppContent);
  465. return result;
  466. }
  467. void JucerDocument::refreshCustomCodeFromDocument()
  468. {
  469. const String cppContent (cpp->getCodeDocument().getAllContent());
  470. extractCustomPaintSnippetsFromCppFile (cppContent);
  471. }
  472. void JucerDocument::extractCustomPaintSnippetsFromCppFile (const String& cppContent)
  473. {
  474. StringArray customPaintSnippets;
  475. auto lines = StringArray::fromLines (cppContent);
  476. int last = 0;
  477. while (last >= 0)
  478. {
  479. const int start = indexOfLineStartingWith (lines, "//[UserPaintCustomArguments]", last);
  480. if (start < 0)
  481. break;
  482. const int end = indexOfLineStartingWith (lines, "//[/UserPaintCustomArguments]", start);
  483. if (end < 0)
  484. break;
  485. last = end + 1;
  486. String result;
  487. for (int i = start + 1; i < end; ++i)
  488. result << lines [i] << newLine;
  489. customPaintSnippets.add (CodeHelpers::unindent (result, 4));
  490. }
  491. applyCustomPaintSnippets (customPaintSnippets);
  492. }
  493. std::unique_ptr<XmlElement> JucerDocument::pullMetaDataFromCppFile (const String& cpp)
  494. {
  495. auto lines = StringArray::fromLines (cpp);
  496. auto startLine = indexOfLineStartingWith (lines, "BEGIN_JUCER_METADATA", 0);
  497. if (startLine > 0)
  498. {
  499. auto endLine = indexOfLineStartingWith (lines, "END_JUCER_METADATA", startLine);
  500. if (endLine > startLine)
  501. return parseXML (lines.joinIntoString ("\n", startLine + 1, endLine - startLine - 1));
  502. }
  503. return nullptr;
  504. }
  505. bool JucerDocument::isValidJucerCppFile (const File& f)
  506. {
  507. if (f.hasFileExtension (cppFileExtensions))
  508. {
  509. std::unique_ptr<XmlElement> xml (pullMetaDataFromCppFile (f.loadFileAsString()));
  510. if (xml != nullptr)
  511. return xml->hasTagName (jucerCompXmlTag);
  512. }
  513. return false;
  514. }
  515. static JucerDocument* createDocument (SourceCodeDocument* cpp)
  516. {
  517. auto& codeDoc = cpp->getCodeDocument();
  518. std::unique_ptr<XmlElement> xml (JucerDocument::pullMetaDataFromCppFile (codeDoc.getAllContent()));
  519. if (xml == nullptr || ! xml->hasTagName (JucerDocument::jucerCompXmlTag))
  520. return nullptr;
  521. const String docType (xml->getStringAttribute ("documentType"));
  522. std::unique_ptr<JucerDocument> newDoc;
  523. if (docType.equalsIgnoreCase ("Button"))
  524. newDoc.reset (new ButtonDocument (cpp));
  525. if (docType.equalsIgnoreCase ("Component") || docType.isEmpty())
  526. newDoc.reset (new ComponentDocument (cpp));
  527. if (newDoc != nullptr && newDoc->reloadFromDocument())
  528. return newDoc.release();
  529. return nullptr;
  530. }
  531. JucerDocument* JucerDocument::createForCppFile (Project* p, const File& file)
  532. {
  533. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  534. if (SourceCodeDocument* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file)))
  535. if (dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file.withFileExtension (".h"))) != nullptr)
  536. return createDocument (cpp);
  537. return nullptr;
  538. }
  539. //==============================================================================
  540. class JucerComponentDocument : public SourceCodeDocument
  541. {
  542. public:
  543. JucerComponentDocument (Project* p, const File& f)
  544. : SourceCodeDocument (p, f)
  545. {
  546. }
  547. bool save() override
  548. {
  549. return SourceCodeDocument::save() && saveHeader();
  550. }
  551. bool saveHeader()
  552. {
  553. auto& odm = ProjucerApplication::getApp().openDocumentManager;
  554. if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h")))
  555. {
  556. if (header->save())
  557. {
  558. odm.closeFile (getFile().withFileExtension(".h"), OpenDocumentManager::SaveIfNeeded::no);
  559. return true;
  560. }
  561. }
  562. return false;
  563. }
  564. Component* createEditor() override
  565. {
  566. if (ProjucerApplication::getApp().isGUIEditorEnabled())
  567. {
  568. std::unique_ptr<JucerDocument> jucerDoc (JucerDocument::createForCppFile (getProject(), getFile()));
  569. if (jucerDoc != nullptr)
  570. return new JucerDocumentEditor (jucerDoc.release());
  571. }
  572. return SourceCodeDocument::createEditor();
  573. }
  574. struct Type : public OpenDocumentManager::DocumentType
  575. {
  576. Type() {}
  577. bool canOpenFile (const File& f) override { return JucerDocument::isValidJucerCppFile (f); }
  578. Document* openFile (Project* p, const File& f) override { return new JucerComponentDocument (p, f); }
  579. };
  580. };
  581. OpenDocumentManager::DocumentType* createGUIDocumentType();
  582. OpenDocumentManager::DocumentType* createGUIDocumentType()
  583. {
  584. return new JucerComponentDocument::Type();
  585. }
  586. //==============================================================================
  587. struct NewGUIComponentWizard : public NewFileWizard::Type
  588. {
  589. NewGUIComponentWizard() {}
  590. String getName() override { return "GUI Component"; }
  591. void createNewFile (Project& project, Project::Item parent) override
  592. {
  593. auto newFile = askUserToChooseNewFile (String (defaultClassName) + ".h", "*.h;*.cpp", parent);
  594. if (newFile != File())
  595. {
  596. auto headerFile = newFile.withFileExtension (".h");
  597. auto cppFile = newFile.withFileExtension (".cpp");
  598. headerFile.replaceWithText (String());
  599. cppFile.replaceWithText (String());
  600. auto& odm = ProjucerApplication::getApp().openDocumentManager;
  601. if (auto* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, cppFile)))
  602. {
  603. if (auto* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, headerFile)))
  604. {
  605. std::unique_ptr<JucerDocument> jucerDoc (new ComponentDocument (cpp));
  606. if (jucerDoc != nullptr)
  607. {
  608. jucerDoc->setClassName (newFile.getFileNameWithoutExtension());
  609. jucerDoc->flushChangesToDocuments (&project, true);
  610. jucerDoc.reset();
  611. cpp->save();
  612. header->save();
  613. odm.closeDocument (cpp, OpenDocumentManager::SaveIfNeeded::yes);
  614. odm.closeDocument (header, OpenDocumentManager::SaveIfNeeded::yes);
  615. parent.addFileRetainingSortOrder (headerFile, true);
  616. parent.addFileRetainingSortOrder (cppFile, true);
  617. }
  618. }
  619. }
  620. }
  621. }
  622. };
  623. NewFileWizard::Type* createGUIComponentWizard();
  624. NewFileWizard::Type* createGUIComponentWizard()
  625. {
  626. return new NewGUIComponentWizard();
  627. }