/* 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.appear;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPopupMenu;
import com.cburch.draw.actions.ModelAddAction;
import com.cburch.draw.actions.ModelReorderAction;
import com.cburch.draw.canvas.ActionDispatcher;
import com.cburch.draw.canvas.Canvas;
import com.cburch.draw.canvas.CanvasTool;
import com.cburch.draw.model.CanvasModel;
import com.cburch.draw.model.CanvasModelEvent;
import com.cburch.draw.model.CanvasModelListener;
import com.cburch.draw.model.CanvasObject;
import com.cburch.draw.model.ReorderRequest;
import com.cburch.draw.undo.Action;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.circuit.appear.AppearanceElement;
import com.cburch.logisim.data.Bounds;
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.proj.Project;
@SuppressWarnings("serial")
public class AppearanceCanvas extends Canvas
implements CanvasPaneContents, ActionDispatcher {
private static final int BOUNDS_BUFFER = 70;
// pixels shown in canvas beyond outermost boundaries
private static final int THRESH_SIZE_UPDATE = 10;
// don't bother to update the size if it hasn't changed more than this
private class Listener
implements CanvasModelListener, PropertyChangeListener {
@Override
public void modelChanged(CanvasModelEvent event) {
computeSize(false);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String prop = evt.getPropertyName();
if (prop.equals(GridPainter.ZOOM_PROPERTY)) {
CanvasTool t = getTool();
if (t != null) {
t.zoomFactorChanged(AppearanceCanvas.this);
}
}
}
}
private CanvasTool selectTool;
private Project proj;
private CircuitState circuitState;
private Listener listener;
private GridPainter grid;
private CanvasPane canvasPane;
private Bounds oldPreferredSize;
private LayoutPopupManager popupManager;
public AppearanceCanvas(CanvasTool selectTool) {
this.selectTool = selectTool;
this.grid = new GridPainter(this);
this.listener = new Listener();
this.oldPreferredSize = null;
setSelection(new AppearanceSelection());
setTool(selectTool);
CanvasModel model = super.getModel();
if (model != null) {
model.addCanvasModelListener(listener);
}
grid.addPropertyChangeListener(GridPainter.ZOOM_PROPERTY, listener);
}
@Override
public void setTool(CanvasTool value) {
hidePopup();
super.setTool(value);
}
@Override
public void toolGestureComplete(CanvasTool tool, CanvasObject created) {
if (tool == getTool() && tool != selectTool) {
setTool(selectTool);
if (created != null) {
getSelection().clearSelected();
getSelection().setSelected(created, true);
}
}
}
@Override
public void setModel(CanvasModel value, ActionDispatcher dispatcher) {
CanvasModel oldModel = super.getModel();
if (oldModel != null) {
oldModel.removeCanvasModelListener(listener);
}
super.setModel(value, dispatcher);
if (value != null) {
value.addCanvasModelListener(listener);
}
}
public void setCircuit(Project proj, CircuitState circuitState) {
this.proj = proj;
this.circuitState = circuitState;
Circuit circuit = circuitState.getCircuit();
setModel(circuit.getAppearance(), this);
}
Project getProject() {
return proj;
}
Circuit getCircuit() {
return circuitState.getCircuit();
}
CircuitState getCircuitState() {
return circuitState;
}
GridPainter getGridPainter() {
return grid;
}
@Override
public void doAction(Action canvasAction) {
Circuit circuit = circuitState.getCircuit();
if (!proj.getLogisimFile().contains(circuit)) {
return;
}
if (canvasAction instanceof ModelReorderAction) {
int max = getMaxIndex(getModel());
ModelReorderAction reorder = (ModelReorderAction) canvasAction;
List<ReorderRequest> rs = reorder.getReorderRequests();
List<ReorderRequest> mod = new ArrayList<ReorderRequest>(rs.size());
boolean changed = false;
boolean movedToMax = false;
for (ReorderRequest r : rs) {
CanvasObject o = r.getObject();
if (o instanceof AppearanceElement) {
changed = true;
} else {
if (r.getToIndex() > max) {
int from = r.getFromIndex();
changed = true;
movedToMax = true;
if (from == max && !movedToMax) {
// this change is ineffective - don't add it
;
} else {
mod.add(new ReorderRequest(o, from, max));
}
} else {
if (r.getToIndex() == max) {
movedToMax = true;
}
mod.add(r);
}
}
}
if (changed) {
if (mod.isEmpty()) {
return;
}
canvasAction = new ModelReorderAction(getModel(), mod);
}
}
if (canvasAction instanceof ModelAddAction) {
ModelAddAction addAction = (ModelAddAction) canvasAction;
int cur = addAction.getDestinationIndex();
int max = getMaxIndex(getModel());
if (cur > max) {
canvasAction = new ModelAddAction(getModel(),
addAction.getObjects(), max + 1);
}
}
proj.doAction(new CanvasActionAdapter(circuit, canvasAction));
}
@Override
public double getZoomFactor() {
return grid.getZoomFactor();
}
@Override
public int snapX(int x) {
if (x < 0) {
return -((-x + 5) / 10 * 10);
} else {
return (x + 5) / 10 * 10;
}
}
@Override
public int snapY(int y) {
if (y < 0) {
return -((-y + 5) / 10 * 10);
} else {
return (y + 5) / 10 * 10;
}
}
@Override
protected void paintBackground(Graphics g) {
super.paintBackground(g);
grid.paintGrid(g);
}
@Override
protected void paintForeground(Graphics g) {
double zoom = grid.getZoomFactor();
Graphics gScaled = g.create();
if (zoom != 1.0 && zoom != 0.0 && gScaled instanceof Graphics2D) {
((Graphics2D) gScaled).scale(zoom, zoom);
}
super.paintForeground(gScaled);
gScaled.dispose();
}
@Override
public void repaintCanvasCoords(int x, int y, int width, int height) {
double zoom = grid.getZoomFactor();
if (zoom != 1.0) {
x = (int) (x * zoom - 1);
y = (int) (y * zoom - 1);
width = (int) (width * zoom + 4);
height = (int) (height * zoom + 4);
}
super.repaintCanvasCoords(x, y, width, height);
}
@Override
protected void processMouseEvent(MouseEvent e) {
repairEvent(e, grid.getZoomFactor());
super.processMouseEvent(e);
}
@Override
public JPopupMenu showPopupMenu(MouseEvent e, CanvasObject clicked) {
double zoom = grid.getZoomFactor();
int x = (int) Math.round(e.getX() * zoom);
int y = (int) Math.round(e.getY() * zoom);
if (clicked != null && getSelection().isSelected(clicked)) {
AppearanceEditPopup popup = new AppearanceEditPopup(this);
popup.show(this, x, y);
return popup;
}
return null;
}
@Override
protected void processMouseMotionEvent(MouseEvent e) {
repairEvent(e, grid.getZoomFactor());
super.processMouseMotionEvent(e);
}
private void hidePopup() {
LayoutPopupManager man = popupManager;
if (man != null) {
man.hideCurrentPopup();
}
}
private void repairEvent(MouseEvent e, double zoom) {
if (zoom != 1.0) {
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);
}
}
private void computeSize(boolean immediate) {
hidePopup();
Bounds bounds;
CircuitState circState = circuitState;
if (circState == null) {
bounds = Bounds.create(0, 0, 50, 50);
} else {
bounds = circState.getCircuit().getAppearance().getAbsoluteBounds();
}
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();
}
//
// CanvasPaneContents methods
//
@Override
public void setCanvasPane(CanvasPane value) {
canvasPane = value;
computeSize(true);
popupManager = new LayoutPopupManager(value, this);
}
@Override
public void recomputeSize() {
computeSize(true);
repaint();
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@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);
}
static int getMaxIndex(CanvasModel model) {
List<CanvasObject> objects = model.getObjectsFromBottom();
for (int i = objects.size() - 1; i >= 0; i--) {
if (!(objects.get(i) instanceof AppearanceElement)) {
return i;
}
}
return -1;
}
}