/******************************************************************************* * This file is part of logisim-evolution. * * logisim-evolution 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 3 of the License, or * (at your option) any later version. * * logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>. * * Original code by Carl Burch (http://www.cburch.com), 2011. * Subsequent modifications by : * + Haute École Spécialisée Bernoise * http://www.bfh.ch * + Haute École du paysage, d'ingénierie et d'architecture de Genève * http://hepia.hesge.ch/ * + Haute École d'Ingénierie et de Gestion du Canton de Vaud * http://www.heig-vd.ch/ * The project is currently maintained by : * + REDS Institute - HEIG-VD * Yverdon-les-Bains, Switzerland * http://reds.heig-vd.ch *******************************************************************************/ package com.cburch.logisim.gui.generic; /** * Code taken from Cornell's version of Logisim: * http://www.cs.cornell.edu/courses/cs3410/2015sp/ */ import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.Icon; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPopupMenu; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.ToolTipManager; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import com.cburch.logisim.circuit.Circuit; import com.cburch.logisim.circuit.SubcircuitFactory; import com.cburch.logisim.comp.ComponentDrawContext; import com.cburch.logisim.comp.ComponentFactory; import com.cburch.logisim.gui.main.Canvas; import com.cburch.logisim.prefs.AppPreferences; import com.cburch.logisim.proj.Project; import com.cburch.logisim.proj.ProjectEvent; import com.cburch.logisim.proj.ProjectListener; import com.cburch.logisim.tools.AddTool; import com.cburch.logisim.tools.Library; import com.cburch.logisim.tools.Tool; import com.cburch.logisim.util.LocaleListener; import com.cburch.logisim.util.LocaleManager; public class ProjectExplorer extends JTree implements LocaleListener { private class DeleteAction extends AbstractAction { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent event) { TreePath path = getSelectionPath(); if (listener != null && path != null && path.getPathCount() == 2) { listener.deleteRequested(new ProjectExplorerEvent(path)); } ProjectExplorer.this.requestFocus(); } } private class MyCellRenderer extends DefaultTreeCellRenderer { private static final long serialVersionUID = 1L; @Override public java.awt.Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { java.awt.Component ret; ret = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); if (ret instanceof JComponent) { JComponent comp = (JComponent) ret; comp.setToolTipText(null); } if (value instanceof ProjectExplorerToolNode) { ProjectExplorerToolNode toolNode = (ProjectExplorerToolNode) value; Tool tool = toolNode.getValue(); if (ret instanceof JLabel) { ((JLabel) ret).setText(tool.getDisplayName()); ((JLabel) ret).setIcon(new ToolIcon(tool)); ((JLabel) ret).setToolTipText(tool.getDescription()); } } else if (value instanceof ProjectExplorerLibraryNode) { ProjectExplorerLibraryNode libNode = (ProjectExplorerLibraryNode) value; Library lib = libNode.getValue(); if (ret instanceof JLabel) { String text = lib.getDisplayName(); if (lib.isDirty()) text += DIRTY_MARKER; ((JLabel) ret).setText(text); } } return ret; } } private class MyListener implements MouseListener, TreeSelectionListener, ProjectListener, PropertyChangeListener { private void checkForPopup(MouseEvent e) { if (e.isPopupTrigger()) { TreePath path = getPathForLocation(e.getX(), e.getY()); if (path != null && listener != null) { JPopupMenu menu = listener .menuRequested(new ProjectExplorerEvent(path)); if (menu != null) { menu.show(ProjectExplorer.this, e.getX(), e.getY()); } } } } public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { TreePath path = getPathForLocation(e.getX(), e.getY()); if (path != null && listener != null) { listener.doubleClicked(new ProjectExplorerEvent(path)); } } } // // MouseListener methods // public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { ProjectExplorer.this.requestFocus(); checkForPopup(e); } public void mouseReleased(MouseEvent e) { checkForPopup(e); } // // project/library file/circuit listener methods // public void projectChanged(ProjectEvent event) { int act = event.getAction(); if (act == ProjectEvent.ACTION_SET_TOOL) { TreePath path = getSelectionPath(); if (path != null && path.getLastPathComponent() != event.getTool()) { clearSelection(); } } else if (act == ProjectEvent.ACTION_SET_CURRENT) { ProjectExplorer.this.repaint(); } } // // PropertyChangeListener methods // public void propertyChange(PropertyChangeEvent event) { if (AppPreferences.GATE_SHAPE.isSource(event)) { repaint(); } } // // TreeSelectionListener methods // public void valueChanged(TreeSelectionEvent e) { TreePath path = e.getNewLeadSelectionPath(); if (listener != null) { listener.selectionChanged(new ProjectExplorerEvent(path)); } } } private class MySelectionModel extends DefaultTreeSelectionModel { private static final long serialVersionUID = 1L; @Override public void addSelectionPath(TreePath path) { if (isPathValid(path)) super.addSelectionPath(path); } @Override public void addSelectionPaths(TreePath[] paths) { paths = getValidPaths(paths); if (paths != null) super.addSelectionPaths(paths); } private TreePath[] getValidPaths(TreePath[] paths) { int count = 0; for (int i = 0; i < paths.length; i++) { if (isPathValid(paths[i])) ++count; } if (count == 0) { return null; } else if (count == paths.length) { return paths; } else { TreePath[] ret = new TreePath[count]; int j = 0; for (int i = 0; i < paths.length; i++) { if (isPathValid(paths[i])) ret[j++] = paths[i]; } return ret; } } private boolean isPathValid(TreePath path) { if (path == null || path.getPathCount() > 3) return false; Object last = path.getLastPathComponent(); return last instanceof ProjectExplorerToolNode; } @Override public void setSelectionPath(TreePath path) { if (isPathValid(path)) super.setSelectionPath(path); } @Override public void setSelectionPaths(TreePath[] paths) { paths = getValidPaths(paths); if (paths != null) super.setSelectionPaths(paths); } } private class ToolIcon implements Icon { Tool tool; Circuit circ = null; ToolIcon(Tool tool) { this.tool = tool; if (tool instanceof AddTool) { ComponentFactory fact = ((AddTool) tool).getFactory(false); if (fact instanceof SubcircuitFactory) { circ = ((SubcircuitFactory) fact).getSubcircuit(); } } } public int getIconHeight() { return 20; } public int getIconWidth() { return 20; } public void paintIcon(java.awt.Component c, Graphics g, int x, int y) { // draw halo if appropriate if (tool == haloedTool && AppPreferences.ATTRIBUTE_HALO.getBoolean()) { g.setColor(Canvas.HALO_COLOR); g.fillRoundRect(x, y, 20, 20, 10, 10); g.setColor(Color.BLACK); } // draw tool icon Graphics gIcon = g.create(); ComponentDrawContext context = new ComponentDrawContext( ProjectExplorer.this, null, null, g, gIcon); tool.paintIcon(context, x, y); gIcon.dispose(); // draw magnifying glass if appropriate if (circ == proj.getCurrentCircuit()) { int tx = x + 13; int ty = y + 13; int[] xp = { tx - 1, x + 18, x + 20, tx + 1 }; int[] yp = { ty + 1, y + 20, y + 18, ty - 1 }; g.setColor(MAGNIFYING_INTERIOR); g.fillOval(x + 5, y + 5, 10, 10); g.setColor(Color.BLACK); g.drawOval(x + 5, y + 5, 10, 10); g.fillPolygon(xp, yp, xp.length); } } } private static final long serialVersionUID = 1L; private static final String DIRTY_MARKER = "*"; public static final Color MAGNIFYING_INTERIOR = new Color(200, 200, 255, 64); private Project proj; private MyListener myListener = new MyListener(); private MyCellRenderer renderer = new MyCellRenderer(); private DeleteAction deleteAction = new DeleteAction(); private ProjectExplorerListener listener = null; private Tool haloedTool = null; public ProjectExplorer(Project proj) { super(); this.proj = proj; setModel(new ProjectExplorerModel(proj)); setRootVisible(true); addMouseListener(myListener); ToolTipManager.sharedInstance().registerComponent(this); MySelectionModel selector = new MySelectionModel(); selector.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); setSelectionModel(selector); setCellRenderer(renderer); addTreeSelectionListener(myListener); InputMap imap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), deleteAction); ActionMap amap = getActionMap(); amap.put(deleteAction, deleteAction); proj.addProjectListener(myListener); AppPreferences.GATE_SHAPE.addPropertyChangeListener(myListener); LocaleManager.addLocaleListener(this); } public Tool getSelectedTool() { TreePath path = getSelectionPath(); if (path == null) return null; Object last = path.getLastPathComponent(); if (last instanceof ProjectExplorerToolNode) { return ((ProjectExplorerToolNode) last).getValue(); } else { return null; } } public void localeChanged() { // repaint() would work, except that names that get longer will be // abbreviated with an ellipsis, even when they fit into the window. ProjectExplorerModel model = (ProjectExplorerModel) getModel(); model.fireStructureChanged(); } public void setHaloedTool(Tool t) { if (haloedTool == t) return; haloedTool = t; repaint(); } public void setListener(ProjectExplorerListener value) { listener = value; } }