diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index ff6ac2621..1ae9bccfb 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -98,6 +98,7 @@ public class Base { ContributionManagerDialog libraryManagerFrame; ContributionManagerDialog toolManagerFrame; ContributionManagerDialog modeManagerFrame; + ContributionManagerDialog exampleManagerFrame; ContributionManagerDialog updateManagerFrame; // set to true after the first time the menu is built. @@ -123,6 +124,8 @@ public class Base { private Mode[] coreModes; //public List contribModes; protected ArrayList modeContribs; + + protected ArrayList exampleContribs; private JMenu sketchbookMenu; @@ -339,6 +342,19 @@ public class Base { } + /** + * Instantiates and adds new contributed modes to the contribModes list. + * Checks for duplicates so the same mode isn't instantiates twice. Does not + * remove modes because modes can't be removed once they are instantiated. + */ + void rebuildContribExamples() { + if (exampleContribs == null) { + exampleContribs = new ArrayList(); + } + ExamplesPackageContribution.loadMissing(this); + } + + public Base(String[] args) throws Exception { // // Get the sketchbook path, and make sure it's set properly // determineSketchbookFolder(); @@ -356,6 +372,8 @@ public class Base { ContributionManager.cleanup(this); buildCoreModes(); rebuildContribModes(); + + rebuildContribExamples(); // Needs to happen after the sketchbook folder has been located. // Also relies on the modes to be loaded so it knows what can be @@ -385,6 +403,8 @@ public class Base { new ContributionManagerDialog(ContributionType.TOOL); modeManagerFrame = new ContributionManagerDialog(ContributionType.MODE); + exampleManagerFrame = + new ContributionManagerDialog(ContributionType.EXAMPLES_PACKAGE); updateManagerFrame = new ContributionManagerDialog(null); @@ -658,6 +678,11 @@ public class Base { } + public ArrayList getExampleContribs() { + return exampleContribs; + } + + // Because of variations in native windowing systems, no guarantees about // changes to the focused and active Windows can be made. Developers must // never assume that this Window is the focused or active Window until this @@ -1483,7 +1508,7 @@ public class Base { JMenu submenu = new JMenu(name); // needs to be separate var otherwise would set ifound to false boolean anything = addSketches(submenu, subfolder, replaceExisting); - if (anything) { + if (anything && !name.equals("old")) { //Don't add old contributions menu.add(submenu); found = true; } @@ -1651,6 +1676,14 @@ public class Base { } + /** + * Show the examples installer window. + */ + public void handleOpenExampleManager() { + exampleManagerFrame.showFrame(activeEditor); + } + + public void handleShowUpdates() { updateManagerFrame.showFrame(activeEditor); } @@ -1923,6 +1956,7 @@ public class Base { getSketchbookLibrariesFolder().mkdir(); getSketchbookToolsFolder().mkdir(); getSketchbookModesFolder().mkdir(); + getSketchbookExamplesPackagesFolder().mkdir(); // System.err.println("sketchbook: " + sketchbookFolder); } @@ -1954,6 +1988,11 @@ public class Base { } + static public File getSketchbookExamplesPackagesFolder() { + return new File(sketchbookFolder, "examples-packages"); + } + + static protected File getDefaultSketchbookFolder() { File sketchbookFolder = null; try { diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index f03cc1e14..c203dec93 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -2396,35 +2396,42 @@ public abstract class Editor extends JFrame implements RunnerListener { // } } + //used to prevent the fileChangeListener from asking for reloads after internal changes + public void setWatcherSave() { + watcherSave = true; + } //set to true when the sketch is saved from inside processing private boolean watcherSave; - private boolean watcherReloaded; - + //the key which is being used to poll the fs for changes private WatchKey watcherKey = null; private void initFileChangeListener() { try { WatchService watchService = FileSystems.getDefault().newWatchService(); - Path folderPath = sketch.getFolder().toPath(); - watcherKey = folderPath.register(watchService, -// StandardWatchEventKinds.ENTRY_CREATE, -// StandardWatchEventKinds.ENTRY_DELETE, - StandardWatchEventKinds.ENTRY_MODIFY); + watcherKey = sketch + .getFolder() + .toPath() + .register(watchService, StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY); } catch (IOException e) { e.printStackTrace(); } final WatchKey finKey = watcherKey; - // if the key is null for some reason, don't bother attaching - // a listener to it, they can deal without one + //if the key is null for some reason, don't bother attaching a listener to it if (finKey != null) { // the key can now be polled for changes in the files addWindowFocusListener(new WindowFocusListener() { @Override public void windowGainedFocus(WindowEvent arg0) { + //we switched locations (saveAs), ignore old things + if (watcherKey != finKey) { + return; + } // check preference here for enabled or not? //if the directory was deleted, then don't scan @@ -2432,10 +2439,18 @@ public abstract class Editor extends JFrame implements RunnerListener { List> events = finKey.pollEvents(); processFileEvents(events); } + + List> events = finKey.pollEvents(); + if (!watcherSave) + processFileEvents(events); } @Override - public void windowLostFocus(WindowEvent arg0){ + public void windowLostFocus(WindowEvent arg0) { + //we switched locations (saveAs), ignore old things + if (watcherKey != finKey) { + return; + } List> events = finKey.pollEvents(); //don't ask to reload a file we saved if (!watcherSave) { @@ -2453,40 +2468,62 @@ public abstract class Editor extends JFrame implements RunnerListener { * @param events the list of events that have occured in the sketch folder */ private void processFileEvents(List> events) { - watcherReloaded = false; for (WatchEvent e : events) { - //the context is the name of the file inside the path - //due to some weird shit, if a file was editted in gedit, the context is .goutputstream-XXXXX - //this makes things.... complicated - //System.out.println(e.context()); - - //if we already reloaded in this cycle, then don't reload again - if (watcherReloaded){ - break; + boolean sketchFile = false; + Path file = ((Path) e.context()).getFileName(); + for (String s : getMode().getExtensions()) { + //if it is a change to a file with a known extension + if (file.toString().endsWith(s)) { + sketchFile = true; + break; + } } - if (e.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)) { -// Path p = (Path) e.context(); -// Path root = (Path) key.watchable(); -// Path path = root.resolve(p); - int response = - Base.showYesNoQuestion(Editor.this, "File Modified", - "A file has been modified externally", - "Would you like to reload the sketch?"); - if (response == 0) { - // reload the sketch + //if the file is not a known type, then go the the next event + if (!sketchFile) { + continue; + } + + int response = Base + .showYesNoQuestion(Editor.this, + "File Modified", + "Your sketch has been modified externally", + "Would you like to reload the sketch?"); + if (response == 0) { + //grab the 'main' code in case this reload tries to delete everything + File sc = sketch.getMainFile(); + //reload the sketch + try { sketch.reload(); header.rebuild(); - watcherReloaded = true; + } catch (Exception f) { + if (sketch.getCodeCount() < 1) { + Base + .showWarning("Canceling Reload", + "You cannot delete the last code file in a sketch!"); + //if they deleted the last file, re-save the SketchCode + try { + //make a blank file + sc.createNewFile(); + } catch (IOException e1) { + //if that didn't work, tell them it's un-recoverable + Base.showError("Reload failed", + "The sketch contians no code files", e1); + //don't try to reload again after the double fail + //this editor is probably trashed by this point, but a save-as might be possible + break; + } + //don't ask for another reload after this save + watcherSave = true; + return; + } } - } else { - // called when a file is created or deleted - // for now, do nothing + //now that we've reloaded once, don't try to reload again + break; } } watcherSave = false; } - /** * Set the title of the PDE window based on the current sketch, i.e. * something like "sketch_070752a - Processing 0126" @@ -2517,7 +2554,7 @@ public abstract class Editor extends JFrame implements RunnerListener { public boolean handleSave(boolean immediately) { // handleStop(); // 0136 - watcherSave = true; + setWatcherSave(); if (sketch.isUntitled()) { return handleSaveAs(); // need to get the name, user might also cancel here @@ -2561,6 +2598,8 @@ public abstract class Editor extends JFrame implements RunnerListener { statusNotice(Language.text("editor.status.saving")); try { if (sketch.saveAs()) { + //a saveAs moves where the files are, so a listener must be attached to the new location + initFileChangeListener(); // statusNotice("Done Saving."); // status is now printed from Sketch so that "Done Saving." // is only printed after Save As when progress bar is shown. diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index 671095f14..be1cfbebc 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -512,6 +512,7 @@ public class EditorHeader extends JComponent { item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { editor.getSketch().handleNewCode(); + editor.setWatcherSave(); } }); menu.add(item); @@ -541,6 +542,7 @@ public class EditorHeader extends JComponent { Language.text("editor.header.delete.warning.text"), null); } else { editor.getSketch().handleDeleteCode(); + editor.setWatcherSave(); } } }); diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java index f88dede9d..483ad765a 100644 --- a/app/src/processing/app/Language.java +++ b/app/src/processing/app/Language.java @@ -165,8 +165,13 @@ public class Language { /** Get translation from bundles. */ static public String text(String text) { - String result = init().bundle.getString(text); - return (result == null) ? text : result; + ResourceBundle bundle = init().bundle; + + try { + return bundle.getString(text); + } catch (MissingResourceException e) { + return text; + } } diff --git a/app/src/processing/app/Mode.java b/app/src/processing/app/Mode.java index 3f669f643..b51e50438 100644 --- a/app/src/processing/app/Mode.java +++ b/app/src/processing/app/Mode.java @@ -29,11 +29,15 @@ import java.io.*; import java.util.*; import javax.swing.*; +import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; +import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.*; +import processing.app.contrib.ContributionType; +import processing.app.contrib.ExamplesPackageContribution; import processing.app.syntax.*; import processing.core.PApplet; @@ -69,6 +73,8 @@ public abstract class Mode { protected File examplesFolder; protected File librariesFolder; protected File referenceFolder; + + protected File examplesContribFolder; public ArrayList coreLibraries; public ArrayList contribLibraries; @@ -100,6 +106,9 @@ public abstract class Mode { examplesFolder = new File(folder, "examples"); librariesFolder = new File(folder, "libraries"); referenceFolder = new File(folder, "reference"); + + // Get path to the contributed examples compatible with this mode + examplesContribFolder = Base.getSketchbookExamplesPackagesFolder(); // rebuildToolbarMenu(); rebuildLibraryList(); @@ -404,6 +413,14 @@ public abstract class Mode { } }); toolbarMenu.add(item); + + item = new JMenuItem("Add Examples..."); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + base.handleOpenExampleManager(); + } + }); + toolbarMenu.add(item); // Add a list of all sketches and subfolders toolbarMenu.addSeparator(); @@ -588,10 +605,10 @@ public abstract class Mode { } - public JTree buildExamplesTree() { + public DefaultMutableTreeNode buildExamplesTree() { DefaultMutableTreeNode node = new DefaultMutableTreeNode("Examples"); - JTree examplesTree = new JTree(node); +// JTree examplesTree = new JTree(node); // rebuildExamplesTree(node); // } @@ -610,30 +627,28 @@ public abstract class Mode { // }); File[] subfolders = getExampleCategoryFolders(); -// DefaultMutableTreeNode examplesParent = new DefaultMutableTreeNode("Examples"); + DefaultMutableTreeNode modeExParent = new DefaultMutableTreeNode("Mode Examples"); + for (File sub : subfolders) { DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(sub.getName()); if (base.addSketches(subNode, sub)) { // examplesParent.add(subNode); - node.add(subNode); + modeExParent.add(subNode); } } -// node.add(examplesParent); -// examplesTree.expandPath(new TreePath(examplesParent)); - + // get library examples boolean any = false; - DefaultMutableTreeNode libParent = new DefaultMutableTreeNode("Libraries"); for (Library lib : coreLibraries) { if (lib.hasExamples()) { DefaultMutableTreeNode libNode = new DefaultMutableTreeNode(lib.getName()); - any |= base.addSketches(libNode, lib.getExamplesFolder()); - libParent.add(libNode); + if (base.addSketches(libNode, lib.getExamplesFolder())) + modeExParent.add(libNode); } } - if (any) { - node.add(libParent); - } + + if (modeExParent.getChildCount() > 0) + node.add(modeExParent); // get contrib library examples any = false; @@ -644,7 +659,7 @@ public abstract class Mode { } if (any) { // menu.addSeparator(); - DefaultMutableTreeNode contribParent = new DefaultMutableTreeNode("Contributed Libraries"); + DefaultMutableTreeNode contribParent = new DefaultMutableTreeNode("Library Examples"); // Base.addDisabledItem(menu, "Contributed"); for (Library lib : contribLibraries) { if (lib.hasExamples()) { @@ -661,7 +676,46 @@ public abstract class Mode { } catch (IOException e) { e.printStackTrace(); } - return examplesTree; + + DefaultMutableTreeNode contribExampleNode = buildContributedExamplesTrees(); + if (contribExampleNode.getChildCount() > 0) + node.add(contribExampleNode); + return node; + } + + + public DefaultMutableTreeNode buildContributedExamplesTrees() { + DefaultMutableTreeNode node = new DefaultMutableTreeNode("Contributed Examples"); + + try { + File[] subfolders = ContributionType.EXAMPLES_PACKAGE.listCandidates(examplesContribFolder); + if (subfolders == null) { + subfolders = new File[0]; //empty array + } + for (File sub : subfolders) { + if (!ExamplesPackageContribution.isExamplesPackageCompatible(base, sub)) + continue; + DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(sub.getName()); + if (base.addSketches(subNode, sub)) { + node.add(subNode); + int exampleNodeNumber = -1; + for (int y = 0; y < subNode.getChildCount(); y++) + if (subNode.getChildAt(y).toString().equals("examples-package")) + exampleNodeNumber = y; + if (exampleNodeNumber == -1) + continue; + TreeNode exampleNode = subNode.getChildAt(exampleNodeNumber); + subNode.remove(exampleNodeNumber); + int count = exampleNode.getChildCount(); + for (int x = 0; x < count; x++) { + subNode.add((DefaultMutableTreeNode) exampleNode.getChildAt(0)); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return node;//examplesTree; } @@ -682,6 +736,98 @@ public abstract class Mode { } + /** + * Function to give a JTree a pretty alternating gray-white colouring for + * its rows. + * + * @param tree + */ + private void colourizeTreeRows(JTree tree) { + // Code in this function adapted from: + // http://mateuszstankiewicz.eu/?p=263 + tree.setCellRenderer(new DefaultTreeCellRenderer() { + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean sel, + boolean expanded, + boolean leaf, int row, + boolean hasFocus) { + JComponent c = (JComponent) super + .getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, + hasFocus); + + if (!tree.isRowSelected(row)) { + if (row % 2 == 0) { + + // Need to set this, else the gray from the odd + // rows colours this gray as well. + c.setBackground(new Color(255, 255, 255)); + + setBackgroundSelectionColor(new Color(0, 0, 255)); + setTextSelectionColor(Color.WHITE); + setBorderSelectionColor(new Color(0, 0, 255)); + } else { + + // Set background for entire component (including the image). + // Using transparency messes things up, probably since the + // transparent colour is not good friends with the images background colour. + c.setBackground(new Color(240, 240, 240)); + + // Can't use setBackgroundSelectionColor() directly, since then, the + // image's background isn't affected. + // The setUI() doesn't fix the image's background because the + // transparency likely interferes with its normal background, + // making its background lighter than the rest. +// setBackgroundNonSelectionColor(new Color(190, 190, 190)); + + setBackgroundSelectionColor(new Color(0, 0, 255)); + setTextSelectionColor(Color.WHITE); + setBorderSelectionColor(new Color(0, 0, 255)); + } + } else {// Transparent blue if selected + c.setBackground(new Color(127, 127, 255)); + } + + c.setOpaque(true); + return c; + } + + }); + + tree.setUI(new BasicTreeUI() { + + @Override + protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, + Rectangle bounds, TreePath path, int row, + boolean isExpanded, boolean hasBeenExpanded, + boolean isLeaf) { + Graphics g2 = g.create(); + + if (!tree.isRowSelected(row)) { + if (row % 2 == 0) { + // Need to set this, else the gray from the odd rows + // affects the even rows too. + g2.setColor(new Color(255, 255, 255, 128)); + } else { + // Transparent light-gray + g2.setColor(new Color(226, 226, 226, 128)); + } + } else + // Transparent blue if selected + g2.setColor(new Color(0, 0, 255, 128)); + + g2.fillRect(0, bounds.y, tree.getWidth(), bounds.height); + + g2.dispose(); + + super.paintRow(g, clipBounds, insets, bounds, path, row, isExpanded, + hasBeenExpanded, isLeaf); + } + }); + } + + public void showExamplesFrame() { if (examplesFrame == null) { examplesFrame = new JFrame(getTitle() + " " + Language.text("examples")); @@ -692,7 +838,49 @@ public abstract class Mode { } }); - final JTree tree = buildExamplesTree(); + JPanel examplesPanel = new JPanel(); + examplesPanel.setLayout(new BorderLayout()); + examplesPanel.setBackground(Color.WHITE); + + final JPanel openExamplesManagerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + JLabel openExamplesManagerLabel = new JLabel("Add Examples..."); +// openExamplesManagerLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + openExamplesManagerPanel.add(openExamplesManagerLabel); + openExamplesManagerPanel.setOpaque(false); + Border lineBorder = BorderFactory.createMatteBorder(0, 0, 1, 0, Color.BLACK); + Border paddingBorder = BorderFactory.createEmptyBorder(3, 5, 1, 4); + openExamplesManagerPanel.setBorder(BorderFactory.createCompoundBorder(lineBorder, paddingBorder)); +// openExamplesManagerLabel.set + openExamplesManagerPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + openExamplesManagerPanel.setCursor(new Cursor(Cursor.HAND_CURSOR)); +// openExamplesManagerLabel.setForeground(new Color(0, 0, 238)); + openExamplesManagerPanel.addMouseListener(new MouseListener() { + + @Override + public void mouseReleased(MouseEvent e) {} + + @Override + public void mousePressed(MouseEvent e) {} + + @Override + public void mouseExited(MouseEvent e) {} + + @Override + public void mouseEntered(MouseEvent e) {} + + @Override + public void mouseClicked(MouseEvent e) { + base.handleOpenExampleManager(); +// openExamplesManagerLabel.setForeground(new Color(85, 26, 139)); + } + }); + + final JTree tree = new JTree(buildExamplesTree()); + + colourizeTreeRows(tree); + + tree.setOpaque(true); + tree.setAlignmentX(Component.LEFT_ALIGNMENT); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setShowsRootHandles(true); @@ -754,16 +942,23 @@ public abstract class Mode { } }); - tree.setBorder(new EmptyBorder(5, 5, 5, 5)); + tree.setBorder(new EmptyBorder(0, 5, 5, 5)); if (Base.isMacOS()) { tree.setToggleClickCount(2); } else { tree.setToggleClickCount(1); } + JScrollPane treePane = new JScrollPane(tree); - treePane.setPreferredSize(new Dimension(250, 450)); - treePane.setBorder(new EmptyBorder(0, 0, 0, 0)); - examplesFrame.getContentPane().add(treePane); + treePane.setPreferredSize(new Dimension(250, 300)); + treePane.setBorder(new EmptyBorder(2, 0, 0, 0)); + treePane.setOpaque(true); + treePane.setBackground(Color.WHITE); + treePane.setAlignmentX(Component.LEFT_ALIGNMENT); + + examplesPanel.add(openExamplesManagerPanel,BorderLayout.PAGE_START); + examplesPanel.add(treePane, BorderLayout.CENTER); + examplesFrame.getContentPane().add(examplesPanel); examplesFrame.pack(); restoreExpanded(tree); diff --git a/app/src/processing/app/contrib/AvailableContribution.java b/app/src/processing/app/contrib/AvailableContribution.java index 1023f2a83..39b1cb1b0 100644 --- a/app/src/processing/app/contrib/AvailableContribution.java +++ b/app/src/processing/app/contrib/AvailableContribution.java @@ -1,4 +1,4 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org @@ -268,6 +268,12 @@ class AvailableContribution extends Contribution { String prettyVersion = properties.get("prettyVersion"); if (prettyVersion == null || prettyVersion.isEmpty()) prettyVersion = getPrettyVersion(); + + String compatibleContribsList = null; + + if (getType() == ContributionType.EXAMPLES_PACKAGE) { + compatibleContribsList = properties.get("compatibleModesList"); + } long lastUpdated; try { @@ -294,6 +300,9 @@ class AvailableContribution extends Contribution { writer.println("version=" + version); writer.println("prettyVersion=" + prettyVersion); writer.println("lastUpdated=" + lastUpdated); + if (getType() == ContributionType.EXAMPLES_PACKAGE) { + writer.println("compatibleModesList=" + compatibleContribsList); + } writer.flush(); writer.close(); diff --git a/app/src/processing/app/contrib/Contribution.java b/app/src/processing/app/contrib/Contribution.java index 451ab7d4e..92f21d155 100644 --- a/app/src/processing/app/contrib/Contribution.java +++ b/app/src/processing/app/contrib/Contribution.java @@ -30,9 +30,10 @@ import processing.core.PApplet; abstract public class Contribution { + static final String SPECIAL_CATEGORY_NAME = "Starred"; static final List validCategories = Arrays.asList("3D", "Animation", "Data", "Geometry", "GUI", "Hardware", - "I/O", "Math", "Simulation", "Sound", "Typography", + "I/O", "Math", "Simulation", "Sound", SPECIAL_CATEGORY_NAME, "Typography", "Utilities", "Video & Vision", "Other"); //protected String category; // "Sound" @@ -165,6 +166,19 @@ abstract public class Contribution { } + /** + * Returns true if the contribution is a starred/recommended contribution, or + * is by the Processing Foundation. + * + * @return + */ + boolean isSpecial() { + if (authorList.indexOf("The Processing Foundation") != -1 || categories.contains(SPECIAL_CATEGORY_NAME)) + return true; + return false; + } + + /** * @return a single element list with "Unknown" as the category. */ diff --git a/app/src/processing/app/contrib/ContributionListing.java b/app/src/processing/app/contrib/ContributionListing.java index 083f85685..e2f172075 100644 --- a/app/src/processing/app/contrib/ContributionListing.java +++ b/app/src/processing/app/contrib/ContributionListing.java @@ -44,6 +44,7 @@ public class ContributionListing { Map> librariesByCategory; ArrayList allContributions; boolean hasDownloadedLatestList; + boolean hasListDownloadFailed; ReentrantLock downloadingListingLock; @@ -369,8 +370,11 @@ public class ContributionListing { ContributionManager.download(url, listingFile, progress); if (!progress.isCanceled() && !progress.isError()) { hasDownloadedLatestList = true; + hasListDownloadFailed = false; setAdvertisedList(listingFile); } + else + hasListDownloadFailed = true; } downloadingListingLock.unlock(); } @@ -436,6 +440,11 @@ public class ContributionListing { } + boolean hasListDownloadFailed() { + return hasListDownloadFailed; + } + + // /** // * @return a lowercase string with all non-alphabetic characters removed // */ diff --git a/app/src/processing/app/contrib/ContributionManager.java b/app/src/processing/app/contrib/ContributionManager.java index 034246be7..b64c42556 100644 --- a/app/src/processing/app/contrib/ContributionManager.java +++ b/app/src/processing/app/contrib/ContributionManager.java @@ -102,7 +102,8 @@ public class ContributionManager { } catch (IOException ioe) { if (progress != null) progress.error(ioe); - ioe.printStackTrace(); + // Hiding stack trace. An error has been shown where needed. +// ioe.printStackTrace(); } if (progress != null) progress.finished(); @@ -155,11 +156,26 @@ public class ContributionManager { } installProgress.finished(); } + else { + if (downloadProgress.exception instanceof SocketTimeoutException) { + status.setErrorMessage(Language + .interpolate("contrib.errors.contrib_download.timeout", + ad.getName())); + } else { + status.setErrorMessage(Language + .interpolate("contrib.errors.download_and_install", + ad.getName())); + } + } contribZip.delete(); } catch (Exception e) { - e.printStackTrace(); - status.setErrorMessage(Language.text("contrib.errors.download_and_install")); + // Hiding stack trace. The error message ought to suffice. +// e.printStackTrace(); + status + .setErrorMessage(Language + .interpolate("contrib.errors.download_and_install", + ad.getName())); } } catch (IOException e) { status.setErrorMessage(Language.text("contrib.errors.temporary_directory")); diff --git a/app/src/processing/app/contrib/ContributionManagerDialog.java b/app/src/processing/app/contrib/ContributionManagerDialog.java index 82d4d79ad..66dc442f3 100644 --- a/app/src/processing/app/contrib/ContributionManagerDialog.java +++ b/app/src/processing/app/contrib/ContributionManagerDialog.java @@ -44,6 +44,7 @@ public class ContributionManagerDialog { static final String ANY_CATEGORY = Language.text("contrib.all"); JFrame dialog; + String title; ContributionFilter filter; JComboBox categoryChooser; JScrollPane scrollPane; @@ -51,6 +52,7 @@ public class ContributionManagerDialog { StatusPanel status; FilterField filterField; JButton restartButton; + JButton retryConnectingButton; // the calling editor, so updates can be applied Editor editor; @@ -60,8 +62,16 @@ public class ContributionManagerDialog { public ContributionManagerDialog(ContributionType type) { if (type == null) { + title = Language.text("contrib.manager_title.update"); filter = ContributionType.createUpdateFilter(); } else { + if (type == ContributionType.MODE) + title = Language.text("contrib.manager_title.mode"); + else if (type == ContributionType.TOOL) + title = Language.text("contrib.manager_title.tool"); + else if (type == ContributionType.LIBRARY) + title = Language.text("contrib.manager_title.library"); + filter = type.createFilter(); } contribListing = ContributionListing.getInstance(); @@ -83,7 +93,7 @@ public class ContributionManagerDialog { this.editor = editor; if (dialog == null) { - dialog = new JFrame(Language.text("contrib")); + dialog = new JFrame(title); restartButton = new JButton(Language.text("contrib.restart")); restartButton.setVisible(false); @@ -97,7 +107,7 @@ public class ContributionManagerDialog { Editor ed = iter.next(); if (ed.getSketch().isModified()) { int option = Base - .showYesNoQuestion(editor, Language.text("contrib"), + .showYesNoQuestion(editor, title, Language.text("contrib.unsaved_changes"), Language.text("contrib.unsaved_changes.prompt")); @@ -132,6 +142,16 @@ public class ContributionManagerDialog { }); + retryConnectingButton = new JButton("Retry"); + retryConnectingButton.setVisible(false); + retryConnectingButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + downloadAndUpdateContributionListing(); + } + }); + Toolkit.setIcon(dialog); createComponents(); registerDisposeListeners(); @@ -146,23 +166,7 @@ public class ContributionManagerDialog { updateContributionListing(); } else { - contribListing.downloadAvailableList(new ProgressMonitor() { - - public void finished() { - super.finished(); - - updateContributionListing(); - updateCategoryChooser(); - if (error) { - if (exception instanceof SocketTimeoutException) { - status.setErrorMessage(Language.text("contrib.errors.list_download.timeout")); - } else { - status.setErrorMessage(Language.text("contrib.errors.list_download")); - } - exception.printStackTrace(); - } - } - }); + downloadAndUpdateContributionListing(); } } @@ -259,7 +263,11 @@ public class ContributionManagerDialog { statusRestartPane.setOpaque(false); statusRestartPane.add(status, BorderLayout.WEST); + + // Adding both of these to EAST shouldn't pose too much of a problem, + // since they can never get added together. statusRestartPane.add(restartButton, BorderLayout.EAST); + statusRestartPane.add(retryConnectingButton, BorderLayout.EAST); pane.add(statusRestartPane, BorderLayout.SOUTH); @@ -393,7 +401,10 @@ public class ContributionManagerDialog { ArrayList modes = editor.getBase().getModeContribs(); contributions.addAll(modes); - + + ArrayList examples = editor.getBase().getExampleContribs(); + contributions.addAll(examples); + // ArrayList compilations = LibraryCompilation.list(libraries); // // // Remove libraries from the list that are part of a compilations @@ -411,7 +422,38 @@ public class ContributionManagerDialog { } } - + + protected void downloadAndUpdateContributionListing() { + status.setMessage("Downloading contribution list..."); + retryConnectingButton.setEnabled(false); + contribListing.downloadAvailableList(new ProgressMonitor() { + + public void finished() { + super.finished(); + + updateContributionListing(); + updateCategoryChooser(); + + retryConnectingButton.setEnabled(true); + + if (error) { + if (exception instanceof SocketTimeoutException) { + status.setErrorMessage(Language.text("contrib.errors.list_download.timeout")); + } else { + status.setErrorMessage(Language.text("contrib.errors.list_download")); + } + exception.printStackTrace(); + retryConnectingButton.setVisible(true); + } + else { + status.setMessage("Done."); + retryConnectingButton.setVisible(false); + } + } + }); + } + + protected void setFilterText(String filter) { if (filter == null || filter.isEmpty()) { filterField.setText(""); diff --git a/app/src/processing/app/contrib/ContributionPanel.java b/app/src/processing/app/contrib/ContributionPanel.java index 68434f2bc..d60729b1d 100644 --- a/app/src/processing/app/contrib/ContributionPanel.java +++ b/app/src/processing/app/contrib/ContributionPanel.java @@ -42,6 +42,7 @@ import javax.swing.text.html.StyleSheet; import processing.app.Base; import processing.app.Editor; +import processing.app.Toolkit; import processing.app.Language; @@ -515,6 +516,16 @@ class ContributionPanel extends JPanel { public void setContribution(Contribution contrib) { this.contrib = contrib; + + + if (contrib.isSpecial()) { + ImageIcon processingIcon = new ImageIcon(Toolkit.getLibImage("icons/pde-" + + "48" + ".png")); + JLabel iconLabel = new JLabel(processingIcon); + iconLabel.setBorder(new EmptyBorder(4, 7, 7, 7)); + iconLabel.setVerticalAlignment(SwingConstants.TOP); + add(iconLabel, BorderLayout.WEST); + } // StringBuilder nameText = new StringBuilder(); // nameText.append(""); @@ -761,8 +772,10 @@ class ContributionPanel extends JPanel { if (contrib != null) { updateButton.setVisible((contribListing.hasUpdates(contrib) && !contrib.isUpdateFlagged() && !contrib.isDeletionFlagged()) || isUpdateInProgress); + updateButton.setEnabled(!contribListing.hasListDownloadFailed()); } installRemoveButton.setVisible(isSelected() || installRemoveButton.getText().equals(Language.text("contrib.remove")) || isUpdateInProgress); + installRemoveButton.setEnabled(installRemoveButton.getText().equals(Language.text("contrib.remove")) ||!contribListing.hasListDownloadFailed()); reorganizePaneComponents(); // for (JTextPane textPane : headerPaneSet) { diff --git a/app/src/processing/app/contrib/ContributionType.java b/app/src/processing/app/contrib/ContributionType.java index 8b882ee59..a2e661d98 100644 --- a/app/src/processing/app/contrib/ContributionType.java +++ b/app/src/processing/app/contrib/ContributionType.java @@ -31,7 +31,7 @@ import processing.app.Editor; import processing.app.Library; public enum ContributionType { - LIBRARY, TOOL, MODE; + LIBRARY, TOOL, MODE, EXAMPLES_PACKAGE; public String toString() { @@ -42,6 +42,8 @@ public enum ContributionType { return "tool"; case MODE: return "mode"; + case EXAMPLES_PACKAGE: + return "examples-package"; } return null; // should be unreachable }; @@ -53,7 +55,13 @@ public enum ContributionType { */ public String getTitle() { String s = toString(); - return Character.toUpperCase(s.charAt(0)) + s.substring(1); + if (this == EXAMPLES_PACKAGE) + return Character.toUpperCase(s.charAt(0)) + + s.substring(1, s.indexOf('-') + 1) + + Character.toUpperCase(s.charAt(s.indexOf('-') + 1)) + + s.substring(s.indexOf('-') + 2); + else + return Character.toUpperCase(s.charAt(0)) + s.substring(1); } @@ -65,6 +73,8 @@ public enum ContributionType { return "tools"; case MODE: return "modes"; + case EXAMPLES_PACKAGE: + return "examples-package"; } return null; // should be unreachable } @@ -106,6 +116,9 @@ public enum ContributionType { if ("mode".equalsIgnoreCase(s)) { return MODE; } + if ("examples-package".equalsIgnoreCase(s)) { + return EXAMPLES_PACKAGE; + } } return null; } @@ -119,6 +132,8 @@ public enum ContributionType { return Base.getSketchbookToolsFolder(); case MODE: return Base.getSketchbookModesFolder(); + case EXAMPLES_PACKAGE: + return Base.getSketchbookExamplesPackagesFolder(); } return null; } @@ -136,7 +151,7 @@ public enum ContributionType { * contribution type. For instance, a list of folders that have a 'mode' * subfolder if this is a ModeContribution. */ - File[] listCandidates(File folder) { + public File[] listCandidates(File folder) { return folder.listFiles(new FileFilter() { public boolean accept(File potential) { return isCandidate(potential); @@ -181,6 +196,8 @@ public enum ContributionType { return ToolContribution.load(folder); case MODE: return ModeContribution.load(base, folder); + case EXAMPLES_PACKAGE: + return ExamplesPackageContribution.load(folder); } return null; } @@ -198,6 +215,9 @@ public enum ContributionType { case MODE: contribs.addAll(editor.getBase().getModeContribs()); break; + case EXAMPLES_PACKAGE: + contribs.addAll(editor.getBase().getExampleContribs()); + break; } return contribs; } diff --git a/app/src/processing/app/contrib/ExamplesPackageContribution.java b/app/src/processing/app/contrib/ExamplesPackageContribution.java new file mode 100644 index 000000000..f94cc6669 --- /dev/null +++ b/app/src/processing/app/contrib/ExamplesPackageContribution.java @@ -0,0 +1,89 @@ +package processing.app.contrib; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +import processing.app.Base; +import processing.core.PApplet; + +public class ExamplesPackageContribution extends LocalContribution { + + private ArrayList compatibleModesList; + + static public ExamplesPackageContribution load(File folder) { + return new ExamplesPackageContribution(folder); + } + + private ExamplesPackageContribution(File folder) { + super(folder); + compatibleModesList = parseCompatibleModesList(properties + .get("compatibleModesList")); + } + + private static ArrayList parseCompatibleModesList(String unparsedModes) { + ArrayList modesList = new ArrayList(); + if (unparsedModes == null || unparsedModes.isEmpty()) + return modesList; + String[] splitStr = PApplet.trim(PApplet.split(unparsedModes, ','));//unparsedModes.split(","); + for (String mode : splitStr) + modesList.add(mode.trim()); + return modesList; + } + + /** + * Function to determine whether or not the example present in the + * exampleLocation directory is compatible with the present mode. + * + * @param base + * @param exampleLocationFolder + * @return true if the example is compatible with the mode of the currently + * active editor + */ + public static boolean isExamplesPackageCompatible(Base base, + File exampleLocationFolder) { + File propertiesFile = new File(exampleLocationFolder, + ContributionType.EXAMPLES_PACKAGE.toString() + + ".properties"); + if (propertiesFile.exists()) { + ArrayList compModesList = parseCompatibleModesList(Base + .readSettings(propertiesFile).get("compatibleModesList")); + for (String c : compModesList) { + if (c.equalsIgnoreCase(base.getActiveEditor().getMode().getIdentifier())) { + return true; + } + } + } + return false; + } + + static public void loadMissing(Base base) { + File examplesFolder = Base.getSketchbookExamplesPackagesFolder(); + ArrayList contribExamples = base.getExampleContribs(); + + HashMap existing = new HashMap(); + for (ExamplesPackageContribution contrib : contribExamples) { + existing.put(contrib.getFolder(), contrib); + } + File[] potential = ContributionType.EXAMPLES_PACKAGE.listCandidates(examplesFolder); + // If modesFolder does not exist or is inaccessible (folks might like to + // mess with folders then report it as a bug) 'potential' will be null. + if (potential != null) { + for (File folder : potential) { + if (!existing.containsKey(folder)) { + contribExamples.add(new ExamplesPackageContribution(folder)); + } + } + } + } + + @Override + public ContributionType getType() { + return ContributionType.EXAMPLES_PACKAGE; + } + + public ArrayList getCompatibleModesList() { + return compatibleModesList; + } + +} diff --git a/app/src/processing/app/contrib/LocalContribution.java b/app/src/processing/app/contrib/LocalContribution.java index 1cedbd652..099514c76 100644 --- a/app/src/processing/app/contrib/LocalContribution.java +++ b/app/src/processing/app/contrib/LocalContribution.java @@ -91,6 +91,19 @@ public abstract class LocalContribution extends Contribution { name = folder.getName(); categories = defaultCategory(); } + + if (categories.contains(SPECIAL_CATEGORY_NAME)) + validateSpecial(); + } + + + private void validateSpecial() { + for (AvailableContribution available : ContributionListing.getInstance().advertisedContributions) + if (available.getName().equals(name)) { + if (!available.isSpecial()) + categories.remove(SPECIAL_CATEGORY_NAME); + } + return; } diff --git a/app/src/processing/app/languages/PDE.properties b/app/src/processing/app/languages/PDE.properties index f10af27c6..6e4850983 100644 --- a/app/src/processing/app/languages/PDE.properties +++ b/app/src/processing/app/languages/PDE.properties @@ -260,6 +260,10 @@ editor.status.printing.canceled = Printing canceled. # Contribution Panel contrib = Contribution Manager +contrib.manager_title.update = Update Manager +contrib.manager_title.mode = Mode Manager +contrib.manager_title.tool = Tool Manager +contrib.manager_title.library = Library Manager contrib.category = Category: contrib.filter_your_search = Filter your search... contrib.restart = Restart Processing @@ -270,15 +274,17 @@ contrib.messages.install_restart = Please restart Processing to finish installin contrib.messages.update_restart = Please restart Processing to finish updating this item. contrib.errors.list_download = Could not download the list of available contributions. contrib.errors.list_download.timeout = Connection timed out while downloading the contribution list. -contrib.errors.download_and_install = Error during download and install. +contrib.errors.download_and_install = Error during download and install of %s. contrib.errors.description_unavailable = Description unavailable. -contrib.errors.malformed_url = "The link fetched from Processing.org is not valid.\nYou can still install this library manually by visiting\nthe library's website. +contrib.errors.malformed_url = The link fetched from Processing.org is not valid.\nYou can still install this library manually by visiting\nthe library's website. contrib.errors.needs_repackage = %s needs to be repackaged according to the %s guidelines. contrib.errors.no_contribution_found = Could not find a %s in the downloaded file. contrib.errors.overwriting_properties = Error overwriting .properties file. contrib.errors.install_failed = Install failed. contrib.errors.update_on_restart_failed = Update on restart of %s failed. contrib.errors.temporary_directory = Could not write to temporary directory. +contrib.errors.contrib_download.timeout = Connection timed out while downloading %s. +contrib.errors.no_internet_connection = You don't seem to be connected to the Internet. contrib.all = All contrib.undo = Undo contrib.remove = Remove diff --git a/app/src/processing/app/languages/PDE_zh.properties b/app/src/processing/app/languages/PDE_zh.properties index 7efc3fc5a..0fc905d93 100644 --- a/app/src/processing/app/languages/PDE_zh.properties +++ b/app/src/processing/app/languages/PDE_zh.properties @@ -19,11 +19,11 @@ menu.file.examples = 范例程序... menu.file.close = 关闭 menu.file.save = 保存 menu.file.save_as = 另存为... -menu.file.export_application = 导出程序... +menu.file.export_application = 输出程序... menu.file.page_setup = 页面设置 menu.file.print = 打印... menu.file.preferences = 偏好设定... -menu.file.quit = Quit +menu.file.quit = 退出 # | File | Edit | Sketch | Library | Tools | Help | # | Edit | @@ -34,7 +34,7 @@ menu.edit.cut = 剪切 menu.edit.copy = 复制 menu.edit.copy_as_html = 复制为HTML menu.edit.paste = 黏贴 -menu.edit.select_all = 选择全部 +menu.edit.select_all = 全部选择 menu.edit.auto_format = 自动对齐 menu.edit.comment_uncomment = 注释/取消注释 menu.edit.increase_indent = 增加缩进量 @@ -91,26 +91,26 @@ menu.help.visit.url = http://processing.org/ # Basics # Buttons -prompt.yes = Yes -prompt.no = No -prompt.cancel = Cancel -prompt.ok = OK -prompt.browse = Browse -prompt.export = Export +prompt.yes = 是 +prompt.no = 否 +prompt.cancel = 取消 +prompt.ok = 确认 +prompt.browse = 浏览 +prompt.export = 输出 # --------------------------------------- # Frames # Open (Frame) -open = Open a Processing sketch... +open = 打开 Processing 速写本... # Save (Frame) -save = Save sketch folder as... -save.title = Do you want to save changes to this sketch
before closing? -save.hint = If you don't save, your changes will be lost. -save.btn.save = Save -save.btn.dont_save = Don't Save +save = 保存速写本文件夹为... +save.title = 在关闭前你想要保存该速写本更改内容么
? +save.hint = 如果你没保存, 你所有的更改内容将丢失. +save.btn.save = 保存 +save.btn.dont_save = 不保存 # Preferences (Frame) preferences = 偏好设置 @@ -121,16 +121,16 @@ preferences.sketchbook_location.popup = 速写本位置 preferences.language = 语言 preferences.editor_and_console_font = 编辑器和控制台字体 preferences.editor_and_console_font.tip = \ -Select the font used in the Editor and the Console.
\ +为编辑器和控制台选择字体.
\ Only monospaced (fixed-width) fonts may be used,
\ though the list may be imperfect. preferences.editor_font_size = 编辑器字体大小 preferences.console_font_size = 控制台字体大小 preferences.background_color = 展示模式时的背景颜色 preferences.background_color.tip = \ -Select the background color used when using Present.
\ -Present is used to present a sketch in full-screen,
\ -accessible from the Sketch menu. +选择背景颜色当使用展示模式时.
\ +展示模式通常被用来在全屏模式下展示速写程序,
\ +可从速写本菜单中访问. preferences.use_smooth_text = 在编辑器窗口中使用平滑字体 preferences.enable_complex_text_input = 启用复杂字体输入 preferences.enable_complex_text_input_example = i.e. Japanese @@ -141,70 +141,70 @@ preferences.trigger_with = Trigger with preferences.cmd_space = space preferences.increase_max_memory = 增加最大内存至 preferences.delete_previous_folder_on_export = 当导出时删除先前的文件夹 -preferences.hide_toolbar_background_image = Hide tab/toolbar background image -preferences.check_for_updates_on_startup = Check for updates on startup +preferences.hide_toolbar_background_image = 隐藏标签/工具栏背景图片 +preferences.check_for_updates_on_startup = 在启动时检查有无更新 preferences.run_sketches_on_display = Run sketches on display preferences.run_sketches_on_display.tip = \ Sets the display where sketches are initially placed.
\ As usual, if the sketch window is moved, it will re-open
\ at the same location, however when running in present
\ (full screen) mode, this display will always be used. -preferences.automatically_associate_pde_files = Automatically associate .pde files with Processing +preferences.automatically_associate_pde_files = 自动关联 .pde 文件通过 Processing preferences.launch_programs_in = Launch programs in -preferences.launch_programs_in.mode = mode -preferences.file = More preferences can be edited directly in the file -preferences.file.hint = edit only when Processing is not running +preferences.launch_programs_in.mode = 模式 +preferences.file = 更多选项可直接编辑该文件 +preferences.file.hint = 请在Processing不在运行时编辑该文件 # Sketchbook Location (Frame) -sketchbook_location = Select new sketchbook location +sketchbook_location = 选择新速写本位置 # Sketchbook (Frame) sketchbook = Sketchbook sketchbook.tree = Sketchbook # examples (Frame) -examples = Examples +examples = 范例程序 # Export (Frame) -export = 导出选项 +export = 输出选项 export.platforms = 系统平台 export.options = 选项 -export.options.fullscreen = 全拼 (展示模式) +export.options.fullscreen = 全屏 (展示模式) export.options.show_stop_button = 显示停止按钮 -export.description.line1 = 为导出程序创建双击事件, +export.description.line1 = 为输出程序创建双击事件, export.description.line2 = 为所选系统创建独立运行程序. # Find (Frame) -find = Find -find.find = Find: -find.replace_with = Replace with: -find.ignore_case = Ignore Case -find.all_tabs = All Tabs +find = 搜索 +find.find = 搜索: +find.replace_with = 替换为: +find.ignore_case = 忽略大小写 +find.all_tabs = 所有标签 find.wrap_around = Wrap Around -find.btn.replace_all = Replace All -find.btn.replace = Replace -find.btn.find_and_replace = Find & Replace -find.btn.previous = Previous -find.btn.find = Find +find.btn.replace_all = 全部替换 +find.btn.replace = 替换 +find.btn.find_and_replace = 搜索 & 替换 +find.btn.previous = 上一个 +find.btn.find = 搜索 # Find in reference (Frame) -find_in_reference = Find in Reference +find_in_reference = 在参考文档中搜索 # Library Manager (Frame) -library.category = Category: -library.filter_your_search = Filter your search... +library.category = 目录: +library.filter_your_search = 筛查需找... # File (Frame) -file = Select an image or other data file to copy to your sketch +file = 选择一个图片或其它文件拷贝至你的速写本中 # Create Font (Frame) -create_font = Create Font +create_font = 创建字体 # Color Selector (Frame) -color_selector = Color Selector +color_selector = 颜色选择 # Archive Sketch (Frame) -archive_sketch = Archive sketch as... +archive_sketch = 速写本压缩输出... # --------------------------------------- @@ -225,11 +225,11 @@ toolbar.add_mode = 添加模式... # Editor # [Tab1] [Tab2] [v] -editor.header.new_tab = 新建Tab +editor.header.new_tab = 新建标签 editor.header.rename = 重命名 editor.header.delete = 删除 -editor.header.previous_tab = 前一个Tab -editor.header.next_tab = 后一个Tab +editor.header.previous_tab = 前一个标签 +editor.header.next_tab = 后一个标签 editor.header.delete.warning.title = Yeah, no. editor.header.delete.warning.text = You can't delete the last tab of the last open sketch. @@ -267,7 +267,7 @@ contrib.install = 安装 contrib.progress.starting = 开始 contrib.progress.downloading = 下载 contrib.download_error = 下载时出现问题. -contrib.unsupported_operating_system = 你当前的操作系统似乎不被自持. 你应该访问 %s's 该库文件地址得到更多信息. +contrib.unsupported_operating_system = 你当前的操作系统似乎不被支持. 你应该访问 %s's 该库文件地址得到更多信息. # ---------------------------------------