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.

462 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. availableVersionsList.setColour (ListBox::backgroundColourId, Colours::white.withAlpha (0.4f));
  50. updateInstallButtonStatus();
  51. versionsToDownload = ValueTree ("modules");
  52. versionsToDownload.addListener (this);
  53. setSize (600, 500);
  54. checkNow();
  55. }
  56. JuceUpdater::~JuceUpdater()
  57. {
  58. checkNowButton.removeListener (this);
  59. filenameComp.removeListener (this);
  60. }
  61. //==============================================================================
  62. class UpdateDialogWindow : public DialogWindow
  63. {
  64. public:
  65. UpdateDialogWindow (JuceUpdater* updater, Component* componentToCentreAround)
  66. : DialogWindow ("JUCE Module Updater",
  67. Colours::lightgrey, true, true)
  68. {
  69. setUsingNativeTitleBar (true);
  70. setContentOwned (updater, true);
  71. centreAroundComponent (componentToCentreAround, getWidth(), getHeight());
  72. setResizable (true, true);
  73. }
  74. void closeButtonPressed()
  75. {
  76. setVisible (false);
  77. }
  78. private:
  79. JUCE_DECLARE_NON_COPYABLE (UpdateDialogWindow);
  80. };
  81. void JuceUpdater::show (ModuleList& moduleList, Component* mainWindow, const String& message)
  82. {
  83. UpdateDialogWindow w (new JuceUpdater (moduleList, message), mainWindow);
  84. w.runModalLoop();
  85. }
  86. void JuceUpdater::resized()
  87. {
  88. messageLabel.setBounds (20, 10, getWidth() - 40, messageLabel.getText().isEmpty() ? 0 : 30);
  89. filenameComp.setBounds (20, messageLabel.getBottom() + 20, getWidth() - 40, 22);
  90. label.setBounds (filenameComp.getX(), filenameComp.getY() - 18, filenameComp.getWidth(), 18);
  91. currentVersionLabel.setBounds (filenameComp.getX(), filenameComp.getBottom(), filenameComp.getWidth(), 25);
  92. checkNowButton.changeWidthToFitText (22);
  93. checkNowButton.setCentrePosition (getWidth() / 2, filenameComp.getBottom() + 20);
  94. availableVersionsList.setBounds (filenameComp.getX(), checkNowButton.getBottom() + 20,
  95. filenameComp.getWidth(),
  96. getHeight() - 30 - (checkNowButton.getBottom() + 20));
  97. installButton.changeWidthToFitText (22);
  98. installButton.setTopRightPosition (availableVersionsList.getRight(), getHeight() - 28);
  99. selectAllButton.setBounds (availableVersionsList.getX(),
  100. availableVersionsList.getBottom() + 4,
  101. installButton.getX() - availableVersionsList.getX() - 20, 22);
  102. }
  103. void JuceUpdater::paint (Graphics& g)
  104. {
  105. g.setGradientFill (ColourGradient (Colour::greyLevel (0.85f), 0, 0,
  106. Colour::greyLevel (0.65f), 0, (float) getHeight(), false));
  107. g.fillAll();
  108. }
  109. void JuceUpdater::buttonClicked (Button* b)
  110. {
  111. if (b == &installButton)
  112. install();
  113. else if (b == &selectAllButton)
  114. selectAll();
  115. else
  116. checkNow();
  117. }
  118. void JuceUpdater::refresh()
  119. {
  120. availableVersionsList.updateContent();
  121. availableVersionsList.repaint();
  122. }
  123. class WebsiteContacterThread : public Thread,
  124. private AsyncUpdater
  125. {
  126. public:
  127. WebsiteContacterThread (JuceUpdater& owner_, const ModuleList& latestList)
  128. : Thread ("Module updater"),
  129. owner (owner_),
  130. downloaded (latestList)
  131. {
  132. startThread();
  133. }
  134. ~WebsiteContacterThread()
  135. {
  136. stopThread (10000);
  137. }
  138. void run()
  139. {
  140. if (downloaded.loadFromWebsite())
  141. triggerAsyncUpdate();
  142. else
  143. AlertWindow::showMessageBox (AlertWindow::InfoIcon,
  144. "Module Update",
  145. "Couldn't connect to the JUCE webserver!");
  146. }
  147. void handleAsyncUpdate()
  148. {
  149. owner.backgroundUpdateComplete (downloaded);
  150. }
  151. private:
  152. JuceUpdater& owner;
  153. ModuleList downloaded;
  154. };
  155. void JuceUpdater::checkNow()
  156. {
  157. websiteContacterThread = nullptr;
  158. websiteContacterThread = new WebsiteContacterThread (*this, latestList);
  159. }
  160. void JuceUpdater::backgroundUpdateComplete (const ModuleList& newList)
  161. {
  162. latestList = newList;
  163. websiteContacterThread = nullptr;
  164. if (latestList == moduleList)
  165. AlertWindow::showMessageBox (AlertWindow::InfoIcon,
  166. "Module Update",
  167. "No new modules are available");
  168. refresh();
  169. }
  170. int JuceUpdater::getNumCheckedModules() const
  171. {
  172. int numChecked = 0;
  173. for (int i = latestList.modules.size(); --i >= 0;)
  174. if (versionsToDownload [latestList.modules.getUnchecked(i)->uid])
  175. ++numChecked;
  176. return numChecked;
  177. }
  178. bool JuceUpdater::isLatestVersion (const String& moduleID) const
  179. {
  180. const ModuleList::Module* m1 = moduleList.findModuleInfo (moduleID);
  181. const ModuleList::Module* m2 = latestList.findModuleInfo (moduleID);
  182. return m1 != nullptr && m2 != nullptr && m1->version == m2->version;
  183. }
  184. void JuceUpdater::updateInstallButtonStatus()
  185. {
  186. const int numChecked = getNumCheckedModules();
  187. installButton.setEnabled (numChecked > 0);
  188. selectAllButton.setToggleState (numChecked > latestList.modules.size() / 2, false);
  189. }
  190. void JuceUpdater::filenameComponentChanged (FilenameComponent*)
  191. {
  192. moduleList.rescan (filenameComp.getCurrentFile());
  193. filenameComp.setCurrentFile (moduleList.getModulesFolder(), true, false);
  194. if (! ModuleList::isModulesFolder (moduleList.getModulesFolder()))
  195. currentVersionLabel.setText ("(Not a Juce folder)", false);
  196. else
  197. currentVersionLabel.setText (String::empty, false);
  198. refresh();
  199. }
  200. void JuceUpdater::selectAll()
  201. {
  202. bool enable = getNumCheckedModules() < latestList.modules.size() / 2;
  203. versionsToDownload.removeAllProperties (nullptr);
  204. if (enable)
  205. {
  206. for (int i = latestList.modules.size(); --i >= 0;)
  207. if (! isLatestVersion (latestList.modules.getUnchecked(i)->uid))
  208. versionsToDownload.setProperty (latestList.modules.getUnchecked(i)->uid, true, nullptr);
  209. }
  210. }
  211. //==============================================================================
  212. int JuceUpdater::getNumRows()
  213. {
  214. return latestList.modules.size();
  215. }
  216. void JuceUpdater::paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
  217. {
  218. if (rowIsSelected)
  219. g.fillAll (findColour (TextEditor::highlightColourId));
  220. }
  221. Component* JuceUpdater::refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate)
  222. {
  223. class UpdateListComponent : public Component
  224. {
  225. public:
  226. UpdateListComponent (JuceUpdater& updater_)
  227. : updater (updater_)
  228. {
  229. addChildComponent (&toggle);
  230. toggle.setWantsKeyboardFocus (false);
  231. setInterceptsMouseClicks (false, true);
  232. }
  233. void setModule (const ModuleList::Module* newModule,
  234. const ModuleList::Module* existingModule,
  235. const Value& value)
  236. {
  237. if (newModule != nullptr)
  238. {
  239. toggle.getToggleStateValue().referTo (value);
  240. toggle.setVisible (true);
  241. toggle.setEnabled (true);
  242. name = newModule->uid;
  243. status = String::empty;
  244. if (existingModule == nullptr)
  245. {
  246. status << " (not currently installed)";
  247. }
  248. else if (existingModule->version != newModule->version)
  249. {
  250. status << " installed: " << existingModule->version
  251. << ", available: " << newModule->version;
  252. }
  253. else
  254. {
  255. status << " (latest version already installed: " << existingModule->version << ")";
  256. toggle.setEnabled (false);
  257. }
  258. }
  259. else
  260. {
  261. name = status = String::empty;
  262. toggle.setVisible (false);
  263. }
  264. }
  265. void paint (Graphics& g)
  266. {
  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. void resized()
  275. {
  276. toggle.setBounds (2, 2, getHeight() - 4, getHeight() - 4);
  277. }
  278. private:
  279. JuceUpdater& updater;
  280. ToggleButton toggle;
  281. String name, status;
  282. };
  283. UpdateListComponent* c = dynamic_cast <UpdateListComponent*> (existingComponentToUpdate);
  284. if (c == nullptr)
  285. c = new UpdateListComponent (*this);
  286. ModuleList::Module* m = latestList.modules [rowNumber];
  287. if (m != nullptr)
  288. c->setModule (m,
  289. moduleList.findModuleInfo (m->uid),
  290. versionsToDownload.getPropertyAsValue (m->uid, nullptr));
  291. else
  292. c->setModule (nullptr, nullptr, Value());
  293. return c;
  294. }
  295. //==============================================================================
  296. class InstallThread : public ThreadWithProgressWindow
  297. {
  298. public:
  299. InstallThread (const ModuleList& targetList_,
  300. const ModuleList& list_, const StringArray& itemsToInstall_)
  301. : ThreadWithProgressWindow ("Installing New Modules", true, true),
  302. result (Result::ok()),
  303. targetList (targetList_),
  304. list (list_),
  305. itemsToInstall (itemsToInstall_)
  306. {
  307. }
  308. void run()
  309. {
  310. for (int i = 0; i < itemsToInstall.size(); ++i)
  311. {
  312. const ModuleList::Module* m = list.findModuleInfo (itemsToInstall[i]);
  313. jassert (m != nullptr);
  314. if (m != nullptr)
  315. {
  316. setProgress (i / (double) itemsToInstall.size());
  317. MemoryBlock downloaded;
  318. result = download (*m, downloaded);
  319. if (result.failed())
  320. break;
  321. if (threadShouldExit())
  322. break;
  323. result = unzip (*m, downloaded);
  324. if (result.failed())
  325. break;
  326. }
  327. if (threadShouldExit())
  328. break;
  329. }
  330. }
  331. Result download (const ModuleList::Module& m, MemoryBlock& dest)
  332. {
  333. setStatusMessage ("Downloading " + m.uid + "...");
  334. if (m.url.readEntireBinaryStream (dest, false))
  335. return Result::ok();
  336. return Result::fail ("Failed to download from: " + m.url.toString (false));
  337. }
  338. Result unzip (const ModuleList::Module& m, const MemoryBlock& data)
  339. {
  340. setStatusMessage ("Installing " + m.uid + "...");
  341. MemoryInputStream input (data, false);
  342. ZipFile zip (input);
  343. if (zip.getNumEntries() == 0)
  344. return Result::fail ("The downloaded file wasn't a valid module file!");
  345. return zip.uncompressTo (targetList.getModulesFolder(), true);
  346. }
  347. Result result;
  348. private:
  349. ModuleList targetList, list;
  350. StringArray itemsToInstall;
  351. };
  352. void JuceUpdater::install()
  353. {
  354. if (! moduleList.getModulesFolder().createDirectory())
  355. {
  356. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  357. "Module Update",
  358. "Couldn't create the target folder!");
  359. return;
  360. }
  361. StringArray itemsWanted;
  362. for (int i = latestList.modules.size(); --i >= 0;)
  363. if (versionsToDownload [latestList.modules.getUnchecked(i)->uid])
  364. itemsWanted.add (latestList.modules.getUnchecked(i)->uid);
  365. {
  366. InstallThread thread (moduleList, latestList, itemsWanted);
  367. thread.runThread();
  368. }
  369. moduleList.rescan();
  370. refresh();
  371. }
  372. void JuceUpdater::valueTreePropertyChanged (ValueTree&, const Identifier&) { updateInstallButtonStatus(); }
  373. void JuceUpdater::valueTreeChildAdded (ValueTree&, ValueTree&) {}
  374. void JuceUpdater::valueTreeChildRemoved (ValueTree&, ValueTree&) {}
  375. void JuceUpdater::valueTreeChildOrderChanged (ValueTree&) {}
  376. void JuceUpdater::valueTreeParentChanged (ValueTree&) {}