/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2013-15 The Processing Foundation 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.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.FlowLayout; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.util.Enumeration; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import processing.app.Base; import processing.app.Language; import processing.app.Library; import processing.app.Mode; import processing.app.Platform; import processing.app.Preferences; import processing.app.SketchReference; import processing.app.contrib.Contribution; import processing.app.contrib.ContributionManager; import processing.app.contrib.ContributionType; import processing.app.contrib.ExamplesContribution; import processing.core.PApplet; import processing.data.StringDict; public class ExamplesFrame extends JFrame { protected Base base; protected Mode mode; protected File examplesContribFolder; public ExamplesFrame(final Base base, final Mode mode) { super(Language.interpolate("examples.title", mode.getTitle())); this.base = base; this.mode = mode; // Get path to the contributed examples compatible with this mode examplesContribFolder = Base.getSketchbookExamplesFolder(); Toolkit.setIcon(this); Toolkit.registerWindowCloseKeys(getRootPane(), new ActionListener() { public void actionPerformed(ActionEvent e) { setVisible(false); } }); JPanel examplesPanel = new JPanel(); examplesPanel.setLayout(new BorderLayout()); examplesPanel.setBackground(Color.WHITE); final JPanel openExamplesManagerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); JButton addExamplesButton = new JButton(Language.text("examples.add_examples")); openExamplesManagerPanel.add(addExamplesButton); openExamplesManagerPanel.setOpaque(false); Border lineBorder = BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY); Border paddingBorder = BorderFactory.createEmptyBorder(3, 5, 1, 4); openExamplesManagerPanel.setBorder(BorderFactory.createCompoundBorder(lineBorder, paddingBorder)); openExamplesManagerPanel.setAlignmentX(Component.LEFT_ALIGNMENT); openExamplesManagerPanel.setCursor(new Cursor(Cursor.HAND_CURSOR)); addExamplesButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ContributionManager.openExamples(); } }); final JTree tree = new JTree(buildTree()); tree.setOpaque(true); tree.setAlignmentX(Component.LEFT_ALIGNMENT); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setShowsRootHandles(true); // expand the root tree.expandRow(0); // now hide the root tree.setRootVisible(false); // After 2.0a7, no longer expanding each of the categories at Casey's // request. He felt that the window was too complicated too quickly. // for (int row = tree.getRowCount()-1; row >= 0; --row) { // tree.expandRow(row); // } tree.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); int selRow = tree.getRowForLocation(e.getX(), e.getY()); //TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); //if (node != null && node.isLeaf() && node.getPath().equals(selPath)) { if (node != null && node.isLeaf() && selRow != -1) { SketchReference sketch = (SketchReference) node.getUserObject(); base.handleOpen(sketch.getPath()); } } } }); tree.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { // doesn't fire keyTyped() setVisible(false); } } public void keyTyped(KeyEvent e) { if (e.getKeyChar() == KeyEvent.VK_ENTER) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (node != null && node.isLeaf()) { SketchReference sketch = (SketchReference) node.getUserObject(); base.handleOpen(sketch.getPath()); } } } }); tree.addTreeExpansionListener(new TreeExpansionListener() { @Override public void treeExpanded(TreeExpansionEvent event) { updateExpanded(tree); } @Override public void treeCollapsed(TreeExpansionEvent event) { updateExpanded(tree); } }); tree.setBorder(new EmptyBorder(0, 5, 5, 5)); if (Platform.isMacOS()) { tree.setToggleClickCount(2); } else { tree.setToggleClickCount(1); } // Special cell renderer that takes the UI zoom into account tree.setCellRenderer(new ZoomTreeCellRenderer(mode)); JScrollPane treePane = new JScrollPane(tree); treePane.setPreferredSize(Toolkit.zoom(250, 300)); treePane.setBorder(new EmptyBorder(Toolkit.zoom(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); getContentPane().add(examplesPanel); pack(); restoreExpanded(tree); } public void setVisible() { // Space for the editor plus a li'l gap int roughWidth = getWidth() + 20; Point p = null; // If no window open, or the editor is at the edge of the screen Editor editor = base.getActiveEditor(); if (editor == null || (p = editor.getLocation()).x < roughWidth) { // Center the window on the screen setLocationRelativeTo(null); } else { // Open the window relative to the editor setLocation(p.x - roughWidth, p.y); } setVisible(true); } protected void updateExpanded(JTree tree) { Enumeration en = tree.getExpandedDescendants(new TreePath(tree.getModel().getRoot())); //en.nextElement(); // skip the root "Examples" node StringBuilder s = new StringBuilder(); while (en.hasMoreElements()) { //System.out.println(en.nextElement()); TreePath tp = (TreePath) en.nextElement(); Object[] path = tp.getPath(); for (Object o : path) { DefaultMutableTreeNode p = (DefaultMutableTreeNode) o; String name = (String) p.getUserObject(); //System.out.print(p.getUserObject().getClass().getName() + ":" + p.getUserObject() + " -> "); //System.out.print(name + " -> "); s.append(name); s.append(File.separatorChar); } //System.out.println(); s.setCharAt(s.length() - 1, File.pathSeparatorChar); } s.setLength(s.length() - 1); // nix that last separator String pref = "examples." + getClass().getName() + ".visible"; Preferences.set(pref, s.toString()); Preferences.save(); // System.out.println(s); // System.out.println(); } protected void restoreExpanded(JTree tree) { String pref = "examples." + getClass().getName() + ".visible"; String value = Preferences.get(pref); if (value != null) { String[] paths = PApplet.split(value, File.pathSeparator); for (String path : paths) { // System.out.println("trying to expand " + path); String[] items = PApplet.split(path, File.separator); DefaultMutableTreeNode[] nodes = new DefaultMutableTreeNode[items.length]; expandTree(tree, null, items, nodes, 0); } } } void expandTree(JTree tree, Object object, String[] items, DefaultMutableTreeNode[] nodes, int index) { // if (object == null) { // object = model.getRoot(); // } TreeModel model = tree.getModel(); if (index == 0) { nodes[0] = (DefaultMutableTreeNode) model.getRoot(); expandTree(tree, nodes[0], items, nodes, 1); } else if (index < items.length) { // String item = items[0]; // TreeModel model = object.getModel(); // System.out.println(object.getClass().getName()); DefaultMutableTreeNode node = (DefaultMutableTreeNode) object; int count = model.getChildCount(node); // System.out.println("child count is " + count); for (int i = 0; i < count; i++) { DefaultMutableTreeNode child = (DefaultMutableTreeNode) model.getChild(node, i); if (items[index].equals(child.getUserObject())) { nodes[index] = child; expandTree(tree, child, items, nodes, index+1); } } } else { // last one // PApplet.println(nodes); tree.expandPath(new TreePath(nodes)); } } protected DefaultMutableTreeNode buildTree() { DefaultMutableTreeNode root = new DefaultMutableTreeNode(); //"Examples"); try { // Get the list of Mode-specific examples, in the order the Mode wants // to present them (i.e. Basics, then Topics, then Demos...) File[] examples = mode.getExampleCategoryFolders(); for (File subFolder : examples) { DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(subFolder.getName()); if (base.addSketches(subNode, subFolder, true)) { root.add(subNode); } } DefaultMutableTreeNode foundationLibraries = new DefaultMutableTreeNode(Language.text("examples.core_libraries")); // Get examples for core libraries for (Library lib : mode.coreLibraries) { if (lib.hasExamples()) { DefaultMutableTreeNode libNode = new DefaultMutableTreeNode(lib.getName()); if (base.addSketches(libNode, lib.getExamplesFolder(), true)) { foundationLibraries.add(libNode); } } } if (foundationLibraries.getChildCount() > 0) { root.add(foundationLibraries); } // Get examples for third party libraries DefaultMutableTreeNode contributedLibExamples = new DefaultMutableTreeNode(Language.text("examples.libraries")); for (Library lib : mode.contribLibraries) { if (lib.hasExamples()) { DefaultMutableTreeNode libNode = new DefaultMutableTreeNode(lib.getName()); base.addSketches(libNode, lib.getExamplesFolder(), true); contributedLibExamples.add(libNode); } } if (contributedLibExamples.getChildCount() > 0) { root.add(contributedLibExamples); } } catch (IOException e) { e.printStackTrace(); } DefaultMutableTreeNode contributedExamplesNode = buildContribTree(); if (contributedExamplesNode.getChildCount() > 0) { root.add(contributedExamplesNode); } return root; } protected DefaultMutableTreeNode buildContribTree() { DefaultMutableTreeNode contribExamplesNode = new DefaultMutableTreeNode(Language.text("examples.contributed")); try { File[] subfolders = ContributionType.EXAMPLES.listCandidates(examplesContribFolder); if (subfolders != null) { for (File sub : subfolders) { StringDict props = Contribution.loadProperties(sub, ContributionType.EXAMPLES); if (props != null) { if (ExamplesContribution.isCompatible(base, props)) { DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(props.get("name")); if (base.addSketches(subNode, sub, true)) { contribExamplesNode.add(subNode); // TODO there has to be a simpler way of handling this along // with addSketches() as well [fry 150811] int exampleNodeNumber = -1; // The contrib may have other items besides the examples folder for (int i = 0; i < subNode.getChildCount(); i++) { if (subNode.getChildAt(i).toString().equals("examples")) { exampleNodeNumber = i; } } if (exampleNodeNumber != -1) { TreeNode exampleNode = subNode.getChildAt(exampleNodeNumber); subNode.remove(exampleNodeNumber); int count = exampleNode.getChildCount(); for (int j = 0; j < count; j++) { subNode.add((DefaultMutableTreeNode) exampleNode.getChildAt(0)); } } // if (subNode.getChildCount() != 1) { // System.err.println("more children than expected when one is enough"); // } // TreeNode exampleNode = subNode.getChildAt(0); // subNode.add((DefaultMutableTreeNode) exampleNode.getChildAt(0)); } } } } } } catch (IOException e) { e.printStackTrace(); } return contribExamplesNode; } }