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.

714 lines
24KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "../../Application/jucer_Headers.h"
  19. #include "../../ProjectSaving/jucer_ProjectSaver.h"
  20. #include "../../ProjectSaving/jucer_ProjectExport_Xcode.h"
  21. #include "../../Application/jucer_Application.h"
  22. //==============================================================================
  23. LibraryModule::LibraryModule (const ModuleDescription& d)
  24. : moduleInfo (d)
  25. {
  26. }
  27. void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out)
  28. {
  29. auto& project = projectSaver.getProject();
  30. auto& modules = project.getEnabledModules();
  31. auto moduleID = getID();
  32. if (modules.shouldCopyModuleFilesLocally (moduleID))
  33. {
  34. auto juceModuleFolder = moduleInfo.getFolder();
  35. auto localModuleFolder = project.getLocalModuleFolder (moduleID);
  36. localModuleFolder.createDirectory();
  37. projectSaver.copyFolder (juceModuleFolder, localModuleFolder);
  38. }
  39. out << "#include <" << moduleInfo.getModuleFolder().getFileName() << "/"
  40. << moduleInfo.getHeader().getFileName()
  41. << ">" << newLine;
  42. }
  43. void LibraryModule::addSearchPathsToExporter (ProjectExporter& exporter) const
  44. {
  45. auto moduleRelativePath = exporter.getModuleFolderRelativeToProject (getID());
  46. exporter.addToExtraSearchPaths (moduleRelativePath.getParentDirectory());
  47. const auto libDirPlatform = [&]() -> String
  48. {
  49. if (exporter.isLinux())
  50. return "Linux";
  51. if (exporter.isCodeBlocks() && exporter.isWindows())
  52. return "MinGW";
  53. return exporter.getTypeInfoForExporter (exporter.getExporterIdentifier()).targetFolder;
  54. }();
  55. auto libSubdirPath = moduleRelativePath.toUnixStyle() + "/libs/" + libDirPlatform;
  56. auto moduleLibDir = exporter.getProject().resolveFilename (libSubdirPath);
  57. if (moduleLibDir.exists())
  58. exporter.addToModuleLibPaths ({ libSubdirPath, moduleRelativePath.getRoot() });
  59. auto extraInternalSearchPaths = moduleInfo.getExtraSearchPaths().trim();
  60. if (extraInternalSearchPaths.isNotEmpty())
  61. {
  62. auto paths = StringArray::fromTokens (extraInternalSearchPaths, true);
  63. for (auto& path : paths)
  64. exporter.addToExtraSearchPaths (moduleRelativePath.getChildFile (path.unquoted()));
  65. }
  66. }
  67. void LibraryModule::addDefinesToExporter (ProjectExporter& exporter) const
  68. {
  69. auto extraDefs = moduleInfo.getPreprocessorDefs().trim();
  70. if (extraDefs.isNotEmpty())
  71. exporter.getExporterPreprocessorDefsValue() = exporter.getExporterPreprocessorDefsString() + "\n" + extraDefs;
  72. }
  73. void LibraryModule::addCompileUnitsToExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
  74. {
  75. auto& project = exporter.getProject();
  76. auto& modules = project.getEnabledModules();
  77. auto moduleID = getID();
  78. auto localModuleFolder = modules.shouldCopyModuleFilesLocally (moduleID) ? project.getLocalModuleFolder (moduleID)
  79. : moduleInfo.getFolder();
  80. Array<File> compiled;
  81. findAndAddCompiledUnits (exporter, &projectSaver, compiled);
  82. if (modules.shouldShowAllModuleFilesInProject (moduleID))
  83. addBrowseableCode (exporter, compiled, localModuleFolder);
  84. }
  85. void LibraryModule::addLibsToExporter (ProjectExporter& exporter) const
  86. {
  87. auto parseAndAddLibsToList = [] (StringArray& libList, const String& libs)
  88. {
  89. libList.addTokens (libs, ", ", {});
  90. libList.trim();
  91. libList.removeDuplicates (false);
  92. };
  93. auto& project = exporter.getProject();
  94. if (exporter.isXcode())
  95. {
  96. auto& xcodeExporter = dynamic_cast<XcodeProjectExporter&> (exporter);
  97. if (project.isAUPluginHost())
  98. {
  99. xcodeExporter.xcodeFrameworks.add ("CoreAudioKit");
  100. if (xcodeExporter.isOSX())
  101. xcodeExporter.xcodeFrameworks.add ("AudioUnit");
  102. }
  103. auto frameworks = moduleInfo.getModuleInfo() [xcodeExporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString();
  104. xcodeExporter.xcodeFrameworks.addTokens (frameworks, ", ", {});
  105. parseAndAddLibsToList (xcodeExporter.xcodeLibs, moduleInfo.getModuleInfo() [exporter.isOSX() ? "OSXLibs" : "iOSLibs"].toString());
  106. }
  107. else if (exporter.isLinux())
  108. {
  109. parseAndAddLibsToList (exporter.linuxLibs, moduleInfo.getModuleInfo() ["linuxLibs"].toString());
  110. parseAndAddLibsToList (exporter.linuxPackages, moduleInfo.getModuleInfo() ["linuxPackages"].toString());
  111. }
  112. else if (exporter.isWindows())
  113. {
  114. if (exporter.isCodeBlocks())
  115. parseAndAddLibsToList (exporter.mingwLibs, moduleInfo.getModuleInfo() ["mingwLibs"].toString());
  116. else
  117. parseAndAddLibsToList (exporter.windowsLibs, moduleInfo.getModuleInfo() ["windowsLibs"].toString());
  118. }
  119. else if (exporter.isAndroid())
  120. {
  121. parseAndAddLibsToList (exporter.androidLibs, moduleInfo.getModuleInfo() ["androidLibs"].toString());
  122. }
  123. }
  124. void LibraryModule::addSettingsForModuleToExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
  125. {
  126. addSearchPathsToExporter (exporter);
  127. addDefinesToExporter (exporter);
  128. addCompileUnitsToExporter (exporter, projectSaver);
  129. addLibsToExporter (exporter);
  130. }
  131. void LibraryModule::getConfigFlags (Project& project, OwnedArray<Project::ConfigFlag>& flags) const
  132. {
  133. auto header = moduleInfo.getHeader();
  134. jassert (header.exists());
  135. StringArray lines;
  136. header.readLines (lines);
  137. for (int i = 0; i < lines.size(); ++i)
  138. {
  139. auto line = lines[i].trim();
  140. if (line.startsWith ("/**") && line.containsIgnoreCase ("Config:"))
  141. {
  142. auto config = std::make_unique<Project::ConfigFlag>();
  143. config->sourceModuleID = getID();
  144. config->symbol = line.fromFirstOccurrenceOf (":", false, false).trim();
  145. if (config->symbol.length() > 2)
  146. {
  147. ++i;
  148. while (! (lines[i].contains ("*/") || lines[i].contains ("@see")))
  149. {
  150. if (lines[i].trim().isNotEmpty())
  151. config->description = config->description.trim() + " " + lines[i].trim();
  152. ++i;
  153. }
  154. config->description = config->description.upToFirstOccurrenceOf ("*/", false, false);
  155. config->value = project.getConfigFlag (config->symbol);
  156. i += 2;
  157. if (lines[i].contains ("#define " + config->symbol))
  158. {
  159. auto value = lines[i].fromFirstOccurrenceOf ("#define " + config->symbol, false, true).trim();
  160. config->value.setDefault (value == "0" ? false : true);
  161. }
  162. auto currentValue = config->value.get().toString();
  163. if (currentValue == "enabled") config->value = true;
  164. else if (currentValue == "disabled") config->value = false;
  165. flags.add (std::move (config));
  166. }
  167. }
  168. }
  169. }
  170. static void addFileWithGroups (Project::Item& group, const build_tools::RelativePath& file, const String& path)
  171. {
  172. auto slash = path.indexOfChar (File::getSeparatorChar());
  173. if (slash >= 0)
  174. {
  175. auto topLevelGroup = path.substring (0, slash);
  176. auto remainingPath = path.substring (slash + 1);
  177. auto newGroup = group.getOrCreateSubGroup (topLevelGroup);
  178. addFileWithGroups (newGroup, file, remainingPath);
  179. }
  180. else
  181. {
  182. if (! group.containsChildForFile (file))
  183. group.addRelativeFile (file, -1, false);
  184. }
  185. }
  186. struct FileSorter
  187. {
  188. static int compareElements (const File& f1, const File& f2)
  189. {
  190. return f1.getFileName().compareNatural (f2.getFileName());
  191. }
  192. };
  193. void LibraryModule::findBrowseableFiles (const File& folder, Array<File>& filesFound) const
  194. {
  195. Array<File> tempList;
  196. FileSorter sorter;
  197. for (const auto& iter : RangedDirectoryIterator (folder, true, "*", File::findFiles))
  198. if (! iter.isHidden() && iter.getFile().hasFileExtension (browseableFileExtensions))
  199. tempList.addSorted (sorter, iter.getFile());
  200. filesFound.addArray (tempList);
  201. }
  202. bool LibraryModule::CompileUnit::isNeededForExporter (ProjectExporter& exporter) const
  203. {
  204. if ((hasSuffix (file, "_OSX") && ! exporter.isOSX())
  205. || (hasSuffix (file, "_iOS") && ! exporter.isiOS())
  206. || (hasSuffix (file, "_Windows") && ! exporter.isWindows())
  207. || (hasSuffix (file, "_Linux") && ! exporter.isLinux())
  208. || (hasSuffix (file, "_Android") && ! exporter.isAndroid()))
  209. return false;
  210. auto targetType = Project::getTargetTypeFromFilePath (file, false);
  211. if (targetType != build_tools::ProjectType::Target::unspecified && ! exporter.shouldBuildTargetType (targetType))
  212. return false;
  213. return exporter.usesMMFiles() ? isCompiledForObjC
  214. : isCompiledForNonObjC;
  215. }
  216. String LibraryModule::CompileUnit::getFilenameForProxyFile() const
  217. {
  218. return "include_" + file.getFileName();
  219. }
  220. bool LibraryModule::CompileUnit::hasSuffix (const File& f, const char* suffix)
  221. {
  222. auto fileWithoutSuffix = f.getFileNameWithoutExtension() + ".";
  223. return fileWithoutSuffix.containsIgnoreCase (suffix + String ("."))
  224. || fileWithoutSuffix.containsIgnoreCase (suffix + String ("_"));
  225. }
  226. Array<LibraryModule::CompileUnit> LibraryModule::getAllCompileUnits (build_tools::ProjectType::Target::Type forTarget) const
  227. {
  228. auto files = getFolder().findChildFiles (File::findFiles, false);
  229. FileSorter sorter;
  230. files.sort (sorter);
  231. Array<LibraryModule::CompileUnit> units;
  232. for (auto& file : files)
  233. {
  234. if (file.getFileName().startsWithIgnoreCase (getID())
  235. && file.hasFileExtension (sourceFileExtensions))
  236. {
  237. if (forTarget == build_tools::ProjectType::Target::unspecified
  238. || forTarget == Project::getTargetTypeFromFilePath (file, true))
  239. {
  240. CompileUnit cu;
  241. cu.file = file;
  242. units.add (std::move (cu));
  243. }
  244. }
  245. }
  246. for (auto& cu : units)
  247. {
  248. cu.isCompiledForObjC = true;
  249. cu.isCompiledForNonObjC = ! cu.file.hasFileExtension ("mm;m;metal");
  250. if (cu.isCompiledForNonObjC)
  251. if (cu.file.withFileExtension ("mm").existsAsFile())
  252. cu.isCompiledForObjC = false;
  253. jassert (cu.isCompiledForObjC || cu.isCompiledForNonObjC);
  254. }
  255. return units;
  256. }
  257. void LibraryModule::findAndAddCompiledUnits (ProjectExporter& exporter,
  258. ProjectSaver* projectSaver,
  259. Array<File>& result,
  260. build_tools::ProjectType::Target::Type forTarget) const
  261. {
  262. for (auto& cu : getAllCompileUnits (forTarget))
  263. {
  264. if (cu.isNeededForExporter (exporter))
  265. {
  266. auto localFile = exporter.getProject().getGeneratedCodeFolder()
  267. .getChildFile (cu.getFilenameForProxyFile());
  268. result.add (localFile);
  269. if (projectSaver != nullptr)
  270. projectSaver->addFileToGeneratedGroup (localFile);
  271. }
  272. }
  273. }
  274. void LibraryModule::addBrowseableCode (ProjectExporter& exporter, const Array<File>& compiled, const File& localModuleFolder) const
  275. {
  276. if (sourceFiles.isEmpty())
  277. findBrowseableFiles (localModuleFolder, sourceFiles);
  278. auto sourceGroup = Project::Item::createGroup (exporter.getProject(), getID(), "__mainsourcegroup" + getID(), false);
  279. auto moduleFromProject = exporter.getModuleFolderRelativeToProject (getID());
  280. auto moduleHeader = moduleInfo.getHeader();
  281. auto& project = exporter.getProject();
  282. if (project.getEnabledModules().shouldCopyModuleFilesLocally (getID()))
  283. moduleHeader = project.getLocalModuleFolder (getID()).getChildFile (moduleHeader.getFileName());
  284. auto isModuleHeader = [&] (const File& f) { return f.getFileName() == moduleHeader.getFileName(); };
  285. for (auto& sourceFile : sourceFiles)
  286. {
  287. auto pathWithinModule = build_tools::getRelativePathFrom (sourceFile, localModuleFolder);
  288. // (Note: in exporters like MSVC we have to avoid adding the same file twice, even if one of those instances
  289. // is flagged as being excluded from the build, because this overrides the other and it fails to compile)
  290. if ((exporter.canCopeWithDuplicateFiles() || ! compiled.contains (sourceFile)) && ! isModuleHeader (sourceFile))
  291. addFileWithGroups (sourceGroup, moduleFromProject.getChildFile (pathWithinModule), pathWithinModule);
  292. }
  293. sourceGroup.sortAlphabetically (true, true);
  294. sourceGroup.addFileAtIndex (moduleHeader, -1, false);
  295. exporter.getModulesGroup().state.appendChild (sourceGroup.state.createCopy(), nullptr);
  296. }
  297. //==============================================================================
  298. EnabledModulesList::EnabledModulesList (Project& p, const ValueTree& s)
  299. : project (p), state (s)
  300. {
  301. }
  302. StringArray EnabledModulesList::getAllModules() const
  303. {
  304. StringArray moduleIDs;
  305. for (int i = 0; i < getNumModules(); ++i)
  306. moduleIDs.add (getModuleID (i));
  307. return moduleIDs;
  308. }
  309. void EnabledModulesList::createRequiredModules (OwnedArray<LibraryModule>& modules)
  310. {
  311. for (int i = 0; i < getNumModules(); ++i)
  312. modules.add (new LibraryModule (getModuleInfo (getModuleID (i))));
  313. }
  314. void EnabledModulesList::sortAlphabetically()
  315. {
  316. struct ModuleTreeSorter
  317. {
  318. static int compareElements (const ValueTree& m1, const ValueTree& m2)
  319. {
  320. return m1[Ids::ID].toString().compareIgnoreCase (m2[Ids::ID]);
  321. }
  322. };
  323. ModuleTreeSorter sorter;
  324. const ScopedLock sl (stateLock);
  325. state.sort (sorter, getUndoManager(), false);
  326. }
  327. File EnabledModulesList::getDefaultModulesFolder() const
  328. {
  329. File globalPath (getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString());
  330. if (globalPath.exists())
  331. return globalPath;
  332. for (auto& exporterPathModule : project.getExporterPathsModulesList().getAllModules())
  333. {
  334. auto f = exporterPathModule.second;
  335. if (f.isDirectory())
  336. return f.getParentDirectory();
  337. }
  338. return File::getCurrentWorkingDirectory();
  339. }
  340. ModuleDescription EnabledModulesList::getModuleInfo (const String& moduleID) const
  341. {
  342. return ModuleDescription (project.getModuleWithID (moduleID).second);
  343. }
  344. bool EnabledModulesList::isModuleEnabled (const String& moduleID) const
  345. {
  346. const ScopedLock sl (stateLock);
  347. return state.getChildWithProperty (Ids::ID, moduleID).isValid();
  348. }
  349. static void getDependencies (Project& project, const String& moduleID, StringArray& dependencies)
  350. {
  351. auto info = project.getEnabledModules().getModuleInfo (moduleID);
  352. for (auto uid : info.getDependencies())
  353. {
  354. if (! dependencies.contains (uid, true))
  355. {
  356. dependencies.add (uid);
  357. getDependencies (project, uid, dependencies);
  358. }
  359. }
  360. }
  361. StringArray EnabledModulesList::getExtraDependenciesNeeded (const String& moduleID) const
  362. {
  363. StringArray dependencies, extraDepsNeeded;
  364. getDependencies (project, moduleID, dependencies);
  365. for (auto dep : dependencies)
  366. if (dep != moduleID && ! isModuleEnabled (dep))
  367. extraDepsNeeded.add (dep);
  368. return extraDepsNeeded;
  369. }
  370. bool EnabledModulesList::tryToFixMissingDependencies (const String& moduleID)
  371. {
  372. auto copyLocally = areMostModulesCopiedLocally();
  373. auto useGlobalPath = areMostModulesUsingGlobalPath();
  374. StringArray missing;
  375. for (auto missingModule : getExtraDependenciesNeeded (moduleID))
  376. {
  377. auto mod = project.getModuleWithID (missingModule);
  378. if (mod.second != File())
  379. addModule (mod.second, copyLocally, useGlobalPath);
  380. else
  381. missing.add (missingModule);
  382. }
  383. return (missing.size() == 0);
  384. }
  385. bool EnabledModulesList::doesModuleHaveHigherCppStandardThanProject (const String& moduleID) const
  386. {
  387. auto projectCppStandard = project.getCppStandardString();
  388. if (projectCppStandard == Project::getCppStandardVars().getLast().toString())
  389. return false;
  390. auto moduleCppStandard = getModuleInfo (moduleID).getMinimumCppStandard();
  391. return (moduleCppStandard.getIntValue() > projectCppStandard.getIntValue());
  392. }
  393. bool EnabledModulesList::shouldUseGlobalPath (const String& moduleID) const
  394. {
  395. const ScopedLock sl (stateLock);
  396. return (bool) shouldUseGlobalPathValue (moduleID).getValue();
  397. }
  398. Value EnabledModulesList::shouldUseGlobalPathValue (const String& moduleID) const
  399. {
  400. const ScopedLock sl (stateLock);
  401. return state.getChildWithProperty (Ids::ID, moduleID)
  402. .getPropertyAsValue (Ids::useGlobalPath, getUndoManager());
  403. }
  404. bool EnabledModulesList::shouldShowAllModuleFilesInProject (const String& moduleID) const
  405. {
  406. return (bool) shouldShowAllModuleFilesInProjectValue (moduleID).getValue();
  407. }
  408. Value EnabledModulesList::shouldShowAllModuleFilesInProjectValue (const String& moduleID) const
  409. {
  410. const ScopedLock sl (stateLock);
  411. return state.getChildWithProperty (Ids::ID, moduleID)
  412. .getPropertyAsValue (Ids::showAllCode, getUndoManager());
  413. }
  414. bool EnabledModulesList::shouldCopyModuleFilesLocally (const String& moduleID) const
  415. {
  416. return (bool) shouldCopyModuleFilesLocallyValue (moduleID).getValue();
  417. }
  418. Value EnabledModulesList::shouldCopyModuleFilesLocallyValue (const String& moduleID) const
  419. {
  420. const ScopedLock sl (stateLock);
  421. return state.getChildWithProperty (Ids::ID, moduleID)
  422. .getPropertyAsValue (Ids::useLocalCopy, getUndoManager());
  423. }
  424. bool EnabledModulesList::areMostModulesUsingGlobalPath() const
  425. {
  426. int numYes = 0, numNo = 0;
  427. for (auto i = getNumModules(); --i >= 0;)
  428. {
  429. if (shouldUseGlobalPath (getModuleID (i)))
  430. ++numYes;
  431. else
  432. ++numNo;
  433. }
  434. return numYes > numNo;
  435. }
  436. bool EnabledModulesList::areMostModulesCopiedLocally() const
  437. {
  438. int numYes = 0, numNo = 0;
  439. for (auto i = getNumModules(); --i >= 0;)
  440. {
  441. if (shouldCopyModuleFilesLocally (getModuleID (i)))
  442. ++numYes;
  443. else
  444. ++numNo;
  445. }
  446. return numYes > numNo;
  447. }
  448. StringArray EnabledModulesList::getModulesWithHigherCppStandardThanProject() const
  449. {
  450. StringArray list;
  451. for (auto& module : getAllModules())
  452. if (doesModuleHaveHigherCppStandardThanProject (module))
  453. list.add (module);
  454. return list;
  455. }
  456. StringArray EnabledModulesList::getModulesWithMissingDependencies() const
  457. {
  458. StringArray list;
  459. for (auto& module : getAllModules())
  460. if (getExtraDependenciesNeeded (module).size() > 0)
  461. list.add (module);
  462. return list;
  463. }
  464. String EnabledModulesList::getHighestModuleCppStandard() const
  465. {
  466. auto highestCppStandard = Project::getCppStandardVars()[0].toString();
  467. for (auto& mod : getAllModules())
  468. {
  469. auto moduleCppStandard = getModuleInfo (mod).getMinimumCppStandard();
  470. if (moduleCppStandard == "latest")
  471. return moduleCppStandard;
  472. if (moduleCppStandard.getIntValue() > highestCppStandard.getIntValue())
  473. highestCppStandard = moduleCppStandard;
  474. }
  475. return highestCppStandard;
  476. }
  477. void EnabledModulesList::addModule (const File& moduleFolder, bool copyLocally, bool useGlobalPath)
  478. {
  479. ModuleDescription info (moduleFolder);
  480. if (info.isValid())
  481. {
  482. auto moduleID = info.getID();
  483. if (! isModuleEnabled (moduleID))
  484. {
  485. ValueTree module (Ids::MODULE);
  486. module.setProperty (Ids::ID, moduleID, getUndoManager());
  487. {
  488. const ScopedLock sl (stateLock);
  489. state.appendChild (module, getUndoManager());
  490. }
  491. sortAlphabetically();
  492. shouldShowAllModuleFilesInProjectValue (moduleID) = true;
  493. shouldCopyModuleFilesLocallyValue (moduleID) = copyLocally;
  494. shouldUseGlobalPathValue (moduleID) = useGlobalPath;
  495. build_tools::RelativePath path (moduleFolder.getParentDirectory(),
  496. project.getProjectFolder(),
  497. build_tools::RelativePath::projectFolder);
  498. for (Project::ExporterIterator exporter (project); exporter.next();)
  499. exporter->getPathForModuleValue (moduleID) = path.toUnixStyle();
  500. if (! useGlobalPath)
  501. project.rescanExporterPathModules (false);
  502. }
  503. }
  504. }
  505. void EnabledModulesList::addModuleInteractive (const String& moduleID)
  506. {
  507. auto f = project.getModuleWithID (moduleID).second;
  508. if (f != File())
  509. {
  510. addModule (f, areMostModulesCopiedLocally(), areMostModulesUsingGlobalPath());
  511. return;
  512. }
  513. addModuleFromUserSelectedFile();
  514. }
  515. void EnabledModulesList::addModuleFromUserSelectedFile()
  516. {
  517. chooser = std::make_unique<FileChooser> ("Select a module to add...", getDefaultModulesFolder(), "");
  518. auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
  519. chooser->launchAsync (flags, [this] (const FileChooser& fc)
  520. {
  521. if (fc.getResult() == File{})
  522. return;
  523. addModuleOfferingToCopy (fc.getResult(), true);
  524. });
  525. }
  526. void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder)
  527. {
  528. ModuleDescription m (f);
  529. if (! m.isValid())
  530. {
  531. AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
  532. "Add Module", "This wasn't a valid module folder!");
  533. return;
  534. }
  535. if (isModuleEnabled (m.getID()))
  536. {
  537. AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
  538. "Add Module", "The project already contains this module!");
  539. return;
  540. }
  541. addModule (m.getModuleFolder(), areMostModulesCopiedLocally(),
  542. isFromUserSpecifiedFolder ? false : areMostModulesUsingGlobalPath());
  543. }
  544. void EnabledModulesList::removeModule (String moduleID) // must be pass-by-value, and not a const ref!
  545. {
  546. {
  547. const ScopedLock sl (stateLock);
  548. for (auto i = state.getNumChildren(); --i >= 0;)
  549. if (state.getChild(i) [Ids::ID] == moduleID)
  550. state.removeChild (i, getUndoManager());
  551. }
  552. for (Project::ExporterIterator exporter (project); exporter.next();)
  553. exporter->removePathForModule (moduleID);
  554. }