All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.wizard.mvc.loco.view.LocoView Maven / Gradle / Ivy

There is a newer version: 2.0.29
Show newest version
package org.bidib.wizard.mvc.loco.view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.basic.BasicSliderUI;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.bidib.jbidibc.messages.enums.SpeedStepsEnum;
import org.bidib.jbidibc.messages.utils.ConversionUtils;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.client.common.beans.WizardPropertyConnector;
import org.bidib.wizard.client.common.binding.SwingPropertyAdapter;
import org.bidib.wizard.client.common.converter.BidibSpeedToDccSpeedConverter;
import org.bidib.wizard.client.common.converter.StringConverter;
import org.bidib.wizard.client.common.text.InputValidationDocument;
import org.bidib.wizard.client.common.text.IntegerRangeFilter;
import org.bidib.wizard.client.common.text.WizardComponentFactory;
import org.bidib.wizard.client.common.uils.SwingUtils;
import org.bidib.wizard.client.common.view.TabPanelProvider;
import org.bidib.wizard.client.common.view.slider.JZeroSlider;
import org.bidib.wizard.common.script.loco.LocoViewScripting;
import org.bidib.wizard.common.service.SettingsService;
import org.bidib.wizard.common.utils.ImageUtils;
import org.bidib.wizard.model.loco.LocoModel;
import org.bidib.wizard.model.loco.listener.LocoModelListener;
import org.bidib.wizard.model.locolist.LocoListModel;
import org.bidib.wizard.model.status.BinStateValue;
import org.bidib.wizard.model.status.DirectionStatus;
import org.bidib.wizard.model.status.RfBasisMode;
import org.bidib.wizard.model.status.SpeedSteps;
import org.bidib.wizard.mvc.common.view.ViewCloseListener;
import org.bidib.wizard.mvc.common.view.graph.LedBarGraph;
import org.bidib.wizard.mvc.common.view.graph.LedBarGraph.Orientation;
import org.bidib.wizard.mvc.common.view.panel.DisabledPanel;
import org.bidib.wizard.mvc.loco.controller.LocoControlListener;
import org.bidib.wizard.mvc.loco.model.LocoConfigModel;
import org.bidib.wizard.mvc.loco.view.listener.LocoViewListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.value.ConverterValueModel;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.FormsSetup;
import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.builder.FormBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.factories.Paddings;
import com.jidesoft.swing.JideButton;
import com.jidesoft.swing.JideToggleButton;

import eu.hansolo.steelseries.gauges.Radial;

public class LocoView implements LocoViewScripting, TabPanelProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(LocoView.class);

    private static final String ENCODED_DIALOG_COLUMN_SPECS = "pref, 3dlu, pref:grow";

    private static final String ENCODED_DIALOG_ROW_SPECS = "pref, 3dlu, pref, 3dlu, pref, 9dlu, pref, 3dlu, pref";

    private static final int PAGE_STEPS = 12;

    private final LocoModel locoModel;

    private final LocoConfigModel locoConfigModel;

    private JComboBox speedStepsCombo;

    private JTextField address;

    private JLabel speed;

    private JSlider speedSlider;

    private JLabel reportedSpeed;

    private JButton stopButton;

    private JButton stopEmergencyButton;

    private final Map functionButtonMap = new HashMap<>();

    private final List locoViewListeners = new LinkedList();

    private final List viewCloseListeners = new LinkedList();

    private final LocoModelListener locoModelListener;

    private JPanel directionPanel;

    private JPanel lightAndStopButtonPanel;

    private JPanel functionButtonPanel;

    private JPanel multiRfBaseButtonPanel;

    private JPanel counterPanel;

    private JPanel directBinStatePanel;

    private ScriptPanel scriptPanel;

    private LedBarGraph ledBarGraph;

    private JPanel contentPanel;

    private boolean m4SupportEnabled;

    // private JPanel imageOrVideoContainerPanel;

    // private JPanel imagePanel;

    private final Consumer locoAddressChangeListener;

    private final LocoControlListener locoControlListener;

    private final PropertyChangeListener locoModelPropertyChangeListener;

    private final WizardPropertyConnector addressChangeConnector;

    public LocoView(final LocoModel locoModel, final LocoConfigModel locoConfigModel,
        final SettingsService settingsService, final LocoControlListener locoControlListener,
        final Consumer locoAddressChangeListener) {
        this.locoModel = locoModel;
        this.locoConfigModel = locoConfigModel;
        this.locoAddressChangeListener = locoAddressChangeListener;
        this.locoControlListener = locoControlListener;

        m4SupportEnabled = settingsService.getWizardSettings().isM4SupportEnabled();

        // create form builder
        FormBuilder formBuilder = null;
        boolean debugDialog = false;
        if (debugDialog) {
            JPanel panel = new FormDebugPanel() {
                private static final long serialVersionUID = 1L;

                @Override
                public String getName() {
                    // this is used as tab title
                    return Resources
                        .getString(LocoView.class,
                            "title." + (LocoView.this.locoConfigModel.isCarControlEnabled() ? "car" : "loco"));
                }

                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(super.getPreferredSize().width + 20, super.getPreferredSize().height);
                }
            };
            formBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        else {
            JPanel panel = new JPanel(new BorderLayout()) {

                private static final long serialVersionUID = 1L;

                @Override
                public String getName() {
                    // this is used as tab title
                    return Resources
                        .getString(LocoView.class,
                            "title." + (LocoView.this.locoConfigModel.isCarControlEnabled() ? "car" : "loco"));
                }
            };
            formBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        formBuilder.border(Paddings.TABBED_DIALOG);

        try {
            formBuilder.add(createLocoAddressPanel()).xy(1, 1);
        }
        catch (Exception ex) {
            LOGGER.warn("Create loco address panel failed.", ex);
        }

        try {
            formBuilder.add(directionPanel = createDirectionPanel()).xy(1, 3);
            DisabledPanel.disable(directionPanel);

            formBuilder.add(createSpeedGaugePanel()).xy(3, 3);
        }
        catch (Exception ex) {
            LOGGER.warn("Create direction panel failed.", ex);
        }

        final List functionButtons = new LinkedList();

        try {
            formBuilder.add(lightAndStopButtonPanel = createLightAndStopButtonPanel(functionButtons)).xyw(1, 5, 3);
            DisabledPanel.disable(lightAndStopButtonPanel);
        }
        catch (Exception ex) {
            LOGGER.warn("Create light and buttons panel failed.", ex);
        }

        int row = 11;
        try {
            formBuilder.add(functionButtonPanel = createFunctionButtonPanel(functionButtons)).xyw(1, 7, 1);
            DisabledPanel.disable(functionButtonPanel);

            // TODO
            // imageOrVideoContainerPanel = new JPanel();
            // imageOrVideoContainerPanel.setOpaque(false);
            // formBuilder.add(imageOrVideoContainerPanel).xyw(3, 7, 1);
            //
            // imagePanel = createImagePanel(functionButtons);
            // if (locoModel.isCarControlEnabled()) {
            // imageOrVideoContainerPanel.add(imagePanel);
            // }

            if (this.locoConfigModel.isCarControlEnabled()) {
                // add the RF basis buttons
                this.multiRfBaseButtonPanel = addMultiRfBasisButtons();
                formBuilder.add(this.multiRfBaseButtonPanel).xyw(1, 9, 1);
                DisabledPanel.disable(this.multiRfBaseButtonPanel);

                formBuilder.add(counterPanel = addCounterPanel()).xyw(3, 9, 1);
                DisabledPanel.disable(counterPanel);

                if (settingsService.getWizardSettings().isPowerUser()) {
                    formBuilder.appendRows("3dlu, pref");
                    formBuilder.add(directBinStatePanel = addDirectBinStatePanel()).xy(1, row);
                    DisabledPanel.disable(directBinStatePanel);

                    row += 2;
                }
            }
            else {
                LOGGER.info("No multi RF basis buttons added.");
            }

        }
        catch (Exception ex) {
            LOGGER.warn("Create function buttons panel failed.", ex);
        }

        // prepare the script panel
        scriptPanel = new ScriptPanel(this, settingsService);
        addLocoViewListener(scriptPanel);
        JPanel panel = scriptPanel.createPanel();

        panel
            .setBorder(BorderFactory
                .createTitledBorder(BorderFactory.createEtchedBorder(),
                    Resources.getString(getClass(), "script") + ":"));

        formBuilder.appendRows("3dlu, pref");
        formBuilder.add(panel).xyw(1, row, 3);

        // add loco model listener
        this.locoModelListener = new LocoModelListener() {

            @Override
            public void functionChanged(int index, boolean value) {
                SwingUtils.executeInEDT(() -> functionButtons.get(index).setSelected(value));
            }

            @Override
            public void binaryStateChanged(int state, boolean value) {

            }
        };
        this.locoModel.addLocoModelListener(this.locoModelListener);

        this.locoModelPropertyChangeListener = new PropertyChangeListener() {

            @Override
            public void propertyChange(final PropertyChangeEvent evt) {
                SwingUtils.executeInEDT(() -> handlePropertyChangeEvent(evt));
            }
        };
        this.locoModel.addPropertyChangeListener(this.locoModelPropertyChangeListener);

        contentPanel = formBuilder.build();

        addressChangeHandler = new EmptyAddressHandler(value -> {

            SwingUtils.executeInEDT(() -> {

                LOGGER.info("The address value is not empty: {}", value);
                if (value.booleanValue()) {

                    // check the locoModel
                    SpeedSteps speedSteps = this.locoModel.getSpeedSteps();
                    speedStepsCombo.setSelectedItem(speedSteps);

                    DisabledPanel.enable(directionPanel);
                    DisabledPanel.enable(lightAndStopButtonPanel);
                    DisabledPanel.enable(functionButtonPanel);
                    if (multiRfBaseButtonPanel != null) {
                        DisabledPanel.enable(multiRfBaseButtonPanel);
                    }
                    if (counterPanel != null) {
                        DisabledPanel.enable(counterPanel);
                    }
                    if (directBinStatePanel != null) {
                        DisabledPanel.enable(directBinStatePanel);
                    }
                }
                else {
                    DisabledPanel.disable(directionPanel);
                    DisabledPanel.disable(lightAndStopButtonPanel);
                    DisabledPanel.disable(functionButtonPanel);
                    if (multiRfBaseButtonPanel != null) {
                        DisabledPanel.disable(multiRfBaseButtonPanel);
                    }
                    if (counterPanel != null) {
                        DisabledPanel.disable(counterPanel);
                    }
                    if (directBinStatePanel != null) {
                        DisabledPanel.disable(directBinStatePanel);
                    }
                }
            });
        });
        this.addressChangeConnector =
            WizardPropertyConnector
                .connect(this.locoModel, LocoModel.PROPERTY_ADDRESS, addressChangeHandler,
                    EmptyAddressHandler.PROPERTYNAME_ADDRESS);
        this.addressChangeConnector.updateProperty2();
    }

    private final EmptyAddressHandler addressChangeHandler;

    private void handlePropertyChangeEvent(final PropertyChangeEvent evt) {
        // LOGGER.info(">>> Received pce: {}", evt);

        switch (evt.getPropertyName()) {
            case LocoModel.PROPERTY_ADDRESS:
                LOGGER.info("The address has changed, set the reported speed and dynState energy to 0.");

                // locoModel.setReportedSpeed(0);
                // locoModel.setDynStateEnergy(0);

                break;
            case LocoModel.PROPERTY_SPEED:
                LOGGER.info("The speed has changed: {}", locoModel.getSpeed());
                try {
                    // Integer speedValue = (Integer) evt.getNewValue();
                    Integer speedValue = locoModel.getSpeed();
                    if (speedValue != null) {
                        int speed = speedValue.intValue();
                        speed =
                            ConversionUtils
                                .bidibSpeedToDccSpeed(speed, locoModel.getSpeedSteps() != null
                                    ? locoModel.getSpeedSteps().getType() : SpeedStepsEnum.DCC128);
                        if (speed == 0 || speed == 1) {
                            LOGGER.info("The current speed is stop or emergengy stop: {}", speed);
                            changeSliderSilently(0);
                        }
                        else {
                            if (DirectionStatus.BACKWARD == locoModel.getDirection()) {
                                speed = -speed;
                            }
                            LOGGER.info("Set the current speed value: {}", speed);
                            changeSliderSilently(speed);
                        }
                    }
                }
                catch (Exception ex) {
                    LOGGER.warn("Set speed failed.", ex);
                }
                break;
            case LocoModel.PROPERTY_SPEED_STEPS:
                SpeedSteps speedSteps = locoModel.getSpeedSteps();
                LOGGER.info(">>> The speed steps have changed: {}", speedSteps);
                if (speedSteps != null) {

                    // get the listeners
                    final ChangeListener[] changeListeners = speedSlider.getChangeListeners();
                    try {
                        for (ChangeListener cl : changeListeners) {
                            speedSlider.removeChangeListener(cl);
                        }
                        int steps = speedSteps.getSteps() - 1;
                        speedSlider.setMaximum(steps);
                        if (locoConfigModel.isCarControlEnabled()) {
                            speedSlider.setMinimum(0);
                        }
                        else {
                            speedSlider.setMinimum(-steps);
                        }
                        speedSlider.setMajorTickSpacing(steps);
                    }
                    finally {
                        for (ChangeListener cl : changeListeners) {
                            speedSlider.addChangeListener(cl);
                        }
                    }
                }
                LOGGER.info("<<< The speed steps have changed: {}", speedSteps);
                break;
            case LocoListModel.PROPERTY_FUNCTIONS:
                LOGGER.info("The functions have changed: {}", locoModel.getFunctions());

                // update the functions
                updateFunctionState(locoModel.getFunctions(), functionButtonMap);

                break;
            case LocoModel.PROPERTY_DYNSTATE_ENERGY:
                final int dynStateEnergy = locoModel.getDynStateEnergy();
                LOGGER.info("The dynStateEnergy has changed, dynStateEnergy: {}", dynStateEnergy);

                if (ledBarGraph != null) {
                    SwingUtils.executeInEDT(() -> ledBarGraph.setValue(dynStateEnergy));
                }
                break;

            case LocoModel.PROPERTY_REPORTED_SPEED:
                Integer reportedSpeed = locoModel.getReportedSpeed();
                LOGGER
                    .info("The reported speed has changed in the locoModel. Send the new speed value: {}",
                        reportedSpeed);

                break;

            default:
                break;
        }
    }

    protected Map getFunctionButtonMap() {
        return functionButtonMap;
    }

    private void changeSliderSilently(int speed) {
        ChangeListener[] changeListeners = speedSlider.getChangeListeners();
        // remove the change listeners to prevent signal the speed change to the interface
        // twice
        for (ChangeListener listener : changeListeners) {
            speedSlider.removeChangeListener(listener);
        }

        try {
            speedSlider.setValue(speed);
        }
        finally {
            // add the change listeners again
            for (ChangeListener listener : changeListeners) {
                speedSlider.addChangeListener(listener);
            }
        }
    }

    @Override
    public JPanel getComponent() {
        return contentPanel;
    }

    private JPanel createLocoAddressPanel() {
        final FormBuilder formBuilder =
            FormBuilder
                .create().columns("pref, 3dlu, 40dlu, 10dlu, pref, 3dlu, 60dlu, 10dlu, pref, 3dlu:grow").rows("pref");
        // formBuilder.debug(true);

        address = new JTextField();
        address.setDocument(new InputValidationDocument(5, InputValidationDocument.NUMERIC));

        // set the default address
        if (locoModel.getAddress() != null) {
            address.setText(Integer.toString(locoModel.getAddress()));
        }
        else {
            address.setText(null);
        }

        address.getDocument().addDocumentListener(new DocumentListener() {
            @Override
            public void changedUpdate(DocumentEvent e) {
                valueChanged();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                valueChanged();
            }

            @Override
            public void insertUpdate(DocumentEvent e) {
                valueChanged();
            }

            private void valueChanged() {
                Integer currentAddress = null;

                if (!address.getText().isEmpty()) {
                    try {
                        currentAddress = Integer.valueOf(address.getText());
                    }
                    catch (NumberFormatException e) {
                        LOGGER.warn("Parse decoder address failed.");
                        return;
                    }
                }

                // check if the address has been changed
                if (locoModel != null && (currentAddress == null && locoModel.getAddress() == null)
                    || currentAddress != null && currentAddress.equals(locoModel.getAddress())) {
                    LOGGER.info("Address has not been changed.");
                    return;
                }

                LOGGER.info("Get the locoModel for the new address: {}", currentAddress);

                locoAddressChangeListener.accept(currentAddress);

                // must reset the reported cell number
                // locoModel.setReportedCellNumber(null);
            }
        });

        formBuilder.add(Resources.getString(getClass(), "address")).xy(1, 1);
        formBuilder.add(address).xy(3, 1);

        if (m4SupportEnabled) {
            speedStepsCombo = new JComboBox(SpeedSteps.values());
        }
        else {
            // filter the M4 speed steps

            List speedSteps = new ArrayList<>();
            speedSteps.addAll(Arrays.asList(SpeedSteps.values()));
            CollectionUtils.filter(speedSteps, new Predicate() {

                @Override
                public boolean evaluate(SpeedSteps speedStep) {
                    return (speedStep.ordinal() <= SpeedSteps.DCC_SDF.ordinal());
                }
            });

            speedStepsCombo = new JComboBox(speedSteps.toArray(new SpeedSteps[0]));
        }

        speedStepsCombo
            .setSelectedItem(locoModel.getSpeedSteps() != null ? locoModel.getSpeedSteps() : SpeedSteps.DCC128);

        speedStepsCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                LOGGER.info("Speed steps combo has changed, update loco model.");

                LocoView.this.locoControlListener.setSpeedSteps((SpeedSteps) speedStepsCombo.getSelectedItem());
            }
        });

        formBuilder.add(Resources.getString(getClass(), "speedSteps")).xy(5, 1);
        formBuilder.add(speedStepsCombo).xyw(7, 1, 1);

        final JButton removeButton = new JButton(Resources.getString(LocoView.class, "remove"));
        removeButton.setToolTipText(Resources.getString(LocoView.class, "remove.tooltip"));
        removeButton.addActionListener(evt -> {
            LOGGER.info("Remove loco from DCC refresh.");

            LocoView.this.locoControlListener.clearLoco();

        });
        formBuilder.add(removeButton).xyw(9, 1, 2);

        return formBuilder.build();
    }

    private JPanel createDirectionPanel() {
        final FormBuilder formBuilder =
            FormBuilder
                .create().columns("pref, 3dlu, 60dlu:grow, 3dlu, pref")
                .rows("pref, 3dlu, pref, 3dlu, pref, 3dlu, pref");

        formBuilder.border(new EmptyBorder(10, 0, 10, 0));

        // TODO the speed value must honour the speed steps
        final ValueModel speedModel = new SwingPropertyAdapter(locoModel, LocoModel.PROPERTY_SPEED, true);
        final ValueModel speedConverterModel =
            new ConverterValueModel(speedModel,
                new BidibSpeedToDccSpeedConverter(() -> locoModel.getSpeedSteps(), () -> locoModel.getDirection()));

        speed = WizardComponentFactory.createLabel(speedConverterModel);
        formBuilder.add(Resources.getString(getClass(), "speed")).xy(1, 1);
        formBuilder.add(speed).xy(3, 1);

        final ValueModel reportedSpeedModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTY_REPORTED_SPEED, true);
        final ValueModel reportedSpeedConverterModel =
            new ConverterValueModel(reportedSpeedModel, new StringConverter(new DecimalFormat("#")));

        reportedSpeed = WizardComponentFactory.createLabel(reportedSpeedConverterModel);
        formBuilder.add(Resources.getString(getClass(), "reportedSpeed")).xy(1, 3);
        formBuilder.add(reportedSpeed).xy(3, 3);

        speedSlider = new JZeroSlider();
        speedSlider.setOpaque(FormsSetup.getOpaqueDefault());

        int steps =
            locoModel.getSpeedSteps() != null ? locoModel.getSpeedSteps().getSteps() - 1
                : SpeedSteps.DCC128.getSteps() - 1;
        speedSlider.setMaximum(steps);
        if (locoConfigModel.isCarControlEnabled()) {
            speedSlider.setMinimum(0);
        }
        else {
            speedSlider.setMinimum(-steps);
        }
        speedSlider.setPaintTicks(true);

        InputMap keyMap = (InputMap) UIManager.get("Slider.focusInputMap", speedSlider.getLocale());
        if (LOGGER.isDebugEnabled()) {
            if (keyMap != null) {
                Object binding = keyMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0));
                LOGGER.debug("HOME is binded: {}", binding);
            }
        }

        speedSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), "speedToMaxValue");
        speedSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), "speedToMinValue");
        speedSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "speedUpValue");
        speedSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "speedDownValue");
        speedSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "speedStop");

        speedSlider.getActionMap().put("speedToMaxValue", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                LOGGER.info("speedToMaxValue was called.");

                DirectionStatus dir = locoModel.getDirection();
                int steps = locoModel.getSpeedSteps().getSteps() - 1;
                if (DirectionStatus.FORWARD == dir) {
                    speedSlider.setValue(steps);
                }
                else {
                    speedSlider.setValue(-steps);
                }
            }
        });
        speedSlider.getActionMap().put("speedToMinValue", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                LOGGER.info("speedToMinValue was called.");
                speedSlider.setValue(0);
            }
        });
        speedSlider.getActionMap().put("speedUpValue", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                // increase speed
                Integer speed = locoModel.getSpeed();
                DirectionStatus dir = locoModel.getDirection();
                LOGGER.info("speedUpValue was called, current speed: {}, dir: {}", speed, dir);

                if (speed == null) {
                    speed = 0;
                }

                if (dir == DirectionStatus.FORWARD) {
                    if (speed < steps) {
                        speed += PAGE_STEPS;
                    }
                    if (speed > steps) {
                        speed = steps;
                    }
                }
                else {
                    // BACKWARDS
                    if (speed > 0) {
                        speed -= PAGE_STEPS + 1;

                        if (speed < 0) {
                            speed = 0;
                        }

                        // provide negative speed
                        speed = -speed;
                    }
                    else if (speed == 0) {
                        // change the direction
                        LOGGER.info("The speed is 0 and we must change the direction and increase speed.");
                        speed += PAGE_STEPS;
                    }
                }

                LOGGER.info("speedUpValue was called, new speed: {}", speed);
                speedSlider.setValue(speed);
            }
        });
        speedSlider.getActionMap().put("speedDownValue", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                // decrease speed
                Integer speed = locoModel.getSpeed();
                DirectionStatus dir = locoModel.getDirection();
                LOGGER.info("speedDownValue was called, current speed: {}, dir: {}", speed, dir);

                if (speed == null) {
                    speed = 0;
                }

                if (dir == DirectionStatus.FORWARD) {
                    if (speed > 0) {
                        speed -= PAGE_STEPS + 1;
                        if (speed < 0) {
                            speed = 0;
                        }
                    }
                    else if (speed == 0) {
                        // change the direction
                        LOGGER.info("The speed is 0 and we must change the direction and 'increase' speed.");
                        speed += PAGE_STEPS;

                        // provide negative speed
                        speed = -speed;
                    }
                }
                else {
                    // BACKWARDS
                    if (speed < steps) {
                        speed += PAGE_STEPS;
                    }
                    if (speed > steps) {
                        speed = steps;
                    }
                    speed = -speed;
                }
                LOGGER.info("speedDownValue was called, new speed: {}", speed);
                speedSlider.setValue(speed);
            }
        });
        speedSlider.getActionMap().put("speedStop", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                LOGGER.info("speedStop was called.");
                fireStop();
            }
        });

        // init value before add the change listener because otherwise stops the loco ...
        speedSlider.setValue(0);

        speedSlider.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                JSlider source = (JSlider) e.getSource();
                if (!source.getValueIsAdjusting()) {

                    int value = speedSlider.getValue();
                    LOGGER.info("Current speedSlider value: {}", value);

                    DirectionStatus direction = null;
                    if (value > 0) {
                        direction = DirectionStatus.FORWARD;
                        // value++;
                    }
                    else if (value < 0) {
                        direction = DirectionStatus.BACKWARD;
                        // value--;
                    }
                    value = Math.abs(value);

                    LOGGER.info("Set the speed value: {}", value);

                    // TODO this is no longer correct
                    // locoModel.setSpeed(value);
                    LocoView.this.locoControlListener.setSpeed(value, direction);
                }
            }
        });

        // let the slider jump to the position the user clicked
        speedSlider.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                JSlider sourceSlider = (JSlider) e.getSource();
                BasicSliderUI ui = (BasicSliderUI) sourceSlider.getUI();
                int value = ui.valueForXPosition(e.getX());

                // consume the mouse event to no let the other listeners be triggered
                e.consume();

                LOGGER.info("Mouse pressed on slider. Set the new value: {}", value);
                speedSlider.setValue(value);
            }
        });

        // allow control speed with mouse wheel
        int speedDelta = 1;
        this.speedSlider.addMouseWheelListener(evt -> {
            if (evt.getWheelRotation() < 0)// mouse wheel was rotated up/away from the user
            {
                int iNewValue = this.speedSlider.getValue() - speedDelta;
                if (iNewValue >= this.speedSlider.getMinimum()) {
                    this.speedSlider.setValue(iNewValue);
                }
                else {
                    this.speedSlider.setValue(0);
                }
            }
            else {
                int iNewValue = this.speedSlider.getValue() + speedDelta;
                if (iNewValue <= this.speedSlider.getMaximum()) {
                    this.speedSlider.setValue(iNewValue);
                }
                else {
                    this.speedSlider.setValue(this.speedSlider.getMaximum());
                }
            }
        });

        formBuilder.add(speedSlider).xyw(1, 5, 5);

        final JLabel minimumValueLabel = new JLabel(Resources.getString(getClass(), "backwards"));
        formBuilder.add(minimumValueLabel).xy(1, 7);
        if (locoConfigModel.isCarControlEnabled()) {
            minimumValueLabel.setText(Resources.getString(LocoView.class, "stop"));
        }

        formBuilder.add(Resources.getString(LocoView.class, "forwards")).xy(5, 7);

        JPanel helperPanel = new JPanel(new BorderLayout());
        helperPanel.setOpaque(false);

        ledBarGraph = new LedBarGraph(10, Orientation.vertical);
        ledBarGraph.setOpaque(FormsSetup.getOpaqueDefault());

        helperPanel.add(formBuilder.build(), BorderLayout.CENTER);
        helperPanel.add(ledBarGraph, BorderLayout.EAST);

        locoModel.addPropertyChangeListener(LocoConfigModel.PROPERTY_CARCONTROL_ENABLED, new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                LOGGER.info(">>> Received pce: {}", evt);

                if (LocoConfigModel.PROPERTY_CARCONTROL_ENABLED.equals(evt.getPropertyName())) {

                    SwingUtils.executeInEDT(() -> {
                        LOGGER.info("The car control enabled flag has changed.");
                        if (locoModel.getSpeedSteps() != null) {
                            int steps = locoModel.getSpeedSteps().getSteps() - 1;
                            speedSlider.setMaximum(steps);
                            if (locoConfigModel.isCarControlEnabled()) {

                                // remove the speed after change slider range
                                try {
                                    speedSlider.setMinimum(0);
                                }
                                finally {
                                    // locoModel.setSpeed(null);
                                    LocoView.this.locoControlListener.setSpeed(null, null);
                                }

                                minimumValueLabel.setText(Resources.getString(LocoView.class, "stop"));

                                // imageOrVideoContainerPanel.add(imagePanel);
                            }
                            else {
                                // remove the speed after change slider range
                                try {
                                    speedSlider.setMinimum(-steps);
                                }
                                finally {
                                    // locoModel.setSpeed(null);
                                    LocoView.this.locoControlListener.setSpeed(null, null);
                                }
                                // imageOrVideoContainerPanel.remove(imagePanel);
                            }
                        }
                    });
                }
            }
        });

        locoModel.addPropertyChangeListener(LocoModel.PROPERTY_SPEED, evt -> {
            LOGGER.info(">>> Received pce: {}", evt);

            if (LocoModel.PROPERTY_SPEED.equals(evt.getPropertyName())) {

                SwingUtils.executeInEDT(() -> {
                    boolean stopped = isSpeedStopped(locoModel.getSpeed());
                    speedStepsCombo.setEnabled(stopped);
                });
            }
        });
        // locoModel.addPropertyChangeListener(LocoModel.PROPERTY_REPORTED_SPEED, evt -> {
        // LOGGER.info(">>> Received pce: {}", evt);
        //
        // if (LocoModel.PROPERTY_REPORTED_SPEED.equals(evt.getPropertyName())) {
        //
        // SwingUtils.executeInEDT(() -> {
        // LOGGER.info("Set the reported speed: {}", locoModel.getReportedSpeed());
        //
        // Integer reportedSpeedValue = locoModel.getReportedSpeed();
        // reportedSpeed.setText(reportedSpeedValue != null ? reportedSpeedValue.toString() : null);
        // });
        // }
        // });

        boolean stopped = isSpeedStopped(locoModel.getSpeed());
        speedStepsCombo.setEnabled(stopped);

        return helperPanel;
    }

    private boolean isSpeedStopped(Integer speed) {
        boolean stopped = false;
        if (speed != null) {
            switch (speed.intValue()) {
                case 0:
                case 1:
                    // stopped
                    stopped = true;
                    break;
                default:
                    break;
            }
        }
        else {
            // assume stopped
            stopped = true;
        }
        return stopped;
    }

    private JComponent createSpeedGaugePanel() {
        final Radial speedGauge = SpeedGaugeBuilder.speedGauge("Speed", "km/h");

        JPanel panel = new JPanel() {
            private static final long serialVersionUID = 1L;

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
        };
        panel.setOpaque(false);

        panel.setLayout(new BorderLayout());
        panel.add(speedGauge, BorderLayout.CENTER);

        final ValueModel reportedSpeedModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTY_REPORTED_SPEED, true);

        Bindings.bind(speedGauge, "valueAnimated", reportedSpeedModel);

        return panel;
    }

    private JPanel createLightAndStopButtonPanel(final List functionButtons) {

        final FormBuilder formBuilder = FormBuilder.create().columns("pref, 3dlu, 60dlu:grow, 3dlu, pref").rows("pref");

        // add the light icons
        ImageIcon lightOnIcon = ImageUtils.createImageIcon(LocoView.class, "/icons/16x16/lightbulb.png");
        ImageIcon lightOffIcon = ImageUtils.createImageIcon(LocoView.class, "/icons/16x16/lightbulb_off.png");

        final JideToggleButton lightButton = new JideToggleButton(lightOffIcon);
        lightButton.setSelectedIcon(lightOnIcon);
        lightButton.setRolloverSelectedIcon(lightOnIcon);
        lightButton.setButtonStyle(JideButton.TOOLBOX_STYLE);

        lightButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // locoModel.setFunction(0, lightButton.isSelected());
                LocoView.this.locoControlListener.setFunction(0, lightButton.isSelected());
            }
        });
        functionButtons.add(lightButton);
        functionButtonMap.put("F0", lightButton);

        formBuilder.add(lightButton).xy(1, 1);

        lightButton.setSelected(locoModel.getFunction(LocoModel.FUNCTIONINDEX_LIGHTS));

        JPanel stopPanel = new JPanel(new GridBagLayout());

        stopPanel
            .setBorder(BorderFactory
                .createTitledBorder(BorderFactory.createEtchedBorder(),
                    Resources.getString(getClass(), "stopLoco") + ":"));

        stopButton = new JButton(Resources.getString(getClass(), "stop"));

        stopButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireStop();
            }
        });

        stopEmergencyButton = new JButton(Resources.getString(getClass(), "emergencyStop"));

        stopEmergencyButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireEmergencyStop();
            }
        });

        JPanel buttonPanel = new ButtonBarBuilder().addGlue().addButton(stopButton, stopEmergencyButton).build();
        formBuilder.add(buttonPanel).xy(3, 1);

        return formBuilder.build();
    }

    private JPanel createFunctionButtonPanel(final List functionButtons) {

        final FormBuilder formBuilder =
            FormBuilder
                .create()
                .columns(
                    "pref, 8dlu, pref, 8dlu, pref, 8dlu, pref, 8dlu, pref, 8dlu, pref, 8dlu, pref, 8dlu, pref, 8dlu, pref, 8dlu, pref")
                .rows("pref, 3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu, pref");

        formBuilder.addSeparator(Resources.getString(getClass(), "additionalFunctions")).xyw(1, 1, 19);

        final int columns = 10;

        for (int row = 0; row < 4; row++) {

            for (int column = 0; column < columns; column++) {
                final int functionIndex = row * columns + (column + 1);
                String buttonText = "F" + functionIndex;
                if (functionIndex > 28) {
                    buttonText = "B" + functionIndex;
                }

                // use JideToggleButton for function and binary function buttons
                final JideButton functionButton = new JideToggleButton(buttonText);
                functionButton.setButtonStyle(JideButton.TOOLBOX_STYLE);

                functionButton.addActionListener(event -> {
                    LOGGER.info("Set function with index: {}", functionIndex);
                    if (functionIndex < 29) {
                        LocoView.this.locoControlListener.setFunction(functionIndex, functionButton.isSelected());
                    }
                    else {
                        LocoView.this.locoControlListener.setBinaryState(functionIndex, true);
                    }
                });
                functionButtons.add(functionButton);

                functionButtonMap.put(buttonText, functionButton);

                formBuilder.add(functionButton).xy((column * 2) + 1, (row * 2) + 3);
            }
        }

        return formBuilder.build();
    }

    protected void updateFunctionState(final BitSet functions, final Map functionButtonMap) {

        int maxFunctionIndex = functions.size();
        LOGGER.info("Current functions count: {}", maxFunctionIndex);

        for (int index = 0; index < maxFunctionIndex; index++) {
            if (index < 29) {
                String buttonText = "F" + index;
                boolean value = functions.get(index);

                JButton functionButton = functionButtonMap.get(buttonText);
                if (functionButton != null) {
                    functionButton.setSelected(value);
                }
                else {
                    LOGGER.warn("No functionButton available for index: {}", index);
                }
            }
            else {
                String buttonText = "B" + index;
                boolean value = functions.get(index);
                JButton functionButton = functionButtonMap.get(buttonText);
                if (functionButton != null) {
                    functionButton.setSelected(value);
                }
                else {
                    LOGGER.warn("No binary functionButton available for index: {}", index);
                }
            }
        }
    }

    private JComponent[] rfBaseButtons;

    private JPanel addMultiRfBasisButtons() {

        final FormBuilder formBuilder =
            FormBuilder
                .create()
                .columns(
                    "pref, 5dlu, pref, 5dlu, pref, 5dlu, pref, 5dlu, pref, 5dlu, pref, 5dlu, pref, 5dlu, pref:grow")
                .rows("pref, 3dlu, pref");

        formBuilder.addSeparator(Resources.getString(getClass(), "multiRFBasis")).xyw(1, 1, 15);

        ValueModel activeBaseModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTY_ACTIVE_BASE, true);

        // add the radio buttons for the basis change
        rfBaseButtons = new JComponent[RfBasisMode.values().length];
        int column = 0;
        int col = 0;
        int row = 3;
        for (RfBasisMode rfBaseMode : RfBasisMode.values()) {

            JRadioButton radio =
                WizardComponentFactory
                    .createRadioButton(activeBaseModel, rfBaseMode,
                        Resources.getString(RfBasisMode.class, rfBaseMode.getKey()));
            rfBaseButtons[column] = radio;

            // add radio button
            formBuilder.add(radio).xy(col * 2 + 1, row);
            column++;
            col++;

            if (rfBaseMode == RfBasisMode.SINGLE) {
                formBuilder.appendRows("3dlu, pref");
                col = 0;
                row += 2;
            }
        }

        formBuilder.appendRows("3dlu, pref");
        col = 0;
        row += 2;

        ValueModel reportedCellNumberModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTY_REPORTED_CELLNUMBER, true);
        final ValueModel reportedCellNumberConverterModel =
            new ConverterValueModel(reportedCellNumberModel, new StringConverter(new DecimalFormat("#")));

        JTextField reportedCellNumberText =
            WizardComponentFactory.createTextField(reportedCellNumberConverterModel, false);
        reportedCellNumberText.setEditable(false);
        formBuilder.add("Reported RF Cellnumber").xyw(1, row, 5);
        formBuilder.add(reportedCellNumberText).xy(7, row);

        activeBaseModel.addValueChangeListener(evt -> {
            LOGGER.info("The active RF base has changed: {}", evt.getNewValue());

            RfBasisMode activeRfBase = locoModel.getActiveBase();
            if (activeRfBase != null) {
                final Integer functionIndex = activeRfBase.getFunctionIndex();

                if (functionIndex != null) {
                    // send the binary state
                    LocoView.this.locoControlListener.setBinaryState(functionIndex, true);
                }
            }
        });

        return formBuilder.build();
    }

    private JPanel addCounterPanel() {
        final FormBuilder formBuilder =
            FormBuilder
                .create().columns("pref, 5dlu, pref:grow").rows("pref, 3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu, pref");

        formBuilder.addSeparator(Resources.getString(getClass(), "Counter")).xyw(1, 1, 3);

        // CS_DRIVE
        ValueModel counterCsDriveModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTYNAME_COUNTER_CS_DRIVE, true);
        final ValueModel counterCsDriveConverterModel =
            new ConverterValueModel(counterCsDriveModel, new StringConverter(new DecimalFormat("#")));

        JTextField counterCsDriveText = WizardComponentFactory.createTextField(counterCsDriveConverterModel, false);
        counterCsDriveText.setEditable(false);
        formBuilder.add("CS_DRIVE").xy(1, 3);
        formBuilder.add(counterCsDriveText).xy(3, 3);

        // CS_BIN_STATE
        ValueModel counterCsBinStateModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTYNAME_COUNTER_CS_BIN_STATE, true);
        final ValueModel counterCsBinStateConverterModel =
            new ConverterValueModel(counterCsBinStateModel, new StringConverter(new DecimalFormat("#")));

        JTextField counterCsBinStateText =
            WizardComponentFactory.createTextField(counterCsBinStateConverterModel, false);
        counterCsBinStateText.setEditable(false);
        formBuilder.add("CS_BIN_STATE").xy(1, 5);
        formBuilder.add(counterCsBinStateText).xy(3, 5);

        // CS_DRIVE_ACK
        ValueModel counterCsDriveAckModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTYNAME_COUNTER_CS_DRIVE_ACK, true);
        final ValueModel counterCsDriveAckConverterModel =
            new ConverterValueModel(counterCsDriveAckModel, new StringConverter(new DecimalFormat("#")));

        JTextField counterCsDriveAckText =
            WizardComponentFactory.createTextField(counterCsDriveAckConverterModel, false);
        counterCsDriveAckText.setEditable(false);
        formBuilder.add("CS_DRIVE_ACK").xy(1, 7);
        formBuilder.add(counterCsDriveAckText).xy(3, 7);

        // reset button
        JButton resetButton = new JButton(Resources.getString(getClass(), "reset"));
        resetButton.addActionListener(evt -> {
            LOGGER.info("Reset the counters.");
            locoModel.resetCounterCsDrive();
            locoModel.resetCounterCsAckDrive();
            locoModel.resetCounterCsBinState();
        });

        formBuilder.add(resetButton).xy(1, 9);

        return formBuilder.build();
    }

    private JComponent[] devBinStateValueButtons;

    private JPanel addDirectBinStatePanel() {
        final FormBuilder formBuilder =
            FormBuilder
                .create().columns("pref, 5dlu, max(pref;30dlu), 5dlu, pref, 5dlu, pref, 5dlu, pref, 5dlu:grow")
                .rows("pref, 3dlu, pref");

        formBuilder.addSeparator(Resources.getString(getClass(), "devBinState")).xyw(1, 1, 10);

        ValueModel devBinStateNumberModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTYNAME_DEV_BINSTATE_NUMBER, true);
        final ValueModel binStateNumberConverterModel =
            new ConverterValueModel(devBinStateNumberModel, new StringConverter(new DecimalFormat("#")));

        JTextField devBinStateNumberText = WizardComponentFactory.createTextField(binStateNumberConverterModel, false);
        InputValidationDocument stateNumberDocument = new InputValidationDocument(5, InputValidationDocument.NUMERIC);
        stateNumberDocument.setDocumentFilter(new IntegerRangeFilter(0, 32767));
        devBinStateNumberText.setDocument(stateNumberDocument);
        formBuilder.add("Number").xy(1, 3);
        formBuilder.add(devBinStateNumberText).xy(3, 3);

        ValueModel devBinStateValueModel =
            new SwingPropertyAdapter(locoModel, LocoModel.PROPERTYNAME_DEV_BINSTATE_VALUE, true);

        devBinStateValueButtons = new JComponent[BinStateValue.values().length];
        int column = 0;
        for (BinStateValue binStateValue : BinStateValue.values()) {

            JRadioButton radio =
                WizardComponentFactory
                    .createRadioButton(devBinStateValueModel, binStateValue,
                        Resources.getString(BinStateValue.class, binStateValue.getKey()));
            devBinStateValueButtons[column] = radio;

            // add radio button
            formBuilder.add(radio).xy(5 + column * 2, 3);
            column++;
        }

        JButton sendButton = new JButton(Resources.getString(getClass(), "send"));
        sendButton.addActionListener(evt -> {
            BinStateValue devBinStateValue = locoModel.getDevBinStateValue();
            if (devBinStateValue != null) {
                final boolean functionValue = devBinStateValue.getFunctionValue();
                Integer binStateNumber = locoModel.getDevBinStateNumber();

                if (binStateNumber != null) {
                    // send the binary state
                    LocoView.this.locoControlListener.setBinaryState(binStateNumber.intValue(), functionValue);
                }
                else {
                    LOGGER.info("No bin state number available. The BIN_STATE is not fired.");
                }
            }
        });
        sendButton.setEnabled(false);

        formBuilder.add(sendButton).xy(9, 3);

        devBinStateNumberModel.addValueChangeListener(evt -> {
            Integer binStateNumber = locoModel.getDevBinStateNumber();
            sendButton.setEnabled(binStateNumber != null);
        });

        return formBuilder.build();
    }

    private BlinkerLed blinkerRight;

    private BlinkerLed blinkerLeft;

    private JPanel createImagePanel(final List functionButtons) {
        JPanel imagePanel = new JPanel(new BorderLayout());

        JLayeredPane layeredPane = new JLayeredPane();
        // layeredPane.setLayout(null);
        layeredPane.setPreferredSize(new Dimension(140, 140));

        // use the image
        ImageIcon carFrontIcon = ImageUtils.loadImageIcon(LocoView.class, "/images/Truck-Front.png", 120, 120);
        JLabel backgroundLabel = new JLabel(carFrontIcon);
        backgroundLabel.setOpaque(true);
        // backgroundLabel.setBounds(0, 0, carFrontIcon.getIconWidth(), carFrontIcon.getIconHeight());
        backgroundLabel.setBounds(0, 0, 140, 140);

        layeredPane.add(backgroundLabel, Integer.valueOf(10));

        blinkerRight = new BlinkerLed(0, 0, 8, 6, Color.ORANGE.brighter(), Color.ORANGE);
        blinkerLeft = new BlinkerLed(0, 0, 8, 6, Color.ORANGE.brighter(), Color.ORANGE);

        final BlinkerLed lightRight = new BlinkerLed(0, 0, 8, 11, Color.YELLOW, Color.WHITE);
        final BlinkerLed lightLeft = new BlinkerLed(0, 0, 8, 11, Color.YELLOW, Color.WHITE);

        layeredPane.add(blinkerLeft, Integer.valueOf(20));
        layeredPane.add(blinkerRight, Integer.valueOf(20));
        layeredPane.add(lightLeft, Integer.valueOf(20));
        layeredPane.add(lightRight, Integer.valueOf(20));

        blinkerLeft.setBounds(37, 84, 10, 7);
        blinkerRight.setBounds(93, 84, 10, 7);
        lightLeft.setBounds(37, 92, 10, 11);
        lightRight.setBounds(93, 92, 10, 11);

        final Cursor cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
        blinkerLeft.setCursor(cursor);
        blinkerRight.setCursor(cursor);
        lightLeft.setCursor(cursor);
        lightRight.setCursor(cursor);

        final JideToggleButton functionButtonLeft = getButton(functionButtons.get(2), JideToggleButton.class);
        functionButtonLeft.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                blinkerLeft.setActive(functionButtonLeft.isSelected(), true);
            }
        });

        final JideToggleButton functionButtonRight = getButton(functionButtons.get(1), JideToggleButton.class);
        functionButtonRight.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                blinkerRight.setActive(functionButtonRight.isSelected(), true);
            }
        });

        final JideToggleButton functionButtonLights = getButton(functionButtons.get(0), JideToggleButton.class);
        functionButtonLights.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                LOGGER.info("Lights state has changed, selected: {}", functionButtonLights.isSelected());
                lightRight.setActive(functionButtonLights.isSelected(), false);
                lightLeft.setActive(functionButtonLights.isSelected(), false);
            }
        });

        backgroundLabel.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {

                Point point = e.getPoint();
                LOGGER.info("Mouse clicked, point: {}", point);

                if (point.getX() > 37 && point.getX() < 44) {
                    // left side

                    if (point.getY() > 83 && point.getY() < 89) {
                        // blinker
                        LOGGER.info("Turn left blinker on/off.");

                        int functionIndex = 2;
                        JideToggleButton functionButton =
                            getButton(functionButtons.get(functionIndex), JideToggleButton.class);
                        // toggle the button
                        functionButton.setSelected(!functionButton.isSelected());

                        LocoView.this.locoControlListener.setFunction(functionIndex, functionButton.isSelected());
                    }
                    else if (point.getY() > 91 && point.getY() < 102) {
                        // front lights
                        LOGGER.info("Turn front lights on/off.");

                        int functionIndex = 0;
                        JideToggleButton functionButton =
                            getButton(functionButtons.get(functionIndex), JideToggleButton.class);
                        // toggle the button
                        functionButton.setSelected(!functionButton.isSelected());

                        LocoView.this.locoControlListener.setFunction(functionIndex, functionButton.isSelected());
                    }
                }
                else if (point.getX() > 93 && point.getX() < 101) {
                    // right side

                    if (point.getY() > 83 && point.getY() < 89) {
                        // blinker
                        LOGGER.info("Turn right blinker on/off.");

                        int functionIndex = 1;
                        JideToggleButton functionButton =
                            getButton(functionButtons.get(functionIndex), JideToggleButton.class);
                        // toggle the button
                        functionButton.setSelected(!functionButton.isSelected());

                        LocoView.this.locoControlListener.setFunction(functionIndex, functionButton.isSelected());
                    }
                    else if (point.getY() > 91 && point.getY() < 102) {
                        // front lights
                        LOGGER.info("Turn front lights on/off.");

                        int functionIndex = 0;
                        JideToggleButton functionButton =
                            getButton(functionButtons.get(functionIndex), JideToggleButton.class);
                        // toggle the button
                        functionButton.setSelected(!functionButton.isSelected());

                        LocoView.this.locoControlListener.setFunction(functionIndex, functionButton.isSelected());
                    }
                }
            }
        });

        imagePanel.add(layeredPane, BorderLayout.CENTER);
        return imagePanel;
    }

    private static  T getButton(JButton button, Class type) {
        // if same type
        if (type.isInstance(button)) {
            return type.cast(button);
        }

        throw new IllegalArgumentException("The provided button has not the requested type.");
    }

    protected void fireWriteState(int state, boolean value) {
        LocoView.this.locoControlListener.setBinaryState(state, value);
    }

    private void addLocoViewListener(LocoViewListener listener) {
        locoViewListeners.add(listener);
    }

    public void addViewCloseListener(ViewCloseListener listener) {
        viewCloseListeners.add(listener);
    }

    private void fireEmergencyStop() {
        LocoView.this.locoControlListener.setSpeed(1, null);

        for (LocoViewListener listener : locoViewListeners) {
            listener.emergencyStop();
        }
    }

    private void fireStop() {
        LocoView.this.locoControlListener.setSpeed(0, null);

        for (LocoViewListener listener : locoViewListeners) {
            listener.stop();
        }
    }

    @Override
    public void selectDecoderAddress(int dccAddress) {
        LOGGER.info("Select the decoder address: {}", dccAddress);
        address.setText(String.valueOf(dccAddress));

        // set the speed to null because we want the new speed value triggered
        LocoView.this.locoControlListener.setSpeed(null, null);
    }

    @Override
    public void setSpeedSteps(SpeedSteps speedSteps) {
        LOGGER.info("Set the speed steps: {}", speedSteps);
        speedStepsCombo.setSelectedItem(speedSteps);
    }

    @Override
    public void setSpeed(int speed) {
        LOGGER.info("Set the speed: {}", speed);

        changeSliderSilently(speed);
        LocoView.this.locoControlListener.setSpeed(speed, null);
    }

    @Override
    public void setFunction(int function) {
        LOGGER.info("Set the function: {}", function);

        JideToggleButton functionButton = getButton(functionButtonMap.get("F" + function), JideToggleButton.class);
        LOGGER.warn("Fetched functionButton: {}", functionButton);

        functionButton.doClick();
    }

    @Override
    public void setBinState(int binStateNumber, boolean flag) {
        LOGGER.info("Set the binState, binStateNumber: {}, flag: {}", binStateNumber, flag);

        // send the binary state
        LocoView.this.locoControlListener.setBinaryState(binStateNumber, flag);
    }

    @Override
    public void setStop() {
        LOGGER.info("Set stop.");

        stopButton.doClick();
    }

    @Override
    public void setStopEmergency() {
        LOGGER.info("Set stop emergency.");

        stopEmergencyButton.doClick();
    }

    public void cleanup(final SettingsService settingsService) {
        LOGGER.info("The LocoView is disposed. Remove all listeners from the locoModel.");

        for (ViewCloseListener closeListener : viewCloseListeners) {
            try {
                closeListener.close();
            }
            catch (Exception ex) {
                LOGGER.warn("Notify view close listener failed.", ex);
            }
        }

        final SpeedSteps lastSelectedSpeedSteps = this.locoModel.getSpeedSteps();
        settingsService.getWizardSettings().setLastSelectedSpeedSteps(lastSelectedSpeedSteps);

        if (blinkerLeft != null) {
            blinkerLeft.setActive(false, true);
        }
        if (blinkerRight != null) {
            blinkerRight.setActive(false, true);
        }

        viewCloseListeners.clear();

        locoViewListeners.clear();

        // if (locoModelListener != null) {
        this.locoModel.removeLocoModelListener(locoModelListener);
        // locoModelListener = null;
        // }

        this.locoModel.removePropertyChangeListener(this.locoModelPropertyChangeListener);
        this.addressChangeConnector.release();

        if (scriptPanel != null) {
            scriptPanel.close();
            scriptPanel = null;
        }

        functionButtonMap.clear();
    }

    private static class BlinkerLed extends JComponent {

        private static final long serialVersionUID = 1L;

        private Timer blinkerTimer;

        private Color colorOn;

        private Color colorOff;

        private boolean on;

        private int x;

        private int y;

        private int width;

        private int height;

        public BlinkerLed(int x, int y, int width, int height, Color colorOn, Color colorOff) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.colorOn = colorOn;
            this.colorOff = colorOff;
        }

        public void setActive(boolean active, boolean useTimer) {

            repaint();

            if (active) {
                if (useTimer && blinkerTimer == null) {
                    blinkerTimer = new Timer(500, evt -> {
                        on = !on;
                        repaint();
                    });
                }
                on = true;
                if (blinkerTimer != null) {
                    blinkerTimer.start();
                }
            }
            else if (!active) {
                on = false;
                if (blinkerTimer != null) {
                    blinkerTimer.stop();
                }
            }
        }

        @Override
        public void paintComponent(Graphics graphics) {

            if (on) {
                graphics.setColor(colorOn);
            }
            else {
                graphics.setColor(colorOff);
            }
            graphics.fillRect(x, y, width, height);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy