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.

797 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../Application/jucer_Headers.h"
  20. #include "../Application/jucer_Application.h"
  21. #include "../Wizards/jucer_NewFileWizard.h"
  22. #include "jucer_JucerDocument.h"
  23. #include "jucer_ObjectTypes.h"
  24. #include "UI/jucer_JucerDocumentEditor.h"
  25. #include "UI/jucer_TestComponent.h"
  26. #include "jucer_UtilityFunctions.h"
  27. #include "Documents/jucer_ComponentDocument.h"
  28. #include "Documents/jucer_ButtonDocument.h"
  29. const char* const defaultClassName = "NewComponent";
  30. const char* const defaultParentClasses = "public Component";
  31. //==============================================================================
  32. JucerDocument::JucerDocument (SourceCodeDocument* c)
  33. : cpp (c),
  34. className (defaultClassName),
  35. parentClasses (defaultParentClasses)
  36. {
  37. jassert (cpp != nullptr);
  38. resources.setDocument (this);
  39. ProjucerApplication::getCommandManager().commandStatusChanged();
  40. cpp->getCodeDocument().addListener (this);
  41. }
  42. JucerDocument::~JucerDocument()
  43. {
  44. cpp->getCodeDocument().removeListener (this);
  45. ProjucerApplication::getCommandManager().commandStatusChanged();
  46. }
  47. //==============================================================================
  48. void JucerDocument::changed()
  49. {
  50. sendChangeMessage();
  51. ProjucerApplication::getCommandManager().commandStatusChanged();
  52. startTimer (800);
  53. }
  54. struct UserDocChangeTimer : public Timer
  55. {
  56. UserDocChangeTimer (JucerDocument& d) : doc (d) {}
  57. void timerCallback() override { doc.reloadFromDocument(); }
  58. JucerDocument& doc;
  59. };
  60. void JucerDocument::userEditedCpp()
  61. {
  62. if (userDocChangeTimer == nullptr)
  63. userDocChangeTimer.reset (new UserDocChangeTimer (*this));
  64. userDocChangeTimer->startTimer (500);
  65. }
  66. void JucerDocument::beginTransaction()
  67. {
  68. getUndoManager().beginNewTransaction();
  69. }
  70. void JucerDocument::beginTransaction (const String& name)
  71. {
  72. getUndoManager().beginNewTransaction (name);
  73. }
  74. void JucerDocument::timerCallback()
  75. {
  76. if (! Component::isMouseButtonDownAnywhere())
  77. {
  78. stopTimer();
  79. beginTransaction();
  80. flushChangesToDocuments (nullptr, false);
  81. }
  82. }
  83. void JucerDocument::codeDocumentTextInserted (const String&, int) { userEditedCpp(); }
  84. void JucerDocument::codeDocumentTextDeleted (int, int) { userEditedCpp(); }
  85. bool JucerDocument::perform (UndoableAction* const action, const String& actionName)
  86. {
  87. return undoManager.perform (action, actionName);
  88. }
  89. void JucerDocument::refreshAllPropertyComps()
  90. {
  91. if (ComponentLayout* l = getComponentLayout())
  92. l->getSelectedSet().changed();
  93. for (int i = getNumPaintRoutines(); --i >= 0;)
  94. {
  95. getPaintRoutine (i)->getSelectedElements().changed();
  96. getPaintRoutine (i)->getSelectedPoints().changed();
  97. }
  98. }
  99. //==============================================================================
  100. void JucerDocument::setClassName (const String& newName)
  101. {
  102. if (newName != className
  103. && CodeHelpers::makeValidIdentifier (newName, false, false, true).isNotEmpty())
  104. {
  105. className = CodeHelpers::makeValidIdentifier (newName, false, false, true);
  106. changed();
  107. }
  108. }
  109. void JucerDocument::setComponentName (const String& newName)
  110. {
  111. if (newName != componentName)
  112. {
  113. componentName = newName;
  114. changed();
  115. }
  116. }
  117. void JucerDocument::setParentClasses (const String& classes)
  118. {
  119. if (classes != parentClasses)
  120. {
  121. StringArray parentClassLines (getCleanedStringArray (StringArray::fromTokens (classes, ",", StringRef())));
  122. for (int i = parentClassLines.size(); --i >= 0;)
  123. {
  124. String s (parentClassLines[i]);
  125. String type;
  126. if (s.startsWith ("public ")
  127. || s.startsWith ("protected ")
  128. || s.startsWith ("private "))
  129. {
  130. type = s.upToFirstOccurrenceOf (" ", true, false);
  131. s = s.fromFirstOccurrenceOf (" ", false, false);
  132. if (s.trim().isEmpty())
  133. type = s = String();
  134. }
  135. s = type + CodeHelpers::makeValidIdentifier (s.trim(), false, false, true, true);
  136. parentClassLines.set (i, s);
  137. }
  138. parentClasses = parentClassLines.joinIntoString (", ");
  139. changed();
  140. }
  141. }
  142. void JucerDocument::setConstructorParams (const String& newParams)
  143. {
  144. if (constructorParams != newParams)
  145. {
  146. constructorParams = newParams;
  147. changed();
  148. }
  149. }
  150. void JucerDocument::setVariableInitialisers (const String& newInitlialisers)
  151. {
  152. if (variableInitialisers != newInitlialisers)
  153. {
  154. variableInitialisers = newInitlialisers;
  155. changed();
  156. }
  157. }
  158. void JucerDocument::setFixedSize (const bool isFixed)
  159. {
  160. if (fixedSize != isFixed)
  161. {
  162. fixedSize = isFixed;
  163. changed();
  164. }
  165. }
  166. void JucerDocument::setInitialSize (int w, int h)
  167. {
  168. w = jmax (1, w);
  169. h = jmax (1, h);
  170. if (initialWidth != w || initialHeight != h)
  171. {
  172. initialWidth = w;
  173. initialHeight = h;
  174. changed();
  175. }
  176. }
  177. //==============================================================================
  178. bool JucerDocument::isSnapActive (const bool disableIfCtrlKeyDown) const noexcept
  179. {
  180. return snapActive != (disableIfCtrlKeyDown && ModifierKeys::currentModifiers.isCtrlDown());
  181. }
  182. int JucerDocument::snapPosition (int pos) const noexcept
  183. {
  184. if (isSnapActive (true))
  185. {
  186. jassert (snapGridPixels > 0);
  187. pos = ((pos + snapGridPixels * 1024 + snapGridPixels / 2) / snapGridPixels - 1024) * snapGridPixels;
  188. }
  189. return pos;
  190. }
  191. void JucerDocument::setSnappingGrid (const int numPixels, const bool active, const bool shown)
  192. {
  193. if (numPixels != snapGridPixels
  194. || active != snapActive
  195. || shown != snapShown)
  196. {
  197. snapGridPixels = numPixels;
  198. snapActive = active;
  199. snapShown = shown;
  200. changed();
  201. }
  202. }
  203. void JucerDocument::setComponentOverlayOpacity (const float alpha)
  204. {
  205. if (alpha != componentOverlayOpacity)
  206. {
  207. componentOverlayOpacity = alpha;
  208. changed();
  209. }
  210. }
  211. //==============================================================================
  212. void JucerDocument::addMethod (const String& base, const String& returnVal, const String& method, const String& initialContent,
  213. StringArray& baseClasses, StringArray& returnValues, StringArray& methods, StringArray& initialContents)
  214. {
  215. baseClasses.add (base);
  216. returnValues.add (returnVal);
  217. methods.add (method);
  218. initialContents.add (initialContent);
  219. }
  220. void JucerDocument::getOptionalMethods (StringArray& baseClasses,
  221. StringArray& returnValues,
  222. StringArray& methods,
  223. StringArray& initialContents) const
  224. {
  225. addMethod ("Component", "void", "visibilityChanged()", "", baseClasses, returnValues, methods, initialContents);
  226. addMethod ("Component", "void", "moved()", "", baseClasses, returnValues, methods, initialContents);
  227. addMethod ("Component", "void", "parentHierarchyChanged()", "", baseClasses, returnValues, methods, initialContents);
  228. addMethod ("Component", "void", "parentSizeChanged()", "", baseClasses, returnValues, methods, initialContents);
  229. addMethod ("Component", "void", "lookAndFeelChanged()", "", baseClasses, returnValues, methods, initialContents);
  230. addMethod ("Component", "bool", "hitTest (int x, int y)", "return true;", baseClasses, returnValues, methods, initialContents);
  231. addMethod ("Component", "void", "broughtToFront()", "", baseClasses, returnValues, methods, initialContents);
  232. addMethod ("Component", "void", "filesDropped (const StringArray& filenames, int mouseX, int mouseY)", "", baseClasses, returnValues, methods, initialContents);
  233. addMethod ("Component", "void", "handleCommandMessage (int commandId)", "", baseClasses, returnValues, methods, initialContents);
  234. addMethod ("Component", "void", "childrenChanged()", "", baseClasses, returnValues, methods, initialContents);
  235. addMethod ("Component", "void", "enablementChanged()", "", baseClasses, returnValues, methods, initialContents);
  236. addMethod ("Component", "void", "mouseMove (const MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  237. addMethod ("Component", "void", "mouseEnter (const MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  238. addMethod ("Component", "void", "mouseExit (const MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  239. addMethod ("Component", "void", "mouseDown (const MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  240. addMethod ("Component", "void", "mouseDrag (const MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  241. addMethod ("Component", "void", "mouseUp (const MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  242. addMethod ("Component", "void", "mouseDoubleClick (const MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
  243. addMethod ("Component", "void", "mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)", "", baseClasses, returnValues, methods, initialContents);
  244. addMethod ("Component", "bool", "keyPressed (const 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);
  245. addMethod ("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);
  246. addMethod ("Component", "void", "modifierKeysChanged (const ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents);
  247. addMethod ("Component", "void", "focusGained (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  248. addMethod ("Component", "void", "focusLost (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  249. addMethod ("Component", "void", "focusOfChildComponentChanged (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
  250. addMethod ("Component", "void", "modifierKeysChanged (const ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents);
  251. addMethod ("Component", "void", "inputAttemptWhenModal()", "", baseClasses, returnValues, methods, initialContents);
  252. }
  253. void JucerDocument::setOptionalMethodEnabled (const String& methodSignature, const bool enable)
  254. {
  255. if (enable)
  256. activeExtraMethods.addIfNotAlreadyThere (methodSignature);
  257. else
  258. activeExtraMethods.removeString (methodSignature);
  259. changed();
  260. }
  261. bool JucerDocument::isOptionalMethodEnabled (const String& sig) const noexcept
  262. {
  263. return activeExtraMethods.contains (sig);
  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. forEachXmlChildElementWithTagName (*methods, e, "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 (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 (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 == "Component" || baseClassToAdd == "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 (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,
  432. existingHeader, project);
  433. generated.applyToCode (cppTemplate, headerFile.withFileExtension (".cpp"),
  434. existingCpp, project);
  435. if (isInitial)
  436. {
  437. jassert (project != nullptr);
  438. auto lineFeed = project->getProjectLineFeed();
  439. headerTemplate = replaceLineFeeds (headerTemplate, lineFeed);
  440. cppTemplate = replaceLineFeeds (cppTemplate, lineFeed);
  441. }
  442. else
  443. {
  444. headerTemplate = replaceLineFeeds (headerTemplate, getLineFeedForFile (existingHeader));
  445. cppTemplate = replaceLineFeeds (cppTemplate, getLineFeedForFile (existingCpp));
  446. }
  447. if (header->getCodeDocument().getAllContent() != headerTemplate)
  448. header->getCodeDocument().replaceAllContent (headerTemplate);
  449. if (cpp->getCodeDocument().getAllContent() != cppTemplate)
  450. cpp->getCodeDocument().replaceAllContent (cppTemplate);
  451. }
  452. userDocChangeTimer.reset();
  453. return true;
  454. }
  455. bool JucerDocument::reloadFromDocument()
  456. {
  457. const String cppContent (cpp->getCodeDocument().getAllContent());
  458. std::unique_ptr<XmlElement> newXML (pullMetaDataFromCppFile (cppContent));
  459. if (newXML == nullptr || ! newXML->hasTagName (jucerCompXmlTag))
  460. return false;
  461. if (currentXML != nullptr && currentXML->isEquivalentTo (newXML.get(), true))
  462. return true;
  463. currentXML.reset (newXML.release());
  464. stopTimer();
  465. resources.loadFromCpp (getCppFile(), cppContent);
  466. bool result = loadFromXml (*currentXML);
  467. extractCustomPaintSnippetsFromCppFile (cppContent);
  468. return result;
  469. }
  470. void JucerDocument::refreshCustomCodeFromDocument()
  471. {
  472. const String cppContent (cpp->getCodeDocument().getAllContent());
  473. extractCustomPaintSnippetsFromCppFile (cppContent);
  474. }
  475. void JucerDocument::extractCustomPaintSnippetsFromCppFile (const String& cppContent)
  476. {
  477. StringArray customPaintSnippets;
  478. auto lines = StringArray::fromLines (cppContent);
  479. int last = 0;
  480. while (last >= 0)
  481. {
  482. const int start = indexOfLineStartingWith (lines, "//[UserPaintCustomArguments]", last);
  483. if (start < 0)
  484. break;
  485. const int end = indexOfLineStartingWith (lines, "//[/UserPaintCustomArguments]", start);
  486. if (end < 0)
  487. break;
  488. last = end + 1;
  489. String result;
  490. for (int i = start + 1; i < end; ++i)
  491. result << lines [i] << newLine;
  492. customPaintSnippets.add (CodeHelpers::unindent (result, 4));
  493. }
  494. applyCustomPaintSnippets (customPaintSnippets);
  495. }
  496. std::unique_ptr<XmlElement> JucerDocument::pullMetaDataFromCppFile (const String& cpp)
  497. {
  498. auto lines = StringArray::fromLines (cpp);
  499. auto startLine = indexOfLineStartingWith (lines, "BEGIN_JUCER_METADATA", 0);
  500. if (startLine > 0)
  501. {
  502. auto endLine = indexOfLineStartingWith (lines, "END_JUCER_METADATA", startLine);
  503. if (endLine > startLine)
  504. return parseXML (lines.joinIntoString ("\n", startLine + 1, endLine - startLine - 1));
  505. }
  506. return nullptr;
  507. }
  508. bool JucerDocument::isValidJucerCppFile (const File& f)
  509. {
  510. if (f.hasFileExtension (cppFileExtensions))
  511. {
  512. std::unique_ptr<XmlElement> xml (pullMetaDataFromCppFile (f.loadFileAsString()));
  513. if (xml != nullptr)
  514. return xml->hasTagName (jucerCompXmlTag);
  515. }
  516. return false;
  517. }
  518. static JucerDocument* createDocument (SourceCodeDocument* cpp)
  519. {
  520. auto& codeDoc = cpp->getCodeDocument();
  521. std::unique_ptr<XmlElement> xml (JucerDocument::pullMetaDataFromCppFile (codeDoc.getAllContent()));
  522. if (xml == nullptr || ! xml->hasTagName (JucerDocument::jucerCompXmlTag))
  523. return nullptr;
  524. const String docType (xml->getStringAttribute ("documentType"));
  525. std::unique_ptr<JucerDocument> newDoc;
  526. if (docType.equalsIgnoreCase ("Button"))
  527. newDoc.reset (new ButtonDocument (cpp));
  528. if (docType.equalsIgnoreCase ("Component") || docType.isEmpty())
  529. newDoc.reset (new ComponentDocument (cpp));
  530. if (newDoc != nullptr && newDoc->reloadFromDocument())
  531. return newDoc.release();
  532. return nullptr;
  533. }
  534. JucerDocument* JucerDocument::createForCppFile (Project* p, const File& file)
  535. {
  536. OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
  537. if (SourceCodeDocument* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file)))
  538. if (dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file.withFileExtension (".h"))) != nullptr)
  539. return createDocument (cpp);
  540. return nullptr;
  541. }
  542. //==============================================================================
  543. class JucerComponentDocument : public SourceCodeDocument
  544. {
  545. public:
  546. JucerComponentDocument (Project* p, const File& f)
  547. : SourceCodeDocument (p, f)
  548. {
  549. }
  550. bool save() override
  551. {
  552. return SourceCodeDocument::save() && saveHeader();
  553. }
  554. bool saveHeader()
  555. {
  556. auto& odm = ProjucerApplication::getApp().openDocumentManager;
  557. if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h")))
  558. {
  559. if (header->save())
  560. {
  561. odm.closeFile (getFile().withFileExtension(".h"), false);
  562. return true;
  563. }
  564. }
  565. return false;
  566. }
  567. Component* createEditor() override
  568. {
  569. std::unique_ptr<JucerDocument> jucerDoc (JucerDocument::createForCppFile (getProject(), getFile()));
  570. if (jucerDoc != nullptr)
  571. return new JucerDocumentEditor (jucerDoc.release());
  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, true);
  614. odm.closeDocument (header, true);
  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. }