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.

458 lines
15KB

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