/* Copyright (c) 2006, 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.main;
import java.awt.Color;
import java.awt.Font;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.CircuitEvent;
import com.cburch.logisim.circuit.CircuitListener;
import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.circuit.SimulatorEvent;
import com.cburch.logisim.circuit.SimulatorListener;
import com.cburch.logisim.circuit.WidthIncompatibilityData;
import com.cburch.logisim.circuit.WireSet;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.comp.ComponentUserEvent;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeEvent;
import com.cburch.logisim.data.AttributeListener;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.file.LibraryEvent;
import com.cburch.logisim.file.LibraryListener;
import com.cburch.logisim.file.LogisimFile;
import com.cburch.logisim.file.MouseMappings;
import com.cburch.logisim.file.Options;
import com.cburch.logisim.proj.LogisimPreferences;
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.EditTool;
import com.cburch.logisim.tools.Library;
import com.cburch.logisim.tools.Tool;
import com.cburch.logisim.tools.ToolTipMaker;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.LocaleListener;
import com.cburch.logisim.util.LocaleManager;
import com.cburch.logisim.util.StringGetter;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Collection;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.event.MouseInputListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
public class Canvas extends JPanel
implements LocaleListener, Scrollable {
private static final int BOUNDS_BUFFER = 70;
// pixels shown in canvas beyond outermost boundaries
static final double SQRT_2 = Math.sqrt(2.0);
private static final int BUTTONS_MASK = InputEvent.BUTTON1_DOWN_MASK
| InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK;
private static final Color GRID_ZOOMED_OUT_COLOR = new Color(210, 210, 210);
private class MyListener
implements MouseInputListener, KeyListener, PopupMenuListener, ComponentListener,
PropertyChangeListener {
boolean menu_on = false;
//
// MouseListener methods
//
public void mouseClicked(MouseEvent e) { }
public void mouseMoved(MouseEvent e) {
if((e.getModifiersEx() & BUTTONS_MASK) != 0) {
// If the control key is down while the mouse is being
// dragged, mouseMoved is called instead. This may well be
// an issue specific to the MacOS Java implementation,
// but it exists there in the 1.4 and 5.0 versions.
mouseDragged(e);
return;
}
Tool tool = getToolFor(e);
if(tool != null) {
repairMouseEvent(e);
tool.mouseMoved(Canvas.this, getGraphics(), e);
}
}
public void mouseDragged(MouseEvent e) {
if(drag_tool != null) {
repairMouseEvent(e);
drag_tool.mouseDragged(Canvas.this, getGraphics(), e);
}
}
public void mouseEntered(MouseEvent e) {
if(drag_tool != null) {
repairMouseEvent(e);
drag_tool.mouseEntered(Canvas.this, getGraphics(), e);
} else {
Tool tool = getToolFor(e);
if(tool != null) {
repairMouseEvent(e);
tool.mouseEntered(Canvas.this, getGraphics(), e);
}
}
}
public void mouseExited(MouseEvent e) {
if(drag_tool != null) {
repairMouseEvent(e);
drag_tool.mouseExited(Canvas.this, getGraphics(), e);
} else {
Tool tool = getToolFor(e);
if(tool != null) {
repairMouseEvent(e);
tool.mouseExited(Canvas.this, getGraphics(), e);
}
}
}
public void mousePressed(MouseEvent e) {
viewport.setErrorMessage(null);
proj.setStartupScreen(false);
Canvas.this.requestFocus();
drag_tool = getToolFor(e);
if(drag_tool != null) {
repairMouseEvent(e);
drag_tool.mousePressed(Canvas.this, getGraphics(), e);
}
completeAction();
}
public void mouseReleased(MouseEvent e) {
if(drag_tool != null) {
repairMouseEvent(e);
drag_tool.mouseReleased(Canvas.this, getGraphics(), e);
drag_tool = null;
}
Tool tool = proj.getTool();
if(tool != null) {
tool.mouseMoved(Canvas.this, getGraphics(), e);
}
completeAction();
}
private Tool getToolFor(MouseEvent e) {
if(menu_on) return null;
Tool ret = mappings.getToolFor(e);
if(ret == null) return proj.getTool();
else return ret;
}
private void repairMouseEvent(MouseEvent e) {
if(zoomFactor != 1.0) {
int oldx = e.getX();
int oldy = e.getY();
int newx = (int) Math.round(e.getX() / zoomFactor);
int newy = (int) Math.round(e.getY() / zoomFactor);
e.translatePoint(newx - oldx, newy - oldy);
}
}
//
// KeyListener methods
//
public void keyPressed(KeyEvent e) {
Tool tool = proj.getTool();
if(tool != null) tool.keyPressed(Canvas.this, e);
}
public void keyReleased(KeyEvent e) {
Tool tool = proj.getTool();
if(tool != null) tool.keyReleased(Canvas.this, e);
}
public void keyTyped(KeyEvent e) {
Tool tool = proj.getTool();
if(tool != null) tool.keyTyped(Canvas.this, e);
}
//
// PopupMenuListener mtehods
//
public void popupMenuCanceled(PopupMenuEvent e) {
menu_on = false;
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
menu_on = false;
}
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
//
// ComponentListener methods
//
public void componentResized(ComponentEvent arg0) {
computeSize();
}
public void componentMoved(ComponentEvent arg0) { }
public void componentShown(ComponentEvent arg0) { }
public void componentHidden(ComponentEvent arg0) { }
public void propertyChange(PropertyChangeEvent event) {
String prop = event.getPropertyName();
if(prop.equals(LogisimPreferences.GATE_SHAPE)) {
repaint();
}
}
}
private class MyProjectListener
implements ProjectListener, LibraryListener, CircuitListener,
AttributeListener, SimulatorListener, Selection.Listener {
public void projectChanged(ProjectEvent event) {
int act = event.getAction();
if(act == ProjectEvent.ACTION_SET_CURRENT) {
viewport.setErrorMessage(null);
if(haloedComponent != null) {
proj.getFrame().viewComponentAttributes(null, null);
}
} else if(act == ProjectEvent.ACTION_SET_FILE) {
LogisimFile old = (LogisimFile) event.getOldData();
if(old != null) old.getOptions().getAttributeSet().removeAttributeListener(this);
LogisimFile file = (LogisimFile) event.getData();
if(file != null) {
AttributeSet attrs = file.getOptions().getAttributeSet();
attrs.addAttributeListener(this);
loadOptions(attrs);
mappings = file.getOptions().getMouseMappings();
}
} else if(act == ProjectEvent.ACTION_SET_TOOL) {
viewport.setErrorMessage(null);
Tool t = event.getTool();
if(t == null) setCursor(Cursor.getDefaultCursor());
else setCursor(t.getCursor());
}
if(act != ProjectEvent.ACTION_SELECTION) {
completeAction();
}
}
public void libraryChanged(LibraryEvent event) {
if(event.getAction() == LibraryEvent.REMOVE_TOOL) {
Object t = event.getData();
if(t instanceof AddTool) t = ((AddTool) t).getFactory();
if(t == proj.getCurrentCircuit() && t != null) {
proj.setCurrentCircuit(proj.getLogisimFile().getMainCircuit());
}
if(proj.getTool() == event.getData()) {
Tool next = findTool(proj.getLogisimFile().getOptions()
.getToolbarData().getContents());
if(next == null) {
List libs = proj.getLogisimFile().getLibraries();
for(Iterator it = libs.iterator(); it.hasNext(); ) {
Library lib = (Library) it.next();
next = findTool(lib.getTools());
if(next != null) break;
}
}
proj.setTool(next);
}
if(t instanceof Circuit) {
CircuitState state = getCircuitState();
CircuitState last = state;
while(state != null && state.getCircuit() != t) {
last = state;
state = state.getParentState();
}
if(state != null) {
getProject().setCircuitState(last.cloneState());
}
}
}
}
private Tool findTool(List opts) {
Tool ret = null;
for(Iterator it = opts.iterator(); it.hasNext(); ) {
Object o = it.next();
if(o instanceof Tool && ret == null) ret = (Tool) o;
else if(o instanceof EditTool) ret = (Tool) o;
}
return ret;
}
public void circuitChanged(CircuitEvent event) {
int act = event.getAction();
if(act == CircuitEvent.ACTION_REMOVE) {
Component c = (Component) event.getData();
if(c == haloedComponent) {
proj.getFrame().viewComponentAttributes(null, null);
}
} else if(act == CircuitEvent.ACTION_CLEAR) {
if(haloedComponent != null) {
proj.getFrame().viewComponentAttributes(null, null);
}
} else if(act == CircuitEvent.ACTION_INVALIDATE) {
completeAction();
}
}
public void propagationCompleted(SimulatorEvent e) {
/* This was a good idea for a while... but it leads to problems
* when a repaint is done just before a user action takes place.
// repaint - but only if it's been a while since the last one
long now = System.currentTimeMillis();
if(now > lastRepaint + repaintDuration) {
lastRepaint = now; // (ensure that multiple requests aren't made
repaintDuration = 15 + (int) (20 * Math.random());
// repaintDuration is for jittering the repaints to
// reduce aliasing effects
repaint();
}
*/
repaint();
}
public void tickCompleted(SimulatorEvent e) {
waitForRepaintDone();
}
public void simulatorStateChanged(SimulatorEvent e) { }
public void attributeListChanged(AttributeEvent e) { }
public void attributeValueChanged(AttributeEvent e) {
Attribute attr = e.getAttribute();
Object val = e.getValue();
if(attr == Options.zoom_attr) {
double f = zoomFactor;
double cx = 0.0;
double cy = 0.0;
if(parent != null) {
Rectangle r = parent.getViewport().getViewRect();
cx = (r.x + r.width / 2) / f;
cy = (r.y + r.height / 2) / f;
}
f = ((Double) val).doubleValue();
zoomFactor = f;
computeSize();
repaint();
if(parent != null) {
Rectangle r = parent.getViewport().getViewRect();
int hv = (int) (cx * f) - r.width / 2;
int vv = (int) (cy * f) - r.height / 2;
parent.getHorizontalScrollBar().setValue(hv);
parent.getVerticalScrollBar().setValue(vv);
}
} else if(attr == Options.showgrid_attr) {
showGrid = ((Boolean) val).booleanValue();
repaint();
} else if(attr == Options.preview_attr) {
printerView = ((Boolean) val).booleanValue();
repaint();
} else if(attr == Options.showhalo_attr) {
showHalo = ((Boolean) val).booleanValue();
repaint();
} else if(attr == Options.showtips_attr) {
showTips = ((Boolean) val).booleanValue();
setToolTipText(showTips ? "" : null);
} else if(attr == Options.ATTR_GATE_UNDEFINED) {
CircuitState circState = getCircuitState();
circState.markComponentsDirty(getCircuit().getNonWires());
// TODO actually, we'd want to mark all components in
// subcircuits as dirty as well
}
}
public void selectionChanged(Selection.Event event) {
repaint();
}
}
private class MyViewport extends JViewport {
StringGetter errorMessage = null;
String widthMessage = null;
boolean isNorth = false;
boolean isSouth = false;
boolean isWest = false;
boolean isEast = false;
boolean isNortheast = false;
boolean isNorthwest = false;
boolean isSoutheast = false;
boolean isSouthwest = false;
MyViewport() { }
void setErrorMessage(StringGetter msg) {
if(errorMessage != msg) {
errorMessage = msg;
repaint();
}
}
void setWidthMessage(String msg) {
widthMessage = msg;
isNorth = false;
isSouth = false;
isWest = false;
isEast = false;
isNortheast = false;
isNorthwest = false;
isSoutheast = false;
isSouthwest = false;
}
void setNorth(boolean value) { isNorth = value; }
void setSouth(boolean value) { isSouth = value; }
void setEast(boolean value) { isEast = value; }
void setWest(boolean value) { isWest = value; }
void setNortheast(boolean value) { isNortheast = value; }
void setNorthwest(boolean value) { isNorthwest = value; }
void setSoutheast(boolean value) { isSoutheast = value; }
void setSouthwest(boolean value) { isSouthwest = value; }
public void paintChildren(Graphics g) {
super.paintChildren(g);
paintContents(g);
}
public Color getBackground() {
return getView() == null ? super.getBackground() : getView().getBackground();
}
void paintContents(Graphics g) {
/* TODO this is for the SimulatorPrototype class
int speed = proj.getSimulator().getSimulationSpeed();
String speedStr;
if(speed >= 10000000) {
speedStr = (speed / 1000000) + " MHz";
} else if(speed >= 1000000) {
speedStr = (speed / 100000) / 10.0 + " MHz";
} else if(speed >= 10000) {
speedStr = (speed / 1000) + " KHz";
} else if(speed >= 10000) {
speedStr = (speed / 100) / 10.0 + " KHz";
} else {
speedStr = speed + " Hz";
}
FontMetrics fm = g.getFontMetrics();
g.drawString(speedStr, getWidth() - 10 - fm.stringWidth(speedStr),
getHeight() - 10);
*/
if(errorMessage != null) {
g.setColor(Color.RED);
paintString(g, errorMessage.get());
return;
}
if(proj.getSimulator().isOscillating()) {
g.setColor(Color.RED);
paintString(g, Strings.get("canvasOscillationError"));
return;
}
if(proj.getSimulator().isExceptionEncountered()) {
g.setColor(Color.RED);
paintString(g, Strings.get("canvasExceptionError"));
return;
}
computeViewportContents();
Dimension sz = getSize();
g.setColor(Value.WIDTH_ERROR_COLOR);
if(widthMessage != null) {
paintString(g, widthMessage);
}
GraphicsUtil.switchToWidth(g, 3);
if(isNorth) GraphicsUtil.drawArrow(g, sz.width / 2, 20,
sz.width / 2, 2, 10, 30);
if(isSouth) GraphicsUtil.drawArrow(g, sz.width / 2, sz.height - 20,
sz.width / 2, sz.height - 2, 10, 30);
if(isEast) GraphicsUtil.drawArrow(g, sz.width - 20, sz.height / 2,
sz.width - 2, sz.height / 2, 10, 30);
if(isWest) GraphicsUtil.drawArrow(g, 20, sz.height / 2,
2, sz.height / 2, 10, 30);
if(isNortheast) GraphicsUtil.drawArrow(g, sz.width - 14, 14,
sz.width - 2, 2, 10, 30);
if(isNorthwest) GraphicsUtil.drawArrow(g, 14, 14,
2, 2, 10, 30);
if(isSoutheast) GraphicsUtil.drawArrow(g, sz.width - 14, sz.height - 14,
sz.width - 2, sz.height - 2, 10, 30);
if(isSouthwest) GraphicsUtil.drawArrow(g, 14, sz.height - 14,
2, sz.height - 2, 10, 30);
GraphicsUtil.switchToWidth(g, 1);
g.setColor(Color.BLACK);
}
private void paintString(Graphics g, String msg) {
Font old = g.getFont();
g.setFont(old.deriveFont(Font.BOLD).deriveFont(18.0f));
FontMetrics fm = g.getFontMetrics();
int x = (getWidth() - fm.stringWidth(msg)) / 2;
if(x < 0) x = 0;
g.drawString(msg, x, getHeight() - 23);
g.setFont(old);
return;
}
}
private Project proj;
private Tool drag_tool;
private MouseMappings mappings;
private JScrollPane parent = null;
private MyListener myListener = new MyListener();
private MyViewport viewport = new MyViewport();
private MyProjectListener myProjectListener = new MyProjectListener();
private boolean showGrid = true;
private boolean printerView = false;
private boolean showHalo = true;
private boolean showTips = true;
private double zoomFactor = 1.0;
private boolean paintDirty = false; // only for within paintComponent
private boolean inPaint = false; // only for within paintComponent
private Object repaintLock = new Object(); // for waitForRepaintDone
private Component haloedComponent = null;
private Circuit haloedCircuit = null;
private WireSet highlightedWires = WireSet.EMPTY;
public Canvas(Project proj) {
this.proj = proj;
this.mappings = proj.getOptions().getMouseMappings();
setBackground(Color.white);
addMouseListener(myListener);
addMouseMotionListener(myListener);
addKeyListener(myListener);
proj.addProjectListener(myProjectListener);
proj.addLibraryListener(myProjectListener);
proj.addCircuitListener(myProjectListener);
proj.getSelection().addListener(myProjectListener);
LocaleManager.addLocaleListener(this);
AttributeSet options = proj.getOptions().getAttributeSet();
options.addAttributeListener(myProjectListener);
LogisimPreferences.addPropertyChangeListener(LogisimPreferences.GATE_SHAPE, myListener);
loadOptions(options);
}
private void loadOptions(AttributeSet options) {
printerView = ((Boolean) options.getValue(Options.preview_attr)).booleanValue();
showGrid = ((Boolean) options.getValue(Options.showgrid_attr)).booleanValue();
showHalo = ((Boolean) options.getValue(Options.showhalo_attr)).booleanValue();
showTips = ((Boolean) options.getValue(Options.showtips_attr)).booleanValue();
zoomFactor = ((Double) options.getValue(Options.zoom_attr)).doubleValue();
setToolTipText(showTips ? "" : null);
proj.getSimulator().removeSimulatorListener(myProjectListener);
proj.getSimulator().addSimulatorListener(myProjectListener);
}
public void repaint() {
if(inPaint) paintDirty = true;
else super.repaint();
}
public void setErrorMessage(StringGetter message) {
viewport.setErrorMessage(message);
}
//
// access methods
//
public Circuit getCircuit() {
return proj.getCurrentCircuit();
}
public CircuitState getCircuitState() {
return proj.getCircuitState();
}
public Project getProject() {
return proj;
}
public Selection getSelection() {
return proj.getSelection();
}
public boolean getShowHalo() { return showHalo; }
//
// graphics methods
//
Component getHaloedComponent() {
return haloedComponent;
}
void setHaloedComponent(Circuit circ, Component comp) {
if(comp == haloedComponent) return;
Graphics g = getGraphics();
exposeHaloedComponent(g);
haloedCircuit = circ;
haloedComponent = comp;
exposeHaloedComponent(g);
}
private void exposeHaloedComponent(Graphics g) {
Component c = haloedComponent;
if(c == null) return;
Bounds bds = c.getBounds(g).expand(7);
int w = bds.getWidth();
int h = bds.getHeight();
double a = SQRT_2 * w;
double b = SQRT_2 * h;
repaint((int) Math.round(bds.getX() + w/2.0 - a/2.0),
(int) Math.round(bds.getY() + h/2.0 - b/2.0),
(int) Math.round(a), (int) Math.round(b));
}
public void setHighlightedWires(WireSet value) {
highlightedWires = value == null ? WireSet.EMPTY : value;
}
public void showPopupMenu(JPopupMenu menu, int x, int y) {
if(zoomFactor != 1.0) {
x = (int) Math.round(x * zoomFactor);
y = (int) Math.round(y * zoomFactor);
}
myListener.menu_on = true;
menu.addPopupMenuListener(myListener);
menu.show(this, x, y);
}
private void completeAction() {
computeSize();
// TODO for SimulatorPrototype: proj.getSimulator().releaseUserEvents();
proj.getSimulator().requestPropagate();
// repaint will occur after propagation completes
}
public void setScrollPane(JScrollPane value) {
if(parent != null) {
parent.removeComponentListener(myListener);
}
parent = value;
if(parent != null) {
parent.setViewport(viewport);
viewport.setView(this);
setOpaque(false);
parent.addComponentListener(myListener);
}
computeSize();
}
public void computeSize() {
Bounds bounds = proj.getCurrentCircuit().getBounds();
int width = bounds.getX() + bounds.getWidth() + BOUNDS_BUFFER;
int height = bounds.getY() + bounds.getHeight() + BOUNDS_BUFFER;
if(zoomFactor != 1.0) {
width = (int) Math.ceil(width * zoomFactor);
height = (int) Math.ceil(height * zoomFactor);
}
if(parent != null) {
Dimension min_size = new Dimension();
parent.getViewport().getSize(min_size);
if(min_size.width > width) width = min_size.width;
if(min_size.height > height) height = min_size.height;
}
setPreferredSize(new Dimension(width, height));
revalidate();
}
private void waitForRepaintDone() {
synchronized(repaintLock) {
try {
while(inPaint) {
repaintLock.wait();
}
} catch(InterruptedException e) { }
}
}
public void paintComponent(Graphics g) {
inPaint = true;
try {
super.paintComponent(g);
do {
paintContents(g);
} while(paintDirty);
if(parent == null) viewport.paintContents(g);
} finally {
inPaint = false;
synchronized(repaintLock) {
repaintLock.notifyAll();
}
}
}
private void paintContents(Graphics g) {
Rectangle clip = g.getClipBounds();
Dimension size = this.getSize();
if(clip == null || paintDirty) {
clip = new Rectangle(0, 0, size.width, size.height);
paintDirty = false;
}
g.setColor(Color.white);
g.fillRect(clip.x, clip.y, clip.width, clip.height);
if(showGrid) {
g.setColor(Color.gray);
double f = zoomFactor;
if(f == 1.0) {
int start_x = ((clip.x + 9) / 10) * 10;
int start_y = ((clip.y + 9) / 10) * 10;
for(int x = 0; x < clip.width; x += 10) {
for(int y = 0; y < clip.height; y += 10) {
g.fillRect(start_x + x, start_y + y, 1, 1);
}
}
} else {
/* Original code that Carl Burch wrote. Kevin Walsh of Cornell
* suggested replacing it with the code below instead. */
int x0 = 10 * (int) Math.ceil(clip.x / f / 10);
int x1 = x0 + (int) (clip.width / f);
int y0 = 10 * (int) Math.ceil(clip.y / f / 10);
int y1 = y0 + (int) (clip.height / f);
if(f <= 0.5) g.setColor(GRID_ZOOMED_OUT_COLOR);
for(double x = x0; x < x1; x += 10) {
for(double y = y0; y < y1; y += 10) {
int sx = (int) Math.round(f * x);
int sy = (int) Math.round(f * y);
g.fillRect(sx, sy, 1, 1);
}
}
if(f <= 0.5) { // make every 5th pixel darker
g.setColor(Color.gray);
x0 = 50 * (int) Math.ceil(clip.x / f / 50);
y0 = 50 * (int) Math.ceil(clip.y / f / 50);
for(double x = x0; x < x1; x += 50) {
for(double y = y0; y < y1; y += 50) {
int sx = (int) Math.round(f * x);
int sy = (int) Math.round(f * y);
g.fillRect(sx, sy, 1, 1);
}
}
}
/*
int x0 = 10 * (int) Math.ceil(clip.x / f / 10);
int x1 = x0 + (int)(clip.width / f);
int y0 = 10 * (int) Math.ceil(clip.y / f / 10);
int y1 = y0 + (int) (clip.height / f);
int s = f > 0.5 ? 1 : f > 0.25 ? 2 : 3;
int i0 = s - ((x0 + 10*s - 1) % (s * 10)) / 10 - 1;
int j0 = s - ((y1 + 10*s - 1) % (s * 10)) / 10 - 1;
for(int i = 0; i < s; i++) {
for(int x = x0+i*10; x < x1; x += s*10) {
for(int j = 0; j < s; j++) {
g.setColor(i == i0 && j == j0 ? Color.gray : GRID_ZOOMED_OUT_COLOR);
for(int y = y0+j*10; y < y1; y += s*10) {
int sx = (int) Math.round(f * x);
int sy = (int) Math.round(f * y);
g.fillRect(sx, sy, 1, 1);
}
}
}
}
*/
}
}
g.setColor(Color.black);
Graphics gScaled = g.create();
if(zoomFactor != 1.0 && gScaled instanceof Graphics2D) {
((Graphics2D) gScaled).scale(zoomFactor, zoomFactor);
}
drawWithUserState(g, gScaled, printerView);
drawWidthIncompatibilityData(g, gScaled);
Circuit circ = proj.getCurrentCircuit();
CircuitState circState = proj.getCircuitState();
ComponentDrawContext ptContext = new ComponentDrawContext(this,
circ, circState, g, gScaled);
ptContext.setHighlightedWires(highlightedWires);
gScaled.setColor(Color.RED);
circState.drawOscillatingPoints(ptContext);
gScaled.setColor(Color.BLUE);
proj.getSimulator().drawStepPoints(ptContext);
gScaled.dispose();
}
private void drawWithUserState(Graphics base, Graphics g, boolean printView) {
Circuit circ = proj.getCurrentCircuit();
Selection sel = proj.getSelection();
Collection hidden = sel.getHiddenComponents();
// draw halo around component whose attributes we are viewing
if(showHalo && haloedComponent != null && haloedCircuit == circ
&& !hidden.contains(haloedComponent)) {
GraphicsUtil.switchToWidth(g, 3);
g.setColor(AttributeTable.HALO_COLOR);
Bounds bds = haloedComponent.getBounds(g).expand(5);
int w = bds.getWidth();
int h = bds.getHeight();
double a = SQRT_2 * w;
double b = SQRT_2 * h;
g.drawOval((int) Math.round(bds.getX() + w/2.0 - a/2.0),
(int) Math.round(bds.getY() + h/2.0 - b/2.0),
(int) Math.round(a), (int) Math.round(b));
GraphicsUtil.switchToWidth(g, 1);
g.setColor(Color.BLACK);
}
// draw circuit and selection
CircuitState circState = proj.getCircuitState();
ComponentDrawContext context = new ComponentDrawContext(this,
circ, circState, base, g, printView);
context.setHighlightedWires(highlightedWires);
circ.draw(context, hidden);
sel.draw(context);
// draw tool
Tool tool = drag_tool != null ? drag_tool : proj.getTool();
if(tool != null && !myListener.menu_on) {
Graphics gCopy = g.create();
context.setGraphics(gCopy);
tool.draw(this, context);
gCopy.dispose();
}
}
private void drawWidthIncompatibilityData(Graphics base, Graphics g) {
Set exceptions = proj.getCurrentCircuit().getWidthIncompatibilityData();
if(exceptions == null || exceptions.size() == 0) return;
g.setColor(Value.WIDTH_ERROR_COLOR);
GraphicsUtil.switchToWidth(g, 2);
FontMetrics fm = base.getFontMetrics(g.getFont());
for(Iterator it = exceptions.iterator(); it.hasNext(); ) {
WidthIncompatibilityData ex = (WidthIncompatibilityData) it.next();
for(int i = 0; i < ex.size(); i++) {
Location p = ex.getPoint(i);
BitWidth w = ex.getBitWidth(i);
// ensure it hasn't already been drawn
boolean drawn = false;
for(int j = 0; j < i; j++) {
if(ex.getPoint(j).equals(p)) { drawn = true; break; }
}
if(drawn) continue;
// compute the caption combining all similar points
String caption = "" + w.getWidth();
for(int j = i + 1; j < ex.size(); j++) {
if(ex.getPoint(j).equals(p)) { caption += "/" + ex.getBitWidth(j); break; }
}
g.drawOval(p.getX() - 4, p.getY() - 4, 8, 8);
g.drawString(caption, p.getX() + 5, p.getY() + 2 + fm.getAscent());
}
}
g.setColor(Color.BLACK);
GraphicsUtil.switchToWidth(g, 1);
}
private void computeViewportContents() {
Set exceptions = proj.getCurrentCircuit().getWidthIncompatibilityData();
if(exceptions == null || exceptions.size() == 0) {
viewport.setWidthMessage(null);
return;
}
Rectangle viewableBase;
Rectangle viewable;
if(parent != null) {
viewableBase = parent.getViewport().getViewRect();
} else {
Bounds bds = proj.getCurrentCircuit().getBounds();
viewableBase = new Rectangle(0, 0, bds.getWidth(), bds.getHeight());
}
if(zoomFactor == 1.0) {
viewable = viewableBase;
} else {
viewable = new Rectangle((int) (viewableBase.x / zoomFactor),
(int) (viewableBase.y / zoomFactor),
(int) (viewableBase.width / zoomFactor),
(int) (viewableBase.height / zoomFactor));
}
viewport.setWidthMessage(Strings.get("canvasWidthError")
+ (exceptions.size() == 1 ? "" : " (" + exceptions.size() + ")"));
for(Iterator it = exceptions.iterator(); it.hasNext(); ) {
WidthIncompatibilityData ex = (WidthIncompatibilityData) it.next();
// See whether any of the points are on the canvas.
boolean isWithin = false;
for(int i = 0; i < ex.size(); i++) {
Location p = ex.getPoint(i);
int x = p.getX();
int y = p.getY();
if(x >= viewable.x && x < viewable.x + viewable.width
&& y >= viewable.y && y < viewable.y + viewable.height) {
isWithin = true;
break;
}
}
// If none are, insert an arrow.
if(!isWithin) {
Location p = ex.getPoint(0);
int x = p.getX();
int y = p.getY();
boolean isWest = x < viewable.x;
boolean isEast = x >= viewable.x + viewable.width;
boolean isNorth = y < viewable.y;
boolean isSouth = y >= viewable.y + viewable.height;
if(isNorth) {
if(isEast) viewport.setNortheast(true);
else if(isWest) viewport.setNorthwest(true);
else viewport.setNorth(true);
} else if(isSouth) {
if(isEast) viewport.setSoutheast(true);
else if(isWest) viewport.setSouthwest(true);
else viewport.setSouth(true);
} else {
if(isEast) viewport.setEast(true);
else if(isWest) viewport.setWest(true);
}
}
}
}
public void repaint(Rectangle r) {
if(zoomFactor == 1.0) {
super.repaint(r);
} else {
this.repaint(r.x, r.y, r.width, r.height);
}
}
public void repaint(int x, int y, int width, int height) {
if(zoomFactor != 1.0) {
x = (int) Math.round(x * zoomFactor);
y = (int) Math.round(y * zoomFactor);
width = (int) Math.round(width * zoomFactor);
height = (int) Math.round(height * zoomFactor);
}
super.repaint(x, y, width, height);
}
public String getToolTipText(MouseEvent event) {
if(showTips) {
Canvas.snapToGrid(event);
Location loc = Location.create(event.getX(), event.getY());
ComponentUserEvent e = null;
for(Iterator it = getCircuit().getAllContaining(loc).iterator(); it.hasNext(); ) {
Component comp = (Component) it.next();
Object makerObj = comp.getFeature(ToolTipMaker.class);
if(makerObj != null && makerObj instanceof ToolTipMaker) {
ToolTipMaker maker = (ToolTipMaker) makerObj;
if(e == null) {
e = new ComponentUserEvent(this, loc.getX(), loc.getY());
}
String ret = maker.getToolTip(e);
if(ret != null) return ret;
}
}
}
return null;
}
//
// Scrollable methods
//
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
int unit = getScrollableUnitIncrement(visibleRect, orientation,
direction);
if(direction == SwingConstants.VERTICAL) {
return visibleRect.height / unit * unit;
} else {
return visibleRect.width / unit * unit;
}
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
public boolean getScrollableTracksViewportWidth() {
return false;
}
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
return (int) Math.round(10 * zoomFactor);
}
//
// static methods
//
public static int snapXToGrid(int x) {
if(x < 0) {
return -((-x + 5) / 10) * 10;
} else {
return ((x + 5) / 10) * 10;
}
}
public static int snapYToGrid(int y) {
if(y < 0) {
return -((-y + 5) / 10) * 10;
} else {
return ((y + 5) / 10) * 10;
}
}
public static void snapToGrid(MouseEvent e) {
int old_x = e.getX();
int old_y = e.getY();
int new_x = snapXToGrid(old_x);
int new_y = snapYToGrid(old_y);
e.translatePoint(new_x - old_x, new_y - old_y);
}
public void localeChanged() {
repaint();
}
}
|