|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2013 - Raw Material Software Ltd.
-
- Permission is granted to use this software under the terms of either:
- a) the GPL v2 (or any later version)
- b) the Affero GPL v3
-
- Details of these licenses can be found at: www.gnu.org/licenses
-
- JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- ------------------------------------------------------------------------------
-
- To release a closed-source product which uses JUCE, commercial licenses are
- available: visit www.juce.com for more information.
-
- ==============================================================================
- */
-
-
- class ModulesPanel : public Component,
- private TableListBoxModel,
- private ValueTree::Listener,
- private Button::Listener
- {
- public:
- ModulesPanel (Project& p)
- : project (p),
- modulesValueTree (p.getModules().state),
- addWebModuleButton ("Download and add a module..."),
- updateModuleButton ("Install updates to modules..."),
- setCopyModeButton ("Set copy-mode for all modules..."),
- copyPathButton ("Set paths for all modules...")
- {
- table.getHeader().addColumn ("Module", nameCol, 180, 100, 400, TableHeaderComponent::notSortable);
- table.getHeader().addColumn ("Installed Version", versionCol, 100, 100, 100, TableHeaderComponent::notSortable);
- table.getHeader().addColumn ("Available Version", updateCol, 100, 100, 100, TableHeaderComponent::notSortable);
- table.getHeader().addColumn ("Make Local Copy", copyCol, 100, 100, 100, TableHeaderComponent::notSortable);
- table.getHeader().addColumn ("Paths", pathCol, 250, 100, 600, TableHeaderComponent::notSortable);
-
- table.setModel (this);
- table.setColour (TableListBox::backgroundColourId, Colours::transparentBlack);
- addAndMakeVisible (table);
- table.updateContent();
- table.setRowHeight (20);
-
- addAndMakeVisible (addWebModuleButton);
- addAndMakeVisible (updateModuleButton);
- addAndMakeVisible (setCopyModeButton);
- addAndMakeVisible (copyPathButton);
- addWebModuleButton.addListener (this);
- updateModuleButton.addListener (this);
- updateModuleButton.setEnabled (false);
- setCopyModeButton.addListener (this);
- setCopyModeButton.setTriggeredOnMouseDown (true);
- copyPathButton.addListener (this);
- copyPathButton.setTriggeredOnMouseDown (true);
-
- modulesValueTree.addListener (this);
- lookAndFeelChanged();
- }
-
- void paint (Graphics& g) override
- {
- if (webUpdateThread == nullptr)
- webUpdateThread = new WebsiteUpdateFetchThread (*this);
-
- IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
- }
-
- void resized() override
- {
- Rectangle<int> r (getLocalBounds().reduced (5, 4));
-
- table.setBounds (r.removeFromTop (table.getRowPosition (getNumRows() - 1, true).getBottom() + 20));
-
- Rectangle<int> buttonRow (r.removeFromTop (32).removeFromBottom (28));
- addWebModuleButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
- buttonRow.removeFromLeft (8);
- updateModuleButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
- buttonRow.removeFromLeft (8);
-
- buttonRow = r.removeFromTop (34).removeFromBottom (28);
- setCopyModeButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
- buttonRow.removeFromLeft (8);
- copyPathButton.setBounds (buttonRow.removeFromLeft (jmin (260, r.getWidth() / 3)));
- }
-
- int getNumRows() override
- {
- return project.getModules().getNumModules();
- }
-
- void paintRowBackground (Graphics& g, int /*rowNumber*/, int width, int height, bool rowIsSelected) override
- {
- g.setColour (rowIsSelected ? Colours::lightblue.withAlpha (0.4f)
- : Colours::white.withAlpha (0.4f));
- g.fillRect (0, 0, width, height - 1);
- }
-
- void paintCell (Graphics& g, int rowNumber, int columnId, int width, int height, bool /*rowIsSelected*/) override
- {
- String text;
- const String moduleID (project.getModules().getModuleID (rowNumber));
-
- if (columnId == nameCol)
- {
- text = moduleID;
- }
- else if (columnId == versionCol)
- {
- text = project.getModules().getModuleInfo (moduleID).getVersion();
-
- if (text.isEmpty())
- text = "?";
- }
- else if (columnId == updateCol)
- {
- if (listFromWebsite != nullptr)
- {
- if (const ModuleDescription* m = listFromWebsite->getModuleWithID (moduleID))
- {
- if (m->getVersion() != project.getModules().getModuleInfo (moduleID).getVersion())
- text = m->getVersion() + " available";
- else
- text = "Up-to-date";
- }
- else
- text = "?";
- }
- else
- {
- text = "-";
- }
- }
- else if (columnId == copyCol)
- {
- text = project.getModules().shouldCopyModuleFilesLocally (moduleID).getValue()
- ? "Yes" : "No";
- }
- else if (columnId == pathCol)
- {
- StringArray paths;
-
- for (Project::ExporterIterator exporter (project); exporter.next();)
- paths.addIfNotAlreadyThere (exporter->getPathForModuleString (moduleID).trim());
-
- text = paths.joinIntoString (", ");
- }
-
- g.setColour (Colours::black);
- g.setFont (height * 0.65f);
- g.drawText (text, Rectangle<int> (width, height).reduced (4, 0), Justification::centredLeft, true);
- }
-
- void cellDoubleClicked (int rowNumber, int, const MouseEvent&) override
- {
- const String moduleID (project.getModules().getModuleID (rowNumber));
-
- if (moduleID.isNotEmpty())
- if (ProjectContentComponent* pcc = findParentComponentOfClass<ProjectContentComponent>())
- pcc->showModule (moduleID);
- }
-
- void deleteKeyPressed (int row) override
- {
- project.getModules().removeModule (project.getModules().getModuleID (row));
- }
-
- void webUpdateFinished (const ModuleList& newList)
- {
- listFromWebsite = new ModuleList (newList);
-
- table.updateContent();
- table.repaint();
-
- updateModuleButton.setEnabled (getUpdatableModules().size() != 0);
- }
-
- void buttonClicked (Button* b)
- {
- if (b == &addWebModuleButton) showAddModuleMenu();
- else if (b == &updateModuleButton) showUpdateModulesMenu();
- else if (b == &setCopyModeButton) showCopyModeMenu();
- else if (b == ©PathButton) showSetPathsMenu();
- }
-
- private:
- enum
- {
- nameCol = 1,
- versionCol,
- updateCol,
- copyCol,
- pathCol
- };
-
- Project& project;
- ValueTree modulesValueTree;
- TableListBox table;
- TextButton addWebModuleButton, updateModuleButton, setCopyModeButton, copyPathButton;
- ScopedPointer<ModuleList> listFromWebsite;
-
- void valueTreePropertyChanged (ValueTree&, const Identifier&) override { itemChanged(); }
- void valueTreeChildAdded (ValueTree&, ValueTree&) override { itemChanged(); }
- void valueTreeChildRemoved (ValueTree&, ValueTree&) override { itemChanged(); }
- void valueTreeChildOrderChanged (ValueTree&) override { itemChanged(); }
- void valueTreeParentChanged (ValueTree&) override { itemChanged(); }
-
- void itemChanged()
- {
- table.updateContent();
- resized();
- repaint();
- }
-
- StringArray getUpdatableModules() const
- {
- StringArray result;
-
- if (listFromWebsite != nullptr)
- {
- for (int i = 0; i < listFromWebsite->modules.size(); ++i)
- {
- const ModuleDescription* m = listFromWebsite->modules.getUnchecked(i);
- const String v1 (m->getVersion());
- const String v2 (project.getModules().getModuleInfo (m->getID()).getVersion());
-
- if (v1 != v2 && v1.isNotEmpty() && v2.isNotEmpty())
- result.add (m->getID());
- }
- }
-
- return result;
- }
-
- StringArray getAddableModules() const
- {
- StringArray result;
-
- if (listFromWebsite != nullptr)
- {
- for (int i = 0; i < listFromWebsite->modules.size(); ++i)
- {
- const ModuleDescription* m = listFromWebsite->modules.getUnchecked(i);
-
- if (! project.getModules().isModuleEnabled (m->getID()))
- result.add (m->getID());
- }
- }
-
- return result;
- }
-
- void showUpdateModulesMenu()
- {
- StringArray mods (getUpdatableModules());
-
- PopupMenu m;
- m.addItem (1000, "Update all modules");
- m.addSeparator();
-
- for (int i = 0; i < mods.size(); ++i)
- m.addItem (1 + i, "Update " + mods[i]);
-
- int res = m.showAt (&updateModuleButton);
-
- if (res > 0 && listFromWebsite != nullptr)
- {
- if (res != 1000)
- mods = StringArray (mods[res - 1]);
-
- Array<ModuleDescription> modsToUpdate;
-
- for (int i = 0; i < mods.size(); ++i)
- {
- if (const ModuleDescription* md = listFromWebsite->getModuleWithID (mods[i]))
- {
- ModuleDescription modToUpdate (*md);
- modToUpdate.manifestFile = project.getModules().getModuleInfo (modToUpdate.getID()).manifestFile;
- modsToUpdate.add (modToUpdate);
- }
- }
-
- DownloadAndInstallThread::updateModulesFromWeb (project, modsToUpdate);
- }
- }
-
- void showAddModuleMenu()
- {
- if (listFromWebsite == nullptr)
- {
- AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
- "Couldn't contact the website!",
- "Failed to get the latest module list from juce.com - "
- "maybe network or server problems - try again soon!");
- return;
- }
-
- StringArray mods (getAddableModules());
-
- if (mods.size() == 0)
- {
- AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
- "No modules to add!",
- "Couldn't find any new modules that aren't already in your project!");
- return;
- }
-
- PopupMenu m;
-
- for (int i = 0; i < mods.size(); ++i)
- m.addItem (i + 1, "Install " + mods[i]);
-
- int res = m.showAt (&addWebModuleButton);
-
- if (res > 0 && listFromWebsite != nullptr)
- if (const ModuleDescription* md = listFromWebsite->getModuleWithID (mods[res - 1]))
- DownloadAndInstallThread::addModuleFromWebsite (project, *md);
- }
-
- void showCopyModeMenu()
- {
- PopupMenu m;
- m.addItem (1, "Set all modules to copy locally");
- m.addItem (2, "Set all modules to not copy locally");
-
- int res = m.showAt (&setCopyModeButton);
-
- if (res != 0)
- project.getModules().setLocalCopyModeForAllModules (res == 1);
- }
-
- void showSetPathsMenu()
- {
- EnabledModuleList& moduleList = project.getModules();
-
- const String moduleToCopy (moduleList.getModuleID (table.getSelectedRow()));
-
- if (moduleToCopy.isNotEmpty())
- {
- PopupMenu m;
- m.addItem (1, "Copy the paths from the module '" + moduleToCopy + "' to all other modules");
-
- int res = m.showAt (©PathButton);
-
- if (res != 0)
- {
- for (Project::ExporterIterator exporter (project); exporter.next();)
- {
- for (int i = 0; i < moduleList.getNumModules(); ++i)
- {
- String modID = moduleList.getModuleID (i);
-
- if (modID != moduleToCopy)
- exporter->getPathForModuleValue (modID) = exporter->getPathForModuleValue (moduleToCopy).getValue();
- }
- }
- }
-
- table.repaint();
- }
- else
- {
- PopupMenu m;
- m.addItem (1, "Copy the paths from the selected module to all other modules", false);
-
- m.showAt (©PathButton);
- }
- }
-
- struct WebsiteUpdateFetchThread : private Thread,
- private AsyncUpdater
- {
- WebsiteUpdateFetchThread (ModulesPanel& p) : Thread ("Web Updater"), panel (p)
- {
- startThread (3);
- }
-
- ~WebsiteUpdateFetchThread()
- {
- stopThread (15000);
- }
-
- void run() override
- {
- static Time lastDownloadTime;
- static ModuleList lastList;
-
- if (Time::getCurrentTime() < lastDownloadTime + RelativeTime::minutes (2.0))
- {
- list = lastList;
- triggerAsyncUpdate();
- }
- else
- {
- if (list.loadFromWebsite() && ! threadShouldExit())
- {
- lastList = list;
- lastDownloadTime = Time::getCurrentTime();
- triggerAsyncUpdate();
- }
- }
- }
-
- void handleAsyncUpdate() override
- {
- panel.webUpdateFinished (list);
- }
-
- private:
- ModuleList list;
- ModulesPanel& panel;
- };
-
- ScopedPointer<WebsiteUpdateFetchThread> webUpdateThread;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModulesPanel)
- };
-
- //==============================================================================
- class DownloadAndInstallThread : public ThreadWithProgressWindow
- {
- public:
- DownloadAndInstallThread (const Array<ModuleDescription>& modulesToInstall)
- : ThreadWithProgressWindow ("Installing New Modules", true, true),
- result (Result::ok()),
- modules (modulesToInstall)
- {
- }
-
- static void updateModulesFromWeb (Project& project, const Array<ModuleDescription>& mods)
- {
- DownloadAndInstallThread d (mods);
-
- if (d.runThread())
- {
- if (d.result.failed())
- {
- AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
- "Module Install Failed",
- d.result.getErrorMessage());
- }
- else
- {
- for (int i = 0; i < d.modules.size(); ++i)
- project.getModules().addModule (d.modules.getReference(i).manifestFile,
- project.getModules().areMostModulesCopiedLocally());
- }
- }
- }
-
- static void addModuleFromWebsite (Project& project, const ModuleDescription& module)
- {
- Array<ModuleDescription> mods;
- mods.add (module);
-
- static File lastLocation (EnabledModuleList::findDefaultModulesFolder (project));
-
- FileChooser fc ("Select the parent folder for the new module...", lastLocation, String::empty, false);
-
- if (fc.browseForDirectory())
- {
- lastLocation = fc.getResult();
-
- if (lastLocation.getChildFile (ModuleDescription::getManifestFileName()).exists())
- {
- AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
- "Adding Module",
- "You chose a folder that appears to be a module.\n\n"
- "You need to select the *parent* folder inside which the new modules will be created.");
- return;
- }
-
- for (int i = 0; i < mods.size(); ++i)
- mods.getReference(i).manifestFile = lastLocation.getChildFile (mods.getReference(i).getID())
- .getChildFile (ModuleDescription::getManifestFileName());
-
- updateModulesFromWeb (project, mods);
- }
- }
-
- void run() override
- {
- for (int i = 0; i < modules.size(); ++i)
- {
- const ModuleDescription& m = modules.getReference(i);
-
- setProgress (i / (double) modules.size());
-
- MemoryBlock downloaded;
- result = download (m, downloaded);
-
- if (result.failed() || threadShouldExit())
- break;
-
- result = unzip (m, downloaded);
-
- if (result.failed() || threadShouldExit())
- break;
- }
- }
-
- Result download (const ModuleDescription& m, MemoryBlock& dest)
- {
- setStatusMessage ("Downloading " + m.getID() + "...");
-
- const ScopedPointer<InputStream> in (m.url.createInputStream (false, nullptr, nullptr, String::empty, 10000));
-
- if (in != nullptr && in->readIntoMemoryBlock (dest))
- return Result::ok();
-
- return Result::fail ("Failed to download from: " + m.url.toString (false));
- }
-
- Result unzip (const ModuleDescription& m, const MemoryBlock& data)
- {
- setStatusMessage ("Installing " + m.getID() + "...");
-
- MemoryInputStream input (data, false);
- ZipFile zip (input);
-
- if (zip.getNumEntries() == 0)
- return Result::fail ("The downloaded file wasn't a valid module file!");
-
- if (! m.getFolder().deleteRecursively())
- return Result::fail ("Couldn't delete the existing folder:\n" + m.getFolder().getFullPathName());
-
- return zip.uncompressTo (m.getFolder().getParentDirectory(), true);
- }
-
- Result result;
- Array<ModuleDescription> modules;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadAndInstallThread)
- };
|