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.

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