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.

457 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../../Application/jucer_Headers.h"
  20. #ifdef BUILDING_JUCE_COMPILEENGINE
  21. const char* getPreferredLinefeed() { return "\r\n"; }
  22. #endif
  23. //==============================================================================
  24. String joinLinesIntoSourceFile (StringArray& lines)
  25. {
  26. while (lines.size() > 10 && lines [lines.size() - 1].isEmpty())
  27. lines.remove (lines.size() - 1);
  28. return lines.joinIntoString (getPreferredLinefeed()) + getPreferredLinefeed();
  29. }
  30. String trimCommentCharsFromStartOfLine (const String& line)
  31. {
  32. return line.trimStart().trimCharactersAtStart ("*/").trimStart();
  33. }
  34. String createAlphaNumericUID()
  35. {
  36. String uid;
  37. const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  38. Random r;
  39. uid << chars[r.nextInt (52)]; // make sure the first character is always a letter
  40. for (int i = 5; --i >= 0;)
  41. {
  42. r.setSeedRandomly();
  43. uid << chars [r.nextInt (62)];
  44. }
  45. return uid;
  46. }
  47. String hexString8Digits (int value)
  48. {
  49. return String::toHexString (value).paddedLeft ('0', 8);
  50. }
  51. String createGUID (const String& seed)
  52. {
  53. auto hex = MD5 ((seed + "_guidsalt").toUTF8()).toHexString().toUpperCase();
  54. return "{" + hex.substring (0, 8)
  55. + "-" + hex.substring (8, 12)
  56. + "-" + hex.substring (12, 16)
  57. + "-" + hex.substring (16, 20)
  58. + "-" + hex.substring (20, 32)
  59. + "}";
  60. }
  61. String escapeSpaces (const String& s)
  62. {
  63. return s.replace (" ", "\\ ");
  64. }
  65. String addQuotesIfContainsSpaces (const String& text)
  66. {
  67. return (text.containsChar (' ') && ! text.isQuotedString()) ? text.quoted() : text;
  68. }
  69. void setValueIfVoid (Value value, const var& defaultValue)
  70. {
  71. if (value.getValue().isVoid())
  72. value = defaultValue;
  73. }
  74. //==============================================================================
  75. StringPairArray parsePreprocessorDefs (const String& text)
  76. {
  77. StringPairArray result;
  78. auto s = text.getCharPointer();
  79. while (! s.isEmpty())
  80. {
  81. String token, value;
  82. s = s.findEndOfWhitespace();
  83. while ((! s.isEmpty()) && *s != '=' && ! s.isWhitespace())
  84. token << s.getAndAdvance();
  85. s = s.findEndOfWhitespace();
  86. if (*s == '=')
  87. {
  88. ++s;
  89. while ((! s.isEmpty()) && *s == ' ')
  90. ++s;
  91. while ((! s.isEmpty()) && ! s.isWhitespace())
  92. {
  93. if (*s == ',')
  94. {
  95. ++s;
  96. break;
  97. }
  98. if (*s == '\\' && (s[1] == ' ' || s[1] == ','))
  99. ++s;
  100. value << s.getAndAdvance();
  101. }
  102. }
  103. if (token.isNotEmpty())
  104. result.set (token, value);
  105. }
  106. return result;
  107. }
  108. StringPairArray mergePreprocessorDefs (StringPairArray inheritedDefs, const StringPairArray& overridingDefs)
  109. {
  110. for (int i = 0; i < overridingDefs.size(); ++i)
  111. inheritedDefs.set (overridingDefs.getAllKeys()[i], overridingDefs.getAllValues()[i]);
  112. return inheritedDefs;
  113. }
  114. String createGCCPreprocessorFlags (const StringPairArray& defs)
  115. {
  116. String s;
  117. for (int i = 0; i < defs.size(); ++i)
  118. {
  119. auto def = defs.getAllKeys()[i];
  120. auto value = defs.getAllValues()[i];
  121. if (value.isNotEmpty())
  122. def << "=" << value;
  123. s += " -D" + def;
  124. }
  125. return s;
  126. }
  127. String replacePreprocessorDefs (const StringPairArray& definitions, String sourceString)
  128. {
  129. for (int i = 0; i < definitions.size(); ++i)
  130. {
  131. const String key (definitions.getAllKeys()[i]);
  132. const String value (definitions.getAllValues()[i]);
  133. sourceString = sourceString.replace ("${" + key + "}", value);
  134. }
  135. return sourceString;
  136. }
  137. StringArray getSearchPathsFromString (const String& searchPath)
  138. {
  139. StringArray s;
  140. s.addTokens (searchPath, ";\r\n", StringRef());
  141. return getCleanedStringArray (s);
  142. }
  143. StringArray getCommaOrWhitespaceSeparatedItems (const String& sourceString)
  144. {
  145. StringArray s;
  146. s.addTokens (sourceString, ", \t\r\n", StringRef());
  147. return getCleanedStringArray (s);
  148. }
  149. StringArray getCleanedStringArray (StringArray s)
  150. {
  151. s.trim();
  152. s.removeEmptyStrings();
  153. return s;
  154. }
  155. //==============================================================================
  156. static bool keyFoundAndNotSequentialDuplicate (XmlElement* xml, const String& key)
  157. {
  158. forEachXmlChildElementWithTagName (*xml, element, "key")
  159. {
  160. if (element->getAllSubText().trim().equalsIgnoreCase (key))
  161. {
  162. if (element->getNextElement() != nullptr && element->getNextElement()->hasTagName ("key"))
  163. {
  164. // found broken plist format (sequential duplicate), fix by removing
  165. xml->removeChildElement (element, true);
  166. return false;
  167. }
  168. // key found (not sequential duplicate)
  169. return true;
  170. }
  171. }
  172. // key not found
  173. return false;
  174. }
  175. static bool addKeyIfNotFound (XmlElement* xml, const String& key)
  176. {
  177. if (! keyFoundAndNotSequentialDuplicate (xml, key))
  178. {
  179. xml->createNewChildElement ("key")->addTextElement (key);
  180. return true;
  181. }
  182. return false;
  183. }
  184. void addPlistDictionaryKey (XmlElement* xml, const String& key, const String& value)
  185. {
  186. if (addKeyIfNotFound (xml, key))
  187. xml->createNewChildElement ("string")->addTextElement (value);
  188. }
  189. void addPlistDictionaryKeyBool (XmlElement* xml, const String& key, const bool value)
  190. {
  191. if (addKeyIfNotFound (xml, key))
  192. xml->createNewChildElement (value ? "true" : "false");
  193. }
  194. void addPlistDictionaryKeyInt (XmlElement* xml, const String& key, int value)
  195. {
  196. if (addKeyIfNotFound (xml, key))
  197. xml->createNewChildElement ("integer")->addTextElement (String (value));
  198. }
  199. //==============================================================================
  200. void autoScrollForMouseEvent (const MouseEvent& e, bool scrollX, bool scrollY)
  201. {
  202. if (Viewport* const viewport = e.eventComponent->findParentComponentOfClass<Viewport>())
  203. {
  204. const MouseEvent e2 (e.getEventRelativeTo (viewport));
  205. viewport->autoScroll (scrollX ? e2.x : 20, scrollY ? e2.y : 20, 8, 16);
  206. }
  207. }
  208. //==============================================================================
  209. int indexOfLineStartingWith (const StringArray& lines, const String& text, int index)
  210. {
  211. const int len = text.length();
  212. for (const String* i = lines.begin() + index, * const e = lines.end(); i < e; ++i)
  213. {
  214. if (CharacterFunctions::compareUpTo (i->getCharPointer().findEndOfWhitespace(),
  215. text.getCharPointer(), len) == 0)
  216. return index;
  217. ++index;
  218. }
  219. return -1;
  220. }
  221. //==============================================================================
  222. bool fileNeedsCppSyntaxHighlighting (const File& file)
  223. {
  224. if (file.hasFileExtension (sourceOrHeaderFileExtensions))
  225. return true;
  226. // This is a bit of a bodge to deal with libc++ headers with no extension..
  227. char fileStart[128] = { 0 };
  228. FileInputStream fin (file);
  229. fin.read (fileStart, sizeof (fileStart) - 4);
  230. return CharPointer_UTF8::isValidString (fileStart, sizeof (fileStart))
  231. && String (fileStart).trimStart().startsWith ("// -*- C++ -*-");
  232. }
  233. //==============================================================================
  234. StringArray getJUCEModules() noexcept
  235. {
  236. static StringArray juceModuleIds =
  237. {
  238. "juce_analytics",
  239. "juce_audio_basics",
  240. "juce_audio_devices",
  241. "juce_audio_formats",
  242. "juce_audio_plugin_client",
  243. "juce_audio_processors",
  244. "juce_audio_utils",
  245. "juce_blocks_basics",
  246. "juce_box2d",
  247. "juce_core",
  248. "juce_cryptography",
  249. "juce_data_structures",
  250. "juce_dsp",
  251. "juce_events",
  252. "juce_graphics",
  253. "juce_gui_basics",
  254. "juce_gui_extra",
  255. "juce_opengl",
  256. "juce_osc",
  257. "juce_product_unlocking",
  258. "juce_video"
  259. };
  260. return juceModuleIds;
  261. }
  262. bool isJUCEModule (const String& moduleID) noexcept
  263. {
  264. return getJUCEModules().contains (moduleID);
  265. }
  266. StringArray getModulesRequiredForConsole() noexcept
  267. {
  268. return { "juce_core",
  269. "juce_data_structures",
  270. "juce_events"
  271. };
  272. }
  273. StringArray getModulesRequiredForComponent() noexcept
  274. {
  275. return { "juce_core",
  276. "juce_data_structures",
  277. "juce_events",
  278. "juce_graphics",
  279. "juce_gui_basics"
  280. };
  281. }
  282. StringArray getModulesRequiredForAudioProcessor() noexcept
  283. {
  284. return { "juce_audio_basics",
  285. "juce_audio_devices",
  286. "juce_audio_formats",
  287. "juce_audio_plugin_client",
  288. "juce_audio_processors",
  289. "juce_audio_utils",
  290. "juce_core",
  291. "juce_data_structures",
  292. "juce_events",
  293. "juce_graphics",
  294. "juce_gui_basics",
  295. "juce_gui_extra"
  296. };
  297. }
  298. bool isPIPFile (const File& file) noexcept
  299. {
  300. for (auto line : StringArray::fromLines (file.loadFileAsString()))
  301. {
  302. auto trimmedLine = trimCommentCharsFromStartOfLine (line);
  303. if (trimmedLine.startsWith ("BEGIN_JUCE_PIP_METADATA"))
  304. return true;
  305. }
  306. return false;
  307. }
  308. bool isValidJUCEExamplesDirectory (const File& directory) noexcept
  309. {
  310. if (! directory.exists() || ! directory.isDirectory() || ! directory.containsSubDirectories())
  311. return false;
  312. return directory.getChildFile ("Assets").getChildFile ("juce_icon.png").existsAsFile();
  313. }
  314. //==============================================================================
  315. static var parseJUCEHeaderMetadata (const StringArray& lines)
  316. {
  317. auto* o = new DynamicObject();
  318. var result (o);
  319. for (auto& line : lines)
  320. {
  321. line = trimCommentCharsFromStartOfLine (line);
  322. auto colon = line.indexOfChar (':');
  323. if (colon >= 0)
  324. {
  325. auto key = line.substring (0, colon).trim();
  326. auto value = line.substring (colon + 1).trim();
  327. o->setProperty (key, value);
  328. }
  329. }
  330. return result;
  331. }
  332. static String parseMetadataItem (const StringArray& lines, int& index)
  333. {
  334. String result = lines[index++];
  335. while (index < lines.size())
  336. {
  337. auto continuationLine = trimCommentCharsFromStartOfLine (lines[index]);
  338. if (continuationLine.isEmpty() || continuationLine.indexOfChar (':') != -1
  339. || continuationLine.startsWith ("END_JUCE_"))
  340. break;
  341. result += " " + continuationLine;
  342. ++index;
  343. }
  344. return result;
  345. }
  346. var parseJUCEHeaderMetadata (const File& file)
  347. {
  348. StringArray lines;
  349. file.readLines (lines);
  350. for (int i = 0; i < lines.size(); ++i)
  351. {
  352. auto trimmedLine = trimCommentCharsFromStartOfLine (lines[i]);
  353. if (trimmedLine.startsWith ("BEGIN_JUCE_"))
  354. {
  355. StringArray desc;
  356. auto j = i + 1;
  357. while (j < lines.size())
  358. {
  359. if (trimCommentCharsFromStartOfLine (lines[j]).startsWith ("END_JUCE_"))
  360. return parseJUCEHeaderMetadata (desc);
  361. desc.add (parseMetadataItem (lines, j));
  362. }
  363. }
  364. }
  365. return {};
  366. }