/*******************************************************************************
* 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.tools;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.JOptionPane;
import com.cburch.logisim.LogisimVersion;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.CircuitException;
import com.cburch.logisim.circuit.CircuitMutation;
import com.cburch.logisim.circuit.SubcircuitFactory;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.comp.ComponentFactory;
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.Direction;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.gui.main.Canvas;
import com.cburch.logisim.gui.main.SelectionActions;
import com.cburch.logisim.gui.main.ToolAttributeAction;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.proj.Action;
import com.cburch.logisim.proj.Dependencies;
import com.cburch.logisim.proj.Project;
import com.cburch.logisim.std.gates.GateKeyboardModifier;
import com.cburch.logisim.tools.key.KeyConfigurationEvent;
import com.cburch.logisim.tools.key.KeyConfigurationResult;
import com.cburch.logisim.tools.key.KeyConfigurator;
import com.cburch.logisim.util.AutoLabel;
import com.cburch.logisim.util.StringUtil;
public class AddTool extends Tool {
private class MyAttributeListener implements AttributeListener {
public void attributeListChanged(AttributeEvent e) {
bounds = null;
}
public void attributeValueChanged(AttributeEvent e) {
bounds = null;
}
}
private static int INVALID_COORD = Integer.MIN_VALUE;
private static int SHOW_NONE = 0;
private static int SHOW_GHOST = 1;
private static int SHOW_ADD = 2;
private static int SHOW_ADD_NO = 3;
private static Cursor cursor = Cursor
.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
private Class<? extends Library> descriptionBase;
private FactoryDescription description;
private boolean sourceLoadAttempted;
private ComponentFactory factory;
private AttributeSet attrs;
private Bounds bounds;
private boolean shouldSnap;
private int lastX = INVALID_COORD;
private int lastY = INVALID_COORD;
private int state = SHOW_GHOST;
private Action lastAddition;
private boolean keyHandlerTried;
private KeyConfigurator keyHandler;
private AutoLabel AutoLabler = new AutoLabel();
private AddTool(AddTool base) {
this.descriptionBase = base.descriptionBase;
this.description = base.description;
this.sourceLoadAttempted = base.sourceLoadAttempted;
this.factory = base.factory;
this.bounds = base.bounds;
this.shouldSnap = base.shouldSnap;
this.attrs = (AttributeSet) base.attrs.clone();
attrs.addAttributeListener(new MyAttributeListener());
}
public AddTool(Class<? extends Library> base, FactoryDescription description) {
this.descriptionBase = base;
this.description = description;
this.sourceLoadAttempted = false;
this.shouldSnap = true;
this.attrs = new FactoryAttributes(base, description);
attrs.addAttributeListener(new MyAttributeListener());
this.keyHandlerTried = false;
}
public AddTool(ComponentFactory source) {
this.description = null;
this.sourceLoadAttempted = true;
this.factory = source;
this.bounds = null;
this.attrs = new FactoryAttributes(source);
attrs.addAttributeListener(new MyAttributeListener());
Boolean value = (Boolean) source.getFeature(
ComponentFactory.SHOULD_SNAP, attrs);
this.shouldSnap = value == null ? true : value.booleanValue();
}
public void cancelOp() {
}
@Override
public Tool cloneTool() {
return new AddTool(this);
}
@Override
public void deselect(Canvas canvas) {
setState(canvas, SHOW_GHOST);
moveTo(canvas, canvas.getGraphics(), INVALID_COORD, INVALID_COORD);
bounds = null;
lastAddition = null;
}
private Tool determineNext(Project proj) {
String afterAdd = AppPreferences.ADD_AFTER.get();
if (afterAdd.equals(AppPreferences.ADD_AFTER_UNCHANGED)) {
return null;
} else { // switch to Edit Tool
Library base = proj.getLogisimFile().getLibrary("Base");
if (base == null) {
return null;
} else {
return base.getTool("Edit Tool");
}
}
}
@Override
public void draw(Canvas canvas, ComponentDrawContext context) {
// next "if" suggested roughly by Kevin Walsh of Cornell to take care of
// repaint problems on OpenJDK under Ubuntu
int x = lastX;
int y = lastY;
if (x == INVALID_COORD || y == INVALID_COORD)
return;
ComponentFactory source = getFactory();
if (source == null)
return;
/* take care of coloring the components differently that require a label */
if (state == SHOW_GHOST) {
source.drawGhost(context, Color.GRAY, x, y, getBaseAttributes());
} else if (state == SHOW_ADD) {
source.drawGhost(context, Color.BLACK, x, y, getBaseAttributes());
}
}
@Override
public boolean equals(Object other) {
if (!(other instanceof AddTool))
return false;
AddTool o = (AddTool) other;
if (this.description != null) {
return this.descriptionBase == o.descriptionBase
&& this.description.equals(o.description);
} else {
return this.factory.equals(o.factory);
}
}
private void expose(java.awt.Component c, int x, int y) {
Bounds bds = getBounds();
c.repaint(x + bds.getX(), y + bds.getY(), bds.getWidth(),
bds.getHeight());
}
@Override
public AttributeSet getAttributeSet() {
return attrs;
}
private AttributeSet getBaseAttributes() {
AttributeSet ret = attrs;
if (ret instanceof FactoryAttributes) {
ret = ((FactoryAttributes) ret).getBase();
}
return ret;
}
private Bounds getBounds() {
Bounds ret = bounds;
if (ret == null) {
ComponentFactory source = getFactory();
if (source == null) {
ret = Bounds.EMPTY_BOUNDS;
} else {
AttributeSet base = getBaseAttributes();
ret = source.getOffsetBounds(base).expand(5);
}
bounds = ret;
}
return ret;
}
@Override
public Cursor getCursor() {
return cursor;
}
@Override
public Object getDefaultAttributeValue(Attribute<?> attr, LogisimVersion ver) {
return getFactory().getDefaultAttributeValue(attr, ver);
}
@Override
public String getDescription() {
String ret;
FactoryDescription desc = description;
if (desc != null) {
ret = desc.getToolTip();
} else {
ComponentFactory source = getFactory();
if (source != null) {
ret = (String) source.getFeature(ComponentFactory.TOOL_TIP,
getAttributeSet());
} else {
ret = null;
}
}
if (ret == null) {
ret = StringUtil.format(Strings.get("addToolText"),
getDisplayName());
}
return ret;
}
@Override
public String getDisplayName() {
FactoryDescription desc = description;
return desc == null ? factory.getDisplayName() : desc.getDisplayName();
}
public ComponentFactory getFactory() {
ComponentFactory ret = factory;
if (ret != null || sourceLoadAttempted) {
return ret;
} else {
ret = description.getFactory(descriptionBase);
if (ret != null) {
AttributeSet base = getBaseAttributes();
Boolean value = (Boolean) ret.getFeature(
ComponentFactory.SHOULD_SNAP, base);
shouldSnap = value == null ? true : value.booleanValue();
}
factory = ret;
sourceLoadAttempted = true;
return ret;
}
}
public ComponentFactory getFactory(boolean forceLoad) {
return forceLoad ? getFactory() : factory;
}
@Override
public String getName() {
FactoryDescription desc = description;
return desc == null ? factory.getName() : desc.getName();
}
@Override
public int hashCode() {
FactoryDescription desc = description;
return desc != null ? desc.hashCode() : factory.hashCode();
}
@Override
public boolean isAllDefaultValues(AttributeSet attrs, LogisimVersion ver) {
return this.attrs == attrs && attrs instanceof FactoryAttributes
&& !((FactoryAttributes) attrs).isFactoryInstantiated();
}
@Override
public void keyPressed(Canvas canvas, KeyEvent event) {
processKeyEvent(canvas, event, KeyConfigurationEvent.KEY_PRESSED);
if (!event.isConsumed() && event.getModifiersEx() == 0) {
int KeybEvent = event.getKeyCode();
String Component = getFactory().getDisplayName();
if (!GateKeyboardModifier.TookKeyboardStrokes(KeybEvent, null,attrs, canvas,null,false))
if (AutoLabler.LabelKeyboardHandler(KeybEvent, getAttributeSet(), Component, null,getFactory(), canvas.getCircuit(),null,false)) {
canvas.repaint();
} else
switch (KeybEvent) {
case KeyEvent.VK_UP:
setFacing(canvas, Direction.NORTH);
break;
case KeyEvent.VK_DOWN:
setFacing(canvas, Direction.SOUTH);
break;
case KeyEvent.VK_LEFT:
setFacing(canvas, Direction.WEST);
break;
case KeyEvent.VK_RIGHT:
setFacing(canvas, Direction.EAST);
break;
case KeyEvent.VK_R:
Direction current = getFacing();
if (current == Direction.NORTH)
setFacing(canvas, Direction.EAST);
else
if (current == Direction.EAST)
setFacing(canvas, Direction.SOUTH);
else
if (current == Direction.SOUTH)
setFacing(canvas, Direction.WEST);
else
setFacing(canvas, Direction.NORTH);
break;
case KeyEvent.VK_ESCAPE:
Project proj = canvas.getProject();
Library base = proj.getLogisimFile().getLibrary("Base");
Tool next = (base==null) ? null : base.getTool("Edit Tool");
if (next != null) {
proj.setTool(next);
Action act = SelectionActions.dropAll(canvas.getSelection());
if (act != null) {
proj.doAction(act);
}
}
break;
case KeyEvent.VK_BACK_SPACE:
if (lastAddition != null
&& canvas.getProject().getLastAction() == lastAddition) {
canvas.getProject().undoAction();
lastAddition = null;
}
}
}
}
@Override
public void keyReleased(Canvas canvas, KeyEvent event) {
processKeyEvent(canvas, event, KeyConfigurationEvent.KEY_RELEASED);
}
@Override
public void keyTyped(Canvas canvas, KeyEvent event) {
processKeyEvent(canvas, event, KeyConfigurationEvent.KEY_TYPED);
}
@Override
public void mouseDragged(Canvas canvas, Graphics g, MouseEvent e) {
if (state != SHOW_NONE) {
if (shouldSnap)
Canvas.snapToGrid(e);
moveTo(canvas, g, e.getX(), e.getY());
}
}
@Override
public void mouseEntered(Canvas canvas, Graphics g, MouseEvent e) {
if (state == SHOW_GHOST || state == SHOW_NONE) {
setState(canvas, SHOW_GHOST);
canvas.requestFocusInWindow();
} else if (state == SHOW_ADD_NO) {
setState(canvas, SHOW_ADD);
canvas.requestFocusInWindow();
}
}
@Override
public void mouseExited(Canvas canvas, Graphics g, MouseEvent e) {
if (state == SHOW_GHOST) {
moveTo(canvas, canvas.getGraphics(), INVALID_COORD, INVALID_COORD);
setState(canvas, SHOW_NONE);
} else if (state == SHOW_ADD) {
moveTo(canvas, canvas.getGraphics(), INVALID_COORD, INVALID_COORD);
setState(canvas, SHOW_ADD_NO);
}
}
@Override
public void mouseMoved(Canvas canvas, Graphics g, MouseEvent e) {
if (state != SHOW_NONE) {
if (shouldSnap)
Canvas.snapToGrid(e);
moveTo(canvas, g, e.getX(), e.getY());
}
}
@Override
public void mousePressed(Canvas canvas, Graphics g, MouseEvent e) {
// verify the addition would be valid
Circuit circ = canvas.getCircuit();
if (!canvas.getProject().getLogisimFile().contains(circ)) {
canvas.setErrorMessage(Strings.getter("cannotModifyError"));
return;
}
if (factory instanceof SubcircuitFactory) {
SubcircuitFactory circFact = (SubcircuitFactory) factory;
Dependencies depends = canvas.getProject().getDependencies();
if (!depends.canAdd(circ, circFact.getSubcircuit())) {
canvas.setErrorMessage(Strings.getter("circularError"));
return;
}
}
if (shouldSnap)
Canvas.snapToGrid(e);
moveTo(canvas, g, e.getX(), e.getY());
setState(canvas, SHOW_ADD);
}
@Override
public void mouseReleased(Canvas canvas, Graphics g, MouseEvent e) {
Component added = null;
if (state == SHOW_ADD) {
Circuit circ = canvas.getCircuit();
if (!canvas.getProject().getLogisimFile().contains(circ))
return;
if (shouldSnap)
Canvas.snapToGrid(e);
moveTo(canvas, g, e.getX(), e.getY());
Location loc = Location.create(e.getX(), e.getY());
ComponentFactory source = getFactory();
AttributeSet attrsCopy = (AttributeSet) attrs.clone();
if (attrsCopy.containsAttribute(StdAttr.LABEL)) {
attrsCopy.setValue(StdAttr.LABEL, AutoLabler.GetCurrent(canvas.getCircuit(),source));
if (AutoLabler.IsActive(canvas.getCircuit())) {
if (AutoLabler.hasNext(canvas.getCircuit()))
AutoLabler.GetNext(canvas.getCircuit(),source);
else
AutoLabler.Stop(canvas.getCircuit());
} else AutoLabler.SetLabel("", canvas.getCircuit(),source);
}
if (source == null)
return;
Component c = source.createComponent(loc, attrsCopy);
if (circ.hasConflict(c)) {
canvas.setErrorMessage(Strings.getter("exclusiveError"));
return;
}
Bounds bds = c.getBounds(g);
if (bds.getX() < 0 || bds.getY() < 0) {
canvas.setErrorMessage(Strings.getter("negativeCoordError"));
return;
}
try {
CircuitMutation mutation = new CircuitMutation(circ);
mutation.add(c);
Action action = mutation.toAction(Strings.getter(
"addComponentAction", factory.getDisplayGetter()));
canvas.getProject().doAction(action);
lastAddition = action;
added = c;
canvas.repaint();
} catch (CircuitException ex) {
JOptionPane.showMessageDialog(canvas.getProject().getFrame(),
ex.getMessage());
}
setState(canvas, SHOW_GHOST);
} else if (state == SHOW_ADD_NO) {
setState(canvas, SHOW_NONE);
}
Project proj = canvas.getProject();
Tool next = determineNext(proj);
if (next != null) {
proj.setTool(next);
Action act = SelectionActions.dropAll(canvas.getSelection());
if (act != null) {
proj.doAction(act);
}
if (added != null)
canvas.getSelection().add(added);
}
}
private synchronized void moveTo(Canvas canvas, Graphics g, int x, int y) {
if (state != SHOW_NONE)
expose(canvas, lastX, lastY);
lastX = x;
lastY = y;
if (state != SHOW_NONE)
expose(canvas, lastX, lastY);
}
@Override
public void paintIcon(ComponentDrawContext c, int x, int y) {
FactoryDescription desc = description;
if (desc != null && !desc.isFactoryLoaded()) {
Icon icon = desc.getIcon();
if (icon != null) {
icon.paintIcon(c.getDestination(), c.getGraphics(), x + 2,
y + 2);
return;
}
}
ComponentFactory source = getFactory();
if (source != null) {
AttributeSet base = getBaseAttributes();
source.paintIcon(c, x, y, base);
}
}
private void processKeyEvent(Canvas canvas, KeyEvent event, int type) {
KeyConfigurator handler = keyHandler;
if (!keyHandlerTried) {
ComponentFactory source = getFactory();
AttributeSet baseAttrs = getBaseAttributes();
handler = (KeyConfigurator) source.getFeature(
KeyConfigurator.class, baseAttrs);
keyHandler = handler;
keyHandlerTried = true;
}
if (handler != null) {
AttributeSet baseAttrs = getBaseAttributes();
KeyConfigurationEvent e = new KeyConfigurationEvent(type,
baseAttrs, event, this);
KeyConfigurationResult r = handler.keyEventReceived(e);
if (r != null) {
Action act = ToolAttributeAction.create(r);
canvas.getProject().doAction(act);
}
}
}
@Override
public void select(Canvas canvas) {
setState(canvas, SHOW_GHOST);
bounds = null;
}
private void setFacing(Canvas canvas, Direction facing) {
ComponentFactory source = getFactory();
if (source == null)
return;
AttributeSet base = getBaseAttributes();
Object feature = source.getFeature(
ComponentFactory.FACING_ATTRIBUTE_KEY, base);
@SuppressWarnings("unchecked")
Attribute<Direction> attr = (Attribute<Direction>) feature;
if (attr != null) {
Action act = ToolAttributeAction.create(this, attr, facing);
canvas.getProject().doAction(act);
}
}
private Direction getFacing() {
ComponentFactory source = getFactory();
if (source == null)
return Direction.NORTH;
AttributeSet base = getBaseAttributes();
Object feature = source.getFeature(ComponentFactory.FACING_ATTRIBUTE_KEY, base);
@SuppressWarnings("unchecked")
Attribute<Direction> attr = (Attribute<Direction>) feature;
if (attr != null)
return base.getValue(attr);
else
return Direction.NORTH;
}
private void setState(Canvas canvas, int value) {
if (value == SHOW_GHOST) {
if (canvas.getProject().getLogisimFile()
.contains(canvas.getCircuit())
&& AppPreferences.ADD_SHOW_GHOSTS.getBoolean()) {
state = SHOW_GHOST;
} else {
state = SHOW_NONE;
}
} else {
state = value;
}
}
@Override
public boolean sharesSource(Tool other) {
if (!(other instanceof AddTool))
return false;
AddTool o = (AddTool) other;
if (this.sourceLoadAttempted && o.sourceLoadAttempted) {
return this.factory.equals(o.factory);
} else if (this.description == null) {
return o.description == null;
} else {
return this.description.equals(o.description);
}
}
}