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.

772 lines
27KB

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