/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #include "../Application/jucer_Headers.h" #include "../Application/jucer_Application.h" #include "../Utility/Helpers/jucer_NewFileWizard.h" #include "jucer_JucerDocument.h" #include "jucer_ObjectTypes.h" #include "UI/jucer_JucerDocumentEditor.h" #include "UI/jucer_TestComponent.h" #include "jucer_UtilityFunctions.h" #include "Documents/jucer_ComponentDocument.h" #include "Documents/jucer_ButtonDocument.h" const char* const defaultClassName = "NewComponent"; const char* const defaultParentClasses = "public juce::Component"; //============================================================================== JucerDocument::JucerDocument (SourceCodeDocument* c) : cpp (c), className (defaultClassName), parentClasses (defaultParentClasses) { jassert (cpp != nullptr); resources.setDocument (this); ProjucerApplication::getCommandManager().commandStatusChanged(); cpp->getCodeDocument().addListener (this); } JucerDocument::~JucerDocument() { cpp->getCodeDocument().removeListener (this); ProjucerApplication::getCommandManager().commandStatusChanged(); } //============================================================================== void JucerDocument::changed() { sendChangeMessage(); ProjucerApplication::getCommandManager().commandStatusChanged(); startTimer (800); } struct UserDocChangeTimer : public Timer { explicit UserDocChangeTimer (JucerDocument& d) : doc (d) {} void timerCallback() override { doc.reloadFromDocument(); } JucerDocument& doc; }; void JucerDocument::userEditedCpp() { if (userDocChangeTimer == nullptr) userDocChangeTimer.reset (new UserDocChangeTimer (*this)); userDocChangeTimer->startTimer (500); } void JucerDocument::beginTransaction() { getUndoManager().beginNewTransaction(); } void JucerDocument::beginTransaction (const String& name) { getUndoManager().beginNewTransaction (name); } void JucerDocument::timerCallback() { if (! Component::isMouseButtonDownAnywhere()) { stopTimer(); beginTransaction(); flushChangesToDocuments (nullptr, false); } } void JucerDocument::codeDocumentTextInserted (const String&, int) { userEditedCpp(); } void JucerDocument::codeDocumentTextDeleted (int, int) { userEditedCpp(); } bool JucerDocument::perform (UndoableAction* const action, const String& actionName) { return undoManager.perform (action, actionName); } void JucerDocument::refreshAllPropertyComps() { if (ComponentLayout* l = getComponentLayout()) l->getSelectedSet().changed(); for (int i = getNumPaintRoutines(); --i >= 0;) { getPaintRoutine (i)->getSelectedElements().changed(); getPaintRoutine (i)->getSelectedPoints().changed(); } } //============================================================================== void JucerDocument::setClassName (const String& newName) { if (newName != className && build_tools::makeValidIdentifier (newName, false, false, true).isNotEmpty()) { className = build_tools::makeValidIdentifier (newName, false, false, true); changed(); } } void JucerDocument::setComponentName (const String& newName) { if (newName != componentName) { componentName = newName; changed(); } } void JucerDocument::setParentClasses (const String& classes) { if (classes != parentClasses) { StringArray parentClassLines (getCleanedStringArray (StringArray::fromTokens (classes, ",", StringRef()))); for (int i = parentClassLines.size(); --i >= 0;) { String s (parentClassLines[i]); String type; if (s.startsWith ("public ") || s.startsWith ("protected ") || s.startsWith ("private ")) { type = s.upToFirstOccurrenceOf (" ", true, false); s = s.fromFirstOccurrenceOf (" ", false, false); if (s.trim().isEmpty()) type = s = String(); } s = type + build_tools::makeValidIdentifier (s.trim(), false, false, true, true); parentClassLines.set (i, s); } parentClasses = parentClassLines.joinIntoString (", "); changed(); } } void JucerDocument::setConstructorParams (const String& newParams) { if (constructorParams != newParams) { constructorParams = newParams; changed(); } } void JucerDocument::setVariableInitialisers (const String& newInitlialisers) { if (variableInitialisers != newInitlialisers) { variableInitialisers = newInitlialisers; changed(); } } void JucerDocument::setFixedSize (const bool isFixed) { if (fixedSize != isFixed) { fixedSize = isFixed; changed(); } } void JucerDocument::setInitialSize (int w, int h) { w = jmax (1, w); h = jmax (1, h); if (initialWidth != w || initialHeight != h) { initialWidth = w; initialHeight = h; changed(); } } //============================================================================== bool JucerDocument::isSnapActive (const bool disableIfCtrlKeyDown) const noexcept { return snapActive != (disableIfCtrlKeyDown && ModifierKeys::currentModifiers.isCtrlDown()); } int JucerDocument::snapPosition (int pos) const noexcept { if (isSnapActive (true)) { jassert (snapGridPixels > 0); pos = ((pos + snapGridPixels * 1024 + snapGridPixels / 2) / snapGridPixels - 1024) * snapGridPixels; } return pos; } void JucerDocument::setSnappingGrid (const int numPixels, const bool active, const bool shown) { if (numPixels != snapGridPixels || active != snapActive || shown != snapShown) { snapGridPixels = numPixels; snapActive = active; snapShown = shown; changed(); } } void JucerDocument::setComponentOverlayOpacity (const float alpha) { if (alpha != componentOverlayOpacity) { componentOverlayOpacity = alpha; changed(); } } //============================================================================== void JucerDocument::addMethod (const String& base, const String& returnVal, const String& method, const String& initialContent, StringArray& baseClasses, StringArray& returnValues, StringArray& methods, StringArray& initialContents) { baseClasses.add (base); returnValues.add (returnVal); methods.add (method); initialContents.add (initialContent); } void JucerDocument::getOptionalMethods (StringArray& baseClasses, StringArray& returnValues, StringArray& methods, StringArray& initialContents) const { addMethod ("juce::Component", "void", "visibilityChanged()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "moved()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "parentHierarchyChanged()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "parentSizeChanged()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "lookAndFeelChanged()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "bool", "hitTest (int x, int y)", "return true;", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "broughtToFront()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "filesDropped (const juce::StringArray& filenames, int mouseX, int mouseY)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "handleCommandMessage (int commandId)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "childrenChanged()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "enablementChanged()", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseMove (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseEnter (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseExit (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseDown (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseDrag (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseUp (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseDoubleClick (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "mouseWheelMove (const juce::MouseEvent& e, const juce::MouseWheelDetails& wheel)", "", baseClasses, returnValues, methods, initialContents); 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); 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); addMethod ("juce::Component", "void", "modifierKeysChanged (const juce::ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "focusGained (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "focusLost (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "focusOfChildComponentChanged (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "modifierKeysChanged (const juce::ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents); addMethod ("juce::Component", "void", "inputAttemptWhenModal()", "", baseClasses, returnValues, methods, initialContents); } void JucerDocument::setOptionalMethodEnabled (const String& methodSignature, const bool enable) { if (enable) activeExtraMethods.addIfNotAlreadyThere (methodSignature); else activeExtraMethods.removeString (methodSignature); changed(); } bool JucerDocument::isOptionalMethodEnabled (const String& sig) const noexcept { return activeExtraMethods.contains (sig) || activeExtraMethods.contains (sig.replace ("juce::", {})); } void JucerDocument::addExtraClassProperties (PropertyPanel&) { } //============================================================================== const char* const JucerDocument::jucerCompXmlTag = "JUCER_COMPONENT"; std::unique_ptr JucerDocument::createXml() const { auto doc = std::make_unique (jucerCompXmlTag); doc->setAttribute ("documentType", getTypeName()); doc->setAttribute ("className", className); if (templateFile.trim().isNotEmpty()) doc->setAttribute ("template", templateFile); doc->setAttribute ("componentName", componentName); doc->setAttribute ("parentClasses", parentClasses); doc->setAttribute ("constructorParams", constructorParams); doc->setAttribute ("variableInitialisers", variableInitialisers); doc->setAttribute ("snapPixels", snapGridPixels); doc->setAttribute ("snapActive", snapActive); doc->setAttribute ("snapShown", snapShown); doc->setAttribute ("overlayOpacity", String (componentOverlayOpacity, 3)); doc->setAttribute ("fixedSize", fixedSize); doc->setAttribute ("initialWidth", initialWidth); doc->setAttribute ("initialHeight", initialHeight); if (activeExtraMethods.size() > 0) { XmlElement* extraMethods = new XmlElement ("METHODS"); doc->addChildElement (extraMethods); for (int i = 0; i < activeExtraMethods.size(); ++i) { XmlElement* e = new XmlElement ("METHOD"); extraMethods ->addChildElement (e); e->setAttribute ("name", activeExtraMethods[i]); } } return doc; } bool JucerDocument::loadFromXml (const XmlElement& xml) { if (xml.hasTagName (jucerCompXmlTag) && getTypeName().equalsIgnoreCase (xml.getStringAttribute ("documentType"))) { className = xml.getStringAttribute ("className", defaultClassName); templateFile = xml.getStringAttribute ("template", String()); componentName = xml.getStringAttribute ("componentName", String()); parentClasses = xml.getStringAttribute ("parentClasses", defaultParentClasses); constructorParams = xml.getStringAttribute ("constructorParams", String()); variableInitialisers = xml.getStringAttribute ("variableInitialisers", String()); fixedSize = xml.getBoolAttribute ("fixedSize", false); initialWidth = xml.getIntAttribute ("initialWidth", 300); initialHeight = xml.getIntAttribute ("initialHeight", 200); snapGridPixels = xml.getIntAttribute ("snapPixels", snapGridPixels); snapActive = xml.getBoolAttribute ("snapActive", snapActive); snapShown = xml.getBoolAttribute ("snapShown", snapShown); componentOverlayOpacity = (float) xml.getDoubleAttribute ("overlayOpacity", 0.0); activeExtraMethods.clear(); if (XmlElement* const methods = xml.getChildByName ("METHODS")) for (auto* e : methods->getChildWithTagNameIterator ("METHOD")) activeExtraMethods.addIfNotAlreadyThere (e->getStringAttribute ("name")); activeExtraMethods.trim(); activeExtraMethods.removeEmptyStrings(); changed(); getUndoManager().clearUndoHistory(); return true; } return false; } //============================================================================== void JucerDocument::fillInGeneratedCode (GeneratedCode& code) const { code.className = className; code.componentName = componentName; code.parentClasses = parentClasses; code.constructorParams = constructorParams; code.initialisers.addLines (variableInitialisers); if (! componentName.isEmpty()) code.constructorCode << "setName (" + quotedString (componentName, false) + ");\n"; // call these now, just to make sure they're the first two methods in the list. code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false) << "//[UserPrePaint] Add your own custom painting code here..\n//[/UserPrePaint]\n\n"; code.getCallbackCode (String(), "void", "resized()", false) << "//[UserPreResize] Add your own custom resize code here..\n//[/UserPreResize]\n\n"; if (ComponentLayout* l = getComponentLayout()) l->fillInGeneratedCode (code); fillInPaintCode (code); std::unique_ptr e (createXml()); jassert (e != nullptr); code.jucerMetadata = e->toString (XmlElement::TextFormat().withoutHeader()); resources.fillInGeneratedCode (code); code.constructorCode << "\n//[UserPreSize]\n" "//[/UserPreSize]\n"; if (initialWidth > 0 || initialHeight > 0) code.constructorCode << "\nsetSize (" << initialWidth << ", " << initialHeight << ");\n"; code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false) << "//[UserPaint] Add your own custom painting code here..\n//[/UserPaint]"; code.getCallbackCode (String(), "void", "resized()", false) << "//[UserResized] Add your own custom resize handling here..\n//[/UserResized]"; // add optional methods StringArray baseClasses, returnValues, methods, initialContents; getOptionalMethods (baseClasses, returnValues, methods, initialContents); for (int i = 0; i < methods.size(); ++i) { if (isOptionalMethodEnabled (methods[i])) { String baseClassToAdd (baseClasses[i]); if (baseClassToAdd == "juce::Component" || baseClassToAdd == "juce::Button") baseClassToAdd.clear(); String& s = code.getCallbackCode (baseClassToAdd, returnValues[i], methods[i], false); if (! s.contains ("//[")) { String userCommentTag ("UserCode_"); userCommentTag += methods[i].upToFirstOccurrenceOf ("(", false, false).trim(); s << "\n//[" << userCommentTag << "] -- Add your code here...\n" << initialContents[i]; if (initialContents[i].isNotEmpty() && ! initialContents[i].endsWithChar ('\n')) s << '\n'; s << "//[/" << userCommentTag << "]\n"; } } } } void JucerDocument::fillInPaintCode (GeneratedCode& code) const { for (int i = 0; i < getNumPaintRoutines(); ++i) getPaintRoutine (i) ->fillInGeneratedCode (code, code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false)); } void JucerDocument::setTemplateFile (const String& newFile) { if (templateFile != newFile) { templateFile = newFile; changed(); } } //============================================================================== bool JucerDocument::findTemplateFiles (String& headerContent, String& cppContent) const { if (templateFile.isNotEmpty()) { const File f (getCppFile().getSiblingFile (templateFile)); const File templateCpp (f.withFileExtension (".cpp")); const File templateH (f.withFileExtension (".h")); headerContent = templateH.loadFileAsString(); cppContent = templateCpp.loadFileAsString(); if (headerContent.isNotEmpty() && cppContent.isNotEmpty()) return true; } headerContent = BinaryData::jucer_ComponentTemplate_h; cppContent = BinaryData::jucer_ComponentTemplate_cpp; return true; } bool JucerDocument::flushChangesToDocuments (Project* project, bool isInitial) { String headerTemplate, cppTemplate; if (! findTemplateFiles (headerTemplate, cppTemplate)) return false; GeneratedCode generated (this); fillInGeneratedCode (generated); const File headerFile (getHeaderFile()); generated.includeFilesCPP.insert (0, headerFile); OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager; if (SourceCodeDocument* header = dynamic_cast (odm.openFile (nullptr, headerFile))) { String existingHeader (header->getCodeDocument().getAllContent()); String existingCpp (cpp->getCodeDocument().getAllContent()); generated.applyToCode (headerTemplate, headerFile, existingHeader); generated.applyToCode (cppTemplate, headerFile.withFileExtension (".cpp"), existingCpp); if (isInitial) { jassert (project != nullptr); auto lineFeed = project->getProjectLineFeed(); headerTemplate = replaceLineFeeds (headerTemplate, lineFeed); cppTemplate = replaceLineFeeds (cppTemplate, lineFeed); } else { headerTemplate = replaceLineFeeds (headerTemplate, getLineFeedForFile (existingHeader)); cppTemplate = replaceLineFeeds (cppTemplate, getLineFeedForFile (existingCpp)); } if (header->getCodeDocument().getAllContent() != headerTemplate) header->getCodeDocument().replaceAllContent (headerTemplate); if (cpp->getCodeDocument().getAllContent() != cppTemplate) cpp->getCodeDocument().replaceAllContent (cppTemplate); } userDocChangeTimer.reset(); return true; } bool JucerDocument::reloadFromDocument() { const String cppContent (cpp->getCodeDocument().getAllContent()); std::unique_ptr newXML (pullMetaDataFromCppFile (cppContent)); if (newXML == nullptr || ! newXML->hasTagName (jucerCompXmlTag)) return false; if (currentXML != nullptr && currentXML->isEquivalentTo (newXML.get(), true)) return true; currentXML.reset (newXML.release()); stopTimer(); resources.loadFromCpp (getCppFile(), cppContent); bool result = loadFromXml (*currentXML); extractCustomPaintSnippetsFromCppFile (cppContent); return result; } void JucerDocument::refreshCustomCodeFromDocument() { const String cppContent (cpp->getCodeDocument().getAllContent()); extractCustomPaintSnippetsFromCppFile (cppContent); } void JucerDocument::extractCustomPaintSnippetsFromCppFile (const String& cppContent) { StringArray customPaintSnippets; auto lines = StringArray::fromLines (cppContent); int last = 0; while (last >= 0) { const int start = indexOfLineStartingWith (lines, "//[UserPaintCustomArguments]", last); if (start < 0) break; const int end = indexOfLineStartingWith (lines, "//[/UserPaintCustomArguments]", start); if (end < 0) break; last = end + 1; String result; for (int i = start + 1; i < end; ++i) result << lines [i] << newLine; customPaintSnippets.add (CodeHelpers::unindent (result, 4)); } applyCustomPaintSnippets (customPaintSnippets); } std::unique_ptr JucerDocument::pullMetaDataFromCppFile (const String& cpp) { auto lines = StringArray::fromLines (cpp); auto startLine = indexOfLineStartingWith (lines, "BEGIN_JUCER_METADATA", 0); if (startLine > 0) { auto endLine = indexOfLineStartingWith (lines, "END_JUCER_METADATA", startLine); if (endLine > startLine) return parseXML (lines.joinIntoString ("\n", startLine + 1, endLine - startLine - 1)); } return nullptr; } bool JucerDocument::isValidJucerCppFile (const File& f) { if (f.hasFileExtension (cppFileExtensions)) { std::unique_ptr xml (pullMetaDataFromCppFile (f.loadFileAsString())); if (xml != nullptr) return xml->hasTagName (jucerCompXmlTag); } return false; } static JucerDocument* createDocument (SourceCodeDocument* cpp) { auto& codeDoc = cpp->getCodeDocument(); std::unique_ptr xml (JucerDocument::pullMetaDataFromCppFile (codeDoc.getAllContent())); if (xml == nullptr || ! xml->hasTagName (JucerDocument::jucerCompXmlTag)) return nullptr; const String docType (xml->getStringAttribute ("documentType")); std::unique_ptr newDoc; if (docType.equalsIgnoreCase ("Button")) newDoc.reset (new ButtonDocument (cpp)); if (docType.equalsIgnoreCase ("Component") || docType.isEmpty()) newDoc.reset (new ComponentDocument (cpp)); if (newDoc != nullptr && newDoc->reloadFromDocument()) return newDoc.release(); return nullptr; } JucerDocument* JucerDocument::createForCppFile (Project* p, const File& file) { OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager; if (SourceCodeDocument* cpp = dynamic_cast (odm.openFile (p, file))) if (dynamic_cast (odm.openFile (p, file.withFileExtension (".h"))) != nullptr) return createDocument (cpp); return nullptr; } //============================================================================== class JucerComponentDocument : public SourceCodeDocument { public: JucerComponentDocument (Project* p, const File& f) : SourceCodeDocument (p, f) { } void saveAsync (std::function callback) override { SourceCodeDocument::saveAsync ([parent = WeakReference { this }, callback] (bool saveResult) { if (parent == nullptr) return; if (! saveResult) { callback (false); return; } parent->saveHeaderAsync ([parent, callback] (bool headerSaveResult) { if (parent != nullptr) callback (headerSaveResult); }); }); } void saveHeaderAsync (std::function callback) { auto& odm = ProjucerApplication::getApp().openDocumentManager; if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h"))) { header->saveAsync ([parent = WeakReference { this }, callback] (bool saveResult) { if (parent == nullptr) return; if (saveResult) ProjucerApplication::getApp() .openDocumentManager .closeFileWithoutSaving (parent->getFile().withFileExtension (".h")); callback (saveResult); }); return; } callback (false); } std::unique_ptr createEditor() override { if (ProjucerApplication::getApp().isGUIEditorEnabled()) { std::unique_ptr jucerDoc (JucerDocument::createForCppFile (getProject(), getFile())); if (jucerDoc != nullptr) return std::make_unique (jucerDoc.release()); } return SourceCodeDocument::createEditor(); } struct Type : public OpenDocumentManager::DocumentType { Type() {} bool canOpenFile (const File& f) override { return JucerDocument::isValidJucerCppFile (f); } Document* openFile (Project* p, const File& f) override { return new JucerComponentDocument (p, f); } }; JUCE_DECLARE_WEAK_REFERENCEABLE (JucerComponentDocument) }; OpenDocumentManager::DocumentType* createGUIDocumentType(); OpenDocumentManager::DocumentType* createGUIDocumentType() { return new JucerComponentDocument::Type(); } //============================================================================== struct NewGUIComponentWizard : public NewFileWizard::Type { NewGUIComponentWizard (Project& proj) : project (proj) {} String getName() override { return "GUI Component"; } void createNewFile (Project& p, Project::Item parent) override { jassert (&p == &project); askUserToChooseNewFile (String (defaultClassName) + ".h", "*.h;*.cpp", parent, [this, parent] (File newFile) mutable { if (newFile != File()) { auto headerFile = newFile.withFileExtension (".h"); auto cppFile = newFile.withFileExtension (".cpp"); headerFile.replaceWithText (String()); cppFile.replaceWithText (String()); auto& odm = ProjucerApplication::getApp().openDocumentManager; if (auto* cpp = dynamic_cast (odm.openFile (&project, cppFile))) { if (auto* header = dynamic_cast (odm.openFile (&project, headerFile))) { std::unique_ptr jucerDoc (new ComponentDocument (cpp)); if (jucerDoc != nullptr) { jucerDoc->setClassName (newFile.getFileNameWithoutExtension()); jucerDoc->flushChangesToDocuments (&project, true); jucerDoc.reset(); for (auto* doc : { cpp, header }) { doc->saveAsync ([doc] (bool) { ProjucerApplication::getApp() .openDocumentManager .closeDocumentAsync (doc, OpenDocumentManager::SaveIfNeeded::yes, nullptr); }); } parent.addFileRetainingSortOrder (headerFile, true); parent.addFileRetainingSortOrder (cppFile, true); } } } } }); } Project& project; }; NewFileWizard::Type* createGUIComponentWizard (Project&); NewFileWizard::Type* createGUIComponentWizard (Project& p) { return new NewGUIComponentWizard (p); }