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.

578 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../jucer_Headers.h"
  19. //==============================================================================
  20. int64 calculateStreamHashCode (InputStream& in)
  21. {
  22. int64 t = 0;
  23. const int bufferSize = 4096;
  24. HeapBlock <uint8> buffer;
  25. buffer.malloc (bufferSize);
  26. for (;;)
  27. {
  28. const int num = in.read (buffer, bufferSize);
  29. if (num <= 0)
  30. break;
  31. for (int i = 0; i < num; ++i)
  32. t = t * 65599 + buffer[i];
  33. }
  34. return t;
  35. }
  36. int64 calculateFileHashCode (const File& file)
  37. {
  38. ScopedPointer <FileInputStream> stream (file.createInputStream());
  39. return stream != 0 ? calculateStreamHashCode (*stream) : 0;
  40. }
  41. bool areFilesIdentical (const File& file1, const File& file2)
  42. {
  43. return file1.getSize() == file2.getSize()
  44. && calculateFileHashCode (file1) == calculateFileHashCode (file2);
  45. }
  46. bool overwriteFileWithNewDataIfDifferent (const File& file, const char* data, int numBytes)
  47. {
  48. if (file.getSize() == numBytes)
  49. {
  50. MemoryInputStream newStream (data, numBytes, false);
  51. if (calculateStreamHashCode (newStream) == calculateFileHashCode (file))
  52. return true;
  53. }
  54. TemporaryFile temp (file);
  55. return temp.getFile().appendData (data, numBytes)
  56. && temp.overwriteTargetFileWithTemporary();
  57. }
  58. bool overwriteFileWithNewDataIfDifferent (const File& file, const MemoryOutputStream& newData)
  59. {
  60. return overwriteFileWithNewDataIfDifferent (file, newData.getData(), newData.getDataSize());
  61. }
  62. bool overwriteFileWithNewDataIfDifferent (const File& file, const String& newData)
  63. {
  64. return overwriteFileWithNewDataIfDifferent (file, newData.toUTF8(), strlen ((const char*) newData.toUTF8()));
  65. }
  66. bool containsAnyNonHiddenFiles (const File& folder)
  67. {
  68. DirectoryIterator di (folder, false);
  69. while (di.next())
  70. if (! di.getFile().isHidden())
  71. return true;
  72. return false;
  73. }
  74. //==============================================================================
  75. const int64 hashCode64 (const String& s)
  76. {
  77. return s.hashCode64() + s.length() * s.hashCode() + s.toUpperCase().hashCode();
  78. }
  79. const String createAlphaNumericUID()
  80. {
  81. String uid;
  82. static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  83. Random r (Random::getSystemRandom().nextInt64());
  84. for (int i = 9; --i >= 0;)
  85. {
  86. r.setSeedRandomly();
  87. uid << (juce_wchar) chars [r.nextInt (sizeof (chars))];
  88. }
  89. return uid;
  90. }
  91. const String randomHexString (Random& random, int numChars)
  92. {
  93. String s;
  94. const char hexChars[] = "0123456789ABCDEF";
  95. while (--numChars >= 0)
  96. s << hexChars [random.nextInt (16)];
  97. return s;
  98. }
  99. const String hexString8Digits (int value)
  100. {
  101. return String::toHexString (value).paddedLeft ('0', 8);
  102. }
  103. const String createGUID (const String& seed)
  104. {
  105. String guid;
  106. Random r (hashCode64 (seed + "_jucersalt"));
  107. guid << "{" << randomHexString (r, 8); // (written as separate statements to enforce the order of execution)
  108. guid << "-" << randomHexString (r, 4);
  109. guid << "-" << randomHexString (r, 4);
  110. guid << "-" << randomHexString (r, 4);
  111. guid << "-" << randomHexString (r, 12) << "}";
  112. return guid;
  113. }
  114. const String unixStylePath (const String& path)
  115. {
  116. return path.replaceCharacter ('\\', '/');
  117. }
  118. const String windowsStylePath (const String& path)
  119. {
  120. return path.replaceCharacter ('/', '\\');
  121. }
  122. const String appendPath (const String& path, const String& subpath)
  123. {
  124. if (File::isAbsolutePath (subpath)
  125. || subpath.startsWithChar ('$')
  126. || subpath.startsWithChar ('~')
  127. || (CharacterFunctions::isLetter (subpath[0]) && subpath[1] == ':'))
  128. return subpath.replaceCharacter ('\\', '/');
  129. String path1 (path.replaceCharacter ('\\', '/'));
  130. if (! path1.endsWithChar ('/'))
  131. path1 << '/';
  132. return path1 + subpath.replaceCharacter ('\\', '/');
  133. }
  134. bool shouldPathsBeRelative (String path1, String path2)
  135. {
  136. path1 = unixStylePath (path1);
  137. path2 = unixStylePath (path2);
  138. const int len = jmin (path1.length(), path2.length());
  139. int commonBitLength = 0;
  140. for (int i = 0; i < len; ++i)
  141. {
  142. if (CharacterFunctions::toLowerCase (path1[i]) != CharacterFunctions::toLowerCase (path2[i]))
  143. break;
  144. ++commonBitLength;
  145. }
  146. return path1.substring (0, commonBitLength).removeCharacters ("/:").isNotEmpty();
  147. }
  148. const String createIncludeStatement (const File& includeFile, const File& targetFile)
  149. {
  150. return "#include \"" + unixStylePath (includeFile.getRelativePathFrom (targetFile.getParentDirectory()))
  151. + "\"";
  152. }
  153. const String makeHeaderGuardName (const File& file)
  154. {
  155. return "__" + file.getFileName().toUpperCase()
  156. .replaceCharacters (" .", "__")
  157. .retainCharacters ("_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
  158. + "_"
  159. + String::toHexString (file.hashCode()).toUpperCase()
  160. + "__";
  161. }
  162. //==============================================================================
  163. bool isJuceFolder (const File& folder)
  164. {
  165. return folder.getFileName().containsIgnoreCase ("juce")
  166. && folder.getChildFile ("juce.h").exists()
  167. && folder.getChildFile ("juce_Config.h").exists();
  168. }
  169. static const File lookInFolderForJuceFolder (const File& folder)
  170. {
  171. for (DirectoryIterator di (folder, false, "*juce*", File::findDirectories); di.next();)
  172. {
  173. if (isJuceFolder (di.getFile()))
  174. return di.getFile();
  175. }
  176. return File::nonexistent;
  177. }
  178. const File findParentJuceFolder (const File& file)
  179. {
  180. File f (file);
  181. while (f.exists() && f.getParentDirectory() != f)
  182. {
  183. if (isJuceFolder (f))
  184. return f;
  185. File found = lookInFolderForJuceFolder (f);
  186. if (found.exists())
  187. return found;
  188. f = f.getParentDirectory();
  189. }
  190. return File::nonexistent;
  191. }
  192. const File findDefaultJuceFolder()
  193. {
  194. File f = findParentJuceFolder (File::getSpecialLocation (File::currentApplicationFile));
  195. if (! f.exists())
  196. f = lookInFolderForJuceFolder (File::getSpecialLocation (File::userHomeDirectory));
  197. if (! f.exists())
  198. f = lookInFolderForJuceFolder (File::getSpecialLocation (File::userDocumentsDirectory));
  199. return f;
  200. }
  201. //==============================================================================
  202. const String replaceCEscapeChars (const String& s)
  203. {
  204. const int len = s.length();
  205. String r;
  206. r.preallocateStorage (len + 2);
  207. bool lastWasHexEscapeCode = false;
  208. for (int i = 0; i < len; ++i)
  209. {
  210. const tchar c = s[i];
  211. switch (c)
  212. {
  213. case '\t': r << "\\t"; lastWasHexEscapeCode = false; break;
  214. case '\r': r << "\\r"; lastWasHexEscapeCode = false; break;
  215. case '\n': r << "\\n"; lastWasHexEscapeCode = false; break;
  216. case '\\': r << "\\\\"; lastWasHexEscapeCode = false; break;
  217. case '\'': r << "\\\'"; lastWasHexEscapeCode = false; break;
  218. case '\"': r << "\\\""; lastWasHexEscapeCode = false; break;
  219. default:
  220. if (c < 128
  221. && ! (lastWasHexEscapeCode
  222. && String ("0123456789abcdefABCDEF").containsChar (c))) // (have to avoid following a hex escape sequence with a valid hex digit)
  223. {
  224. r << c;
  225. lastWasHexEscapeCode = false;
  226. }
  227. else
  228. {
  229. r << "\\x" << String::toHexString ((int) c);
  230. lastWasHexEscapeCode = true;
  231. }
  232. break;
  233. }
  234. }
  235. return r;
  236. }
  237. //==============================================================================
  238. const String makeValidCppIdentifier (String s,
  239. const bool capitalise,
  240. const bool removeColons,
  241. const bool allowTemplates)
  242. {
  243. if (removeColons)
  244. s = s.replaceCharacters (".,;:/@", "______");
  245. else
  246. s = s.replaceCharacters (".,;/@", "_____");
  247. int i;
  248. for (i = s.length(); --i > 0;)
  249. if (CharacterFunctions::isLetter (s[i])
  250. && CharacterFunctions::isLetter (s[i - 1])
  251. && CharacterFunctions::isUpperCase (s[i])
  252. && ! CharacterFunctions::isUpperCase (s[i - 1]))
  253. s = s.substring (0, i) + " " + s.substring (i);
  254. String allowedChars ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_ 0123456789");
  255. if (allowTemplates)
  256. allowedChars += "<>";
  257. if (! removeColons)
  258. allowedChars += ":";
  259. StringArray words;
  260. words.addTokens (s.retainCharacters (allowedChars), false);
  261. words.trim();
  262. String n (words[0]);
  263. if (capitalise)
  264. n = n.toLowerCase();
  265. for (i = 1; i < words.size(); ++i)
  266. {
  267. if (capitalise && words[i].length() > 1)
  268. n << words[i].substring (0, 1).toUpperCase()
  269. << words[i].substring (1).toLowerCase();
  270. else
  271. n << words[i];
  272. }
  273. if (CharacterFunctions::isDigit (n[0]))
  274. n = "_" + n;
  275. // make sure it's not a reserved c++ keyword..
  276. static const char* const reservedWords[] =
  277. {
  278. "auto", "const", "double", "float", "int", "short", "struct",
  279. "return", "static", "union", "while", "asm", "dynamic_cast",
  280. "unsigned", "break", "continue", "else", "for", "long", "signed",
  281. "switch", "void", "case", "default", "enum", "goto", "register",
  282. "sizeof", "typedef", "volatile", "char", "do", "extern", "if",
  283. "namespace", "reinterpret_cast", "try", "bool", "explicit", "new",
  284. "static_cast", "typeid", "catch", "false", "operator", "template",
  285. "typename", "class", "friend", "private", "this", "using", "const_cast",
  286. "inline", "public", "throw", "virtual", "delete", "mutable", "protected",
  287. "true", "wchar_t", "and", "bitand", "compl", "not_eq", "or_eq",
  288. "xor_eq", "and_eq", "bitor", "not", "or", "xor", "cin", "endl",
  289. "INT_MIN", "iomanip", "main", "npos", "std", "cout", "include",
  290. "INT_MAX", "iostream", "MAX_RAND", "NULL", "string", "id", "std"
  291. };
  292. for (i = 0; i < numElementsInArray (reservedWords); ++i)
  293. if (n == reservedWords[i])
  294. n << '_';
  295. return n;
  296. }
  297. //==============================================================================
  298. const String floatToCode (const float v)
  299. {
  300. String s ((double) (float) v, 4);
  301. if (s.containsChar ('.'))
  302. s << 'f';
  303. else
  304. s << ".0f";
  305. return s;
  306. }
  307. const String doubleToCode (const double v)
  308. {
  309. String s (v, 7);
  310. if (! s.containsChar ('.'))
  311. s << ".0";
  312. return s;
  313. }
  314. const String boolToCode (const bool b)
  315. {
  316. return b ? "true" : "false";
  317. }
  318. const String colourToCode (const Colour& col)
  319. {
  320. const Colour colours[] =
  321. {
  322. #define COL(col) Colours::col,
  323. #include "jucer_Colours.h"
  324. #undef COL
  325. Colours::transparentBlack
  326. };
  327. static const char* colourNames[] =
  328. {
  329. #define COL(col) #col,
  330. #include "jucer_Colours.h"
  331. #undef COL
  332. 0
  333. };
  334. for (int i = 0; i < numElementsInArray (colourNames) - 1; ++i)
  335. if (col == colours[i])
  336. return "Colours::" + String (colourNames[i]);
  337. return "Colour (0x" + hexString8Digits ((int) col.getARGB()) + ')';
  338. }
  339. const String justificationToCode (const Justification& justification)
  340. {
  341. switch (justification.getFlags())
  342. {
  343. case Justification::centred: return "Justification::centred";
  344. case Justification::centredLeft: return "Justification::centredLeft";
  345. case Justification::centredRight: return "Justification::centredRight";
  346. case Justification::centredTop: return "Justification::centredTop";
  347. case Justification::centredBottom: return "Justification::centredBottom";
  348. case Justification::topLeft: return "Justification::topLeft";
  349. case Justification::topRight: return "Justification::topRight";
  350. case Justification::bottomLeft: return "Justification::bottomLeft";
  351. case Justification::bottomRight: return "Justification::bottomRight";
  352. case Justification::left: return "Justification::left";
  353. case Justification::right: return "Justification::right";
  354. case Justification::horizontallyCentred: return "Justification::horizontallyCentred";
  355. case Justification::top: return "Justification::top";
  356. case Justification::bottom: return "Justification::bottom";
  357. case Justification::verticallyCentred: return "Justification::verticallyCentred";
  358. case Justification::horizontallyJustified: return "Justification::horizontallyJustified";
  359. default: jassertfalse; break;
  360. }
  361. return "Justification (" + String (justification.getFlags()) + ")";
  362. }
  363. const String castToFloat (const String& expression)
  364. {
  365. if (expression.containsOnly ("0123456789.f"))
  366. {
  367. String s (expression.getFloatValue());
  368. if (s.containsChar (T('.')))
  369. return s + "f";
  370. return s + ".0f";
  371. }
  372. return "(float) (" + expression + ")";
  373. }
  374. const String indentCode (const String& code, const int numSpaces)
  375. {
  376. if (numSpaces == 0)
  377. return code;
  378. const String space (String::repeatedString (" ", numSpaces));
  379. StringArray lines;
  380. lines.addLines (code);
  381. for (int i = 1; i < lines.size(); ++i)
  382. {
  383. String s (lines[i].trimEnd());
  384. if (s.isNotEmpty())
  385. s = space + s;
  386. lines.set (i, s);
  387. }
  388. return lines.joinIntoString ("\n");
  389. }
  390. int indexOfLineStartingWith (const StringArray& lines, const String& text, int startIndex)
  391. {
  392. startIndex = jmax (0, startIndex);
  393. while (startIndex < lines.size())
  394. {
  395. if (lines[startIndex].trimStart().startsWithIgnoreCase (text))
  396. return startIndex;
  397. ++startIndex;
  398. }
  399. return -1;
  400. }
  401. //==============================================================================
  402. PropertyPanelWithTooltips::PropertyPanelWithTooltips()
  403. : lastComp (0)
  404. {
  405. addAndMakeVisible (panel = new PropertyPanel());
  406. startTimer (150);
  407. }
  408. PropertyPanelWithTooltips::~PropertyPanelWithTooltips()
  409. {
  410. deleteAllChildren();
  411. }
  412. void PropertyPanelWithTooltips::paint (Graphics& g)
  413. {
  414. g.setColour (Colour::greyLevel (0.15f));
  415. g.setFont (13.0f);
  416. TextLayout tl;
  417. tl.appendText (lastTip, Font (14.0f));
  418. tl.layout (getWidth() - 10, Justification::left, true); // try to make it look nice
  419. if (tl.getNumLines() > 3)
  420. tl.layout (getWidth() - 10, Justification::left, false); // too big, so just squash it in..
  421. tl.drawWithin (g, 5, panel->getBottom() + 2, getWidth() - 10,
  422. getHeight() - panel->getBottom() - 4,
  423. Justification::centredLeft);
  424. }
  425. void PropertyPanelWithTooltips::resized()
  426. {
  427. panel->setBounds (0, 0, getWidth(), jmax (getHeight() - 60, proportionOfHeight (0.6f)));
  428. }
  429. void PropertyPanelWithTooltips::timerCallback()
  430. {
  431. Component* const newComp = Desktop::getInstance().getMainMouseSource().getComponentUnderMouse();
  432. if (newComp != lastComp)
  433. {
  434. lastComp = newComp;
  435. String newTip (findTip (newComp));
  436. if (newTip != lastTip)
  437. {
  438. lastTip = newTip;
  439. repaint (0, panel->getBottom(), getWidth(), getHeight());
  440. }
  441. }
  442. }
  443. const String PropertyPanelWithTooltips::findTip (Component* c)
  444. {
  445. while (c != 0 && c != this)
  446. {
  447. TooltipClient* const tc = dynamic_cast <TooltipClient*> (c);
  448. if (tc != 0)
  449. {
  450. const String tip (tc->getTooltip());
  451. if (tip.isNotEmpty())
  452. return tip;
  453. }
  454. c = c->getParentComponent();
  455. }
  456. return String::empty;
  457. }