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.

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