# Счётчик кода Грея

Этот курс по библиотекам Logisim завершается довольно сложным счётчиком кода Грея, который позволяет пользователю менять его текущее значение, используя Инструмент Нажатие и размещать на компоненте метку, используя Инструмент Текст. Он также настраивает значок в проводнике, связанный с этим инструментом.

## 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;

    /** Создаёт счётчик, который пробегает по очереди коды Грея. Он демонстрирует
     * некоторые дополнительные возможности по сравнению с классом SimpleGrayCounter. */
    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),
            });

            // У нас будут атрибуты ширина, метка и шрифт метки. Последние два
            // атрибута позволяют нам связать метку с компонентом (однако,
            // нам также нужен configureNewInstance для настройки расположения
            // метки).
            setAttributes(
                    new Attribute[] { StdAttr.WIDTH, StdAttr.LABEL, StdAttr.LABEL_FONT },
                    new Object[] { BitWidth.create(4), "", StdAttr.DEFAULT_LABEL_FONT });

            // Следующее обращение к методу устанавливает всё так, чтобы состоянием
            // экземпляра можно было управлять с помощью Инструмента Нажатие.
            setInstancePoker(CounterPoker.class);

            // Следующие две строчки настраивают его так, чтобы окно проводника показывало
            // определённый значок, представляющий тип компонента. Это должно быть
            // изображение 16x16.
            URL url = getClass().getClassLoader().getResource("com/cburch/gray/counter.gif");
            if(url != null) setIcon(new ImageIcon(url));
        }

        /** Метод configureNewInstance вызывается каждый раз, когда создаётся
         * новый экземпляр. В суперклассе метод ничего не делает, поскольку
         * новый экземпляр уже довольно хорошо настроен по умолчанию. Но
         * иногда вам нужно делать что-то специфическое с каждым экземпляром, так что вам
         * придётся переопределить метод. В этом случае мы должны задать расположение
         * для его метки. */
        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) {
            // Это так же, как и с SimpleGrayCounter, кроме того, что мы используем
            // атрибут StdAttr.WIDTH чтобы задать разрядность.
            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) {
            // Это по существу так же, как и с SimpleGrayCounter, за исключением
            // вызова painter.drawLabel чтобы сделать метку отрисованной.
            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;

    /** Когда пользователь щёлкает на счётчике, используя Инструмент Нажатие, объект CounterPoker
     * создан, и этот объект будет обрабатывать все события пользователя. Обратите внимание, что
     * CounterPoker - это класс, специфичный для GrayCounter, и что он должен быть
     * подклассом InstancePoker из пакета com.cburch.logisim.instance. */
    public class CounterPoker extends InstancePoker {
        public CounterPoker() {}

        /** Определяет, должно ли положение, в котором нажата мышь,
         * инициировать нажатие.
         */
        public boolean init(InstanceState state, MouseEvent e) {
            return state.getInstance().getBounds().contains(e.getX(), e.getY());
                // В любом месте главного прямоугольника инициирует нажатие. Пользователь может
                // нажать на метке, но это будет за пределами границ.
        }

        /** Рисует индикатор того, что указатель был выбран. Здесь мы будем рисовать
         * красный прямоугольник вокруг значения. */
        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);
        }

        /**Обрабатывает клавишу, просто добавив её в конец текущего значения. */
        public void keyTyped(InstanceState state, KeyEvent e) {
            // преобразовать её в шестнадцатеричное число; если она - не шестнадцатеричное число, то отменить.
            int val = Character.digit(e.getKeyChar(), 16);
            BitWidth width = state.getAttributeValue(StdAttr.WIDTH);
            if(val < 0 || (val & width.getMask()) != val) return;

            // вычислить следующее значение
            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();

            // Вы, возможно, подумываете о том, чтобы просчитать значение здесь сразу, используя
            // state.setPort. Однако, схема может просчитываться в данный момент в
            // другом потоке, и непосредственный вызов setPort может помешать
            // этому. Использование fireInvalidated уведомляет поток просчёта о том,
            // чтобы вызвать просчёт для счётчика при первой же возможности.
        }
    }

Далее: Указания.