From d89a2d39ebe8c17e4dca9c56e1415546014c142f Mon Sep 17 00:00:00 2001 From: pesckal Date: Thu, 22 Sep 2011 06:29:18 +0000 Subject: [PATCH] Separate dialog for libraries, tools, and modes --- app/src/processing/app/Base.java | 74 +- .../processing/app/ContributionListPanel.java | 89 +- .../processing/app/ContributionListing.java | 15 +- .../processing/app/ContributionManager.java | 1877 ++++++----------- .../app/ContributionManagerDialog.java | 547 +++++ app/src/processing/app/Editor.java | 27 +- app/src/processing/app/Mode.java | 9 + app/src/processing/app/UpdateCheck.java | 8 +- 8 files changed, 1391 insertions(+), 1255 deletions(-) create mode 100644 app/src/processing/app/ContributionManagerDialog.java diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 3596d41e7..a70eafa59 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -34,6 +34,7 @@ import java.util.zip.*; import javax.swing.*; import javax.swing.tree.*; +import processing.app.contribution.Contribution; import processing.app.contribution.InstalledContribution; import processing.core.*; @@ -90,10 +91,16 @@ public class Base { // A single instance of the preferences window Preferences preferencesFrame; - + // A single instance of the library manager window - ContributionManager contributionManagerFrame; - + ContributionManagerDialog libraryManagerFrame; + + ContributionManagerDialog toolManagerFrame; + + ContributionManagerDialog modeManagerFrame; + + ContributionManagerDialog updateManagerFrame; + // set to true after the first time the menu is built. // so that the errors while building don't show up again. boolean builtOnce; @@ -362,8 +369,40 @@ public class Base { buildCoreModes(); rebuildContribModes(); - contributionManagerFrame = new ContributionManager(); - + libraryManagerFrame = new ContributionManagerDialog("Library Manager", + new ContributionListing.Filter() { + + public boolean matches(Contribution contrib) { + return contrib.getType() == Contribution.Type.LIBRARY + || contrib.getType() == Contribution.Type.LIBRARY_COMPILATION; + } + }); + toolManagerFrame = new ContributionManagerDialog("Tool Manager", + new ContributionListing.Filter() { + + public boolean matches(Contribution contrib) { + return contrib.getType() == Contribution.Type.TOOL; + } + }); + modeManagerFrame = new ContributionManagerDialog("Mode Manager", + new ContributionListing.Filter() { + + public boolean matches(Contribution contrib) { + return contrib.getType() == Contribution.Type.MODE; + } + }); + updateManagerFrame = new ContributionManagerDialog("Update Manager", + new ContributionListing.Filter() { + + public boolean matches(Contribution contrib) { + if (contrib instanceof InstalledContribution) { + return ContributionListing.getInstance().hasUpdates(contrib); + } + + return false; + } + }); + // Make sure ThinkDifferent has library examples too defaultMode.rebuildLibraryList(); @@ -1548,13 +1587,27 @@ public class Base { /** * Show the library installer window. */ - public void handleOpenContributionManager() { - contributionManagerFrame.showFrame(activeEditor); + public void handleOpenLibraryManager() { + libraryManagerFrame.showFrame(activeEditor); + // Contribution.Type.LIBRARY + } + + /** + * Show the tool installer window. + */ + public void handleOpenToolManager() { + toolManagerFrame.showFrame(activeEditor); + } + + /** + * Show the mode installer window. + */ + public void handleOpenModeManager() { + modeManagerFrame.showFrame(activeEditor); } public void handleShowUpdates() { - contributionManagerFrame.showFrame(activeEditor); - contributionManagerFrame.setFilterText("has:updates"); + updateManagerFrame.showFrame(activeEditor); } /** @@ -1562,7 +1615,7 @@ public class Base { * user. Returns the number of libraries installed. */ public boolean handleConfirmAndInstallLibrary(File libFile) { - return contributionManagerFrame.confirmAndInstallLibrary(activeEditor, libFile) != null; + return ContributionManager.confirmAndInstallLibrary(activeEditor, libFile, null) != null; } // ................................................................... @@ -1884,7 +1937,6 @@ public class Base { } public File getSketchbookModesFolder() { - // return new File(getSketchbookFolder(), "libraries"); return new File(sketchbookFolder, "modes"); } diff --git a/app/src/processing/app/ContributionListPanel.java b/app/src/processing/app/ContributionListPanel.java index 067bfdb77..ec18b4f1d 100644 --- a/app/src/processing/app/ContributionListPanel.java +++ b/app/src/processing/app/ContributionListPanel.java @@ -56,7 +56,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib + "You can still intall this library manually by visiting\n" + "the library's website."; - ContributionManager contribManager; + ContributionManagerDialog contribManager; TreeMap panelByContribution; @@ -70,10 +70,19 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib protected JPanel statusPlaceholder; - public ContributionListPanel(ContributionManager libraryManager) { + ContributionListing.Filter permaFilter; + + ContributionListing contribListing; + + public ContributionListPanel(ContributionManagerDialog libraryManager, + ContributionListing.Filter filter) { + super(); this.contribManager = libraryManager; + this.permaFilter = filter; + + contribListing = ContributionListing.getInstance(); setLayout(new GridBagLayout()); setOpaque(true); @@ -88,7 +97,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib } panelByContribution = new TreeMap( - contribManager.getListing().getComparator()); + contribListing.getComparator()); statusPlaceholder = new JPanel(); statusPlaceholder.setVisible(false); @@ -119,29 +128,31 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib public void contributionAdded(final Contribution contribution) { - SwingUtilities.invokeLater(new Runnable() { - - public void run() { - if (panelByContribution.containsKey(contribution)) { - return; - } - - ContributionPanel newPanel = new ContributionPanel(); - - synchronized (panelByContribution) { - panelByContribution.put(contribution, newPanel); - } - - - if (newPanel != null) { - newPanel.setContribution(contribution); + if (permaFilter.matches(contribution)) { + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + if (panelByContribution.containsKey(contribution)) { + return; + } - add(newPanel); - updatePanelOrdering(); - updateColors(); + ContributionPanel newPanel = new ContributionPanel(); + + synchronized (panelByContribution) { + panelByContribution.put(contribution, newPanel); + } + + + if (newPanel != null) { + newPanel.setContribution(contribution); + + add(newPanel); + updatePanelOrdering(); + updateColors(); + } } - } }); + } } public void contributionRemoved(final Contribution contribution) { @@ -186,7 +197,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib synchronized (panelByContribution) { - Set hiddenPanels = new TreeSet(contribManager.getListing().getComparator()); + Set hiddenPanels = new TreeSet(contribListing.getComparator()); hiddenPanels.addAll(panelByContribution.keySet()); for (Contribution info : filteredContributions) { @@ -464,7 +475,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib if (contrib instanceof InstalledContribution) { InstalledContribution installed = (InstalledContribution) contrib; ContributionManager.removeFlagForDeletion(installed); - contribManager.getListing().replaceContribution(contrib, contrib); + contribListing.replaceContribution(contrib, contrib); } } }; @@ -476,15 +487,17 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib installRemoveButton.setEnabled(false); installProgressBar.setVisible(true); - contribManager.removeContribution((InstalledContribution) contrib, - new JProgressMonitor(installProgressBar) { - + + ContributionManager.removeContribution(contribManager.editor, + (InstalledContribution) contrib, + new JProgressMonitor(installProgressBar) { public void finishedAction() { // Finished uninstalling the library resetInstallProgressBarState(); installRemoveButton.setEnabled(true); } - }); + }, + contribManager.statusBar); } } }; @@ -589,8 +602,8 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib }; iconArea.setInheritsPopupMenu(true); iconArea.setOpaque(false); - Dimension d = new Dimension(ContributionManager.ICON_WIDTH, - ContributionManager.ICON_HEIGHT); + Dimension d = new Dimension(ContributionManagerDialog.ICON_WIDTH, + ContributionManagerDialog.ICON_HEIGHT); iconArea.setMinimumSize(d); iconArea.setPreferredSize(d); @@ -654,7 +667,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib public void actionPerformed(ActionEvent e) { updateButton.setEnabled(false); - AdvertisedContribution ad = contribManager.getListing() + AdvertisedContribution ad = contribListing .getAdvertisedContribution(contrib); String url = ad.link; installContribution(ad, url); @@ -766,7 +779,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib descriptionText.setText(description.toString()); setAlignment(descriptionText, StyleConstants.ALIGN_JUSTIFIED); - if (contribManager.getListing().hasUpdates(contrib)) { + if (contribListing.hasUpdates(contrib)) { StringBuilder versionText = new StringBuilder(); versionText.append(""); if (isFlagged) { @@ -788,7 +801,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib updateButton.setEnabled(true); if (contrib != null && !ContributionManager.requiresRestart(contrib)) { updateButton.setVisible(isSelected() - && contribManager.getListing().hasUpdates(contrib)); + && contribListing.hasUpdates(contrib)); } installRemoveButton.removeActionListener(installActionListener); @@ -836,7 +849,8 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib installProgressBar.setVisible(true); - contribManager.downloadAndInstall(downloadUrl, ad, + ContributionManager.downloadAndInstall(contribManager.editor, + downloadUrl, ad, new JProgressMonitor(installProgressBar) { public void finishedAction() { @@ -855,7 +869,8 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib + "downloading the contribution."); } } - } + }, + contribManager.statusBar ); } catch (MalformedURLException e) { @@ -925,7 +940,7 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib if (contrib != null && !ContributionManager.requiresRestart(contrib)) { updateButton.setVisible(isSelected() - && contribManager.getListing().hasUpdates(contrib)); + && contribListing.hasUpdates(contrib)); } installRemoveButton.setVisible(isSelected()); diff --git a/app/src/processing/app/ContributionListing.java b/app/src/processing/app/ContributionListing.java index b64d4f5e3..df85bda8c 100644 --- a/app/src/processing/app/ContributionListing.java +++ b/app/src/processing/app/ContributionListing.java @@ -60,7 +60,9 @@ public class ContributionListing { File listingFile; - public ContributionListing() { + static ContributionListing singleInstance; + + private ContributionListing() { listeners = new ArrayList(); advertisedContributions = new ArrayList(); librariesByCategory = new HashMap>(); @@ -73,7 +75,13 @@ public class ContributionListing { setAdvertisedList(listingFile); } } + + static public ContributionListing getInstance() { + if (singleInstance == null) + singleInstance = new ContributionListing(); + return singleInstance; + } void setAdvertisedList(File file) { @@ -566,4 +574,9 @@ public class ContributionListing { return downloadingListingLock.isLocked(); } + static interface Filter { + + boolean matches(Contribution contrib); + } + } diff --git a/app/src/processing/app/ContributionManager.java b/app/src/processing/app/ContributionManager.java index 2697ccc4f..7e0062eb3 100644 --- a/app/src/processing/app/ContributionManager.java +++ b/app/src/processing/app/ContributionManager.java @@ -1,1195 +1,682 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2004-11 Ben Fry and Casey Reas - Copyright (c) 2001-04 Massachusetts Institute of Technology - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program 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. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package processing.app; - -import java.awt.*; -import java.awt.event.*; -import java.awt.image.CropImageFilter; -import java.awt.image.FilteredImageSource; -import java.io.*; -import java.net.*; -import java.text.*; -import java.util.*; -import java.util.List; -import java.util.zip.*; - -import javax.imageio.ImageIO; -import javax.swing.*; -import javax.swing.event.*; - -import processing.app.ContributionListing.AdvertisedContribution; -import processing.app.contribution.*; -import processing.app.contribution.Contribution.Type; - -public class ContributionManager { - - static public final String DELETION_FLAG = "flagged_for_deletion"; - - static private final String DOUBLE_CLICK_SECONDARY = - "Click “Yes� to install this library to your sketchbook..."; - - static private final String DISCOVERY_INTERNAL_ERROR_MESSAGE = - "An internal error occured while searching for contributions in the downloaded file."; - - static private final String DISCOVERY_NONE_FOUND_ERROR_MESSAGE = - "Maybe it's just us, but it looks like there are no contributions in this file."; - - static private final String ERROR_OVERWRITING_PROPERTIES_MESSAGE = - "Error overwriting .properties file."; - - static final String ANY_CATEGORY = "Any"; - - /** Width of each contribution icon. */ - static final int ICON_WIDTH = 25; - - /** Height of each contribution icon. */ - static final int ICON_HEIGHT = 20; - - JFrame dialog; - - FilterField filterField; - - JScrollPane scrollPane; - - ContributionListPanel contributionListPanel; - - StatusPanel statusBar; - - JComboBox categoryChooser; - - Image[] contributionIcons; - - // the calling editor, so updates can be applied - Editor editor; - - String category; - - ContributionListing contribListing; - - /** - * Initializes the contribution listing and fetches the advertised - * contributions in a separate thread. This does not initialize any AWT - * components. - */ - public ContributionManager() { - contribListing = new ContributionListing(); - - contributionListPanel = new ContributionListPanel(this); - contribListing.addContributionListener(contributionListPanel); - } - - protected void showFrame(Editor editor) { - this.editor = editor; - - if (dialog == null) { - dialog = new JFrame("Contribution Manager"); - - Base.setIcon(dialog); - - createComponents(); - - registerDisposeListeners(); - - dialog.pack(); - Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); - dialog.setLocation((screen.width - dialog.getWidth()) / 2, - (screen.height - dialog.getHeight()) / 2); - - contributionListPanel.grabFocus(); - } - - dialog.setVisible(true); - - if (!contribListing.hasDownloadedLatestList()) { - contribListing.getAdvertisedContributions(new AbstractProgressMonitor() { - public void startTask(String name, int maxValue) { - } - - public void finished() { - super.finished(); - - updateContributionListing(); - updateCategoryChooser(); - if (isError()) { - statusBar.setErrorMessage("An error occured when downloading " + - "the list of available contributions."); - } - } - }); - } - - updateContributionListing(); - - if (contributionIcons == null) { - try { - Image allButtons = ImageIO.read(Base.getLibStream("contributions.gif")); - int count = allButtons.getHeight(dialog) / ContributionManager.ICON_HEIGHT; - contributionIcons = new Image[count]; - contributionIcons[0] = allButtons; - contributionIcons[1] = allButtons; - contributionIcons[2] = allButtons; - contributionIcons[3] = allButtons; - - for (int i = 0; i < count; i++) { - Image image = dialog.createImage( - new FilteredImageSource(allButtons.getSource(), - new CropImageFilter(0, i * ContributionManager.ICON_HEIGHT, - ContributionManager.ICON_WIDTH, - ContributionManager.ICON_HEIGHT))); - contributionIcons[i] = image; - } - - contributionListPanel.updateColors(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - - public Image getContributionIcon(Contribution.Type type) { - - if (contributionIcons == null) - return null; - - switch (type) { - case LIBRARY: - return contributionIcons[0]; - case TOOL: - return contributionIcons[1]; - case MODE: - return contributionIcons[2]; - case LIBRARY_COMPILATION: - return contributionIcons[3]; - } - return null; - } - - /** - * Close the window after an OK or Cancel. - */ - protected void disposeFrame() { - dialog.dispose(); - editor = null; - } - - /** Creates and arranges the Swing components in the dialog. */ - private void createComponents() { - dialog.setResizable(true); - - Container pane = dialog.getContentPane(); - pane.setLayout(new GridBagLayout()); - - { // The filter text area - GridBagConstraints c = new GridBagConstraints(); - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 2; - c.weightx = 1; - c.fill = GridBagConstraints.HORIZONTAL; - filterField = new FilterField(); - - pane.add(filterField, c); - } - - { // The scroll area containing the contribution listing and the status bar. - GridBagConstraints c = new GridBagConstraints(); - c.fill = GridBagConstraints.BOTH; - c.gridx = 0; - c.gridy = 1; - c.gridwidth = 2; - c.weighty = 1; - c.weightx = 1; - - scrollPane = new JScrollPane(); - scrollPane.setPreferredSize(new Dimension(300, 300)); - scrollPane.setViewportView(contributionListPanel); - scrollPane.getViewport().setOpaque(true); - scrollPane.getViewport().setBackground(contributionListPanel.getBackground()); - scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - - statusBar = new StatusPanel(); - statusBar.setBorder(BorderFactory.createEtchedBorder()); - - final JLayeredPane layeredPane = new JLayeredPane(); - layeredPane.add(scrollPane, JLayeredPane.DEFAULT_LAYER); - layeredPane.add(statusBar, JLayeredPane.PALETTE_LAYER); - - layeredPane.addComponentListener(new ComponentAdapter() { - - void resizeLayers() { - scrollPane.setSize(layeredPane.getSize()); - scrollPane.updateUI(); - } - - public void componentShown(ComponentEvent e) { - resizeLayers(); - } - - public void componentResized(ComponentEvent arg0) { - resizeLayers(); - } - }); - - final JViewport viewport = scrollPane.getViewport(); - viewport.addComponentListener(new ComponentAdapter() { - void resizeLayers() { - statusBar.setLocation(0, viewport.getHeight() - 18); - - Dimension d = viewport.getSize(); - d.height = 20; - d.width += 3; - statusBar.setSize(d); - } - public void componentShown(ComponentEvent e) { - resizeLayers(); - } - public void componentResized(ComponentEvent e) { - resizeLayers(); - } - }); - - pane.add(layeredPane, c); - } - - { // Shows "Category:" - GridBagConstraints c = new GridBagConstraints(); - c.gridx = 0; - c.gridy = 2; - pane.add(new Label("Category:"), c); - } - - { // Combo box for selecting a category - GridBagConstraints c = new GridBagConstraints(); - c.fill = GridBagConstraints.HORIZONTAL; - c.gridx = 1; - c.gridy = 2; - - categoryChooser = new JComboBox(); - updateCategoryChooser(); - pane.add(categoryChooser, c); - categoryChooser.addItemListener(new ItemListener() { - - public void itemStateChanged(ItemEvent e) { - category = (String) categoryChooser.getSelectedItem(); - if (ContributionManager.ANY_CATEGORY.equals(category)) { - category = null; - } - - filterLibraries(category, filterField.filters); - } - }); - } - - dialog.setMinimumSize(new Dimension(550, 400)); - } - - private void updateCategoryChooser() { - if (categoryChooser == null) - return; - - ArrayList categories; - categoryChooser.removeAllItems(); - categories = new ArrayList(contribListing.getCategories()); - Collections.sort(categories); - categories.add(0, ContributionManager.ANY_CATEGORY); - for (String s : categories) { - categoryChooser.addItem(s); - } - } - - private void registerDisposeListeners() { - dialog.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent e) { - disposeFrame(); - } - }); - ActionListener disposer = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - disposeFrame(); - } - }; - Base.registerWindowCloseKeys(dialog.getRootPane(), disposer); - - // handle window closing commands for ctrl/cmd-W or hitting ESC. - - dialog.getContentPane().addKeyListener(new KeyAdapter() { - public void keyPressed(KeyEvent e) { - //System.out.println(e); - KeyStroke wc = Base.WINDOW_CLOSE_KEYSTROKE; - if ((e.getKeyCode() == KeyEvent.VK_ESCAPE) || - (KeyStroke.getKeyStrokeForEvent(e).equals(wc))) { - disposeFrame(); - } - } - }); - } - - public void filterLibraries(String category, List filters) { - - List filteredLibraries = contribListing - .getFilteredLibraryList(category, filters); - - contributionListPanel.filterLibraries(filteredLibraries); - } - - protected void updateContributionListing() { - if (editor == null) - return; - - ArrayList libraries = editor.getMode().contribLibraries; - ArrayList compilations = LibraryCompilation.list(libraries); - - // Remove libraries from the list that are part of a compilations - for (LibraryCompilation compilation : compilations) { - Iterator it = libraries.iterator(); - while (it.hasNext()) { - Library current = it.next(); - if (compilation.getFolder().equals(current.getFolder().getParentFile())) { - it.remove(); - } - } - } - - ArrayList contributions = new ArrayList(); - contributions.addAll(editor.contribTools); - contributions.addAll(libraries); - contributions.addAll(compilations); - - contribListing.updateInstalledList(contributions); - } - - /** - * Non-blocking call to remove a contribution in a new thread. - */ - public void removeContribution(final InstalledContribution contribution, - ProgressMonitor pm) { - if (contribution == null) - return; - - final ProgressMonitor progressMonitor = pm != null ? pm : new NullProgressMonitor(); - - new Thread(new Runnable() { - - public void run() { - progressMonitor.startTask("Removing", ProgressMonitor.UNKNOWN); - - boolean doBackup = Preferences.getBoolean("contribution.backup.on_remove"); - if (ContributionManager.requiresRestart(contribution)) { - - if (!doBackup || (doBackup && backupContribution(contribution, false))) { - if (ContributionManager.flagForDeletion(contribution)) { - contribListing.replaceContribution(contribution, contribution); - } - } - } else { - boolean success = false; - if (doBackup) { - success = backupContribution(contribution, true); - } else { - Base.removeDir(contribution.getFolder()); - success = !contribution.getFolder().exists(); - } - - if (success) { - Contribution advertisedVersion = contribListing - .getAdvertisedContribution(contribution); - - if (advertisedVersion == null) { - contribListing.removeContribution(contribution); - } else { - contribListing.replaceContribution(contribution, - advertisedVersion); - } - } else { - // There was a failure backing up the folder - if (doBackup) { - - } else { - statusBar.setErrorMessage("Could not delete the contribution's files"); - } - } - } - refreshInstalled(); - progressMonitor.finished(); - } - }).start(); - - } - - /** - * Non-blocking call to download and install a contribution in a new thread. - * - * @param url - * Direct link to the contribution. - * @param toBeReplaced - * The Contribution that will be replaced by this library being - * installed (e.g. an advertised version of a contribution, or the - * old version of a contribution that is being updated). Must not be - * null. - */ - public void downloadAndInstall(final URL url, - final AdvertisedContribution ad, - final JProgressMonitor downloadProgressMonitor, - final JProgressMonitor installProgressMonitor) { - - final File libDest = getTemporaryFile(url); - - new Thread(new Runnable() { - - public void run() { - - FileDownloader.downloadFile(url, libDest, downloadProgressMonitor); - - - if (!downloadProgressMonitor.isCanceled() && !downloadProgressMonitor.isError()) { - - installProgressMonitor.startTask("Installing", ProgressMonitor.UNKNOWN); - - InstalledContribution contribution = null; - switch (ad.getType()) { - case LIBRARY: - contribution = installLibrary(libDest, ad, false); - break; - case LIBRARY_COMPILATION: - contribution = installLibraryCompilation(libDest); - break; - case TOOL: - contribution = installTool(libDest, ad); - break; - } - - if (contribution != null) { - // XXX contributionListing.getInformationFromAdvertised(contribution); - // get the category at least - contribListing.replaceContribution(ad, contribution); - refreshInstalled(); - } - - dialog.pack(); - installProgressMonitor.finished(); - } - } - }).start(); - - } - - protected LibraryCompilation installLibraryCompilation(File f) { - File parentDir = unzipFileToTemp(f); - - LibraryCompilation compilation = LibraryCompilation.create(parentDir); - - if (compilation == null) { - statusBar.setErrorMessage(DISCOVERY_NONE_FOUND_ERROR_MESSAGE); - return null; - } - - String folderName = compilation.getName(); - - File libraryDestination = editor.getBase().getSketchbookLibrariesFolder(); - File dest = new File(libraryDestination, folderName); - - // XXX: Check for conflicts with other library names, etc. - boolean errorEncountered = false; - if (dest.exists()) { - if (!dest.delete()) { - // Problem - errorEncountered = true; - } - } - - if (!errorEncountered) { - // Install it, return it - if (parentDir.renameTo(dest)) { - return LibraryCompilation.create(dest); - } - } - - return null; - } - - public Library confirmAndInstallLibrary(Editor editor, File libFile) { - this.editor = editor; - - int result = Base.showYesNoQuestion(this.editor, "Install", - "Install libraries from " + libFile.getName() + "?", - ContributionManager.DOUBLE_CLICK_SECONDARY); - - if (result == JOptionPane.YES_OPTION) { - return installLibrary(libFile, null, true); - } - - return null; - } - - /** - * Creates a temporary folder and unzips a file to a subdirectory of the temp - * folder. The subdirectory is the only file of the tempo folder. - * - * e.g. if the contents of foo.zip are /hello and /world, then the resulting - * files will be - * /tmp/foo9432423uncompressed/foo/hello - * /tmp/foo9432423uncompress/foo/world - * ...and "/tmp/id9432423uncompress/foo/" will be returned. - * - * @return the folder where the zips contents have been unzipped to (the - * subdirectory of the temp folder). - */ - File unzipFileToTemp(File libFile) { - - String fileName = ContributionManager.getFileName(libFile); - File tmpFolder = null; - - try { - tmpFolder = Base.createTempFolder(fileName, "uncompressed"); - tmpFolder = new File(tmpFolder, fileName); - tmpFolder.mkdirs(); - } catch (IOException e) { - statusBar.setErrorMessage("Could not create temp folder to uncompressed zip file."); - } - - ContributionManager.unzip(libFile, tmpFolder); - - return tmpFolder; - } - - protected File getTemporaryFile(URL url) { - try { - File tmpFolder = Base.createTempFolder("library", "download"); - - String[] segments = url.getFile().split("/"); - File libFile = new File(tmpFolder, segments[segments.length - 1]); - libFile.setWritable(true); - - return libFile; - } catch (IOException e) { - statusBar.setErrorMessage("Could not create a temp folder for download."); - } - - return null; - } - - /** - * Returns the name of a file without its path or extension. - * - * For example, - * "/path/to/helpfullib.zip" returns "helpfullib" - * "helpfullib-0.1.1.plb" returns "helpfullib-0.1.1" - */ - protected static String getFileName(File libFile) { - String path = libFile.getPath(); - int lastSeparator = path.lastIndexOf(File.separatorChar); - - String fileName; - if (lastSeparator != -1) { - fileName = path.substring(lastSeparator + 1); - } else { - fileName = path; - } - - int lastDot = fileName.lastIndexOf('.'); - if (lastDot != -1) { - return fileName.substring(0, lastDot); - } - - return fileName; - } - - protected ToolContribution installTool(File zippedToolFile, - AdvertisedContribution ad) { - - File tempDir = unzipFileToTemp(zippedToolFile); - - ArrayList discoveredTools = ToolContribution.list(tempDir, false); - if (discoveredTools.isEmpty()) { - // Sometimes tool authors place all their folders in the base - // directory of a zip file instead of in single folder as the - // guidelines suggest. If this is the case, we might be able to find the - // library by stepping up a directory and searching for libraries again. - discoveredTools = ToolContribution.list(tempDir.getParentFile(), false); - } - - if (discoveredTools != null && discoveredTools.size() == 1) { - ToolContribution discoveredTool = discoveredTools.get(0); - File propFile = new File(discoveredTool.getFolder(), "tool.properties"); - - if (ad == null || writePropertiesFile(propFile, ad)) { - return installTool(discoveredTool); - } else { - statusBar.setErrorMessage(ERROR_OVERWRITING_PROPERTIES_MESSAGE); - } - } else { - // Diagnose the problem and notify the user - if (discoveredTools == null || discoveredTools.isEmpty()) { - statusBar.setErrorMessage(DISCOVERY_INTERNAL_ERROR_MESSAGE); - } else { - statusBar.setErrorMessage("There were multiple tools in the file, so we're ignoring it."); - } - } - - return null; - } - - protected ToolContribution installTool(ToolContribution newTool) { - - ArrayList oldTools = editor.contribTools; - - String toolFolderName = newTool.getFolder().getName(); - - File toolDestination = editor.getBase().getSketchbookToolsFolder(); - File newToolDest = new File(toolDestination, toolFolderName); - - for (ToolContribution oldTool : oldTools) { - - // XXX: Handle other cases when installing tools. - // -What if a library by the same name is already installed? - // -What if newLibDest exists, but isn't used by an existing tools? - if (oldTool.getFolder().exists() && oldTool.getFolder().equals(newToolDest)) { - - // XXX: We can't replace stuff, soooooo.... do something different - if (!backupContribution(oldTool, false)) { - return null; - } - } - } - - // Move newTool to the sketchbook library folder - if (newTool.getFolder().renameTo(newToolDest)) { - ToolContribution movedTool = ToolContribution.getTool(newToolDest); - try { - movedTool.initializeToolClass(); - return movedTool; - } catch (Exception e) { - e.printStackTrace(); - } - } else { - statusBar.setErrorMessage("Could not move tool \"" + newTool.getName() - + "\" to sketchbook."); - } - - return null; - } - - protected boolean writePropertiesFile(File propFile, AdvertisedContribution ad) { - try { - if (propFile.delete() && propFile.createNewFile() && propFile.setWritable(true)) { - BufferedWriter bw = new BufferedWriter(new FileWriter(propFile)); - - bw.write("name=" + ad.getName() + "\n"); - bw.write("category=" + ad.getCategory() + "\n"); - bw.write("authorList=" + ad.getAuthorList() + "\n"); - bw.write("url=" + ad.getUrl() + "\n"); - bw.write("sentence=" + ad.getSentence() + "\n"); - bw.write("paragraph=" + ad.getParagraph() + "\n"); - bw.write("version=" + ad.getVersion() + "\n"); - bw.write("prettyVersion=" + ad.getPrettyVersion() + "\n"); - - bw.close(); - } - return true; - } catch (FileNotFoundException e) { - } catch (IOException e) { - } - - return false; - } - - /** - * @param libFile - * a zip file containing the library to install - * @param ad - * the advertised version of this library, if it was downloaded - * through the Contribution Manager, or null. This is used to replace - * the library.properties file in the zip - * @param confirmReplace - * true to open a dialog asking the user to confirm removing/moving - * the library when a library by the same name already exists - * @return - */ - protected Library installLibrary(File libFile, AdvertisedContribution ad, - boolean confirmReplace) { - File tempDir = unzipFileToTemp(libFile); - - try { - ArrayList discoveredLibs = Library.list(tempDir); - if (discoveredLibs.isEmpty()) { - // Sometimes library authors place all their folders in the base - // directory of a zip file instead of in single folder as the - // guidelines suggest. If this is the case, we might be able to find the - // library by stepping up a directory and searching for libraries again. - discoveredLibs = Library.list(tempDir.getParentFile()); - } - - if (discoveredLibs != null && discoveredLibs.size() == 1) { - Library discoveredLib = discoveredLibs.get(0); - File propFile = new File(discoveredLib.getFolder(), "library.properties"); - - if (ad == null || writePropertiesFile(propFile, ad)) { - return installLibrary(discoveredLib, confirmReplace); - } else { - statusBar.setErrorMessage(ERROR_OVERWRITING_PROPERTIES_MESSAGE); - } - } else { - // Diagnose the problem and notify the user - if (discoveredLibs == null) { - statusBar.setErrorMessage(ContributionManager.DISCOVERY_INTERNAL_ERROR_MESSAGE); - } else if (discoveredLibs.isEmpty()) { - statusBar.setErrorMessage(ContributionManager.DISCOVERY_NONE_FOUND_ERROR_MESSAGE); - } else { - statusBar.setErrorMessage("There were multiple libraries in the file, so we're ignoring it."); - } - } - } catch (IOException ioe) { - statusBar.setErrorMessage(ContributionManager.DISCOVERY_INTERNAL_ERROR_MESSAGE); - } - - return null; - } - - /** - * @param confirmReplace - * if true and the library is already installed, opens a prompt to - * ask the user if it's okay to replace the library. If false, the - * library is always replaced with the new copy. - */ - protected Library installLibrary(Library newLib, boolean confirmReplace) { - - ArrayList oldLibs = editor.getMode().contribLibraries; - - String libFolderName = newLib.getFolder().getName(); - - File libraryDestination = editor.getBase().getSketchbookLibrariesFolder(); - File newLibDest = new File(libraryDestination, libFolderName); - - for (Library oldLib : oldLibs) { - - // XXX: Handle other cases when installing libraries. - // -What if a library by the same name is already installed? - // -What if newLibDest exists, but isn't used by an existing library? - if (oldLib.getFolder().exists() && oldLib.getFolder().equals(newLibDest)) { - - int result = 0; - boolean doBackup = Preferences.getBoolean("contribution.backup.on_install"); - if (confirmReplace) { - if (doBackup) { - result = Base.showYesNoQuestion(editor, "Replace", - "Replace pre-existing \"" + oldLib.getName() + "\" library?", - "A pre-existing copy of the \"" + oldLib.getName() + "\" library
"+ - "has been found in your sketchbook. Clicking “Yes”
"+ - "will move the existing library to a backup folder
" + - " in libraries/old before replacing it."); - if (result != JOptionPane.YES_OPTION || !backupContribution(oldLib, true)) { - return null; - } - } else { - result = Base.showYesNoQuestion(editor, "Replace", - "Replace pre-existing \"" + oldLib.getName() + "\" library?", - "A pre-existing copy of the \"" + oldLib.getName() + "\" library
"+ - "has been found in your sketchbook. Clicking “Yes”
"+ - "will permanently delete this library and all of its contents
"+ - "before replacing it."); - if (result != JOptionPane.YES_OPTION || !oldLib.getFolder().delete()) { - return null; - } - } - } else { - if (doBackup && !backupContribution(oldLib, true) - || !doBackup && !oldLib.getFolder().delete()) { - return null; - } - } - } - } - - // Move newLib to the sketchbook library folder - if (newLib.getFolder().renameTo(newLibDest)) { - return new Library(newLibDest, null); -// try { -// FileUtils.copyDirectory(newLib.folder, libFolder); -// FileUtils.deleteQuietly(newLib.folder); -// newLib.folder = libFolder; -// } catch (IOException e) { - } else { - statusBar.setErrorMessage("Could not move library \"" - + newLib.getName() + "\" to sketchbook."); - return null; - } - } - - public void refreshInstalled() { - editor.getMode().rebuildImportMenu(); - editor.rebuildToolMenu(); - } - - /** - * Moves the given contribution to a backup folder. - * @param doDeleteOriginal - * true if the file should be moved to the directory, false if it - * should instead be copied, leaving the original in place - */ - private boolean backupContribution(InstalledContribution contribution, - boolean doDeleteOriginal) { - - File backupFolder = null; - - switch (contribution.getType()) { - case LIBRARY: - case LIBRARY_COMPILATION: - backupFolder = createLibraryBackupFolder(); - break; - case MODE: - break; - case TOOL: - backupFolder = createToolBackupFolder(); - break; - } - - if (backupFolder == null) return false; - - String libFolderName = contribution.getFolder().getName(); - - String prefix = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); - final String backupName = prefix + "_" + libFolderName; - File backupSubFolder = ContributionManager.getUniqueName(backupFolder, backupName); - -// try { -// FileUtils.moveDirectory(lib.folder, backupFolderForLib); -// return true; - - boolean success = false; - if (doDeleteOriginal) { - success = contribution.getFolder().renameTo(backupSubFolder); - } else { - try { - Base.copyDir(contribution.getFolder(), backupSubFolder); - success = true; - } catch (IOException e) { - } - } -// } catch (IOException e) { - if (!success) { - statusBar.setErrorMessage("Could not move contribution to backup folder."); - } - return success; - } - - private File createLibraryBackupFolder() { - - File libraryBackupFolder = new File(editor.getBase() - .getSketchbookLibrariesFolder(), "old"); - - if (!libraryBackupFolder.exists() || !libraryBackupFolder.isDirectory()) { - if (!libraryBackupFolder.mkdirs()) { - statusBar.setErrorMessage("Could not create backup folder for library."); - return null; - } - } - - return libraryBackupFolder; - } - - private File createToolBackupFolder() { - - File toolsBackupFolder = new File(editor.getBase() - .getSketchbookToolsFolder(), "old"); - - if (!toolsBackupFolder.exists() || !toolsBackupFolder.isDirectory()) { - if (!toolsBackupFolder.mkdirs()) { - statusBar.setErrorMessage("Could not create backup folder for tool."); - return null; - } - } - - return toolsBackupFolder; - } - - /** - * Returns a file in the parent folder that does not exist yet. If - * parent/fileName already exists, this will look for parent/fileName(2) - * then parent/fileName(3) and so forth. - * - * @return a file that does not exist yet - */ - public static File getUniqueName(File parentFolder, String fileName) { - File backupFolderForLib; - int i = 1; - do { - String folderName = fileName; - if (i >= 2) { - folderName += "(" + i + ")"; - } - i++; - - backupFolderForLib = new File(parentFolder, folderName); - } while (backupFolderForLib.exists()); - - return backupFolderForLib; - } - - public static void unzip(File zipFile, File dest) { - try { - FileInputStream fis = new FileInputStream(zipFile); - CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32()); - ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum)); - ZipEntry next = null; - while ((next = zis.getNextEntry()) != null) { - File currentFile = new File(dest, next.getName()); - if (next.isDirectory()) { - currentFile.mkdirs(); - } else { - currentFile.createNewFile(); - ContributionManager.unzipEntry(zis, currentFile); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - private static void unzipEntry(ZipInputStream zin, File f) throws IOException { - FileOutputStream out = new FileOutputStream(f); - byte[] b = new byte[512]; - int len = 0; - while ((len = zin.read(b)) != -1) { - out.write(b, 0, len); - } - out.close(); - } - - public void setFilterText(String filter) { - if (filter == null || filter.isEmpty()) { - filterField.setText(""); - filterField.isShowingHint = true; - } else { - filterField.setText(filter); - filterField.isShowingHint = false; - } - filterField.applyFilter(); - - } - - class FilterField extends JTextField { - - final static String filterHint = "Filter your search..."; - - boolean isShowingHint; - - List filters; - - public FilterField () { - super(filterHint); - - isShowingHint = true; - - filters = new ArrayList(); - - addFocusListener(new FocusListener() { - - public void focusLost(FocusEvent focusEvent) { - if (filterField.getText().isEmpty()) { - isShowingHint = true; - } - - updateStyle(); - } - - public void focusGained(FocusEvent focusEvent) { - if (isShowingHint) { - isShowingHint = false; - filterField.setText(""); - } - - updateStyle(); - } - }); - - getDocument().addDocumentListener(new DocumentListener() { - - public void removeUpdate(DocumentEvent e) { - applyFilter(); - } - - public void insertUpdate(DocumentEvent e) { - applyFilter(); - } - - public void changedUpdate(DocumentEvent e) { - applyFilter(); - } - }); - } - - public void applyFilter() { - String filter = filterField.getFilterText(); - filter = filter.toLowerCase(); - - // Replace anything but 0-9, a-z, or : with a space - filter = filter.replaceAll("[^\\x30-\\x39^\\x61-\\x7a^\\x3a]", " "); - filters = Arrays.asList(filter.split(" ")); - filterLibraries(category, filters); - } - - public String getFilterText() { - return isShowingHint ? "" : getText(); - } - - public void updateStyle() { - if (isShowingHint) { - filterField.setText(filterHint); - - // setForeground(UIManager.getColor("TextField.light")); // too light - setForeground(Color.gray); - } else { - setForeground(UIManager.getColor("TextField.foreground")); - } - } - } - - public boolean hasAlreadyBeenOpened() { - return dialog != null; - } - - public ContributionListing getListing() { - return contribListing; - } - - static public boolean flagForDeletion(InstalledContribution contrib) { - // Only returns false if the file already exists, so we can - // ignore the return value. - try { - new File(contrib.getFolder(), ContributionManager.DELETION_FLAG).createNewFile(); - return true; - } catch (IOException e) { - return false; - } - } - - static public boolean removeFlagForDeletion(InstalledContribution contrib) { - return new File(contrib.getFolder(), ContributionManager.DELETION_FLAG).delete(); - } - - static public boolean isFlaggedForDeletion(Contribution contrib) { - if (contrib instanceof InstalledContribution) { - InstalledContribution installed = (InstalledContribution) contrib; - return new File(installed.getFolder(), ContributionManager.DELETION_FLAG).exists(); - } - return false; - } - - /** Returns true if the type of contribution requires the PDE to restart - * when being removed. */ - static public boolean requiresRestart(Contribution contrib) { - return contrib.getType() == Type.TOOL || contrib.getType() == Type.MODE; - } - - class StatusPanel extends JPanel { - - String errorMessage; - - StatusPanel() { - addMouseListener(new MouseAdapter() { - - public void mousePressed(MouseEvent e) { - clearErrorMessage(); - } - }); - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - g.setFont(new Font("SansSerif", Font.PLAIN, 10)); - int baseline = (getSize().height + g.getFontMetrics().getAscent()) / 2; - - if (contribListing.isDownloadingListing()) { - g.setColor(Color.black); - g.drawString("Downloading software listing...", 2, baseline); - setVisible(true); - } else if (errorMessage != null) { - g.setColor(Color.red); - g.drawString(errorMessage, 2, baseline); - setVisible(true); - } else { - setVisible(false); - } - } - - void setErrorMessage(String message) { - errorMessage = message; - setVisible(true); - - JPanel placeholder = ContributionManager.this.contributionListPanel.statusPlaceholder; - Dimension d = getPreferredSize(); - if (Base.isWindows()) { - d.height += 5; - placeholder.setPreferredSize(d); - } - placeholder.setVisible(true); - -// Rectangle rect = scrollPane.getViewport().getViewRect(); -// rect.x += d.height; -// scrollPane.getViewport().scrollRectToVisible(rect); - } - - void clearErrorMessage() { - errorMessage = null; - repaint(); - - ContributionManager.this.contributionListPanel.statusPlaceholder - .setVisible(false); - } - } - -} - -abstract class JProgressMonitor extends AbstractProgressMonitor { - JProgressBar progressBar; - - public JProgressMonitor(JProgressBar progressBar) { - this.progressBar = progressBar; - } - - public void startTask(String name, int maxValue) { - isFinished = false; - progressBar.setString(name); - progressBar.setIndeterminate(maxValue == UNKNOWN); - progressBar.setMaximum(maxValue); - } - - public void setProgress(int value) { - super.setProgress(value); - progressBar.setValue(value); - } - - @Override - public void finished() { - super.finished(); - finishedAction(); - } - - public abstract void finishedAction(); - -} \ No newline at end of file +package processing.app; + +import java.io.*; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.zip.*; + +import javax.swing.JOptionPane; + +import processing.app.ContributionListing.AdvertisedContribution; +import processing.app.contribution.*; +import processing.app.contribution.Contribution.Type; + +interface ErrorWidget { + void setErrorMessage(String msg); +} + +public class ContributionManager { + + static private final String DOUBLE_CLICK_SECONDARY = + "Click “Yes� to install this library to your sketchbook..."; + + static private final String DISCOVERY_INTERNAL_ERROR_MESSAGE = + "An internal error occured while searching for contributions in the downloaded file."; + + static private final String DISCOVERY_NONE_FOUND_ERROR_MESSAGE = + "Maybe it's just us, but it looks like there are no contributions in this file."; + + static private final String ERROR_OVERWRITING_PROPERTIES_MESSAGE = + "Error overwriting .properties file."; + + static public final String DELETION_FLAG = "flagged_for_deletion"; + + static public final ContributionListing contribListing; + + static { + contribListing = ContributionListing.getInstance(); + } + + /** + * Non-blocking call to remove a contribution in a new thread. + */ + static public void removeContribution(final Editor editor, + final InstalledContribution contribution, + final ProgressMonitor pm, + final ErrorWidget statusBar) { + if (contribution == null) + return; + + final ProgressMonitor progressMonitor = pm != null ? pm : new NullProgressMonitor(); + + new Thread(new Runnable() { + + public void run() { + progressMonitor.startTask("Removing", ProgressMonitor.UNKNOWN); + + boolean doBackup = Preferences.getBoolean("contribution.backup.on_remove"); + if (ContributionManager.requiresRestart(contribution)) { + + if (!doBackup || (doBackup && backupContribution(editor, contribution, false, statusBar))) { + if (ContributionManager.flagForDeletion(contribution)) { + contribListing.replaceContribution(contribution, contribution); + } + } + } else { + boolean success = false; + if (doBackup) { + success = backupContribution(editor, contribution, true, statusBar); + } else { + Base.removeDir(contribution.getFolder()); + success = !contribution.getFolder().exists(); + } + + if (success) { + Contribution advertisedVersion = contribListing + .getAdvertisedContribution(contribution); + + if (advertisedVersion == null) { + contribListing.removeContribution(contribution); + } else { + contribListing.replaceContribution(contribution, + advertisedVersion); + } + } else { + // There was a failure backing up the folder + if (doBackup) { + + } else { + statusBar.setErrorMessage("Could not delete the contribution's files"); + } + } + } + refreshInstalled(editor); + progressMonitor.finished(); + } + }).start(); + + } + + /** + * Non-blocking call to download and install a contribution in a new thread. + * + * @param url + * Direct link to the contribution. + * @param toBeReplaced + * The Contribution that will be replaced by this library being + * installed (e.g. an advertised version of a contribution, or the + * old version of a contribution that is being updated). Must not be + * null. + */ + static public void downloadAndInstall(final Editor editor, + final URL url, + final AdvertisedContribution ad, + final JProgressMonitor downloadProgressMonitor, + final JProgressMonitor installProgressMonitor, + final ErrorWidget statusBar) { + + final File libDest = getTemporaryFile(url, statusBar); + + new Thread(new Runnable() { + + public void run() { + + FileDownloader.downloadFile(url, libDest, downloadProgressMonitor); + + + if (!downloadProgressMonitor.isCanceled() && !downloadProgressMonitor.isError()) { + + installProgressMonitor.startTask("Installing", ProgressMonitor.UNKNOWN); + + InstalledContribution contribution = null; + switch (ad.getType()) { + case LIBRARY: + contribution = installLibrary(editor, libDest, ad, false, statusBar); + break; + case LIBRARY_COMPILATION: + contribution = installLibraryCompilation(editor, libDest, statusBar); + break; + case TOOL: + contribution = installTool(editor, libDest, ad, statusBar); + break; + } + + if (contribution != null) { + // XXX contributionListing.getInformationFromAdvertised(contribution); + // get the category at least + contribListing.replaceContribution(ad, contribution); + refreshInstalled(editor); + } + + installProgressMonitor.finished(); + } + } + }).start(); + + } + + static public LibraryCompilation installLibraryCompilation(Editor editor, + File f, + ErrorWidget statusBar) { + File parentDir = unzipFileToTemp(f, statusBar); + + LibraryCompilation compilation = LibraryCompilation.create(parentDir); + + if (compilation == null) { + statusBar.setErrorMessage(DISCOVERY_NONE_FOUND_ERROR_MESSAGE); + return null; + } + + String folderName = compilation.getName(); + + File dest = new File(editor.getBase().getSketchbookLibrariesFolder(), folderName); + + // XXX: Check for conflicts with other library names, etc. + boolean errorEncountered = false; + if (dest.exists()) { + if (!dest.delete()) { + // Problem + errorEncountered = true; + } + } + + if (!errorEncountered) { + // Install it, return it + if (parentDir.renameTo(dest)) { + return LibraryCompilation.create(dest); + } + } + + return null; + } + + static public Library confirmAndInstallLibrary(Editor editor, File libFile, + ErrorWidget statusBar) { + + int result = Base.showYesNoQuestion(editor, "Install", + "Install libraries from " + libFile.getName() + "?", + ContributionManager.DOUBLE_CLICK_SECONDARY); + + if (result == JOptionPane.YES_OPTION) { + return installLibrary(editor, libFile, null, true, statusBar); + } + + return null; + } + + /** + * Creates a temporary folder and unzips a file to a subdirectory of the temp + * folder. The subdirectory is the only file of the tempo folder. + * + * e.g. if the contents of foo.zip are /hello and /world, then the resulting + * files will be + * /tmp/foo9432423uncompressed/foo/hello + * /tmp/foo9432423uncompress/foo/world + * ...and "/tmp/id9432423uncompress/foo/" will be returned. + * + * @return the folder where the zips contents have been unzipped to (the + * subdirectory of the temp folder). + */ + static public File unzipFileToTemp(File libFile, + ErrorWidget statusBar) { + + String fileName = ContributionManager.getFileName(libFile); + File tmpFolder = null; + + try { + tmpFolder = Base.createTempFolder(fileName, "uncompressed"); + tmpFolder = new File(tmpFolder, fileName); + tmpFolder.mkdirs(); + } catch (IOException e) { + statusBar.setErrorMessage("Could not create temp folder to uncompressed zip file."); + } + + ContributionManager.unzip(libFile, tmpFolder); + + return tmpFolder; + } + + static public File getTemporaryFile(URL url, + ErrorWidget statusBar) { + try { + File tmpFolder = Base.createTempFolder("library", "download"); + + String[] segments = url.getFile().split("/"); + File libFile = new File(tmpFolder, segments[segments.length - 1]); + libFile.setWritable(true); + + return libFile; + } catch (IOException e) { + statusBar.setErrorMessage("Could not create a temp folder for download."); + } + + return null; + } + + /** + * Returns the name of a file without its path or extension. + * + * For example, + * "/path/to/helpfullib.zip" returns "helpfullib" + * "helpfullib-0.1.1.plb" returns "helpfullib-0.1.1" + */ + static public String getFileName(File libFile) { + String path = libFile.getPath(); + int lastSeparator = path.lastIndexOf(File.separatorChar); + + String fileName; + if (lastSeparator != -1) { + fileName = path.substring(lastSeparator + 1); + } else { + fileName = path; + } + + int lastDot = fileName.lastIndexOf('.'); + if (lastDot != -1) { + return fileName.substring(0, lastDot); + } + + return fileName; + } + + static public ToolContribution installTool(Editor editor, + File zippedToolFile, + AdvertisedContribution ad, + ErrorWidget statusBar) { + + File tempDir = unzipFileToTemp(zippedToolFile, statusBar); + + ArrayList discoveredTools = ToolContribution.list(tempDir, false); + if (discoveredTools.isEmpty()) { + // Sometimes tool authors place all their folders in the base + // directory of a zip file instead of in single folder as the + // guidelines suggest. If this is the case, we might be able to find the + // library by stepping up a directory and searching for libraries again. + discoveredTools = ToolContribution.list(tempDir.getParentFile(), false); + } + + if (discoveredTools != null && discoveredTools.size() == 1) { + ToolContribution discoveredTool = discoveredTools.get(0); + File propFile = new File(discoveredTool.getFolder(), "tool.properties"); + + if (ad == null || writePropertiesFile(propFile, ad)) { + return installTool(editor, discoveredTool, statusBar); + } else { + statusBar.setErrorMessage(ERROR_OVERWRITING_PROPERTIES_MESSAGE); + } + } else { + // Diagnose the problem and notify the user + if (discoveredTools == null || discoveredTools.isEmpty()) { + statusBar.setErrorMessage(DISCOVERY_INTERNAL_ERROR_MESSAGE); + } else { + statusBar.setErrorMessage("There were multiple tools in the file, so we're ignoring it."); + } + } + + return null; + } + + static public ToolContribution installTool(Editor editor, + ToolContribution newTool, + ErrorWidget statusBar) { + + ArrayList oldTools = editor.contribTools; + + String toolFolderName = newTool.getFolder().getName(); + + File toolDestination = editor.getBase().getSketchbookToolsFolder(); + File newToolDest = new File(toolDestination, toolFolderName); + + for (ToolContribution oldTool : oldTools) { + + // XXX: Handle other cases when installing tools. + // -What if a library by the same name is already installed? + // -What if newLibDest exists, but isn't used by an existing tools? + if (oldTool.getFolder().exists() && oldTool.getFolder().equals(newToolDest)) { + + // XXX: We can't replace stuff, soooooo.... do something different + if (!backupContribution(editor, oldTool, false, statusBar)) { + return null; + } + } + } + + // Move newTool to the sketchbook library folder + if (newTool.getFolder().renameTo(newToolDest)) { + ToolContribution movedTool = ToolContribution.getTool(newToolDest); + try { + movedTool.initializeToolClass(); + return movedTool; + } catch (Exception e) { + e.printStackTrace(); + } + } else { + statusBar.setErrorMessage("Could not move tool \"" + newTool.getName() + + "\" to sketchbook."); + } + + return null; + } + + static public boolean writePropertiesFile(File propFile, AdvertisedContribution ad) { + try { + if (propFile.delete() && propFile.createNewFile() && propFile.setWritable(true)) { + BufferedWriter bw = new BufferedWriter(new FileWriter(propFile)); + + bw.write("name=" + ad.getName() + "\n"); + bw.write("category=" + ad.getCategory() + "\n"); + bw.write("authorList=" + ad.getAuthorList() + "\n"); + bw.write("url=" + ad.getUrl() + "\n"); + bw.write("sentence=" + ad.getSentence() + "\n"); + bw.write("paragraph=" + ad.getParagraph() + "\n"); + bw.write("version=" + ad.getVersion() + "\n"); + bw.write("prettyVersion=" + ad.getPrettyVersion() + "\n"); + + bw.close(); + } + return true; + } catch (FileNotFoundException e) { + } catch (IOException e) { + } + + return false; + } + + /** + * @param libFile + * a zip file containing the library to install + * @param ad + * the advertised version of this library, if it was downloaded + * through the Contribution Manager, or null. This is used to replace + * the library.properties file in the zip + * @param confirmReplace + * true to open a dialog asking the user to confirm removing/moving + * the library when a library by the same name already exists + * @return + */ + static public Library installLibrary(Editor editor, File libFile, + AdvertisedContribution ad, + boolean confirmReplace, + ErrorWidget statusBar) { + File tempDir = unzipFileToTemp(libFile, statusBar); + + try { + ArrayList discoveredLibs = Library.list(tempDir); + if (discoveredLibs.isEmpty()) { + // Sometimes library authors place all their folders in the base + // directory of a zip file instead of in single folder as the + // guidelines suggest. If this is the case, we might be able to find the + // library by stepping up a directory and searching for libraries again. + discoveredLibs = Library.list(tempDir.getParentFile()); + } + + if (discoveredLibs != null && discoveredLibs.size() == 1) { + Library discoveredLib = discoveredLibs.get(0); + File propFile = new File(discoveredLib.getFolder(), "library.properties"); + + if (ad == null || writePropertiesFile(propFile, ad)) { + return installLibrary(editor, discoveredLib, confirmReplace, statusBar); + } else { + statusBar.setErrorMessage(ERROR_OVERWRITING_PROPERTIES_MESSAGE); + } + } else { + // Diagnose the problem and notify the user + if (discoveredLibs == null) { + statusBar.setErrorMessage(ContributionManager.DISCOVERY_INTERNAL_ERROR_MESSAGE); + } else if (discoveredLibs.isEmpty()) { + statusBar.setErrorMessage(ContributionManager.DISCOVERY_NONE_FOUND_ERROR_MESSAGE); + } else { + statusBar.setErrorMessage("There were multiple libraries in the file, so we're ignoring it."); + } + } + } catch (IOException ioe) { + statusBar.setErrorMessage(ContributionManager.DISCOVERY_INTERNAL_ERROR_MESSAGE); + } + + return null; + } + + /** + * @param confirmReplace + * if true and the library is already installed, opens a prompt to + * ask the user if it's okay to replace the library. If false, the + * library is always replaced with the new copy. + */ + static public Library installLibrary(Editor editor, Library newLib, + boolean confirmReplace, ErrorWidget statusBar) { + + ArrayList oldLibs = editor.getMode().contribLibraries; + + String libFolderName = newLib.getFolder().getName(); + + File libraryDestination = editor.getBase().getSketchbookLibrariesFolder(); + File newLibDest = new File(libraryDestination, libFolderName); + + for (Library oldLib : oldLibs) { + + // XXX: Handle other cases when installing libraries. + // -What if a library by the same name is already installed? + // -What if newLibDest exists, but isn't used by an existing library? + if (oldLib.getFolder().exists() && oldLib.getFolder().equals(newLibDest)) { + + int result = 0; + boolean doBackup = Preferences.getBoolean("contribution.backup.on_install"); + if (confirmReplace) { + if (doBackup) { + result = Base.showYesNoQuestion(editor, "Replace", + "Replace pre-existing \"" + oldLib.getName() + "\" library?", + "A pre-existing copy of the \"" + oldLib.getName() + "\" library
"+ + "has been found in your sketchbook. Clicking “Yes”
"+ + "will move the existing library to a backup folder
" + + " in libraries/old before replacing it."); + if (result != JOptionPane.YES_OPTION || !backupContribution(editor, oldLib, true, statusBar)) { + return null; + } + } else { + result = Base.showYesNoQuestion(editor, "Replace", + "Replace pre-existing \"" + oldLib.getName() + "\" library?", + "A pre-existing copy of the \"" + oldLib.getName() + "\" library
"+ + "has been found in your sketchbook. Clicking “Yes”
"+ + "will permanently delete this library and all of its contents
"+ + "before replacing it."); + if (result != JOptionPane.YES_OPTION || !oldLib.getFolder().delete()) { + return null; + } + } + } else { + if (doBackup && !backupContribution(editor, oldLib, true, statusBar) + || !doBackup && !oldLib.getFolder().delete()) { + return null; + } + } + } + } + + // Move newLib to the sketchbook library folder + if (newLib.getFolder().renameTo(newLibDest)) { + return new Library(newLibDest, null); +// try { +// FileUtils.copyDirectory(newLib.folder, libFolder); +// FileUtils.deleteQuietly(newLib.folder); +// newLib.folder = libFolder; +// } catch (IOException e) { + } else { + statusBar.setErrorMessage("Could not move library \"" + + newLib.getName() + "\" to sketchbook."); + return null; + } + } + + static public void refreshInstalled(Editor editor) { + editor.getMode().rebuildImportMenu(); + editor.rebuildToolMenu(); + } + + /** + * Moves the given contribution to a backup folder. + * @param doDeleteOriginal + * true if the file should be moved to the directory, false if it + * should instead be copied, leaving the original in place + */ + static public boolean backupContribution(Editor editor, + InstalledContribution contribution, + boolean doDeleteOriginal, + ErrorWidget statusBar) { + + File backupFolder = null; + + switch (contribution.getType()) { + case LIBRARY: + case LIBRARY_COMPILATION: + backupFolder = createLibraryBackupFolder(editor, statusBar); + break; + case MODE: + break; + case TOOL: + backupFolder = createToolBackupFolder(editor, statusBar); + break; + } + + if (backupFolder == null) return false; + + String libFolderName = contribution.getFolder().getName(); + + String prefix = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + final String backupName = prefix + "_" + libFolderName; + File backupSubFolder = ContributionManager.getUniqueName(backupFolder, backupName); + +// try { +// FileUtils.moveDirectory(lib.folder, backupFolderForLib); +// return true; + + boolean success = false; + if (doDeleteOriginal) { + success = contribution.getFolder().renameTo(backupSubFolder); + } else { + try { + Base.copyDir(contribution.getFolder(), backupSubFolder); + success = true; + } catch (IOException e) { + } + } +// } catch (IOException e) { + if (!success) { + statusBar.setErrorMessage("Could not move contribution to backup folder."); + } + return success; + } + + static public File createBackupFolder(File backupFolder, + ErrorWidget logger, + String errorMessage) { + + if (!backupFolder.exists() || !backupFolder.isDirectory()) { + if (!backupFolder.mkdirs()) { + logger.setErrorMessage(errorMessage); + return null; + } + } + + return backupFolder; + } + + static public File createLibraryBackupFolder(Editor editor, ErrorWidget logger) { + + File libraryBackupFolder = new File(editor.getBase() + .getSketchbookLibrariesFolder(), "old"); + return createBackupFolder(libraryBackupFolder, logger, + "Could not create backup folder for library."); + } + + static public File createToolBackupFolder(Editor editor, ErrorWidget logger) { + + File libraryBackupFolder = new File(editor.getBase() + .getSketchbookToolsFolder(), "old"); + return createBackupFolder(libraryBackupFolder, logger, + "Could not create backup folder for tool."); + } + + /** + * Returns a file in the parent folder that does not exist yet. If + * parent/fileName already exists, this will look for parent/fileName(2) + * then parent/fileName(3) and so forth. + * + * @return a file that does not exist yet + */ + public static File getUniqueName(File parentFolder, String fileName) { + File backupFolderForLib; + int i = 1; + do { + String folderName = fileName; + if (i >= 2) { + folderName += "(" + i + ")"; + } + i++; + + backupFolderForLib = new File(parentFolder, folderName); + } while (backupFolderForLib.exists()); + + return backupFolderForLib; + } + + public static void unzip(File zipFile, File dest) { + try { + FileInputStream fis = new FileInputStream(zipFile); + CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32()); + ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum)); + ZipEntry next = null; + while ((next = zis.getNextEntry()) != null) { + File currentFile = new File(dest, next.getName()); + if (next.isDirectory()) { + currentFile.mkdirs(); + } else { + currentFile.createNewFile(); + ContributionManager.unzipEntry(zis, currentFile); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void unzipEntry(ZipInputStream zin, File f) throws IOException { + FileOutputStream out = new FileOutputStream(f); + byte[] b = new byte[512]; + int len = 0; + while ((len = zin.read(b)) != -1) { + out.write(b, 0, len); + } + out.close(); + } + + /** Returns true if the type of contribution requires the PDE to restart + * when being removed. */ + static public boolean requiresRestart(Contribution contrib) { + return contrib.getType() == Type.TOOL || contrib.getType() == Type.MODE; + } + + static public boolean flagForDeletion(InstalledContribution contrib) { + // Only returns false if the file already exists, so we can + // ignore the return value. + try { + new File(contrib.getFolder(), ContributionManager.DELETION_FLAG).createNewFile(); + return true; + } catch (IOException e) { + return false; + } + } + + static public boolean removeFlagForDeletion(InstalledContribution contrib) { + return new File(contrib.getFolder(), ContributionManager.DELETION_FLAG).delete(); + } + + static public boolean isFlaggedForDeletion(Contribution contrib) { + if (contrib instanceof InstalledContribution) { + InstalledContribution installed = (InstalledContribution) contrib; + return new File(installed.getFolder(), ContributionManager.DELETION_FLAG).exists(); + } + return false; + } + +} diff --git a/app/src/processing/app/ContributionManagerDialog.java b/app/src/processing/app/ContributionManagerDialog.java new file mode 100644 index 000000000..df1fd5955 --- /dev/null +++ b/app/src/processing/app/ContributionManagerDialog.java @@ -0,0 +1,547 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-11 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.app; + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.CropImageFilter; +import java.awt.image.FilteredImageSource; +import java.io.*; +import java.util.*; +import java.util.List; + +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.event.*; + +import processing.app.contribution.*; + +public class ContributionManagerDialog { + + static final String ANY_CATEGORY = "All"; + + /** Width of each contribution icon. */ + static final int ICON_WIDTH = 25; + + /** Height of each contribution icon. */ + static final int ICON_HEIGHT = 20; + + JFrame dialog; + + private String title; + + FilterField filterField; + + JScrollPane scrollPane; + + ContributionListPanel contributionListPanel; + + StatusPanel statusBar; + + JComboBox categoryChooser; + + Image[] contributionIcons; + + // the calling editor, so updates can be applied + Editor editor; + + String category; + + ContributionListing contribListing; + + public ContributionManagerDialog(String title, + ContributionListing.Filter filter) { + + this.title = title; + + contribListing = ContributionListing.getInstance(); + + contributionListPanel = new ContributionListPanel(this, filter); + contribListing.addContributionListener(contributionListPanel); + } + + protected void showFrame(Editor editor) { + this.editor = editor; + + if (dialog == null) { + dialog = new JFrame(title); + + Base.setIcon(dialog); + + createComponents(); + + registerDisposeListeners(); + + dialog.pack(); + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + dialog.setLocation((screen.width - dialog.getWidth()) / 2, + (screen.height - dialog.getHeight()) / 2); + + contributionListPanel.grabFocus(); + } + + dialog.setVisible(true); + + if (!contribListing.hasDownloadedLatestList()) { + contribListing.getAdvertisedContributions(new AbstractProgressMonitor() { + public void startTask(String name, int maxValue) { + } + + public void finished() { + super.finished(); + + updateContributionListing(); + updateCategoryChooser(); + if (isError()) { + statusBar.setErrorMessage("An error occured when downloading " + + "the list of available contributions."); + } + } + }); + } + + updateContributionListing(); + + if (contributionIcons == null) { + try { + Image allButtons = ImageIO.read(Base.getLibStream("contributions.gif")); + int count = allButtons.getHeight(dialog) / ContributionManagerDialog.ICON_HEIGHT; + contributionIcons = new Image[count]; + contributionIcons[0] = allButtons; + contributionIcons[1] = allButtons; + contributionIcons[2] = allButtons; + contributionIcons[3] = allButtons; + + for (int i = 0; i < count; i++) { + Image image = dialog.createImage( + new FilteredImageSource(allButtons.getSource(), + new CropImageFilter(0, i * ContributionManagerDialog.ICON_HEIGHT, + ContributionManagerDialog.ICON_WIDTH, + ContributionManagerDialog.ICON_HEIGHT))); + contributionIcons[i] = image; + } + + contributionListPanel.updateColors(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + public Image getContributionIcon(Contribution.Type type) { + + if (contributionIcons == null) + return null; + + switch (type) { + case LIBRARY: + return contributionIcons[0]; + case TOOL: + return contributionIcons[1]; + case MODE: + return contributionIcons[2]; + case LIBRARY_COMPILATION: + return contributionIcons[3]; + } + return null; + } + + /** + * Close the window after an OK or Cancel. + */ + protected void disposeFrame() { + dialog.dispose(); + editor = null; + } + + /** Creates and arranges the Swing components in the dialog. */ + private void createComponents() { + dialog.setResizable(true); + + Container pane = dialog.getContentPane(); + pane.setLayout(new GridBagLayout()); + + { // The filter text area + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.weightx = 1; + c.fill = GridBagConstraints.HORIZONTAL; + filterField = new FilterField(); + + pane.add(filterField, c); + } + + { // The scroll area containing the contribution listing and the status bar. + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 2; + c.weighty = 1; + c.weightx = 1; + + scrollPane = new JScrollPane(); + scrollPane.setPreferredSize(new Dimension(300, 300)); + scrollPane.setViewportView(contributionListPanel); + scrollPane.getViewport().setOpaque(true); + scrollPane.getViewport().setBackground(contributionListPanel.getBackground()); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + + statusBar = new StatusPanel(); + statusBar.setBorder(BorderFactory.createEtchedBorder()); + + final JLayeredPane layeredPane = new JLayeredPane(); + layeredPane.add(scrollPane, JLayeredPane.DEFAULT_LAYER); + layeredPane.add(statusBar, JLayeredPane.PALETTE_LAYER); + + layeredPane.addComponentListener(new ComponentAdapter() { + + void resizeLayers() { + scrollPane.setSize(layeredPane.getSize()); + scrollPane.updateUI(); + } + + public void componentShown(ComponentEvent e) { + resizeLayers(); + } + + public void componentResized(ComponentEvent arg0) { + resizeLayers(); + } + }); + + final JViewport viewport = scrollPane.getViewport(); + viewport.addComponentListener(new ComponentAdapter() { + void resizeLayers() { + statusBar.setLocation(0, viewport.getHeight() - 18); + + Dimension d = viewport.getSize(); + d.height = 20; + d.width += 3; + statusBar.setSize(d); + } + public void componentShown(ComponentEvent e) { + resizeLayers(); + } + public void componentResized(ComponentEvent e) { + resizeLayers(); + } + }); + + pane.add(layeredPane, c); + } + + { // Shows "Category:" + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 2; + pane.add(new Label("Category:"), c); + } + + { // Combo box for selecting a category + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 2; + + categoryChooser = new JComboBox(); + updateCategoryChooser(); + pane.add(categoryChooser, c); + categoryChooser.addItemListener(new ItemListener() { + + public void itemStateChanged(ItemEvent e) { + category = (String) categoryChooser.getSelectedItem(); + if (ContributionManagerDialog.ANY_CATEGORY.equals(category)) { + category = null; + } + + filterLibraries(category, filterField.filters); + } + }); + } + + dialog.setMinimumSize(new Dimension(550, 400)); + } + + private void updateCategoryChooser() { + if (categoryChooser == null) + return; + + ArrayList categories; + categoryChooser.removeAllItems(); + categories = new ArrayList(contribListing.getCategories()); + Collections.sort(categories); + categories.add(0, ContributionManagerDialog.ANY_CATEGORY); + for (String s : categories) { + categoryChooser.addItem(s); + } + } + + private void registerDisposeListeners() { + dialog.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + disposeFrame(); + } + }); + ActionListener disposer = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + disposeFrame(); + } + }; + Base.registerWindowCloseKeys(dialog.getRootPane(), disposer); + + // handle window closing commands for ctrl/cmd-W or hitting ESC. + + dialog.getContentPane().addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + //System.out.println(e); + KeyStroke wc = Base.WINDOW_CLOSE_KEYSTROKE; + if ((e.getKeyCode() == KeyEvent.VK_ESCAPE) || + (KeyStroke.getKeyStrokeForEvent(e).equals(wc))) { + disposeFrame(); + } + } + }); + } + + public void filterLibraries(String category, List filters) { + + List filteredLibraries = contribListing + .getFilteredLibraryList(category, filters); + + contributionListPanel.filterLibraries(filteredLibraries); + } + + protected void updateContributionListing() { + if (editor == null) + return; + + ArrayList libraries = editor.getMode().contribLibraries; + ArrayList compilations = LibraryCompilation.list(libraries); + + // Remove libraries from the list that are part of a compilations + for (LibraryCompilation compilation : compilations) { + Iterator it = libraries.iterator(); + while (it.hasNext()) { + Library current = it.next(); + if (compilation.getFolder().equals(current.getFolder().getParentFile())) { + it.remove(); + } + } + } + + ArrayList contributions = new ArrayList(); + contributions.addAll(editor.contribTools); + contributions.addAll(libraries); + contributions.addAll(compilations); + + contribListing.updateInstalledList(contributions); + } + + public void setFilterText(String filter) { + if (filter == null || filter.isEmpty()) { + filterField.setText(""); + filterField.isShowingHint = true; + } else { + filterField.setText(filter); + filterField.isShowingHint = false; + } + filterField.applyFilter(); + + } + + class FilterField extends JTextField { + + final static String filterHint = "Filter your search..."; + + boolean isShowingHint; + + List filters; + + public FilterField () { + super(filterHint); + + isShowingHint = true; + + filters = new ArrayList(); + + addFocusListener(new FocusListener() { + + public void focusLost(FocusEvent focusEvent) { + if (filterField.getText().isEmpty()) { + isShowingHint = true; + } + + updateStyle(); + } + + public void focusGained(FocusEvent focusEvent) { + if (isShowingHint) { + isShowingHint = false; + filterField.setText(""); + } + + updateStyle(); + } + }); + + getDocument().addDocumentListener(new DocumentListener() { + + public void removeUpdate(DocumentEvent e) { + applyFilter(); + } + + public void insertUpdate(DocumentEvent e) { + applyFilter(); + } + + public void changedUpdate(DocumentEvent e) { + applyFilter(); + } + }); + } + + public void applyFilter() { + String filter = filterField.getFilterText(); + filter = filter.toLowerCase(); + + // Replace anything but 0-9, a-z, or : with a space + filter = filter.replaceAll("[^\\x30-\\x39^\\x61-\\x7a^\\x3a]", " "); + filters = Arrays.asList(filter.split(" ")); + filterLibraries(category, filters); + } + + public String getFilterText() { + return isShowingHint ? "" : getText(); + } + + public void updateStyle() { + if (isShowingHint) { + filterField.setText(filterHint); + + // setForeground(UIManager.getColor("TextField.light")); // too light + setForeground(Color.gray); + } else { + setForeground(UIManager.getColor("TextField.foreground")); + } + } + } + + public boolean hasAlreadyBeenOpened() { + return dialog != null; + } + + class StatusPanel extends JPanel implements ErrorWidget { + + String errorMessage; + + StatusPanel() { + addMouseListener(new MouseAdapter() { + + public void mousePressed(MouseEvent e) { + clearErrorMessage(); + } + }); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + g.setFont(new Font("SansSerif", Font.PLAIN, 10)); + int baseline = (getSize().height + g.getFontMetrics().getAscent()) / 2; + + if (contribListing.isDownloadingListing()) { + g.setColor(Color.black); + g.drawString("Downloading software listing...", 2, baseline); + setVisible(true); + } else if (errorMessage != null) { + g.setColor(Color.red); + g.drawString(errorMessage, 2, baseline); + setVisible(true); + } else { + setVisible(false); + } + } + + public void setErrorMessage(String message) { + errorMessage = message; + setVisible(true); + + JPanel placeholder = ContributionManagerDialog.this.contributionListPanel.statusPlaceholder; + Dimension d = getPreferredSize(); + if (Base.isWindows()) { + d.height += 5; + placeholder.setPreferredSize(d); + } + placeholder.setVisible(true); + +// Rectangle rect = scrollPane.getViewport().getViewRect(); +// rect.x += d.height; +// scrollPane.getViewport().scrollRectToVisible(rect); + } + + void clearErrorMessage() { + errorMessage = null; + repaint(); + + ContributionManagerDialog.this.contributionListPanel.statusPlaceholder + .setVisible(false); + } + } + +} + +abstract class JProgressMonitor extends AbstractProgressMonitor { + JProgressBar progressBar; + + public JProgressMonitor(JProgressBar progressBar) { + this.progressBar = progressBar; + } + + public void startTask(String name, int maxValue) { + isFinished = false; + progressBar.setString(name); + progressBar.setIndeterminate(maxValue == UNKNOWN); + progressBar.setMaximum(maxValue); + } + + public void setProgress(int value) { + super.setProgress(value); + progressBar.setValue(value); + } + + @Override + public void finished() { + super.finished(); + finishedAction(); + } + + public abstract void finishedAction(); + +} \ No newline at end of file diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index c3d4fb018..2ed844bf8 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -363,6 +363,15 @@ public abstract class Editor extends JFrame implements RunnerListener { modeMenu.add(item); } } + + modeMenu.addSeparator(); + JMenuItem addLib = new JMenuItem("Add Mode..."); + addLib.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + base.handleOpenModeManager(); + } + }); + modeMenu.add(addLib); } @@ -870,20 +879,20 @@ public abstract class Editor extends JFrame implements RunnerListener { toolsMenu.removeAll(); } - JMenuItem item = new JMenuItem("Manage Contributions..."); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - base.handleOpenContributionManager(); - } - }); - toolsMenu.add(item); - toolsMenu.addSeparator(); - rebuildToolList(); addInternalTools(toolsMenu); addTools(toolsMenu, coreTools); addTools(toolsMenu, contribTools); + + toolsMenu.addSeparator(); + JMenuItem item = new JMenuItem("Add Tool..."); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + base.handleOpenToolManager(); + } + }); + toolsMenu.add(item); } diff --git a/app/src/processing/app/Mode.java b/app/src/processing/app/Mode.java index 65dd42195..8cdb39740 100644 --- a/app/src/processing/app/Mode.java +++ b/app/src/processing/app/Mode.java @@ -224,6 +224,15 @@ public abstract class Mode { importMenu.removeAll(); } + JMenuItem addLib = new JMenuItem("Add Library..."); + addLib.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + base.handleOpenLibraryManager(); + } + }); + importMenu.add(addLib); + importMenu.addSeparator(); + rebuildLibraryList(); ActionListener listener = new ActionListener() { diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index a774d28f6..d5727c555 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -123,8 +123,12 @@ public class UpdateCheck { // Wait for xml file to be downloaded and updates to come in. (this // should really be handled better). Thread.sleep(5 * 1000); - if (!base.contributionManagerFrame.hasAlreadyBeenOpened() && - base.contributionManagerFrame.contribListing.hasUpdates()) { + if (!base.libraryManagerFrame.hasAlreadyBeenOpened() + && base.libraryManagerFrame.contribListing.hasUpdates() + || !base.toolManagerFrame.hasAlreadyBeenOpened() + && base.toolManagerFrame.contribListing.hasUpdates() + || !base.modeManagerFrame.hasAlreadyBeenOpened() + && base.modeManagerFrame.contribListing.hasUpdates()) { promptToOpenContributionManager(); } }