Gray-Kode-Zähler

Wir setzen diese Einführung in die Bibliotheken von Logisim jetzt mit einem recht anspruchsvollen Gray-Kode-Zähler fort. Bei diesem läßt sich der Wert mit dem Schaltwerkzeug ändern, und es läßt sich eine Beschriftung auf dem Bauelement anbringen. Das Beispiel zeigt auch, wie das Symbol für das Werkzeug im Übersichtsfenster bearbeitet werden kann.

GrayCounter

package com.cburch.gray;

import java.net.URL;

import javax.swing.ImageIcon;

import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.StringUtil;

/** Erzeugt einen Zähler, der Gray-Kodes durchläuft. Hier werden
 * mehrere weitere Funktionen gezeigt, die über die Klasse SimpleGrayCounter hinausgehen. */
class GrayCounter extends InstanceFactory {
    public GrayCounter() {
        super("Gray Counter");
        setOffsetBounds(Bounds.create(-30, -15, 30, 30));
        setPorts(new Port[] {
                new Port(-30, 0, Port.INPUT, 1),
                new Port(  0, 0, Port.OUTPUT, StdAttr.WIDTH),
        });
        
        // Der Zähler hat Attribute für Bitbreite, Beschriftung und Zeichensatz der Beschriftung. Die letzteren beiden
        // Attribute erlauben es uns, dem Bauelement eine Beschriftung zu geben (auch wenn
        // wir außerdem noch configureNewInstance benötigen, um die Position der Beschriftung
        // anzugeben).
        setAttributes(
                new Attribute[] { StdAttr.WIDTH, StdAttr.LABEL, StdAttr.LABEL_FONT },
                new Object[] { BitWidth.create(4), "", StdAttr.DEFAULT_LABEL_FONT });
        
        // Der folgende Methodenaufruf schafft die Voraussetzungen dafür, daß der Zustand
        // der Instanz mit demSchaltwerkzeug gändert werden kann.
        setInstancePoker(CounterPoker.class);
        
        // Die zwei nächsten Zeilen sorgen dafür, daß
        // das Bauelement mit einem individuellen Symbol dargestellt wird. Hierbei sollte es sich um ein 
        // 16x16 Punkte großes Bild handeln.
        URL url = getClass().getClassLoader().getResource("com/cburch/gray/counter.gif");
        if(url != null) setIcon(new ImageIcon(url));
    }
    
    /** Die Methode configureNewInstance wird jedes Mal aufgerufen, wenn eine neue Instanz 
     * erstellt wird. In der Überklasse macht diese Methode nichts, weil die neue
     * Instanz schon per Voreinstellung gründlich konfiguriert wird. Aber
     * manchmal müssen spezielle Operationen für jede Instanz durchgeführt werden, und
     * dann würden Sie diese Methode überschreiben. In diesem Beispiel soll die Position der Beschriftung
     * festgelegt werden. */
    protected void configureNewInstance(Instance instance) {
        Bounds bds = instance.getBounds();
        instance.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT,
                bds.getX() + bds.getWidth() / 2, bds.getY() - 3,
                GraphicsUtil.H_CENTER, GraphicsUtil.V_BASELINE);
    }

    public void propagate(InstanceState state) {
        // Dieser Abschnitt ist genauso wie beim SimpleGrayCounter, abgesehen davon, daß wir das 
        // StdAttr.WIDTH Attribut benutzen, um die zu verwendende Bitbreite zu bestimmen.
        BitWidth width = state.getAttributeValue(StdAttr.WIDTH);
        CounterData cur = CounterData.get(state, width);
        boolean trigger = cur.updateClock(state.getPort(0));
        if(trigger) cur.setValue(GrayIncrementer.nextGray(cur.getValue()));
        state.setPort(1, cur.getValue(), 9);
    }

    public void paintInstance(InstancePainter painter) {
        // Im Prinzip genauso, wie bei dem SimpleGrayCounter, abgesehen vom
        // Aufruf von painter.drawLabel um die Beschriftung zu zeichnen.
        painter.drawBounds();
        painter.drawClock(0, Direction.EAST);
        painter.drawPort(1);
        painter.drawLabel();
        
        if(painter.getShowState()) {
            BitWidth width = painter.getAttributeValue(StdAttr.WIDTH);
            CounterData state = CounterData.get(painter, width);
            Bounds bds = painter.getBounds();
            GraphicsUtil.drawCenteredText(painter.getGraphics(),
                    StringUtil.toHexString(width.getWidth(), state.getValue().toIntValue()),
                    bds.getX() + bds.getWidth() / 2,
                    bds.getY() + bds.getHeight() / 2);
        }
    }
}

CounterPoker

package com.cburch.gray;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstancePoker;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.StdAttr;

/** Wenn der Anwender den Zähler mit dem Schaltwerkzeug anklickt, wird ein CounterPoker-Objekt
 * erzeugt, daß die Benutzerereignisse behandelt. Beachten Sie, daß
 * CounterPoker eine spezifische Klasse von GrayCounter ist, und daß es eine
 * Unterklasse von InstancePoker aus dem  com.cburch.logisim.instance-Paket sein muß. */
public class CounterPoker extends InstancePoker {
    public CounterPoker() { }

    /** Legt fest, ob die Position des Mausklicks eine Bearbeitung 
     * zur Folge haben soll. 
     */
    public boolean init(InstanceState state, MouseEvent e) {
        return state.getInstance().getBounds().contains(e.getX(), e.getY());
            // Ein Mausklick irgendwo im Hauptrechteck löst eine Bearbeitung aus. Der Anwender hat vielleicht auf die Beschriftung geklickt,
            // dies läge aber außerhalb der Begrenzung.
    }

    / ** Zeichnet einen Indikator dafür, daß der Eingabebereich ausgewählt wurde. Jetzt zeichnen wir
     * ein rotes Rechteck um den Wert. */
    public void paint(InstancePainter painter) {
        Bounds bds = painter.getBounds();
        BitWidth width = painter.getAttributeValue(StdAttr.WIDTH);
        int len = (width.getWidth() + 3) / 4;

        Graphics g = painter.getGraphics();
        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);
    }

    /** Bearbeitet einen Tastendruck, indem es diesen einfach an das Ende des aktuellen Wertes stellt. */
    public void keyTyped(InstanceState state, KeyEvent e) {
        // wandelt um in eine hexadezimale Ziffer, wenn es keine gültige Ziffer ist, wird abgebrochen.
        int val = Character.digit(e.getKeyChar(), 16);
        BitWidth width = state.getAttributeValue(StdAttr.WIDTH);
        if(val < 0 || (val & width.getMask()) != val) return;

        // berechne den nächsten Wert
        CounterData cur = CounterData.get(state, width);
        int newVal = (cur.getValue().toIntValue() * 16 + val) & width.getMask();
        Value newValue = Value.createKnown(width, newVal);
        cur.setValue(newValue);
        state.fireInvalidated();
        
        // Sie könnten versucht sein, den Wert jetzt direkt über
        // state.setPort auszugeben. Aber die Schaltung könnte gerade an einer Stelle beschäftigt sein
        // und der direkte Zugriff auf setPort könnte hier
        // störend eingreifen. Die Benutzung von fireInvalidated benachrichtigt die Aktualisierungsroutine
        // den Wert des Zählers bei nächster Gelegenheit weiterzuleiten.
    }
}

Weiter: Richtlinien.