502 lines
13 KiB
C++
502 lines
13 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
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"
|
||
|
|
||
|
//==============================================================================
|
||
|
String joinLinesIntoSourceFile (StringArray& lines)
|
||
|
{
|
||
|
while (lines.size() > 10 && lines [lines.size() - 1].isEmpty())
|
||
|
lines.remove (lines.size() - 1);
|
||
|
|
||
|
return lines.joinIntoString (getPreferredLineFeed()) + getPreferredLineFeed();
|
||
|
}
|
||
|
|
||
|
String replaceLineFeeds (const String& content, const String& lineFeed)
|
||
|
{
|
||
|
StringArray lines;
|
||
|
lines.addLines (content);
|
||
|
|
||
|
return lines.joinIntoString (lineFeed);
|
||
|
}
|
||
|
|
||
|
String getLineFeedForFile (const String& fileContent)
|
||
|
{
|
||
|
auto t = fileContent.getCharPointer();
|
||
|
|
||
|
while (! t.isEmpty())
|
||
|
{
|
||
|
switch (t.getAndAdvance())
|
||
|
{
|
||
|
case 0: break;
|
||
|
case '\n': return "\n";
|
||
|
case '\r': if (*t == '\n') return "\r\n";
|
||
|
default: continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
String trimCommentCharsFromStartOfLine (const String& line)
|
||
|
{
|
||
|
return line.trimStart().trimCharactersAtStart ("*/").trimStart();
|
||
|
}
|
||
|
|
||
|
String createAlphaNumericUID()
|
||
|
{
|
||
|
String uid;
|
||
|
const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||
|
Random r;
|
||
|
|
||
|
uid << chars[r.nextInt (52)]; // make sure the first character is always a letter
|
||
|
|
||
|
for (int i = 5; --i >= 0;)
|
||
|
{
|
||
|
r.setSeedRandomly();
|
||
|
uid << chars [r.nextInt (62)];
|
||
|
}
|
||
|
|
||
|
return uid;
|
||
|
}
|
||
|
|
||
|
String createGUID (const String& seed)
|
||
|
{
|
||
|
auto hex = MD5 ((seed + "_guidsalt").toUTF8()).toHexString().toUpperCase();
|
||
|
|
||
|
return "{" + hex.substring (0, 8)
|
||
|
+ "-" + hex.substring (8, 12)
|
||
|
+ "-" + hex.substring (12, 16)
|
||
|
+ "-" + hex.substring (16, 20)
|
||
|
+ "-" + hex.substring (20, 32)
|
||
|
+ "}";
|
||
|
}
|
||
|
|
||
|
String escapeSpaces (const String& s)
|
||
|
{
|
||
|
return s.replace (" ", "\\ ");
|
||
|
}
|
||
|
|
||
|
String escapeQuotesAndSpaces (const String& s)
|
||
|
{
|
||
|
return escapeSpaces (s).replace ("'", "\\'").replace ("\"", "\\\"");
|
||
|
}
|
||
|
|
||
|
String addQuotesIfContainsSpaces (const String& text)
|
||
|
{
|
||
|
return (text.containsChar (' ') && ! text.isQuotedString()) ? text.quoted() : text;
|
||
|
}
|
||
|
|
||
|
void setValueIfVoid (Value value, const var& defaultValue)
|
||
|
{
|
||
|
if (value.getValue().isVoid())
|
||
|
value = defaultValue;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
StringPairArray parsePreprocessorDefs (const String& text)
|
||
|
{
|
||
|
StringPairArray result;
|
||
|
auto s = text.getCharPointer();
|
||
|
|
||
|
while (! s.isEmpty())
|
||
|
{
|
||
|
String token, value;
|
||
|
s.incrementToEndOfWhitespace();
|
||
|
|
||
|
while ((! s.isEmpty()) && *s != '=' && ! s.isWhitespace())
|
||
|
token << s.getAndAdvance();
|
||
|
|
||
|
s.incrementToEndOfWhitespace();
|
||
|
|
||
|
if (*s == '=')
|
||
|
{
|
||
|
++s;
|
||
|
|
||
|
while ((! s.isEmpty()) && *s == ' ')
|
||
|
++s;
|
||
|
|
||
|
while ((! s.isEmpty()) && ! s.isWhitespace())
|
||
|
{
|
||
|
if (*s == ',')
|
||
|
{
|
||
|
++s;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (*s == '\\' && (s[1] == ' ' || s[1] == ','))
|
||
|
++s;
|
||
|
|
||
|
value << s.getAndAdvance();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (token.isNotEmpty())
|
||
|
result.set (token, value);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
StringPairArray mergePreprocessorDefs (StringPairArray inheritedDefs, const StringPairArray& overridingDefs)
|
||
|
{
|
||
|
for (int i = 0; i < overridingDefs.size(); ++i)
|
||
|
inheritedDefs.set (overridingDefs.getAllKeys()[i], overridingDefs.getAllValues()[i]);
|
||
|
|
||
|
return inheritedDefs;
|
||
|
}
|
||
|
|
||
|
String createGCCPreprocessorFlags (const StringPairArray& defs)
|
||
|
{
|
||
|
String s;
|
||
|
|
||
|
for (int i = 0; i < defs.size(); ++i)
|
||
|
{
|
||
|
auto def = defs.getAllKeys()[i];
|
||
|
auto value = defs.getAllValues()[i];
|
||
|
|
||
|
if (value.isNotEmpty())
|
||
|
def << "=" << value;
|
||
|
|
||
|
s += " \"" + ("-D" + def).replace ("\"", "\\\"") + "\"";
|
||
|
}
|
||
|
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
StringArray getSearchPathsFromString (const String& searchPath)
|
||
|
{
|
||
|
StringArray s;
|
||
|
s.addTokens (searchPath, ";\r\n", StringRef());
|
||
|
return getCleanedStringArray (s);
|
||
|
}
|
||
|
|
||
|
StringArray getCommaOrWhitespaceSeparatedItems (const String& sourceString)
|
||
|
{
|
||
|
StringArray s;
|
||
|
s.addTokens (sourceString, ", \t\r\n", StringRef());
|
||
|
return getCleanedStringArray (s);
|
||
|
}
|
||
|
|
||
|
StringArray getCleanedStringArray (StringArray s)
|
||
|
{
|
||
|
s.trim();
|
||
|
s.removeEmptyStrings();
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void autoScrollForMouseEvent (const MouseEvent& e, bool scrollX, bool scrollY)
|
||
|
{
|
||
|
if (Viewport* const viewport = e.eventComponent->findParentComponentOfClass<Viewport>())
|
||
|
{
|
||
|
const MouseEvent e2 (e.getEventRelativeTo (viewport));
|
||
|
viewport->autoScroll (scrollX ? e2.x : 20, scrollY ? e2.y : 20, 8, 16);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
int indexOfLineStartingWith (const StringArray& lines, const String& text, int index)
|
||
|
{
|
||
|
const int len = text.length();
|
||
|
|
||
|
for (const String* i = lines.begin() + index, * const e = lines.end(); i < e; ++i)
|
||
|
{
|
||
|
if (CharacterFunctions::compareUpTo (i->getCharPointer().findEndOfWhitespace(),
|
||
|
text.getCharPointer(), len) == 0)
|
||
|
return index;
|
||
|
|
||
|
++index;
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
bool fileNeedsCppSyntaxHighlighting (const File& file)
|
||
|
{
|
||
|
if (file.hasFileExtension (sourceOrHeaderFileExtensions))
|
||
|
return true;
|
||
|
|
||
|
// This is a bit of a bodge to deal with libc++ headers with no extension..
|
||
|
char fileStart[128] = { 0 };
|
||
|
FileInputStream fin (file);
|
||
|
fin.read (fileStart, sizeof (fileStart) - 4);
|
||
|
|
||
|
return CharPointer_UTF8::isValidString (fileStart, sizeof (fileStart))
|
||
|
&& String (fileStart).trimStart().startsWith ("// -*- C++ -*-");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
void writeAutoGenWarningComment (OutputStream& outStream)
|
||
|
{
|
||
|
outStream << "/*" << newLine << newLine
|
||
|
<< " IMPORTANT! This file is auto-generated each time you save your" << newLine
|
||
|
<< " project - if you alter its contents, your changes may be overwritten!" << newLine
|
||
|
<< newLine;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
StringArray getJUCEModules() noexcept
|
||
|
{
|
||
|
static StringArray juceModuleIds =
|
||
|
{
|
||
|
"juce_analytics",
|
||
|
"juce_audio_basics",
|
||
|
"juce_audio_devices",
|
||
|
"juce_audio_formats",
|
||
|
"juce_audio_plugin_client",
|
||
|
"juce_audio_processors",
|
||
|
"juce_audio_utils",
|
||
|
"juce_box2d",
|
||
|
"juce_core",
|
||
|
"juce_cryptography",
|
||
|
"juce_data_structures",
|
||
|
"juce_dsp",
|
||
|
"juce_events",
|
||
|
"juce_graphics",
|
||
|
"juce_gui_basics",
|
||
|
"juce_gui_extra",
|
||
|
"juce_opengl",
|
||
|
"juce_osc",
|
||
|
"juce_product_unlocking",
|
||
|
"juce_video"
|
||
|
};
|
||
|
|
||
|
return juceModuleIds;
|
||
|
}
|
||
|
|
||
|
bool isJUCEModule (const String& moduleID) noexcept
|
||
|
{
|
||
|
return getJUCEModules().contains (moduleID);
|
||
|
}
|
||
|
|
||
|
StringArray getModulesRequiredForConsole() noexcept
|
||
|
{
|
||
|
return
|
||
|
{
|
||
|
"juce_core",
|
||
|
"juce_data_structures",
|
||
|
"juce_events"
|
||
|
};
|
||
|
}
|
||
|
|
||
|
StringArray getModulesRequiredForComponent() noexcept
|
||
|
{
|
||
|
return
|
||
|
{
|
||
|
"juce_core",
|
||
|
"juce_data_structures",
|
||
|
"juce_events",
|
||
|
"juce_graphics",
|
||
|
"juce_gui_basics"
|
||
|
};
|
||
|
}
|
||
|
|
||
|
StringArray getModulesRequiredForAudioProcessor() noexcept
|
||
|
{
|
||
|
return
|
||
|
{
|
||
|
"juce_audio_basics",
|
||
|
"juce_audio_devices",
|
||
|
"juce_audio_formats",
|
||
|
"juce_audio_plugin_client",
|
||
|
"juce_audio_processors",
|
||
|
"juce_audio_utils",
|
||
|
"juce_core",
|
||
|
"juce_data_structures",
|
||
|
"juce_events",
|
||
|
"juce_graphics",
|
||
|
"juce_gui_basics",
|
||
|
"juce_gui_extra"
|
||
|
};
|
||
|
}
|
||
|
|
||
|
bool isPIPFile (const File& file) noexcept
|
||
|
{
|
||
|
for (auto line : StringArray::fromLines (file.loadFileAsString()))
|
||
|
{
|
||
|
auto trimmedLine = trimCommentCharsFromStartOfLine (line);
|
||
|
|
||
|
if (trimmedLine.startsWith ("BEGIN_JUCE_PIP_METADATA"))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool isValidJUCEExamplesDirectory (const File& directory) noexcept
|
||
|
{
|
||
|
if (! directory.exists() || ! directory.isDirectory() || ! directory.containsSubDirectories())
|
||
|
return false;
|
||
|
|
||
|
return directory.getChildFile ("Assets").getChildFile ("juce_icon.png").existsAsFile();
|
||
|
}
|
||
|
|
||
|
bool isJUCEFolder (const File& f)
|
||
|
{
|
||
|
return isJUCEModulesFolder (f.getChildFile ("modules"));
|
||
|
}
|
||
|
|
||
|
bool isJUCEModulesFolder (const File& f)
|
||
|
{
|
||
|
return f.isDirectory() && f.getChildFile ("juce_core").isDirectory();
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static bool isDivider (const String& line)
|
||
|
{
|
||
|
auto afterIndent = line.trim();
|
||
|
|
||
|
if (afterIndent.startsWith ("//") && afterIndent.length() > 20)
|
||
|
{
|
||
|
afterIndent = afterIndent.substring (5);
|
||
|
|
||
|
if (afterIndent.containsOnly ("=")
|
||
|
|| afterIndent.containsOnly ("/")
|
||
|
|| afterIndent.containsOnly ("-"))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static int getIndexOfCommentBlockStart (const StringArray& lines, int endIndex)
|
||
|
{
|
||
|
auto endLine = lines[endIndex];
|
||
|
|
||
|
if (endLine.contains ("*/"))
|
||
|
{
|
||
|
for (int i = endIndex; i >= 0; --i)
|
||
|
if (lines[i].contains ("/*"))
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
if (endLine.trim().startsWith ("//") && ! isDivider (endLine))
|
||
|
{
|
||
|
for (int i = endIndex; i >= 0; --i)
|
||
|
if (! lines[i].startsWith ("//") || isDivider (lines[i]))
|
||
|
return i + 1;
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int findBestLineToScrollToForClass (StringArray lines, const String& className, bool isPlugin)
|
||
|
{
|
||
|
for (auto line : lines)
|
||
|
{
|
||
|
if (line.contains ("struct " + className) || line.contains ("class " + className)
|
||
|
|| (isPlugin && line.contains ("public AudioProcessor") && ! line.contains ("AudioProcessorEditor")))
|
||
|
{
|
||
|
auto index = lines.indexOf (line);
|
||
|
|
||
|
auto commentBlockStartIndex = getIndexOfCommentBlockStart (lines, index - 1);
|
||
|
|
||
|
if (commentBlockStartIndex != -1)
|
||
|
index = commentBlockStartIndex;
|
||
|
|
||
|
if (isDivider (lines[index - 1]))
|
||
|
index -= 1;
|
||
|
|
||
|
return index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
static var parseJUCEHeaderMetadata (const StringArray& lines)
|
||
|
{
|
||
|
auto* o = new DynamicObject();
|
||
|
var result (o);
|
||
|
|
||
|
for (auto& line : lines)
|
||
|
{
|
||
|
auto trimmedLine = trimCommentCharsFromStartOfLine (line);
|
||
|
|
||
|
auto colon = trimmedLine.indexOfChar (':');
|
||
|
|
||
|
if (colon >= 0)
|
||
|
{
|
||
|
auto key = trimmedLine.substring (0, colon).trim();
|
||
|
auto value = trimmedLine.substring (colon + 1).trim();
|
||
|
|
||
|
o->setProperty (key, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static String parseMetadataItem (const StringArray& lines, int& index)
|
||
|
{
|
||
|
String result = lines[index++];
|
||
|
|
||
|
while (index < lines.size())
|
||
|
{
|
||
|
auto continuationLine = trimCommentCharsFromStartOfLine (lines[index]);
|
||
|
|
||
|
if (continuationLine.isEmpty() || continuationLine.indexOfChar (':') != -1
|
||
|
|| continuationLine.startsWith ("END_JUCE_"))
|
||
|
break;
|
||
|
|
||
|
result += " " + continuationLine;
|
||
|
++index;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
var parseJUCEHeaderMetadata (const File& file)
|
||
|
{
|
||
|
StringArray lines;
|
||
|
file.readLines (lines);
|
||
|
|
||
|
for (int i = 0; i < lines.size(); ++i)
|
||
|
{
|
||
|
auto trimmedLine = trimCommentCharsFromStartOfLine (lines[i]);
|
||
|
|
||
|
if (trimmedLine.startsWith ("BEGIN_JUCE_"))
|
||
|
{
|
||
|
StringArray desc;
|
||
|
auto j = i + 1;
|
||
|
|
||
|
while (j < lines.size())
|
||
|
{
|
||
|
if (trimCommentCharsFromStartOfLine (lines[j]).startsWith ("END_JUCE_"))
|
||
|
return parseJUCEHeaderMetadata (desc);
|
||
|
|
||
|
desc.add (parseMetadataItem (lines, j));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {};
|
||
|
}
|