This orientation to the Logisim libraries concludes with a
fairly sophisticated counter that allows the user to alter its
current value using the Poke Tool. This involves providing
an implementation of Component's getFeature
method so that it can return a Pokable implementation;
the Pokable implementation provides access to a
Caret implementation which can handle mouse and key
events appropriately.
This example also illustrates what I consider a better way of structuring a library of components: Having a single class for each component, with the factory class nested privately, along with any supporting classes.
package com.cburch.incr; import java.awt.Color; import java.awt.Graphics; import java.awt.event.KeyEvent; import com.cburch.logisim.circuit.CircuitState; import com.cburch.logisim.comp.AbstractComponentFactory; import com.cburch.logisim.comp.Component; import com.cburch.logisim.comp.ComponentFactory; import com.cburch.logisim.comp.ComponentUserEvent; import com.cburch.logisim.comp.EndData; 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.AttributeSets; import com.cburch.logisim.data.Attributes; 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.tools.AbstractCaret; import com.cburch.logisim.tools.Caret; import com.cburch.logisim.tools.Pokable; /** Implements a counter for an arbitrary number of bits, whose value can be * modified interactively by the user. The primary purpose of this example is * to illustrate the addition of user interaction; the entry point for this * interaction is via thegetFeaturemethod. */ class Counter extends SimpleCounter { // Note that I've extended SimpleCounter to inherit all of its logic // for propagation and drawing. // The previous examples have included two separate classes for each // component. In practice, though, I personally prefer having just // one file per component type. The most convenient technique for this // is to make a private nested class for the factory, and to include // a constant referring to the factory. public static final ComponentFactory factory = new Factory(); // I'll restrict the maximum width to 12, since the rectangle drawn doesn't // have room to display more than 12 bits. static final Attribute WIDTH_ATTRIBUTE = Attributes.forBitWidth("Bit Width", 1, 12); static final BitWidth WIDTH_DEFAULT = BitWidth.create(8); private static class Factory extends AbstractComponentFactory { private Factory() { } public String getName() { return "Counter"; } public String getDisplayName() { return "Counter"; } public Component createComponent(Location loc, AttributeSet attrs) { return new Counter(loc, attrs); } public Bounds getOffsetBounds(AttributeSet arg0) { return Bounds.create(-30, -15, 30, 30); } public AttributeSet createAttributeSet() { return AttributeSets.fixedSet(Counter.WIDTH_ATTRIBUTE, Counter.WIDTH_DEFAULT); } } /** In addition to listening for changes to the width attribute (as with * theIncrementerexample), this also serves for manufacturing * the "caret" for interacting with the user. */ private class MyListener implements AttributeListener, Pokable { public void attributeListChanged(AttributeEvent e) { } public void attributeValueChanged(AttributeEvent e) { if(e.getAttribute() == WIDTH_ATTRIBUTE) computeEnds(); } /** Manufactures the caret for interacting with the user. */ public Caret getPokeCaret(ComponentUserEvent event) { return new PokeCaret(event.getCircuitState()); } } /** Implements all the functionality that interacts with the user when * poking this component. */ private class PokeCaret extends AbstractCaret { /** The circuit state the user is poking with. */ CircuitState circuitState; /** The initial value. We use this in case the user cancels the editing * to return to the initial value. (Canceling an edit is not currently * supported in Logisim, but it may be in a future version.) */ Value initValue; PokeCaret(CircuitState circuitState) { this.circuitState = circuitState; CounterState initial = Counter.this.getCounterState(circuitState); initValue = initial.getValue(); setBounds(Counter.this.getBounds()); } /** Draws an indicator that the caret is being selected. Here, we'll draw * a red rectangle around the value. */ public void draw(Graphics g) { Bounds bds = Counter.this.getBounds(); BitWidth width = (BitWidth) Counter.this.getAttributeSet().getValue(WIDTH_ATTRIBUTE); int len = (width.getWidth() + 3) / 4; g.setColor(Color.RED); int wid = 7 * len + 2; // width of caret rectangle int ht = 16; // height of caret rectangle g.drawRect(bds.getX() + (bds.getWidth() - wid) / 2, bds.getY() + (bds.getHeight() - ht) / 2, wid, ht); g.setColor(Color.BLACK); } /** Processes a key by just adding it onto the end of the current value. */ public void keyTyped(KeyEvent e) { // convert it to a hex digit; if it isn't a hex digit, abort. int val = Character.digit(e.getKeyChar(), 16); if(val < 0) return; // compute the next value. BitWidth width = (BitWidth) Counter.this.getAttributeSet().getValue(WIDTH_ATTRIBUTE); CounterState state = Counter.this.getCounterState(circuitState); Value newValue = Value.createKnown(width, (state.getValue().toIntValue() * 16 + val) & width.getMask()); // change the value immediately in the component's state, and propagate // it immediately. state.setValue(newValue); circuitState.setValue(Counter.this.getEndLocation(1), newValue, Counter.this, 1); } /** Commit the editing. Since this caret modifies the state as the user edits, * there is nothing to do here. */ public void stopEditing() { } /** Cancel the editing. */ public void cancelEditing() { Counter.this.getCounterState(circuitState).setValue(initValue); } } // The listener instance variable, the constructor, and computeEnds all // proceeds just as in the Incrementer class. private MyListener myListener = new MyListener(); private Counter(Location loc, AttributeSet attrs) { super(loc, attrs); attrs.addAttributeListener(myListener); computeEnds(); } private void computeEnds() { Location loc = getLocation(); BitWidth width = (BitWidth) getAttributeSet().getValue(WIDTH_ATTRIBUTE); setEnd(0, loc.translate(-30, 0), BitWidth.ONE, EndData.INPUT_ONLY); setEnd(1, loc, width, EndData.OUTPUT_ONLY); } /** Retrieves a "special feature" associated with this component. Poke support * is considered a special feature. When a user clicks on the component with * the poke tool, Logisim will call this method with the key being the Pokable * class. We should return a Pokable object in response, which it can use to * access the caret for interaction. */ public Object getFeature(Object key) { if(key == Pokable.class) return myListener; return super.getFeature(key); } public ComponentFactory getFactory() { return factory; } }
Next: Guidelines.