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

org.bidib.wizard.mvc.pomupdate.view.PomUpdateView Maven / Gradle / Ivy

There is a newer version: 2.0.0-M1
Show newest version
package org.bidib.wizard.mvc.pomupdate.view;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Frame;
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.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.core.message.CommandStationPomMessage;
import org.bidib.jbidibc.pomupdate.DecoderInformation;
import org.bidib.jbidibc.pomupdate.DecoderPomUpdate;
import org.bidib.wizard.dialog.CustomDialog;
import org.bidib.wizard.dialog.FileDialog;
import org.bidib.wizard.locale.Resources;
import org.bidib.wizard.mvc.common.view.DockKeys;
import org.bidib.wizard.mvc.common.view.button.JSplitButton;
import org.bidib.wizard.mvc.common.view.button.listener.SplitButtonActionListener;
import org.bidib.wizard.mvc.common.view.table.ProgressCellRender;
import org.bidib.wizard.mvc.main.view.menu.BasicPopupMenu;
import org.bidib.wizard.mvc.main.view.table.AbstractEmptyTable;
import org.bidib.wizard.mvc.pomupdate.model.Decoder;
import org.bidib.wizard.mvc.pomupdate.model.PomUpdateModel;
import org.bidib.wizard.mvc.pomupdate.view.listener.DecoderInfoStatusListener;
import org.bidib.wizard.mvc.pomupdate.view.listener.PomUpdatePerformStatusListener;
import org.bidib.wizard.mvc.pomupdate.view.listener.PomUpdateStatusListener;
import org.bidib.wizard.mvc.pomupdate.view.listener.PomUpdateViewListener;
import org.bidib.wizard.mvc.script.view.NodeScriptView;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.prompt.PromptSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.SingleListSelectionAdapter;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.beans.PropertyConnector;
import com.jgoodies.binding.list.SelectionInList;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.builder.ButtonBarBuilder;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.FormLayout;
import com.vlsolutions.swing.docking.DockKey;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.event.DockableStateChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateChangeListener;

public class PomUpdateView implements Dockable {

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

    private static final String SUFFIX_DECODERFILE = "hex";

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

    public static final KeyStroke KEYSTROKE_ADD_DECODER = KeyStroke
        .getKeyStroke(KeyEvent.VK_ADD, ActionEvent.CTRL_MASK);

    private final DockableStateChangeListener dockableStateChangeListener;

    private final DockingDesktop desktop;

    private final JPanel contentPanel;

    private ValueModel decoderFileValueModel;

    private final PomUpdateModel pomUpdateModel;

    private SelectionInList decoderSelection;

    private final PomUpdateTableModel tableModel;

    private final JButton selectDecoderFileButton = new JButton(Resources.getString(getClass(), "select-decoderfile"));

    private final JButton showDecoderFileInfoButton = new JButton(Resources.getString(getClass(),
        "show-decoderfile-info"));

    private final JSplitButton prepareUpdateButton =
        new JSplitButton(Resources.getString(getClass(), "prepare-update"));

    private final JPopupMenu popupMenuSplit;

    private final JButton performUpdateButton = new JButton(Resources.getString(getClass(), "perform-update"));

    private DecoderPomUpdate decoderPomUpdate;

    private final JPopupMenu popupMenu;

    private JMenuItem addAddressItem;

    private JMenuItem removeAddressItem;

    private JMenuItem loadDecoderInfoItem;

    private JMenuItem forcePrepareItem;

    private final AbstractEmptyTable decoderTable;

    private DecoderInformation decoderInformation;

    private final Collection listeners = new LinkedList<>();

    private final FileFilter ff = new FileFilter() {

        @Override
        public boolean accept(File file) {
            boolean result = false;

            if (file != null) {
                if (file.isDirectory()) {
                    result = true;
                }
                else if (FilenameUtils.wildcardMatch(file.getName(), "*." + SUFFIX_DECODERFILE)) {
                    result = true;
                }
            }
            return result;
        }

        @Override
        public String getDescription() {
            return Resources.getString(NodeScriptView.class, "filter") + " (*." + SUFFIX_DECODERFILE + ")";
        }
    };

    public PomUpdateView(final DockingDesktop desktop, final PomUpdateModel pomUpdateModel) {
        this.desktop = desktop;

        DockKeys.DOCKKEY_POM_UPDATE_VIEW.setName(Resources.getString(getClass(), "title"));
        DockKeys.DOCKKEY_POM_UPDATE_VIEW.setFloatEnabled(true);
        DockKeys.DOCKKEY_POM_UPDATE_VIEW.setAutoHideEnabled(false);

        LOGGER.info("Create new PomUpdateView");

        dockableStateChangeListener = new DockableStateChangeListener() {

            @Override
            public void dockableStateChanged(DockableStateChangeEvent event) {
                LOGGER.info("The state has changed, newState: {}, prevState: {}", event.getNewState(),
                    event.getPreviousState());

                DockableState newState = event.getNewState();
                if (newState.getDockable().equals(PomUpdateView.this) && newState.isClosed()) {
                    LOGGER.info("The DebugInterfaceView is closed.");
                    // we are closed
                    desktop.removeDockableStateChangeListener(dockableStateChangeListener);

                    fireClose();
                }

            }
        };
        desktop.addDockableStateChangeListener(dockableStateChangeListener);

        this.pomUpdateModel = pomUpdateModel;

        decoderPomUpdate = new DecoderPomUpdate();

        decoderSelection = new SelectionInList((ListModel) pomUpdateModel.getDecoderListModel());

        tableModel = new PomUpdateTableModel(decoderSelection);

        popupMenu = new BasicPopupMenu() {
            private static final long serialVersionUID = 1L;
        };
        prepareMenuItems(popupMenu);

        // prepare the popup menu for the split button
        popupMenuSplit = new BasicPopupMenu() {
            private static final long serialVersionUID = 1L;
        };
        forcePrepareItem = new JMenuItem(Resources.getString(getClass(), "prepare-force-update"));

        forcePrepareItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                prepareUpdate(true);
            }
        });
        addMenuItem(popupMenuSplit, forcePrepareItem);
        // add the popup menu to the split button
        prepareUpdateButton.setPopupMenu(popupMenuSplit);

        // create a decoder table
        decoderTable = new AbstractEmptyTable(
            tableModel, Resources.getString(getClass(), "empty_table")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isSkipPackColumn() {
                return true;
            }
        };

        decoderTable.setSelectionModel(new SingleListSelectionAdapter(decoderSelection.getSelectionIndexHolder()));
        decoderTable.getColumn(PomUpdateTableModel.COLUMN_PERFORM_PROGRESS).setCellRenderer(
            new ProgressCellRender(true));
        decoderTable.getColumn(PomUpdateTableModel.COLUMN_PREPARE_PROGRESS).setCellRenderer(
            new ProgressCellRender(true));

        Highlighter simpleStriping = HighlighterFactory.createSimpleStriping();
        decoderTable.setHighlighters(simpleStriping);

        decoderTable
            .getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KEYSTROKE_ADD_DECODER, "addDecoder");
        decoderTable.getActionMap().put("addDecoder", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                fireAddAddress();
            }
        });
        decoderTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

        // create form builder
        DefaultFormBuilder dialogBuilder = null;
        boolean debugDialog = false;
        if (debugDialog) {
            JPanel panel = new FormDebugPanel();
            dialogBuilder = new DefaultFormBuilder(new FormLayout(ENCODED_DIALOG_COLUMN_SPECS), panel);
        }
        else {
            JPanel panel = new JPanel(new BorderLayout());
            dialogBuilder = new DefaultFormBuilder(new FormLayout(ENCODED_DIALOG_COLUMN_SPECS), panel);
        }
        dialogBuilder.border(Borders.DIALOG);

        // add some components
        decoderFileValueModel =
            new PropertyAdapter(pomUpdateModel, PomUpdateModel.PROPERTY_DECODER_FILE, true);
        JTextField selectedFileText = BasicComponentFactory.createTextField(decoderFileValueModel, true);
        selectedFileText.setEditable(false);
        PromptSupport.init(Resources.getString(getClass(), "select-decoderfile.prompt"), null, null, selectedFileText);
        dialogBuilder.append(Resources.getString(getClass(), "decoderFile"), selectedFileText);

        // prepare the select and info button
        JPanel firmwareActionButtons =
            new ButtonBarBuilder()
                .addButton(selectDecoderFileButton).addGlue().addButton(showDecoderFileInfoButton).build();
        dialogBuilder.append(firmwareActionButtons);

        // add bindings for enable/disable the show decoder info button
        PropertyConnector.connect(pomUpdateModel, PomUpdateModel.PROPERTY_HAS_FIRMWARE_AVAILABLE,
            showDecoderFileInfoButton, "enabled");

        selectDecoderFileButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                fireSelectDecoderFile();
            }
        });

        showDecoderFileInfoButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                showDecoderFileInfo();
            }
        });
        showDecoderFileInfoButton.setEnabled(false);

        dialogBuilder.appendRow("3dlu");
        dialogBuilder.appendRow("fill:p:grow");
        dialogBuilder.nextLine(2);

        final JScrollPane scrollTable = new JScrollPane(decoderTable);
        dialogBuilder.append(scrollTable, 7);

        dialogBuilder.appendRow("3dlu");
        dialogBuilder.appendRow("pref");
        dialogBuilder.nextLine(2);

        // prepare the prepare and perform update button
        JPanel pomUpdateActionButtons =
            new ButtonBarBuilder()
                .addButton(prepareUpdateButton).addRelatedGap().addButton(performUpdateButton).build();
        dialogBuilder.append(pomUpdateActionButtons, 7);

        prepareUpdateButton.addSplitButtonActionListener(new SplitButtonActionListener() {

            @Override
            public void buttonClicked(ActionEvent e) {
                LOGGER.info("Button clicked!");
                prepareUpdate(false);
            }

            @Override
            public void splitButtonClicked(ActionEvent e) {
                LOGGER.info("Split button clicked!");
                // do nothing here ... the user must click the menu item
            }
        });

        performUpdateButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                performUpdate();
            }
        });
        prepareUpdateButton.setEnabled(false);
        performUpdateButton.setEnabled(false);

        // add bindings for enable/disable the prepare and perform update button
        PropertyConnector.connect(pomUpdateModel, PomUpdateModel.PROPERTY_READY_FOR_PREPARE_UPDATE,
            prepareUpdateButton, "enabled");
        PropertyConnector.connect(pomUpdateModel, PomUpdateModel.PROPERTY_READY_FOR_PERFORM_UPDATE,
            performUpdateButton, "enabled");

        contentPanel = dialogBuilder.build();

        decoderTable.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (e.getClickCount() == 1 && e.isPopupTrigger()) {
                    LOGGER.debug("Show the popup menu.");

                    e.consume();

                    // prepare the popup menu items
                    if (!pomUpdateModel.isUpdateInProgress()) {
                        addAddressItem.setEnabled(true);
                    }
                    else {
                        addAddressItem.setEnabled(false);
                    }

                    if (decoderTable.getSelectedRowCount() > 0 && !pomUpdateModel.isUpdateInProgress()) {
                        removeAddressItem.setEnabled(true);
                        loadDecoderInfoItem.setEnabled(true);
                    }
                    else {
                        removeAddressItem.setEnabled(false);
                        loadDecoderInfoItem.setEnabled(false);
                    }

                    popupMenu.show(decoderTable, e.getX(), e.getY());
                }
            }

            public void mouseReleased(MouseEvent e) {
                LOGGER.debug("Mouse released.");
                if (e.isPopupTrigger()) {
                    mousePressed(e);
                }
            }
        });

        pomUpdateModel.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                LOGGER.info("Property changed: {}", evt);

                if (evt.getPropertyName() == null) {
                    // multiple change
                    LOGGER.info("Update table data.");
                    tableModel.fireTableDataChanged();
                }
            }
        });
    }

    protected void prepareUpdate(boolean forceUpdate) {
        LOGGER.info("Prepare udpate, forceUpdate: {}", forceUpdate);

        if (CollectionUtils.isEmpty(pomUpdateModel.getDecoderListModel())) {
            LOGGER.warn("No decoders to update specified!");
        }

        final Map> prepareUpdateMap = new LinkedHashMap<>();

        DecoderInformation updateDecoderInformation = decoderInformation;
        // support for force the update
        if (forceUpdate) {
            LOGGER.info("Force the firmware update!");
            updateDecoderInformation = new DecoderInformation(decoderInformation);
            updateDecoderInformation.forceFirmwareUpdate();
        }

        for (Decoder decoder : pomUpdateModel.getDecoderListModel()) {
            LOGGER.info("Prepare update list for decoder: {}", decoder);

            List prepareUpdateList =
                decoderPomUpdate.prepareDecoderInfoPomMessages(updateDecoderInformation, decoder.getAddress());

            prepareUpdateMap.put(decoder, prepareUpdateList);
        }

        // let the controller send the prepare update packets to the decoders
        firePrepareUpdate(prepareUpdateMap);
    }

    private void firePrepareUpdate(final Map> prepareUpdateMap) {

        final PomUpdateStatusListener statusListener = new PomUpdateStatusListener() {

            @Override
            public void updateStatus(final Decoder decoder, final int progress) {
                LOGGER.info("Update the prepare status for decoder: {}, progress: {}", decoder, progress);
                if (SwingUtilities.isEventDispatchThread()) {
                    tableModel.updatePrepareStatus(decoder, progress);

                    if (decoder.isPrepareUpdateDone()) {
                        pomUpdateModel.checkPendingPrepare();
                    }
                }
                else {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            tableModel.updatePrepareStatus(decoder, progress);

                            if (decoder.isPrepareUpdateDone()) {
                                pomUpdateModel.checkPendingPrepare();
                            }
                        }
                    });
                }
            }
        };

        for (PomUpdateViewListener l : listeners) {
            l.prepareUpdate(prepareUpdateMap, statusListener);
        }
    }

    protected void performUpdate() {
        // Prepare the prepareDecoderUpdatePomMessages
        List updateMessages =
            decoderPomUpdate.prepareDecoderUpdatePomMessages(pomUpdateModel.getFirmwareContent(), decoderInformation);
        firePerformUpdate(updateMessages);
    }

    private void firePerformUpdate(final List updateMessages) {

        pomUpdateModel.setUpdateInProgress(true);

        final int totalPackets = updateMessages.size();
        LOGGER.info("Perform update with total number of POM packets: {}", totalPackets);

        final PomUpdatePerformStatusListener statusListener = new PomUpdatePerformStatusListener() {

            @Override
            public void updateStatus(final int progress) {
                LOGGER.info("Update the perform status, progress: {}", progress);

                if (SwingUtilities.isEventDispatchThread()) {
                    tableModel.updatePerformStatus(null, progress);
                }
                else {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            tableModel.updatePerformStatus(null, progress);
                        }
                    });
                }
            }

            @Override
            public void finished() {
                LOGGER.info("Perform update has finished.");
                pomUpdateModel.setUpdateInProgress(false);
            }
        };

        for (PomUpdateViewListener l : listeners) {
            l.performUpdate(updateMessages, statusListener);
        }

    }

    private void prepareMenuItems(JPopupMenu menu) {
        addAddressItem = new JMenuItem(Resources.getString(getClass(), "addAddress") + " ...");
        addAddressItem.setAccelerator(KEYSTROKE_ADD_DECODER);

        addAddressItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                fireAddAddress();
            }
        });
        addMenuItem(menu, addAddressItem);

        removeAddressItem = new JMenuItem(Resources.getString(getClass(), "removeAddress") + " ...");

        removeAddressItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                fireRemoveAddress();
            }
        });
        addMenuItem(menu, removeAddressItem);

        loadDecoderInfoItem = new JMenuItem(Resources.getString(getClass(), "loadDecoderInfo") + " ...");

        loadDecoderInfoItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                fireLoadDecoderInfo();
            }
        });
        addMenuItem(menu, loadDecoderInfoItem);
    }

    private void addMenuItem(Object menu, JMenuItem menuItem) {
        if (menu instanceof JMenu) {
            ((JMenu) menu).add(menuItem);
        }
        else if (menu instanceof JPopupMenu) {
            ((JPopupMenu) menu).add(menuItem);
        }
    }

    private void fireClose() {
        for (PomUpdateViewListener l : listeners) {
            l.close();
        }

        desktop.addDockableStateChangeListener(dockableStateChangeListener);
    }

    private void fireAddAddress() {
        Frame frame = JOptionPane.getFrameForComponent(contentPanel);
        // add new address
        CustomDialog customDialog =
            new CustomDialog(frame, Resources.getString(getClass(), "enterDecoderAddress.title"), Resources.getString(
                getClass(), "enterDecoderAddress.message"), Resources.getString(getClass(),
                "enterDecoderAddress.buttonAdd"), Resources.getString(getClass(), "enterDecoderAddress.buttonCancel"));
        customDialog.pack();
        customDialog.setLocationRelativeTo(contentPanel);
        customDialog.setVisible(true);

        String decoderAddress = customDialog.getValidatedText();
        if (StringUtils.isNotBlank(decoderAddress)) {
            LOGGER.info("Adding new decoder address: {}", decoderAddress);
            pomUpdateModel.addDecoder(new Decoder(Integer.parseInt(decoderAddress)));
        }
    }

    private void fireRemoveAddress() {
        List decodersToRemove = new ArrayList<>();
        int[] selectedRows = decoderTable.getSelectedRows();
        for (int row : selectedRows) {
            row = decoderTable.convertRowIndexToModel(row);
            Decoder rowValue = tableModel.getRow(row);
            decodersToRemove.add(rowValue);
        }

        for (Decoder decoderAddress : decodersToRemove) {
            LOGGER.debug("Remove decoder address: {}", decoderAddress);
            pomUpdateModel.removeDecoder(decoderAddress);
        }
    }

    private void fireLoadDecoderInfo() {
        List decodersToLoadInfo = new ArrayList<>();
        int[] selectedRows = decoderTable.getSelectedRows();
        for (int row : selectedRows) {
            row = decoderTable.convertRowIndexToModel(row);
            Decoder rowValue = tableModel.getRow(row);
            decodersToLoadInfo.add(rowValue);
        }

        LOGGER.debug("Load decoder info for decoders: {}", decodersToLoadInfo);

        final DecoderInfoStatusListener statusListener = new DecoderInfoStatusListener() {

            @Override
            public void updateStatus(int progress) {
                LOGGER.info("Update status: {}", progress);
            }

            @Override
            public void finished() {
                LOGGER.info("Get decoder info has finished.");
            }
        };

        for (PomUpdateViewListener l : listeners) {
            l.performLoadDecoderInfo(decodersToLoadInfo, statusListener);
        }
    }

    protected void fireSelectDecoderFile() {
        final FileDialog dialog = new FileDialog(
            contentPanel, FileDialog.OPEN, null, ff) {
            @Override
            public void approve(String selectedFile) {
                File file = new File(selectedFile);

                selectedFile = file.getName();

                try {
                    pomUpdateModel.setDecoderFile(null);
                    decoderInformation = null;

                    loadScript(file);
                    pomUpdateModel.setDecoderFile(file.toString());
                }
                catch (IOException ex) {
                    LOGGER.warn("Load decoder firmware from file failed.", ex);
                    pomUpdateModel.setFirmwareContent(null);
                    JOptionPane.showMessageDialog(contentPanel,
                        Resources.getString(PomUpdateView.class, "load-firmware-failed"));
                }
                catch (IllegalArgumentException ex) {
                    LOGGER.warn("Load firmware from file failed.", ex);
                    pomUpdateModel.setFirmwareContent(null);
                    JOptionPane.showMessageDialog(contentPanel,
                        Resources.getString(PomUpdateView.class, "invalid-firmware-file"));
                }
            }
        };
        dialog.showDialog();
    }

    private void loadScript(File decoderFirmare) throws IOException {
        LOGGER.info("Load decoder firmware from file: {}", decoderFirmare);

        List firmwareContent = decoderPomUpdate.loadFirmwareFile(decoderFirmare);

        // this will throw an IllegalArgumentException if the content does not contain the security line
        decoderInformation = decoderPomUpdate.findDecoderInformation(firmwareContent);

        pomUpdateModel.setFirmwareContent(firmwareContent);
    }

    protected void showDecoderFileInfo() {
        LOGGER.info("Loaded decoder information: {}", decoderInformation);
        // TODO show nice dialog

        DecoderFirmwareInfoDialog infoDialog = new DecoderFirmwareInfoDialog(null, null, true);
        infoDialog.setDecoderInfo(decoderInformation);

        infoDialog.showDialog(contentPanel);
    }

    @Override
    public DockKey getDockKey() {
        return DockKeys.DOCKKEY_POM_UPDATE_VIEW;
    }

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

    public void addPomUpdateViewListener(PomUpdateViewListener l) {
        listeners.add(l);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy