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 thegetFeature
method. */ 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 * theIncrementer
example), 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.