/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 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_Headers.h" //============================================================================== int64 calculateStreamHashCode (InputStream& in) { int64 t = 0; const int bufferSize = 4096; HeapBlock buffer; buffer.malloc (bufferSize); for (;;) { const int num = in.read (buffer, bufferSize); if (num <= 0) break; for (int i = 0; i < num; ++i) t = t * 65599 + buffer[i]; } return t; } int64 calculateFileHashCode (const File& file) { ScopedPointer stream (file.createInputStream()); return stream != 0 ? calculateStreamHashCode (*stream) : 0; } bool areFilesIdentical (const File& file1, const File& file2) { return file1.getSize() == file2.getSize() && calculateFileHashCode (file1) == calculateFileHashCode (file2); } bool overwriteFileWithNewDataIfDifferent (const File& file, const char* data, int numBytes) { if (file.getSize() == numBytes) { MemoryInputStream newStream (data, numBytes, false); if (calculateStreamHashCode (newStream) == calculateFileHashCode (file)) return true; } TemporaryFile temp (file); return temp.getFile().appendData (data, numBytes) && temp.overwriteTargetFileWithTemporary(); } bool overwriteFileWithNewDataIfDifferent (const File& file, const MemoryOutputStream& newData) { return overwriteFileWithNewDataIfDifferent (file, newData.getData(), newData.getDataSize()); } bool overwriteFileWithNewDataIfDifferent (const File& file, const String& newData) { return overwriteFileWithNewDataIfDifferent (file, newData.toUTF8(), strlen ((const char*) newData.toUTF8())); } bool containsAnyNonHiddenFiles (const File& folder) { DirectoryIterator di (folder, false); while (di.next()) if (! di.getFile().isHidden()) return true; return false; } //============================================================================== const int64 hashCode64 (const String& s) { return s.hashCode64() + s.length() * s.hashCode() + s.toUpperCase().hashCode(); } const String createAlphaNumericUID() { String uid; static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; Random r (Random::getSystemRandom().nextInt64()); for (int i = 9; --i >= 0;) { r.setSeedRandomly(); uid << (juce_wchar) chars [r.nextInt (sizeof (chars))]; } return uid; } const String randomHexString (Random& random, int numChars) { String s; const char hexChars[] = "0123456789ABCDEF"; while (--numChars >= 0) s << hexChars [random.nextInt (16)]; return s; } const String hexString8Digits (int value) { return String::toHexString (value).paddedLeft ('0', 8); } const String createGUID (const String& seed) { String guid; Random r (hashCode64 (seed + "_jucersalt")); guid << "{" << randomHexString (r, 8); // (written as separate statements to enforce the order of execution) guid << "-" << randomHexString (r, 4); guid << "-" << randomHexString (r, 4); guid << "-" << randomHexString (r, 4); guid << "-" << randomHexString (r, 12) << "}"; return guid; } const String unixStylePath (const String& path) { return path.replaceCharacter ('\\', '/'); } const String windowsStylePath (const String& path) { return path.replaceCharacter ('/', '\\'); } const String appendPath (const String& path, const String& subpath) { if (File::isAbsolutePath (subpath) || subpath.startsWithChar ('$') || subpath.startsWithChar ('~') || (CharacterFunctions::isLetter (subpath[0]) && subpath[1] == ':')) return subpath.replaceCharacter ('\\', '/'); String path1 (path.replaceCharacter ('\\', '/')); if (! path1.endsWithChar ('/')) path1 << '/'; return path1 + subpath.replaceCharacter ('\\', '/'); } bool shouldPathsBeRelative (String path1, String path2) { path1 = unixStylePath (path1); path2 = unixStylePath (path2); const int len = jmin (path1.length(), path2.length()); int commonBitLength = 0; for (int i = 0; i < len; ++i) { if (CharacterFunctions::toLowerCase (path1[i]) != CharacterFunctions::toLowerCase (path2[i])) break; ++commonBitLength; } return path1.substring (0, commonBitLength).removeCharacters ("/:").isNotEmpty(); } const String createIncludeStatement (const File& includeFile, const File& targetFile) { return "#include \"" + unixStylePath (includeFile.getRelativePathFrom (targetFile.getParentDirectory())) + "\""; } const String makeHeaderGuardName (const File& file) { return "__" + file.getFileName().toUpperCase() .replaceCharacters (" .", "__") .retainCharacters ("_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + "_" + String::toHexString (file.hashCode()).toUpperCase() + "__"; } //============================================================================== bool isJuceFolder (const File& folder) { return folder.getFileName().containsIgnoreCase ("juce") && folder.getChildFile ("juce.h").exists() && folder.getChildFile ("juce_Config.h").exists(); } static const File lookInFolderForJuceFolder (const File& folder) { for (DirectoryIterator di (folder, false, "*juce*", File::findDirectories); di.next();) { if (isJuceFolder (di.getFile())) return di.getFile(); } return File::nonexistent; } const File findParentJuceFolder (const File& file) { File f (file); while (f.exists() && f.getParentDirectory() != f) { if (isJuceFolder (f)) return f; File found = lookInFolderForJuceFolder (f); if (found.exists()) return found; f = f.getParentDirectory(); } return File::nonexistent; } const File findDefaultJuceFolder() { File f = findParentJuceFolder (File::getSpecialLocation (File::currentApplicationFile)); if (! f.exists()) f = lookInFolderForJuceFolder (File::getSpecialLocation (File::userHomeDirectory)); if (! f.exists()) f = lookInFolderForJuceFolder (File::getSpecialLocation (File::userDocumentsDirectory)); return f; } //============================================================================== const String replaceCEscapeChars (const String& s) { const int len = s.length(); String r; r.preallocateStorage (len + 2); bool lastWasHexEscapeCode = false; for (int i = 0; i < len; ++i) { const tchar c = s[i]; switch (c) { case '\t': r << "\\t"; lastWasHexEscapeCode = false; break; case '\r': r << "\\r"; lastWasHexEscapeCode = false; break; case '\n': r << "\\n"; lastWasHexEscapeCode = false; break; case '\\': r << "\\\\"; lastWasHexEscapeCode = false; break; case '\'': r << "\\\'"; lastWasHexEscapeCode = false; break; case '\"': r << "\\\""; lastWasHexEscapeCode = false; break; default: if (c < 128 && ! (lastWasHexEscapeCode && String ("0123456789abcdefABCDEF").containsChar (c))) // (have to avoid following a hex escape sequence with a valid hex digit) { r << c; lastWasHexEscapeCode = false; } else { r << "\\x" << String::toHexString ((int) c); lastWasHexEscapeCode = true; } break; } } return r; } //============================================================================== const String makeValidCppIdentifier (String s, const bool capitalise, const bool removeColons, const bool allowTemplates) { if (removeColons) s = s.replaceCharacters (".,;:/@", "______"); else s = s.replaceCharacters (".,;/@", "_____"); int i; for (i = s.length(); --i > 0;) if (CharacterFunctions::isLetter (s[i]) && CharacterFunctions::isLetter (s[i - 1]) && CharacterFunctions::isUpperCase (s[i]) && ! CharacterFunctions::isUpperCase (s[i - 1])) s = s.substring (0, i) + " " + s.substring (i); String allowedChars ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_ 0123456789"); if (allowTemplates) allowedChars += "<>"; if (! removeColons) allowedChars += ":"; StringArray words; words.addTokens (s.retainCharacters (allowedChars), false); words.trim(); String n (words[0]); if (capitalise) n = n.toLowerCase(); for (i = 1; i < words.size(); ++i) { if (capitalise && words[i].length() > 1) n << words[i].substring (0, 1).toUpperCase() << words[i].substring (1).toLowerCase(); else n << words[i]; } if (CharacterFunctions::isDigit (n[0])) n = "_" + n; // make sure it's not a reserved c++ keyword.. static const tchar* const reservedWords[] = { T("auto"), T("const"), T("double"), T("float"), T("int"), T("short"), T("struct"), T("return"), T("static"), T("union"), T("while"), T("asm"), T("dynamic_cast"), T("unsigned"), T("break"), T("continue"), T("else"), T("for"), T("long"), T("signed"), T("switch"), T("void"), T("case"), T("default"), T("enum"), T("goto"), T("register"), T("sizeof"), T("typedef"), T("volatile"), T("char"), T("do"), T("extern"), T("if"), T("namespace"), T("reinterpret_cast"), T("try"), T("bool"), T("explicit"), T("new"), T("static_cast"), T("typeid"), T("catch"), T("false"), T("operator"), T("template"), T("typename"), T("class"), T("friend"), T("private"), T("this"), T("using"), T("const_cast"), T("inline"), T("public"), T("throw"), T("virtual"), T("delete"), T("mutable"), T("protected"), T("true"), T("wchar_t"), T("and"), T("bitand"), T("compl"), T("not_eq"), T("or_eq"), T("xor_eq"), T("and_eq"), T("bitor"), T("not"), T("or"), T("xor"), T("cin"), T("endl"), T("INT_MIN"), T("iomanip"), T("main"), T("npos"), T("std"), T("cout"), T("include"), T("INT_MAX"), T("iostream"), T("MAX_RAND"), T("NULL"), T("string"), T("id") }; for (i = 0; i < numElementsInArray (reservedWords); ++i) if (n == reservedWords[i]) n << '_'; return n; } //============================================================================== const String floatToCode (const float v) { String s ((double) (float) v, 4); if (s.containsChar ('.')) s << 'f'; else s << ".0f"; return s; } const String doubleToCode (const double v) { String s (v, 7); if (! s.containsChar ('.')) s << ".0"; return s; } const String boolToCode (const bool b) { return b ? "true" : "false"; } const String colourToCode (const Colour& col) { const Colour colours[] = { #define COL(col) Colours::col, #include "jucer_Colours.h" #undef COL Colours::transparentBlack }; static const char* colourNames[] = { #define COL(col) #col, #include "jucer_Colours.h" #undef COL 0 }; for (int i = 0; i < numElementsInArray (colourNames) - 1; ++i) if (col == colours[i]) return "Colours::" + String (colourNames[i]); return "Colour (0x" + hexString8Digits ((int) col.getARGB()) + ')'; } const String justificationToCode (const Justification& justification) { switch (justification.getFlags()) { case Justification::centred: return "Justification::centred"; case Justification::centredLeft: return "Justification::centredLeft"; case Justification::centredRight: return "Justification::centredRight"; case Justification::centredTop: return "Justification::centredTop"; case Justification::centredBottom: return "Justification::centredBottom"; case Justification::topLeft: return "Justification::topLeft"; case Justification::topRight: return "Justification::topRight"; case Justification::bottomLeft: return "Justification::bottomLeft"; case Justification::bottomRight: return "Justification::bottomRight"; case Justification::left: return "Justification::left"; case Justification::right: return "Justification::right"; case Justification::horizontallyCentred: return "Justification::horizontallyCentred"; case Justification::top: return "Justification::top"; case Justification::bottom: return "Justification::bottom"; case Justification::verticallyCentred: return "Justification::verticallyCentred"; case Justification::horizontallyJustified: return "Justification::horizontallyJustified"; default: jassertfalse; break; } return "Justification (" + String (justification.getFlags()) + ")"; } const String castToFloat (const String& expression) { if (expression.containsOnly ("0123456789.f")) { String s (expression.getFloatValue()); if (s.containsChar (T('.'))) return s + "f"; return s + ".0f"; } return "(float) (" + expression + ")"; } const String indentCode (const String& code, const int numSpaces) { if (numSpaces == 0) return code; const String space (String::repeatedString (" ", numSpaces)); StringArray lines; lines.addLines (code); for (int i = 1; i < lines.size(); ++i) { String s (lines[i].trimEnd()); if (s.isNotEmpty()) s = space + s; lines.set (i, s); } return lines.joinIntoString ("\n"); } int indexOfLineStartingWith (const StringArray& lines, const String& text, int startIndex) { startIndex = jmax (0, startIndex); while (startIndex < lines.size()) { if (lines[startIndex].trimStart().startsWithIgnoreCase (text)) return startIndex; ++startIndex; } return -1; } //============================================================================== const char* Coordinate::parentLeftMarkerName = "parent.left"; const char* Coordinate::parentRightMarkerName = "parent.right"; const char* Coordinate::parentTopMarkerName = "parent.top"; const char* Coordinate::parentBottomMarkerName = "parent.bottom"; Coordinate::Coordinate (bool isHorizontal_) : value (0), isProportion (false), isHorizontal (isHorizontal_) { } Coordinate::Coordinate (double absoluteDistanceFromOrigin, bool isHorizontal_) : value (absoluteDistanceFromOrigin), isProportion (false), isHorizontal (isHorizontal_) { } Coordinate::Coordinate (double absoluteDistance, const String& source, bool isHorizontal_) : anchor1 (source), value (absoluteDistance), isProportion (false), isHorizontal (isHorizontal_) { } Coordinate::Coordinate (double relativeProportion, const String& pos1, const String& pos2, bool isHorizontal_) : anchor1 (pos1), anchor2 (pos2), value (relativeProportion), isProportion (true), isHorizontal (isHorizontal_) { } Coordinate::~Coordinate() { } const Coordinate Coordinate::getAnchorPoint1() const { return Coordinate (0.0, anchor1, isHorizontal); } const Coordinate Coordinate::getAnchorPoint2() const { return Coordinate (0.0, anchor2, isHorizontal); } bool Coordinate::isOrigin (const String& name) { return name.isEmpty() || name == parentLeftMarkerName || name == parentTopMarkerName; } const String Coordinate::getOriginMarkerName() const { return isHorizontal ? parentLeftMarkerName : parentTopMarkerName; } const String Coordinate::getExtentMarkerName() const { return isHorizontal ? parentRightMarkerName : parentBottomMarkerName; } const String Coordinate::checkName (const String& name) const { return name.isEmpty() ? getOriginMarkerName() : name; } double Coordinate::getPosition (const String& name, MarkerResolver& markerResolver, int recursionCounter) const { if (isOrigin (name)) return 0.0; return markerResolver.findMarker (name, isHorizontal) .resolve (markerResolver, recursionCounter + 1); } struct RecursivePositionException { }; double Coordinate::resolve (MarkerResolver& markerResolver, int recursionCounter) const { if (recursionCounter > 100) { jassertfalse throw RecursivePositionException(); } const double pos1 = getPosition (anchor1, markerResolver, recursionCounter); return isProportion ? pos1 + (getPosition (anchor2, markerResolver, recursionCounter) - pos1) * value : pos1 + value; } double Coordinate::resolve (MarkerResolver& markerResolver) const { try { return resolve (markerResolver, 0); } catch (RecursivePositionException&) {} return 0.0; } void Coordinate::moveToAbsolute (double newPos, MarkerResolver& markerResolver) { try { const double pos1 = getPosition (anchor1, markerResolver, 0); if (isProportion) { const double size = getPosition (anchor2, markerResolver, 0) - pos1; if (size != 0) value = (newPos - pos1) / size; } else { value = newPos - pos1; } } catch (RecursivePositionException&) {} } bool Coordinate::isRecursive (MarkerResolver& markerResolver) const { try { resolve (markerResolver, 0); } catch (RecursivePositionException&) { return true; } return false; } void Coordinate::skipWhitespace (const String& s, int& i) { while (CharacterFunctions::isWhitespace (s[i])) ++i; } const String Coordinate::readMarkerName (const String& s, int& i) { skipWhitespace (s, i); if (CharacterFunctions::isLetter (s[i]) || s[i] == '_') { int start = i; while (CharacterFunctions::isLetterOrDigit (s[i]) || s[i] == '_' || s[i] == '.') ++i; return s.substring (start, i); } return String::empty; } double Coordinate::readNumber (const String& s, int& i) { skipWhitespace (s, i); int start = i; if (CharacterFunctions::isDigit (s[i]) || s[i] == '.' || s[i] == '-') ++i; while (CharacterFunctions::isDigit (s[i]) || s[i] == '.') ++i; if ((s[i] == 'e' || s[i] == 'E') && (CharacterFunctions::isDigit (s[i + 1]) || s[i + 1] == '-' || s[i + 1] == '+')) { i += 2; while (CharacterFunctions::isDigit (s[i])) ++i; } const double value = s.substring (start, i).getDoubleValue(); while (CharacterFunctions::isWhitespace (s[i]) || s[i] == ',') ++i; return value; } Coordinate::Coordinate (const String& s, bool isHorizontal_) : value (0), isProportion (false), isHorizontal (isHorizontal_) { int i = 0; anchor1 = readMarkerName (s, i); if (anchor1.isNotEmpty()) { skipWhitespace (s, i); if (s[i] == '+') value = readNumber (s, ++i); else if (s[i] == '-') value = -readNumber (s, ++i); } else { value = readNumber (s, i); skipWhitespace (s, i); if (s[i] == '%') { isProportion = true; value /= 100.0; skipWhitespace (s, ++i); if (s[i] == '*') { anchor1 = readMarkerName (s, ++i); skipWhitespace (s, i); if (s[i] == '-' && s[i + 1] == '>') { i += 2; anchor2 = readMarkerName (s, i); } else { anchor2 = anchor1; anchor1 = getOriginMarkerName(); } } else { anchor1 = getOriginMarkerName(); anchor2 = getExtentMarkerName(); } } } } const String Coordinate::toString() const { if (isProportion) { const String percent (value * 100.0); if (isOrigin (anchor1)) { if (anchor2 == parentRightMarkerName || anchor2 == parentBottomMarkerName) return percent + "%"; else return percent + "% * " + checkName (anchor2); } else return percent + "% * " + checkName (anchor1) + " -> " + checkName (anchor2); } else { if (isOrigin (anchor1)) return String (value); else if (value > 0) return checkName (anchor1) + " + " + String (value); else if (value < 0) return checkName (anchor1) + " - " + String (-value); else return checkName (anchor1); } } //============================================================================== RectangleCoordinates::RectangleCoordinates() : left (true), right (true), top (false), bottom (false) { } RectangleCoordinates::RectangleCoordinates (const Rectangle& rect) : left (rect.getX(), true), right (rect.getWidth(), "left", true), top (rect.getY(), false), bottom (rect.getHeight(), "top", false) { } RectangleCoordinates::RectangleCoordinates (const String& stringVersion) : left (true), right (true), top (false), bottom (false) { StringArray tokens; tokens.addTokens (stringVersion, ",", String::empty); left = Coordinate (tokens [0], true); top = Coordinate (tokens [1], false); right = Coordinate (tokens [2], true); bottom = Coordinate (tokens [3], false); } bool RectangleCoordinates::isRecursive (Coordinate::MarkerResolver& markerResolver) const { return left.isRecursive (markerResolver) || right.isRecursive (markerResolver) || top.isRecursive (markerResolver) || bottom.isRecursive (markerResolver); } const Rectangle RectangleCoordinates::resolve (Coordinate::MarkerResolver& markerResolver) const { const int l = roundToInt (left.resolve (markerResolver)); const int r = roundToInt (right.resolve (markerResolver)); const int t = roundToInt (top.resolve (markerResolver)); const int b = roundToInt (bottom.resolve (markerResolver)); return Rectangle (l, t, r - l, b - t); } void RectangleCoordinates::moveToAbsolute (const Rectangle& newPos, Coordinate::MarkerResolver& markerResolver) { left.moveToAbsolute (newPos.getX(), markerResolver); right.moveToAbsolute (newPos.getRight(), markerResolver); top.moveToAbsolute (newPos.getY(), markerResolver); bottom.moveToAbsolute (newPos.getBottom(), markerResolver); } const String RectangleCoordinates::toString() const { return left.toString() + ", " + top.toString() + ", " + right.toString() + ", " + bottom.toString(); }