/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-10 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "jucer_CodeGenerator.h" //============================================================================== CodeGenerator::CodeGenerator() { } CodeGenerator::~CodeGenerator() { } int CodeGenerator::getUniqueSuffix() { return ++suffix; } //============================================================================== String& CodeGenerator::getCallbackCode (const String& requiredParentClass, const String& returnType, const String& prototype, const bool hasPrePostUserSections) { String parentClass (requiredParentClass); if (parentClass.isNotEmpty() && ! (parentClass.startsWith ("public ") || parentClass.startsWith ("private ") || parentClass.startsWith ("protected "))) { parentClass = "public " + parentClass; } for (int i = callbacks.size(); --i >= 0;) { CallbackMethod* const cm = callbacks.getUnchecked(i); if (cm->requiredParentClass == parentClass && cm->returnType == returnType && cm->prototype == prototype) return cm->content; } CallbackMethod* const cm = new CallbackMethod(); callbacks.add (cm); cm->requiredParentClass = parentClass; cm->returnType = returnType; cm->prototype = prototype; cm->hasPrePostUserSections = hasPrePostUserSections; return cm->content; } void CodeGenerator::removeCallback (const String& returnType, const String& prototype) { for (int i = callbacks.size(); --i >= 0;) { CallbackMethod* const cm = callbacks.getUnchecked(i); if (cm->returnType == returnType && cm->prototype == prototype) callbacks.remove (i); } } const StringArray CodeGenerator::getExtraParentClasses() const { StringArray s; for (int i = 0; i < callbacks.size(); ++i) { CallbackMethod* const cm = callbacks.getUnchecked(i); s.add (cm->requiredParentClass); } return s; } const String CodeGenerator::getCallbackDeclarations() const { String s; for (int i = 0; i < callbacks.size(); ++i) { CallbackMethod* const cm = callbacks.getUnchecked(i); s << cm->returnType << " " << cm->prototype << ";" << newLine; } return s; } const String CodeGenerator::getCallbackDefinitions() const { String s; for (int i = 0; i < callbacks.size(); ++i) { CallbackMethod* const cm = callbacks.getUnchecked(i); const String userCodeBlockName ("User" + makeValidCppIdentifier (cm->prototype.upToFirstOccurrenceOf ("(", false, false), true, true, false).trim()); if (userCodeBlockName.isNotEmpty() && cm->hasPrePostUserSections) { s << cm->returnType << " " << className << "::" << cm->prototype << newLine << "{" << newLine << " //[" << userCodeBlockName << "_Pre]" << newLine << " //[/" << userCodeBlockName << "_Pre]" << newLine << newLine << " " << indentCode (cm->content.trim(), 4) << newLine << newLine << " //[" << userCodeBlockName << "_Post]" << newLine << " //[/" << userCodeBlockName << "_Post]" << newLine << "}" << newLine << newLine; } else { s << cm->returnType << " " << className << "::" << cm->prototype << newLine << "{" << newLine << " " << indentCode (cm->content.trim(), 4) << newLine << "}" << newLine << newLine; } } return s; } //============================================================================== const String CodeGenerator::getClassDeclaration() const { StringArray parentClassLines; parentClassLines.addTokens (parentClasses, ",", String::empty); parentClassLines.addArray (getExtraParentClasses()); parentClassLines.trim(); parentClassLines.removeEmptyStrings(); parentClassLines.removeDuplicates (false); if (parentClassLines.contains ("public Button", false)) parentClassLines.removeString ("public Component", false); String r; r << "class " << className << " : "; r << parentClassLines.joinIntoString ("," + String (newLine) + String::repeatedString (" ", r.length())); return r; } const String CodeGenerator::getInitialiserList() const { String s; StringArray inits (memberInitialisers); if (parentClassInitialiser.isNotEmpty()) inits.insert (0, parentClassInitialiser); inits.trim(); inits.removeEmptyStrings(); inits.removeDuplicates (false); if (inits.size() > 0) { s << " : "; for (int i = 0; i < inits.size(); ++i) { String init (inits[i]); while (init.endsWithChar (',')) init = init.dropLastCharacters (1); s << init; if (i < inits.size() - 1) s << "," << newLine << " "; else s << newLine; } } return s; } static const String getIncludeFileCode (StringArray files) { files.trim(); files.removeEmptyStrings(); files.removeDuplicates (false); String s; for (int i = 0; i < files.size(); ++i) s << "#include \"" << files[i] << "\"" << newLine; return s; } //============================================================================== static bool getUserSection (const StringArray& lines, const String& tag, StringArray& resultLines) { const int start = indexOfLineStartingWith (lines, "//[" + tag + "]", 0); if (start < 0) return false; const int end = indexOfLineStartingWith (lines, "//[/" + tag + "]", start + 1); for (int i = start + 1; i < end; ++i) resultLines.add (lines [i]); return true; } //============================================================================== static void replaceTemplate (String& text, const String& itemName, const String& value) { for (;;) { const int index = text.indexOf ("%%" + itemName + "%%"); if (index < 0) break; int indentLevel = 0; for (int i = index; --i >= 0;) { if (text[i] == '\n') break; ++indentLevel; } text = text.replaceSection (index, itemName.length() + 4, indentCode (value, indentLevel)); } } //============================================================================== void CodeGenerator::applyToCode (String& code, const File& targetFile, bool isForPreview, Project* project) const { replaceTemplate (code, "juceVersion", SystemStats::getJUCEVersion()); replaceTemplate (code, "headerGuard", makeHeaderGuardName (targetFile)); replaceTemplate (code, "className", className); replaceTemplate (code, "constructorParams", constructorParams); replaceTemplate (code, "initialisers", getInitialiserList()); replaceTemplate (code, "classDeclaration", getClassDeclaration()); replaceTemplate (code, "privateMemberDeclarations", privateMemberDeclarations); replaceTemplate (code, "publicMemberDeclarations", getCallbackDeclarations() + newLine + publicMemberDeclarations); replaceTemplate (code, "methodDefinitions", getCallbackDefinitions()); if (project != 0) replaceTemplate (code, "defaultJuceInclude", createIncludeStatement (project->getAppIncludeFile(), targetFile)); else replaceTemplate (code, "defaultJuceInclude", "#include \"juce_amalgamated.h\""); replaceTemplate (code, "includeFilesH", getIncludeFileCode (includeFilesH)); replaceTemplate (code, "includeFilesCPP", getIncludeFileCode (includeFilesCPP)); replaceTemplate (code, "constructor", constructorCode); replaceTemplate (code, "destructor", destructorCode); if (! isForPreview) { replaceTemplate (code, "metadata", jucerMetadata); replaceTemplate (code, "staticMemberDefinitions", staticMemberDefinitions); } else { replaceTemplate (code, "metadata", " << Metadata isn't shown in the code preview >>" + String (newLine)); replaceTemplate (code, "staticMemberDefinitions", "// Static member declarations and resources would go here... (these aren't shown in the code preview)"); } } //============================================================================== CodeGenerator::CustomCodeList::Iterator::Iterator (const String& documentText, CustomCodeList& customCode_) : customCode (customCode_), i (0), codeDocument (0) { lines.addLines (documentText); } CodeGenerator::CustomCodeList::Iterator::~Iterator() { } bool CodeGenerator::CustomCodeList::Iterator::next() { textBefore = String::empty; textAfter = String::empty; while (i < lines.size()) { textBefore += lines[i] + "\n"; if (lines[i].trimStart().startsWith ("//[")) { String tag (lines[i].trimStart().substring (3)); tag = tag.upToFirstOccurrenceOf ("]", false, false).trim(); if (! (tag.isEmpty() || tag.startsWithChar ('/'))) { const int endLine = indexOfLineStartingWith (lines, "//[/" + tag + "]", i + 1); if (endLine > i) { sectionName = tag; codeDocument = customCode.getDocumentFor (tag, true); i = endLine; bool isLastTag = true; for (int j = i + 1; j < lines.size(); ++j) { if (lines[j].trimStart().startsWith ("//[")) { isLastTag = false; break; } } if (isLastTag) { textAfter = lines.joinIntoString (newLine, i, lines.size() - i); i = lines.size(); } return true; } } } ++i; } return false; } //============================================================================== CodeGenerator::CustomCodeList::CustomCodeList() { } CodeGenerator::CustomCodeList::~CustomCodeList() { } void CodeGenerator::CustomCodeList::reloadFrom (const String& fileContent) { StringArray newNames; ReferenceCountedArray newContent; StringArray lines; lines.addLines (fileContent); for (int i = 0; i < lines.size(); ++i) { if (lines[i].trimStart().startsWith ("//[")) { String tag (lines[i].trimStart().substring (3)); tag = tag.upToFirstOccurrenceOf ("]", false, false).trim(); jassert (! (tag.isEmpty() || tag.startsWithChar ('/'))); if (! (tag.isEmpty() || tag.startsWithChar ('/'))) { const int endLine = indexOfLineStartingWith (lines, "//[/" + tag + "]", i + 1); if (endLine > i) { String content (lines.joinIntoString (newLine, i + 1, endLine - i - 1)); newNames.add (tag); CodeDocumentRef::Ptr doc (getDocumentFor (tag, false)); if (doc == 0) { CodeDocument* const codeDoc = new CodeDocument(); doc = new CodeDocumentRef (codeDoc); codeDoc->replaceAllContent (content); codeDoc->clearUndoHistory(); codeDoc->setSavePoint(); } newContent.add (doc); i = endLine; } } } } sectionNames = newNames; sectionContent = newContent; sendSynchronousChangeMessage (this); } void CodeGenerator::CustomCodeList::applyTo (String& fileContent) const { StringArray lines; lines.addLines (fileContent); for (int i = 0; i < lines.size(); ++i) { if (lines[i].trimStart().startsWith ("//[")) { String tag (lines[i].trimStart().substring (3)); tag = tag.upToFirstOccurrenceOf ("]", false, false); jassert (! tag.startsWithChar ('/')); if (! tag.startsWithChar ('/')) { const int endLine = indexOfLineStartingWith (lines, "//[/" + tag + "]", i + 1); if (endLine > i) { StringArray sourceLines; sourceLines.addLines (getSectionContent (tag)); if (sourceLines.size() > 0) { lines.removeRange (i + 1, endLine - i - 1); for (int j = 0; j < sourceLines.size(); ++j) lines.insert (++i, sourceLines [j].trimEnd()); ++i; } else { i = endLine; } } } } lines.set (i, lines[i].trimEnd()); } if (lines[lines.size() - 1].isNotEmpty()) lines.add (String::empty); fileContent = lines.joinIntoString (newLine); } bool CodeGenerator::CustomCodeList::needsSaving() const { for (int i = sectionContent.size(); --i >= 0;) if (sectionContent.getUnchecked(i)->getDocument().hasChangedSinceSavePoint()) return true; return false; } int CodeGenerator::CustomCodeList::getNumSections() const { return sectionNames.size(); } const String CodeGenerator::CustomCodeList::getSectionName (int index) const { return sectionNames [index]; } const CodeGenerator::CustomCodeList::CodeDocumentRef::Ptr CodeGenerator::CustomCodeList::getDocument (int index) const { return sectionContent [index]; } const CodeGenerator::CustomCodeList::CodeDocumentRef::Ptr CodeGenerator::CustomCodeList::getDocumentFor (const String& sectionName, bool createIfNotFound) { const int index = sectionNames.indexOf (sectionName); if (index >= 0) return sectionContent [index]; if (createIfNotFound) { sectionNames.add (sectionName); const CodeDocumentRef::Ptr doc (new CodeDocumentRef (new CodeDocument())); sectionContent.add (doc); return doc; } return 0; } const String CodeGenerator::CustomCodeList::getSectionContent (const String& sectionName) const { const int index = sectionNames.indexOf (sectionName); if (index >= 0) return sectionContent[index]->getDocument().getAllContent(); return String::empty; } void CodeGenerator::CustomCodeList::removeSection (const String& sectionName) { const int index = sectionNames.indexOf (sectionName); if (index >= 0) { sectionNames.remove (index); sectionContent.remove (index); } }