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.

696 lines
23KB

  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. state.sort (sorter, getUndoManager(), false);
  324. }
  325. File EnabledModulesList::getDefaultModulesFolder() const
  326. {
  327. File globalPath (getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString());
  328. if (globalPath.exists())
  329. return globalPath;
  330. for (auto& exporterPathModule : project.getExporterPathsModulesList().getAllModules())
  331. {
  332. auto f = exporterPathModule.second;
  333. if (f.isDirectory())
  334. return f.getParentDirectory();
  335. }
  336. return File::getCurrentWorkingDirectory();
  337. }
  338. ModuleDescription EnabledModulesList::getModuleInfo (const String& moduleID) const
  339. {
  340. return ModuleDescription (project.getModuleWithID (moduleID).second);
  341. }
  342. bool EnabledModulesList::isModuleEnabled (const String& moduleID) const
  343. {
  344. return state.getChildWithProperty (Ids::ID, moduleID).isValid();
  345. }
  346. static void getDependencies (Project& project, const String& moduleID, StringArray& dependencies)
  347. {
  348. auto info = project.getEnabledModules().getModuleInfo (moduleID);
  349. for (auto uid : info.getDependencies())
  350. {
  351. if (! dependencies.contains (uid, true))
  352. {
  353. dependencies.add (uid);
  354. getDependencies (project, uid, dependencies);
  355. }
  356. }
  357. }
  358. StringArray EnabledModulesList::getExtraDependenciesNeeded (const String& moduleID) const
  359. {
  360. StringArray dependencies, extraDepsNeeded;
  361. getDependencies (project, moduleID, dependencies);
  362. for (auto dep : dependencies)
  363. if (dep != moduleID && ! isModuleEnabled (dep))
  364. extraDepsNeeded.add (dep);
  365. return extraDepsNeeded;
  366. }
  367. bool EnabledModulesList::tryToFixMissingDependencies (const String& moduleID)
  368. {
  369. auto copyLocally = areMostModulesCopiedLocally();
  370. auto useGlobalPath = areMostModulesUsingGlobalPath();
  371. StringArray missing;
  372. for (auto missingModule : getExtraDependenciesNeeded (moduleID))
  373. {
  374. auto mod = project.getModuleWithID (missingModule);
  375. if (mod.second != File())
  376. addModule (mod.second, copyLocally, useGlobalPath);
  377. else
  378. missing.add (missingModule);
  379. }
  380. return (missing.size() == 0);
  381. }
  382. bool EnabledModulesList::doesModuleHaveHigherCppStandardThanProject (const String& moduleID) const
  383. {
  384. auto projectCppStandard = project.getCppStandardString();
  385. if (projectCppStandard == Project::getCppStandardVars().getLast().toString())
  386. return false;
  387. auto moduleCppStandard = getModuleInfo (moduleID).getMinimumCppStandard();
  388. return (moduleCppStandard.getIntValue() > projectCppStandard.getIntValue());
  389. }
  390. bool EnabledModulesList::shouldUseGlobalPath (const String& moduleID) const
  391. {
  392. return (bool) shouldUseGlobalPathValue (moduleID).getValue();
  393. }
  394. Value EnabledModulesList::shouldUseGlobalPathValue (const String& moduleID) const
  395. {
  396. return state.getChildWithProperty (Ids::ID, moduleID)
  397. .getPropertyAsValue (Ids::useGlobalPath, getUndoManager());
  398. }
  399. bool EnabledModulesList::shouldShowAllModuleFilesInProject (const String& moduleID) const
  400. {
  401. return (bool) shouldShowAllModuleFilesInProjectValue (moduleID).getValue();
  402. }
  403. Value EnabledModulesList::shouldShowAllModuleFilesInProjectValue (const String& moduleID) const
  404. {
  405. return state.getChildWithProperty (Ids::ID, moduleID)
  406. .getPropertyAsValue (Ids::showAllCode, getUndoManager());
  407. }
  408. bool EnabledModulesList::shouldCopyModuleFilesLocally (const String& moduleID) const
  409. {
  410. return (bool) shouldCopyModuleFilesLocallyValue (moduleID).getValue();
  411. }
  412. Value EnabledModulesList::shouldCopyModuleFilesLocallyValue (const String& moduleID) const
  413. {
  414. return state.getChildWithProperty (Ids::ID, moduleID)
  415. .getPropertyAsValue (Ids::useLocalCopy, getUndoManager());
  416. }
  417. bool EnabledModulesList::areMostModulesUsingGlobalPath() const
  418. {
  419. int numYes = 0, numNo = 0;
  420. for (auto i = getNumModules(); --i >= 0;)
  421. {
  422. if (shouldUseGlobalPath (getModuleID (i)))
  423. ++numYes;
  424. else
  425. ++numNo;
  426. }
  427. return numYes > numNo;
  428. }
  429. bool EnabledModulesList::areMostModulesCopiedLocally() const
  430. {
  431. int numYes = 0, numNo = 0;
  432. for (auto i = getNumModules(); --i >= 0;)
  433. {
  434. if (shouldCopyModuleFilesLocally (getModuleID (i)))
  435. ++numYes;
  436. else
  437. ++numNo;
  438. }
  439. return numYes > numNo;
  440. }
  441. StringArray EnabledModulesList::getModulesWithHigherCppStandardThanProject() const
  442. {
  443. StringArray list;
  444. for (auto& module : getAllModules())
  445. if (doesModuleHaveHigherCppStandardThanProject (module))
  446. list.add (module);
  447. return list;
  448. }
  449. StringArray EnabledModulesList::getModulesWithMissingDependencies() const
  450. {
  451. StringArray list;
  452. for (auto& module : getAllModules())
  453. if (getExtraDependenciesNeeded (module).size() > 0)
  454. list.add (module);
  455. return list;
  456. }
  457. String EnabledModulesList::getHighestModuleCppStandard() const
  458. {
  459. auto highestCppStandard = Project::getCppStandardVars()[0].toString();
  460. for (auto& mod : getAllModules())
  461. {
  462. auto moduleCppStandard = getModuleInfo (mod).getMinimumCppStandard();
  463. if (moduleCppStandard == "latest")
  464. return moduleCppStandard;
  465. if (moduleCppStandard.getIntValue() > highestCppStandard.getIntValue())
  466. highestCppStandard = moduleCppStandard;
  467. }
  468. return highestCppStandard;
  469. }
  470. void EnabledModulesList::addModule (const File& moduleFolder, bool copyLocally, bool useGlobalPath)
  471. {
  472. ModuleDescription info (moduleFolder);
  473. if (info.isValid())
  474. {
  475. auto moduleID = info.getID();
  476. if (! isModuleEnabled (moduleID))
  477. {
  478. ValueTree module (Ids::MODULE);
  479. module.setProperty (Ids::ID, moduleID, getUndoManager());
  480. state.appendChild (module, getUndoManager());
  481. sortAlphabetically();
  482. shouldShowAllModuleFilesInProjectValue (moduleID) = true;
  483. shouldCopyModuleFilesLocallyValue (moduleID) = copyLocally;
  484. shouldUseGlobalPathValue (moduleID) = useGlobalPath;
  485. build_tools::RelativePath path (moduleFolder.getParentDirectory(),
  486. project.getProjectFolder(),
  487. build_tools::RelativePath::projectFolder);
  488. for (Project::ExporterIterator exporter (project); exporter.next();)
  489. exporter->getPathForModuleValue (moduleID) = path.toUnixStyle();
  490. if (! useGlobalPath)
  491. project.rescanExporterPathModules (false);
  492. }
  493. }
  494. }
  495. void EnabledModulesList::addModuleInteractive (const String& moduleID)
  496. {
  497. auto f = project.getModuleWithID (moduleID).second;
  498. if (f != File())
  499. {
  500. addModule (f, areMostModulesCopiedLocally(), areMostModulesUsingGlobalPath());
  501. return;
  502. }
  503. addModuleFromUserSelectedFile();
  504. }
  505. void EnabledModulesList::addModuleFromUserSelectedFile()
  506. {
  507. auto lastLocation = getDefaultModulesFolder();
  508. FileChooser fc ("Select a module to add...", lastLocation, {});
  509. if (fc.browseForDirectory())
  510. {
  511. lastLocation = fc.getResult();
  512. addModuleOfferingToCopy (lastLocation, true);
  513. }
  514. }
  515. void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder)
  516. {
  517. ModuleDescription m (f);
  518. if (! m.isValid())
  519. {
  520. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  521. "Add Module", "This wasn't a valid module folder!");
  522. return;
  523. }
  524. if (isModuleEnabled (m.getID()))
  525. {
  526. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  527. "Add Module", "The project already contains this module!");
  528. return;
  529. }
  530. addModule (m.getModuleFolder(), areMostModulesCopiedLocally(),
  531. isFromUserSpecifiedFolder ? false : areMostModulesUsingGlobalPath());
  532. }
  533. void EnabledModulesList::removeModule (String moduleID) // must be pass-by-value, and not a const ref!
  534. {
  535. for (auto i = state.getNumChildren(); --i >= 0;)
  536. if (state.getChild(i) [Ids::ID] == moduleID)
  537. state.removeChild (i, getUndoManager());
  538. for (Project::ExporterIterator exporter (project); exporter.next();)
  539. exporter->removePathForModule (moduleID);
  540. }