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.

440 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "../jucer_Headers.h"
  18. #include "jucer_JuceUpdater.h"
  19. #include "../Project/jucer_Module.h"
  20. //==============================================================================
  21. JuceUpdater::JuceUpdater (ModuleList& moduleList_, const String& message)
  22. : moduleList (moduleList_),
  23. messageLabel (String::empty, message),
  24. filenameComp ("Juce Folder", ModuleList::getLocalModulesFolder (nullptr),
  25. true, true, false, "*", String::empty, "Select your Juce folder"),
  26. checkNowButton ("Check for available updates on the JUCE website...",
  27. "Contacts the website to see if new modules are available"),
  28. installButton ("Download and install selected modules..."),
  29. selectAllButton ("Select/Deselect All")
  30. {
  31. messageLabel.setJustificationType (Justification::centred);
  32. addAndMakeVisible (&messageLabel);
  33. addAndMakeVisible (&label);
  34. addAndMakeVisible (&currentVersionLabel);
  35. addAndMakeVisible (&filenameComp);
  36. addAndMakeVisible (&checkNowButton);
  37. addAndMakeVisible (&installButton);
  38. addAndMakeVisible (&selectAllButton);
  39. checkNowButton.addListener (this);
  40. installButton.addListener (this);
  41. selectAllButton.addListener (this);
  42. filenameComp.addListener (this);
  43. currentVersionLabel.setFont (Font (14.0f, Font::italic));
  44. label.setFont (Font (12.0f));
  45. label.setText ("Local modules folder:", dontSendNotification);
  46. addAndMakeVisible (&availableVersionsList);
  47. availableVersionsList.setModel (this);
  48. availableVersionsList.setColour (ListBox::backgroundColourId, Colours::white.withAlpha (0.4f));
  49. updateInstallButtonStatus();
  50. versionsToDownload = ValueTree ("modules");
  51. versionsToDownload.addListener (this);
  52. setSize (600, 500);
  53. }
  54. JuceUpdater::~JuceUpdater()
  55. {
  56. checkNowButton.removeListener (this);
  57. filenameComp.removeListener (this);
  58. }
  59. void JuceUpdater::show (ModuleList& moduleList, Component* mainWindow, const String& message)
  60. {
  61. DialogWindow::LaunchOptions o;
  62. o.content.setOwned (new JuceUpdater (moduleList, message));
  63. o.dialogTitle = "JUCE Module Updater";
  64. o.dialogBackgroundColour = Colours::lightgrey;
  65. o.componentToCentreAround = mainWindow;
  66. o.escapeKeyTriggersCloseButton = true;
  67. o.useNativeTitleBar = true;
  68. o.resizable = true;
  69. o.runModal();
  70. }
  71. void JuceUpdater::resized()
  72. {
  73. messageLabel.setBounds (20, 10, getWidth() - 40, messageLabel.getText().isEmpty() ? 0 : 30);
  74. filenameComp.setBounds (20, messageLabel.getBottom() + 20, getWidth() - 40, 22);
  75. label.setBounds (filenameComp.getX(), filenameComp.getY() - 18, filenameComp.getWidth(), 18);
  76. currentVersionLabel.setBounds (filenameComp.getX(), filenameComp.getBottom(), filenameComp.getWidth(), 25);
  77. checkNowButton.changeWidthToFitText (22);
  78. checkNowButton.setCentrePosition (getWidth() / 2, filenameComp.getBottom() + 20);
  79. availableVersionsList.setBounds (filenameComp.getX(), checkNowButton.getBottom() + 20,
  80. filenameComp.getWidth(),
  81. getHeight() - 30 - (checkNowButton.getBottom() + 20));
  82. installButton.changeWidthToFitText (22);
  83. installButton.setTopRightPosition (availableVersionsList.getRight(), getHeight() - 28);
  84. selectAllButton.setBounds (availableVersionsList.getX(),
  85. availableVersionsList.getBottom() + 4,
  86. installButton.getX() - availableVersionsList.getX() - 20, 22);
  87. }
  88. void JuceUpdater::paint (Graphics& g)
  89. {
  90. g.setGradientFill (ColourGradient (Colour::greyLevel (0.85f), 0, 0,
  91. Colour::greyLevel (0.65f), 0, (float) getHeight(), false));
  92. g.fillAll();
  93. }
  94. void JuceUpdater::buttonClicked (Button* b)
  95. {
  96. if (b == &installButton)
  97. install();
  98. else if (b == &selectAllButton)
  99. selectAll();
  100. else
  101. checkNow();
  102. }
  103. void JuceUpdater::refresh()
  104. {
  105. availableVersionsList.updateContent();
  106. availableVersionsList.repaint();
  107. }
  108. class WebsiteContacterThread : public Thread,
  109. private AsyncUpdater
  110. {
  111. public:
  112. WebsiteContacterThread (JuceUpdater& owner_, const ModuleList& latestList)
  113. : Thread ("Module updater"),
  114. owner (owner_),
  115. downloaded (latestList)
  116. {
  117. startThread();
  118. }
  119. ~WebsiteContacterThread()
  120. {
  121. stopThread (10000);
  122. }
  123. void run() override
  124. {
  125. if (downloaded.loadFromWebsite())
  126. triggerAsyncUpdate();
  127. else
  128. AlertWindow::showMessageBox (AlertWindow::InfoIcon,
  129. "Module Update",
  130. "Couldn't connect to the JUCE webserver!");
  131. }
  132. void handleAsyncUpdate() override
  133. {
  134. owner.backgroundUpdateComplete (downloaded);
  135. }
  136. private:
  137. JuceUpdater& owner;
  138. ModuleList downloaded;
  139. };
  140. void JuceUpdater::checkNow()
  141. {
  142. websiteContacterThread = nullptr;
  143. websiteContacterThread = new WebsiteContacterThread (*this, latestList);
  144. }
  145. void JuceUpdater::backgroundUpdateComplete (const ModuleList& newList)
  146. {
  147. latestList = newList;
  148. websiteContacterThread = nullptr;
  149. if (latestList == moduleList)
  150. AlertWindow::showMessageBox (AlertWindow::InfoIcon,
  151. "Module Update",
  152. "No new modules are available");
  153. refresh();
  154. }
  155. int JuceUpdater::getNumCheckedModules() const
  156. {
  157. int numChecked = 0;
  158. for (int i = latestList.modules.size(); --i >= 0;)
  159. if (versionsToDownload [latestList.modules.getUnchecked(i)->uid])
  160. ++numChecked;
  161. return numChecked;
  162. }
  163. bool JuceUpdater::isLatestVersion (const String& moduleID) const
  164. {
  165. const ModuleList::Module* m1 = moduleList.findModuleInfo (moduleID);
  166. const ModuleList::Module* m2 = latestList.findModuleInfo (moduleID);
  167. return m1 != nullptr && m2 != nullptr && m1->version == m2->version;
  168. }
  169. void JuceUpdater::updateInstallButtonStatus()
  170. {
  171. const int numChecked = getNumCheckedModules();
  172. installButton.setEnabled (numChecked > 0);
  173. selectAllButton.setToggleState (numChecked > latestList.modules.size() / 2, dontSendNotification);
  174. }
  175. void JuceUpdater::filenameComponentChanged (FilenameComponent*)
  176. {
  177. moduleList.rescan (filenameComp.getCurrentFile());
  178. filenameComp.setCurrentFile (moduleList.getModulesFolder(), true, dontSendNotification);
  179. if (! ModuleList::isModulesFolder (moduleList.getModulesFolder()))
  180. currentVersionLabel.setText ("(Not a Juce folder)", dontSendNotification);
  181. else
  182. currentVersionLabel.setText (String::empty, dontSendNotification);
  183. refresh();
  184. }
  185. void JuceUpdater::selectAll()
  186. {
  187. bool enable = getNumCheckedModules() < latestList.modules.size() / 2;
  188. versionsToDownload.removeAllProperties (nullptr);
  189. if (enable)
  190. {
  191. for (int i = latestList.modules.size(); --i >= 0;)
  192. if (! isLatestVersion (latestList.modules.getUnchecked(i)->uid))
  193. versionsToDownload.setProperty (latestList.modules.getUnchecked(i)->uid, true, nullptr);
  194. }
  195. }
  196. //==============================================================================
  197. int JuceUpdater::getNumRows()
  198. {
  199. return latestList.modules.size();
  200. }
  201. void JuceUpdater::paintListBoxItem (int /*rowNumber*/, Graphics& g, int /*width*/, int /*height*/, bool rowIsSelected)
  202. {
  203. if (rowIsSelected)
  204. g.fillAll (findColour (TextEditor::highlightColourId));
  205. }
  206. class UpdateListComponent : public Component
  207. {
  208. public:
  209. UpdateListComponent()
  210. {
  211. addChildComponent (&toggle);
  212. toggle.setWantsKeyboardFocus (false);
  213. setInterceptsMouseClicks (false, true);
  214. }
  215. void setModule (const ModuleList::Module* newModule,
  216. const ModuleList::Module* existingModule,
  217. const Value& value)
  218. {
  219. if (newModule != nullptr)
  220. {
  221. toggle.getToggleStateValue().referTo (value);
  222. toggle.setVisible (true);
  223. toggle.setEnabled (true);
  224. name = newModule->uid;
  225. status = String::empty;
  226. if (existingModule == nullptr)
  227. {
  228. status << " (not currently installed)";
  229. }
  230. else if (existingModule->version != newModule->version)
  231. {
  232. status << " installed: " << existingModule->version
  233. << ", available: " << newModule->version;
  234. }
  235. else
  236. {
  237. status << " (latest version already installed: " << existingModule->version << ")";
  238. toggle.setEnabled (false);
  239. }
  240. }
  241. else
  242. {
  243. name = status = String::empty;
  244. toggle.setVisible (false);
  245. }
  246. }
  247. void paint (Graphics& g)
  248. {
  249. g.setColour (Colours::black);
  250. g.setFont (getHeight() * 0.7f);
  251. g.drawText (name, toggle.getRight() + 4, 0, getWidth() / 2 - toggle.getRight() - 4, getHeight(),
  252. Justification::centredLeft, true);
  253. g.drawText (status, getWidth() / 2, 0, getWidth() / 2, getHeight(),
  254. Justification::centredLeft, true);
  255. }
  256. void resized()
  257. {
  258. toggle.setBounds (2, 2, getHeight() - 4, getHeight() - 4);
  259. }
  260. private:
  261. ToggleButton toggle;
  262. String name, status;
  263. };
  264. Component* JuceUpdater::refreshComponentForRow (int rowNumber, bool /*isRowSelected*/, Component* existingComponentToUpdate)
  265. {
  266. UpdateListComponent* c = dynamic_cast <UpdateListComponent*> (existingComponentToUpdate);
  267. if (c == nullptr)
  268. c = new UpdateListComponent();
  269. if (ModuleList::Module* m = latestList.modules [rowNumber])
  270. c->setModule (m,
  271. moduleList.findModuleInfo (m->uid),
  272. versionsToDownload.getPropertyAsValue (m->uid, nullptr));
  273. else
  274. c->setModule (nullptr, nullptr, Value());
  275. return c;
  276. }
  277. //==============================================================================
  278. class InstallThread : public ThreadWithProgressWindow
  279. {
  280. public:
  281. InstallThread (const ModuleList& targetList_,
  282. const ModuleList& list_, const StringArray& itemsToInstall_)
  283. : ThreadWithProgressWindow ("Installing New Modules", true, true),
  284. result (Result::ok()),
  285. targetList (targetList_),
  286. list (list_),
  287. itemsToInstall (itemsToInstall_)
  288. {
  289. }
  290. void run() override
  291. {
  292. for (int i = 0; i < itemsToInstall.size(); ++i)
  293. {
  294. const ModuleList::Module* m = list.findModuleInfo (itemsToInstall[i]);
  295. jassert (m != nullptr);
  296. if (m != nullptr)
  297. {
  298. setProgress (i / (double) itemsToInstall.size());
  299. MemoryBlock downloaded;
  300. result = download (*m, downloaded);
  301. if (result.failed())
  302. break;
  303. if (threadShouldExit())
  304. break;
  305. result = unzip (*m, downloaded);
  306. if (result.failed())
  307. break;
  308. }
  309. if (threadShouldExit())
  310. break;
  311. }
  312. }
  313. Result download (const ModuleList::Module& m, MemoryBlock& dest)
  314. {
  315. setStatusMessage ("Downloading " + m.uid + "...");
  316. if (m.url.readEntireBinaryStream (dest, false))
  317. return Result::ok();
  318. return Result::fail ("Failed to download from: " + m.url.toString (false));
  319. }
  320. Result unzip (const ModuleList::Module& m, const MemoryBlock& data)
  321. {
  322. setStatusMessage ("Installing " + m.uid + "...");
  323. MemoryInputStream input (data, false);
  324. ZipFile zip (input);
  325. if (zip.getNumEntries() == 0)
  326. return Result::fail ("The downloaded file wasn't a valid module file!");
  327. return zip.uncompressTo (targetList.getModulesFolder(), true);
  328. }
  329. Result result;
  330. private:
  331. ModuleList targetList, list;
  332. StringArray itemsToInstall;
  333. };
  334. void JuceUpdater::install()
  335. {
  336. if (! moduleList.getModulesFolder().createDirectory())
  337. {
  338. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  339. "Module Update",
  340. "Couldn't create the target folder!");
  341. return;
  342. }
  343. StringArray itemsWanted;
  344. for (int i = latestList.modules.size(); --i >= 0;)
  345. if (versionsToDownload [latestList.modules.getUnchecked(i)->uid])
  346. itemsWanted.add (latestList.modules.getUnchecked(i)->uid);
  347. {
  348. InstallThread thread (moduleList, latestList, itemsWanted);
  349. thread.runThread();
  350. }
  351. moduleList.rescan();
  352. refresh();
  353. }
  354. void JuceUpdater::valueTreePropertyChanged (ValueTree&, const Identifier&) { updateInstallButtonStatus(); }
  355. void JuceUpdater::valueTreeChildAdded (ValueTree&, ValueTree&) {}
  356. void JuceUpdater::valueTreeChildRemoved (ValueTree&, ValueTree&) {}
  357. void JuceUpdater::valueTreeChildOrderChanged (ValueTree&) {}
  358. void JuceUpdater::valueTreeParentChanged (ValueTree&) {}