/*******************************************************************************
* 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.main;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.Set;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JViewport;
import javax.swing.event.MouseInputListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
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.Propagator;
import com.cburch.logisim.circuit.SimulatorEvent;
import com.cburch.logisim.circuit.SimulatorListener;
import com.cburch.logisim.circuit.SubcircuitFactory;
import com.cburch.logisim.circuit.WidthIncompatibilityData;
import com.cburch.logisim.circuit.WireSet;
import com.cburch.logisim.comp.Component;
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.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.gui.generic.CanvasPane;
import com.cburch.logisim.gui.generic.CanvasPaneContents;
import com.cburch.logisim.gui.generic.GridPainter;
import com.cburch.logisim.gui.generic.ZoomModel;
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.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;
public class Canvas extends JPanel implements LocaleListener,
CanvasPaneContents {
private class MyListener implements MouseInputListener, KeyListener,
PopupMenuListener, PropertyChangeListener, MouseWheelListener {
boolean menu_on = false;
private Tool getToolFor(MouseEvent e) {
if (menu_on) {
return null;
}
Tool ret = mappings.getToolFor(e);
if (ret == null) {
return proj.getTool();
} else {
return ret;
}
}
//
// KeyListener methods
//
@Override
public void keyPressed(KeyEvent e) {
Tool tool = proj.getTool();
if (tool != null) {
tool.keyPressed(Canvas.this, e);
}
}
@Override
public void keyReleased(KeyEvent e) {
Tool tool = proj.getTool();
if (tool != null) {
tool.keyReleased(Canvas.this, e);
}
}
@Override
public void keyTyped(KeyEvent e) {
Tool tool = proj.getTool();
if (tool != null) {
tool.keyTyped(Canvas.this, e);
}
}
//
// MouseListener methods
//
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseDragged(MouseEvent e) {
if (drag_tool != null) {
drag_tool.mouseDragged(Canvas.this, getGraphics(), e);
}
}
@Override
public void mouseEntered(MouseEvent e) {
if (drag_tool != null) {
drag_tool.mouseEntered(Canvas.this, getGraphics(), e);
} else {
Tool tool = getToolFor(e);
if (tool != null) {
tool.mouseEntered(Canvas.this, getGraphics(), e);
}
}
}
@Override
public void mouseExited(MouseEvent e) {
if (drag_tool != null) {
drag_tool.mouseExited(Canvas.this, getGraphics(), e);
} else {
Tool tool = getToolFor(e);
if (tool != null) {
tool.mouseExited(Canvas.this, getGraphics(), e);
}
}
}
@Override
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) {
tool.mouseMoved(Canvas.this, getGraphics(), e);
}
}
@Override
public void mousePressed(MouseEvent e) {
viewport.setErrorMessage(null, null);
proj.setStartupScreen(false);
Canvas.this.requestFocus();
drag_tool = getToolFor(e);
if (drag_tool != null) {
drag_tool.mousePressed(Canvas.this, getGraphics(), e);
}
completeAction();
}
@Override
public void mouseReleased(MouseEvent e) {
if (drag_tool != null) {
drag_tool.mouseReleased(Canvas.this, getGraphics(), e);
drag_tool = null;
}
Tool tool = proj.getTool();
if (tool != null) {
tool.mouseMoved(Canvas.this, getGraphics(), e);
}
completeAction();
}
@Override
public void mouseWheelMoved(MouseWheelEvent mwe) {
if (mwe.isControlDown()) {
ZoomModel zoomModel = proj.getFrame().getZoomModel();
double zoom = zoomModel.getZoomFactor();
if (mwe.getWheelRotation() < 0) { // ZOOM IN
zoom += 0.1;
zoomModel.setZoomFactor(zoom >= 4.0 ? 4.0 : zoom);
} else { // ZOOM OUT
zoom -= 0.1;
zoomModel.setZoomFactor(zoom <= 0.2 ? 0.2 : zoom);
}
} else if (mwe.isShiftDown()) {
canvasPane.getHorizontalScrollBar().setValue(
scrollValue(canvasPane.getHorizontalScrollBar(),
mwe.getWheelRotation()));
} else {
canvasPane.getVerticalScrollBar().setValue(
scrollValue(canvasPane.getVerticalScrollBar(),
mwe.getWheelRotation()));
}
}
//
// PopupMenuListener mtehods
//
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
menu_on = false;
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
menu_on = false;
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if (AppPreferences.GATE_SHAPE.isSource(event)
|| AppPreferences.SHOW_TICK_RATE.isSource(event)) {
paintThread.requestRepaint();
} else if (AppPreferences.COMPONENT_TIPS.isSource(event)) {
boolean showTips = AppPreferences.COMPONENT_TIPS.getBoolean();
setToolTipText(showTips ? "" : null);
}
}
private int scrollValue(JScrollBar bar, int val) {
if (val > 0) {
if (bar.getValue() < bar.getMaximum() + val * 2
* bar.getBlockIncrement()) {
return bar.getValue() + val * 2 * bar.getBlockIncrement();
}
} else {
if (bar.getValue() > bar.getMinimum() + val * 2
* bar.getBlockIncrement()) {
return bar.getValue() + val * 2 * bar.getBlockIncrement();
}
}
return 0;
}
}
private class MyProjectListener implements ProjectListener,
LibraryListener, CircuitListener, AttributeListener,
SimulatorListener, Selection.Listener {
@Override
public void attributeListChanged(AttributeEvent e) {
}
@Override
public void attributeValueChanged(AttributeEvent e) {
Attribute<?> attr = e.getAttribute();
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
}
}
@Override
public void circuitChanged(CircuitEvent event) {
int act = event.getAction();
if (act == CircuitEvent.ACTION_REMOVE) {
Component c = (Component) event.getData();
if (c == painter.getHaloedComponent()) {
proj.getFrame().viewComponentAttributes(null, null);
}
} else if (act == CircuitEvent.ACTION_CLEAR) {
if (painter.getHaloedComponent() != null) {
proj.getFrame().viewComponentAttributes(null, null);
}
} else if (act == CircuitEvent.ACTION_INVALIDATE) {
completeAction();
}
}
private Tool findTool(List<? extends Tool> opts) {
Tool ret = null;
for (Tool o : opts) {
if (ret == null && o != null) {
ret = o;
} else if (o instanceof EditTool) {
ret = o;
}
}
return ret;
}
@Override
public void libraryChanged(LibraryEvent event) {
if (event.getAction() == LibraryEvent.REMOVE_TOOL) {
Object t = event.getData();
Circuit circ = null;
if (t instanceof AddTool) {
t = ((AddTool) t).getFactory();
if (t instanceof SubcircuitFactory) {
circ = ((SubcircuitFactory) t).getSubcircuit();
}
}
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) {
for (Library lib : proj.getLogisimFile().getLibraries()) {
next = findTool(lib.getTools());
if (next != null) {
break;
}
}
}
proj.setTool(next);
}
if (circ != null) {
CircuitState state = getCircuitState();
CircuitState last = state;
while (state != null && state.getCircuit() != circ) {
last = state;
state = state.getParentState();
}
if (state != null) {
getProject().setCircuitState(last.cloneState());
}
}
}
}
@Override
public void projectChanged(ProjectEvent event) {
int act = event.getAction();
if (act == ProjectEvent.ACTION_SET_CURRENT) {
viewport.setErrorMessage(null, null);
if (painter.getHaloedComponent() != 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, null);
Tool t = event.getTool();
if (t == null) {
setCursor(Cursor.getDefaultCursor());
} else {
setCursor(t.getCursor());
}
} else if (act == ProjectEvent.ACTION_SET_STATE) {
CircuitState oldState = (CircuitState) event.getOldData();
CircuitState newState = (CircuitState) event.getData();
if (oldState != null && newState != null) {
Propagator oldProp = oldState.getPropagator();
Propagator newProp = newState.getPropagator();
if (oldProp != newProp) {
tickCounter.clear();
}
}
}
if (act != ProjectEvent.ACTION_SELECTION
&& act != ProjectEvent.ACTION_START
&& act != ProjectEvent.UNDO_START) {
completeAction();
}
}
@Override
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(); }
*/
paintThread.requestRepaint();
}
@Override
public void selectionChanged(Selection.Event event) {
repaint();
}
@Override
public void simulatorStateChanged(SimulatorEvent e) {
}
@Override
public void tickCompleted(SimulatorEvent e) {
waitForRepaintDone();
}
}
private class MyViewport extends JViewport {
private static final long serialVersionUID = 1L;
StringGetter errorMessage = null;
String widthMessage = null;
Color errorColor = DEFAULT_ERROR_COLOR;
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() {
}
@Override
public Color getBackground() {
return getView() == null ? super.getBackground() : getView()
.getBackground();
}
@Override
public void paintChildren(Graphics g) {
super.paintChildren(g);
paintContents(g);
}
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);
*/
StringGetter message = errorMessage;
if (message != null) {
g.setColor(errorColor);
paintString(g, message.toString());
return;
}
if (proj.getSimulator().isOscillating()) {
g.setColor(DEFAULT_ERROR_COLOR);
paintString(g, Strings.get("canvasOscillationError"));
return;
}
if (proj.getSimulator().isExceptionEncountered()) {
g.setColor(DEFAULT_ERROR_COLOR);
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);
}
if (AppPreferences.SHOW_TICK_RATE.getBoolean()) {
String hz = tickCounter.getTickRate();
if (hz != null && !hz.equals("")) {
g.setColor(TICK_RATE_COLOR);
g.setFont(TICK_RATE_FONT);
FontMetrics fm = g.getFontMetrics();
int x = getWidth() - fm.stringWidth(hz) - 5;
int y = fm.getAscent() + 5;
g.drawString(hz, x, y);
}
}
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);
}
void setEast(boolean value) {
isEast = value;
}
void setErrorMessage(StringGetter msg, Color color) {
if (errorMessage != msg) {
errorMessage = msg;
errorColor = color == null ? DEFAULT_ERROR_COLOR : color;
paintThread.requestRepaint();
}
}
void setNorth(boolean value) {
isNorth = value;
}
void setNortheast(boolean value) {
isNortheast = value;
}
void setNorthwest(boolean value) {
isNorthwest = value;
}
void setSouth(boolean value) {
isSouth = value;
}
void setSoutheast(boolean value) {
isSoutheast = value;
}
void setSouthwest(boolean value) {
isSouthwest = value;
}
void setWest(boolean value) {
isWest = value;
}
void setWidthMessage(String msg) {
widthMessage = msg;
isNorth = false;
isSouth = false;
isWest = false;
isEast = false;
isNortheast = false;
isNorthwest = false;
isSoutheast = false;
isSouthwest = false;
}
}
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);
}
//
// 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;
}
}
private static final long serialVersionUID = 1L;
public static final Color HALO_COLOR = new Color(255, 0, 255);
// don't bother to update the size if it hasn't changed more than this
static final double SQRT_2 = Math.sqrt(2.0);
private static final int BOUNDS_BUFFER = 70;
// pixels shown in canvas beyond outermost boundaries
private static final int THRESH_SIZE_UPDATE = 10;
private static final int BUTTONS_MASK = InputEvent.BUTTON1_DOWN_MASK
| InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK;
private static final Color DEFAULT_ERROR_COLOR = new Color(192, 0, 0);
private static final Color TICK_RATE_COLOR = new Color(0, 0, 92, 92);
private static final Font TICK_RATE_FONT = new Font("serif", Font.BOLD, 12);
// public static BufferedImage image;
private Project proj;
private Tool drag_tool;
private Selection selection;
private MouseMappings mappings;
private CanvasPane canvasPane;
private Bounds oldPreferredSize;
private MyListener myListener = new MyListener();
private MyViewport viewport = new MyViewport();
private MyProjectListener myProjectListener = new MyProjectListener();
private TickCounter tickCounter;
private CanvasPaintThread paintThread;
private CanvasPainter painter;
private boolean paintDirty = false; // only for within paintComponent
private boolean inPaint = false; // only for within paintComponent
private Object repaintLock = new Object(); // for waitForRepaintDone
public Canvas(Project proj) {
this.proj = proj;
this.selection = new Selection(proj, this);
this.painter = new CanvasPainter(this);
this.oldPreferredSize = null;
this.paintThread = new CanvasPaintThread(this);
this.mappings = proj.getOptions().getMouseMappings();
this.canvasPane = null;
this.tickCounter = new TickCounter();
setBackground(Color.white);
addMouseListener(myListener);
addMouseMotionListener(myListener);
addKeyListener(myListener);
addMouseWheelListener(myListener);
// YSY
// try {
// URL url =
// Canvas.class.getClassLoader().getResource("resources/logisim/img/HESSO.png");
// image = ImageIO.read(url);
// } catch (IOException e) {
// e.printStackTrace();
// }
proj.addProjectListener(myProjectListener);
proj.addLibraryListener(myProjectListener);
proj.addCircuitListener(myProjectListener);
proj.getSimulator().addSimulatorListener(tickCounter);
selection.addListener(myProjectListener);
LocaleManager.addLocaleListener(this);
AttributeSet options = proj.getOptions().getAttributeSet();
options.addAttributeListener(myProjectListener);
AppPreferences.COMPONENT_TIPS.addPropertyChangeListener(myListener);
AppPreferences.GATE_SHAPE.addPropertyChangeListener(myListener);
AppPreferences.SHOW_TICK_RATE.addPropertyChangeListener(myListener);
loadOptions(options);
paintThread.start();
}
public void closeCanvas() {
paintThread.requestStop();
}
private void completeAction() {
computeSize(false);
// TODO for SimulatorPrototype: proj.getSimulator().releaseUserEvents();
proj.getSimulator().requestPropagate();
// repaint will occur after propagation completes
}
public void computeSize(boolean immediate) {
Bounds bounds = proj.getCurrentCircuit().getBounds();
int width = bounds.getX() + bounds.getWidth() + BOUNDS_BUFFER;
int height = bounds.getY() + bounds.getHeight() + BOUNDS_BUFFER;
Dimension dim;
if (canvasPane == null) {
dim = new Dimension(width, height);
} else {
dim = canvasPane.supportPreferredSize(width, height);
}
if (!immediate) {
Bounds old = oldPreferredSize;
if (old != null
&& Math.abs(old.getWidth() - dim.width) < THRESH_SIZE_UPDATE
&& Math.abs(old.getHeight() - dim.height) < THRESH_SIZE_UPDATE) {
return;
}
}
oldPreferredSize = Bounds.create(0, 0, dim.width, dim.height);
setPreferredSize(dim);
revalidate();
}
private void computeViewportContents() {
Set<WidthIncompatibilityData> exceptions = proj.getCurrentCircuit()
.getWidthIncompatibilityData();
if (exceptions == null || exceptions.isEmpty()) {
viewport.setWidthMessage(null);
return;
}
Rectangle viewableBase;
Rectangle viewable;
if (canvasPane != null) {
viewableBase = canvasPane.getViewport().getViewRect();
} else {
Bounds bds = proj.getCurrentCircuit().getBounds();
viewableBase = new Rectangle(0, 0, bds.getWidth(), bds.getHeight());
}
double zoom = getZoomFactor();
if (zoom == 1.0) {
viewable = viewableBase;
} else {
viewable = new Rectangle((int) (viewableBase.x / zoom),
(int) (viewableBase.y / zoom),
(int) (viewableBase.width / zoom),
(int) (viewableBase.height / zoom));
}
viewport.setWidthMessage(Strings.get("canvasWidthError")
+ (exceptions.size() == 1 ? "" : " (" + exceptions.size() + ")"));
for (WidthIncompatibilityData ex : exceptions) {
// 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);
}
}
}
}
}
//
// access methods
//
public Circuit getCircuit() {
return proj.getCurrentCircuit();
}
public CircuitState getCircuitState() {
return proj.getCircuitState();
}
Tool getDragTool() {
return drag_tool;
}
public StringGetter getErrorMessage() {
return viewport.errorMessage;
}
GridPainter getGridPainter() {
return painter.getGridPainter();
}
Component getHaloedComponent() {
return painter.getHaloedComponent();
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public Project getProject() {
return proj;
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation, int direction) {
return canvasPane.supportScrollableBlockIncrement(visibleRect,
orientation, direction);
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation, int direction) {
return canvasPane.supportScrollableUnitIncrement(visibleRect,
orientation, direction);
}
public Selection getSelection() {
return selection;
}
@Override
public String getToolTipText(MouseEvent event) {
boolean showTips = AppPreferences.COMPONENT_TIPS.getBoolean();
if (showTips) {
Canvas.snapToGrid(event);
Location loc = Location.create(event.getX(), event.getY());
ComponentUserEvent e = null;
for (Component comp : getCircuit().getAllContaining(loc)) {
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) {
unrepairMouseEvent(event);
return ret;
}
}
}
}
return null;
}
//
// graphics methods
//
double getZoomFactor() {
CanvasPane pane = canvasPane;
return pane == null ? 1.0 : pane.getZoomFactor();
}
boolean ifPaintDirtyReset() {
if (paintDirty) {
paintDirty = false;
return false;
} else {
return true;
}
}
boolean isPopupMenuUp() {
return myListener.menu_on;
}
private void loadOptions(AttributeSet options) {
boolean showTips = AppPreferences.COMPONENT_TIPS.getBoolean();
setToolTipText(showTips ? "" : null);
proj.getSimulator().removeSimulatorListener(myProjectListener);
proj.getSimulator().addSimulatorListener(myProjectListener);
}
@Override
public void localeChanged() {
paintThread.requestRepaint();
}
@Override
public void paintComponent(Graphics g) {
// int i, j = 0;
// for (i = 0; i <= this.getParent().getWidth() / image.getWidth(); i++)
// {
// for (j = 0; j <= this.getParent().getHeight() / image.getHeight();
// j++) {
// g.drawImage(image, i * image.getWidth(), j * image.getHeight(),
// this);
// }
// }
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
inPaint = true;
try {
super.paintComponent(g);
do {
painter.paintContents(g, proj);
} while (paintDirty);
if (canvasPane == null) {
viewport.paintContents(g);
}
} finally {
inPaint = false;
synchronized (repaintLock) {
repaintLock.notifyAll();
}
}
}
@Override
protected void processMouseEvent(MouseEvent e) {
repairMouseEvent(e);
super.processMouseEvent(e);
}
@Override
protected void processMouseMotionEvent(MouseEvent e) {
repairMouseEvent(e);
super.processMouseMotionEvent(e);
}
@Override
public void recomputeSize() {
computeSize(true);
}
@Override
public void repaint() {
if (inPaint) {
paintDirty = true;
} else {
super.repaint();
}
}
@Override
public void repaint(int x, int y, int width, int height) {
double zoom = getZoomFactor();
if (zoom < 1.0) {
int newX = (int) Math.floor(x * zoom);
int newY = (int) Math.floor(y * zoom);
width += x - newX;
height += y - newY;
x = newX;
y = newY;
} else if (zoom > 1.0) {
int x1 = (int) Math.ceil((x + width) * zoom);
int y1 = (int) Math.ceil((y + height) * zoom);
width = x1 - x;
height = y1 - y;
}
super.repaint(x, y, width, height);
}
@Override
public void repaint(Rectangle r) {
double zoom = getZoomFactor();
if (zoom == 1.0) {
super.repaint(r);
} else {
this.repaint(r.x, r.y, r.width, r.height);
}
}
private void repairMouseEvent(MouseEvent e) {
double zoom = getZoomFactor();
if (zoom != 1.0) {
zoomEvent(e, zoom);
}
}
//
// CanvasPaneContents methods
//
@Override
public void setCanvasPane(CanvasPane value) {
canvasPane = value;
canvasPane.setViewport(viewport);
viewport.setView(this);
setOpaque(false);
computeSize(true);
}
public void setErrorMessage(StringGetter message) {
viewport.setErrorMessage(message, null);
}
public void setErrorMessage(StringGetter message, Color color) {
viewport.setErrorMessage(message, color);
}
void setHaloedComponent(Circuit circ, Component comp) {
painter.setHaloedComponent(circ, comp);
}
public void setHighlightedWires(WireSet value) {
painter.setHighlightedWires(value);
}
public void showPopupMenu(JPopupMenu menu, int x, int y) {
double zoom = getZoomFactor();
if (zoom != 1.0) {
x = (int) Math.round(x * zoom);
y = (int) Math.round(y * zoom);
}
myListener.menu_on = true;
menu.addPopupMenuListener(myListener);
menu.show(this, x, y);
}
private void unrepairMouseEvent(MouseEvent e) {
double zoom = getZoomFactor();
if (zoom != 1.0) {
zoomEvent(e, 1.0 / zoom);
}
}
private void waitForRepaintDone() {
synchronized (repaintLock) {
try {
while (inPaint) {
repaintLock.wait();
}
} catch (InterruptedException e) {
}
}
}
private void zoomEvent(MouseEvent e, double zoom) {
int oldx = e.getX();
int oldy = e.getY();
int newx = (int) Math.round(e.getX() / zoom);
int newy = (int) Math.round(e.getY() / zoom);
e.translatePoint(newx - oldx, newy - oldy);
}
}