/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
package com.cburch.logisim.gui.generic;
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.ComponentFactory;
import com.cburch.logisim.comp.ComponentDrawContext;
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;
@SuppressWarnings("serial")
public class ProjectExplorer extends JTree implements LocaleListener {
private static final String DIRTY_MARKER = "*";
public static final Color MAGNIFYING_INTERIOR = new Color(200, 200, 255, 64);
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();
}
}
}
@Override
public int getIconHeight() {
return 20;
}
@Override
public int getIconWidth() {
return 20;
}
@Override
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 class MyCellRenderer extends DefaultTreeCellRenderer {
@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 MySelectionModel extends DefaultTreeSelectionModel {
@Override
public void addSelectionPath(TreePath path) {
if (isPathValid(path)) {
super.addSelectionPath(path);
}
}
@Override
public void setSelectionPath(TreePath path) {
if (isPathValid(path)) {
super.setSelectionPath(path);
}
}
@Override
public void addSelectionPaths(TreePath[] paths) {
paths = getValidPaths(paths);
if (paths != null) {
super.addSelectionPaths(paths);
}
}
@Override
public void setSelectionPaths(TreePath[] paths) {
paths = getValidPaths(paths);
if (paths != null) {
super.setSelectionPaths(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;
}
}
private class DeleteAction extends AbstractAction {
@Override
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 MyListener
implements MouseListener, TreeSelectionListener,
ProjectListener, PropertyChangeListener {
//
// MouseListener methods
//
@Override
public void mouseEntered(MouseEvent e) { }
@Override
public void mouseExited(MouseEvent e) { }
@Override
public void mousePressed(MouseEvent e) {
ProjectExplorer.this.requestFocus();
checkForPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
checkForPopup(e);
}
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());
}
}
}
}
@Override
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));
}
}
}
//
// TreeSelectionListener methods
//
@Override
public void valueChanged(TreeSelectionEvent e) {
TreePath path = e.getNewLeadSelectionPath();
if (listener != null) {
listener.selectionChanged(new ProjectExplorerEvent(path));
}
}
//
// project/library file/circuit listener methods
//
@Override
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
//
@Override
public void propertyChange(PropertyChangeEvent event) {
if (AppPreferences.GATE_SHAPE.isSource(event)) {
repaint();
}
}
}
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 setListener(ProjectExplorerListener value) {
listener = value;
}
public void setHaloedTool(Tool t) {
if (haloedTool == t) {
return;
}
haloedTool = t;
repaint();
}
@Override
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();
}
}