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

org.bidib.wizard.mvc.debug.view.DebugInterfaceView Maven / Gradle / Ivy

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

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
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.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.bidib.jbidibc.debug.LineEndingEnum;
import org.bidib.jbidibc.messages.exception.InvalidLibraryException;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.rxtx.PortIdentifierUtils;
import org.bidib.jbidibc.scm.ScmPortIdentifierUtils;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.api.model.common.CommPort;
import org.bidib.wizard.client.common.component.TextLineNumber;
import org.bidib.wizard.client.common.text.WizardComponentFactory;
import org.bidib.wizard.client.common.view.BasicPopupMenu;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.common.model.settings.WizardSettingsInterface;
import org.bidib.wizard.common.service.SettingsService;
import org.bidib.wizard.common.utils.ImageUtils;
import org.bidib.wizard.core.dialog.FileDialog;
import org.bidib.wizard.mvc.common.view.text.ChangeDocumentListener;
import org.bidib.wizard.mvc.common.view.text.CopyAllAction;
import org.bidib.wizard.mvc.common.view.text.HistoryModel;
import org.bidib.wizard.mvc.common.view.text.HistoryTextField;
import org.bidib.wizard.mvc.common.view.text.JTextFieldLimitDocument;
import org.bidib.wizard.mvc.debug.controller.listener.DebugInterfaceControllerListener;
import org.bidib.wizard.mvc.debug.model.DebugInterfaceModel;
import org.bidib.wizard.mvc.debug.view.listener.DebugInterfaceViewListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.adapter.ComboBoxAdapter;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.beans.PropertyConnector;
import com.jgoodies.binding.list.SelectionInList;
import com.jgoodies.binding.value.ConverterValueModel;
import com.jgoodies.binding.value.ValueModel;
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.DefaultOverlayable;
import com.jidesoft.swing.JideScrollPane;
import com.jidesoft.swing.StyledLabelBuilder;
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 DebugInterfaceView implements Dockable {
    private static final Logger LOGGER = LoggerFactory.getLogger(DebugInterfaceView.class);

    private static final String ENCODED_DIALOG_COLUMN_SPECS =
        "pref, 3dlu, max(100dlu;pref), 3dlu, 20dlu, 3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu, fill:pref:grow, 3dlu, pref, 3dlu, pref";

    private static final String ENCODED_DIALOG_ROW_SPECS = "p, 3dlu, p, 3dlu, fill:50dlu:grow, 3dlu, p, 3dlu, p";

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

    private final DebugInterfaceModel debugInterfaceModel;

    private final DockableStateChangeListener dockableStateChangeListener;

    private final JComponent contentPanel;

    private ValueModel selectionHolderComPort;

    private ValueModel sendTextValueModel;

    private ValueModel selectionHolderBaudRate;

    private ValueModel sendFileValueModel;

    private JTextField sendText;

    private JTextField sendFile;

    private final JButton refreshButton = new JButton(Resources.getString(getClass(), "refresh"));

    private final JButton connectButton = new JButton(Resources.getString(getClass(), "connect"));

    private final JButton disconnectButton = new JButton(Resources.getString(getClass(), "disconnect"));

    private final JButton transmitButton = new JButton(Resources.getString(getClass(), "transmit"));

    private final JButton transmitFileButton = new JButton(Resources.getString(getClass(), "transmit"));

    private final JButton selectFileButton = new JButton(Resources.getString(getClass(), "selectFile"));

    private JCheckBoxMenuItem autoScrollsItem;

    private final JScrollPane logsPane;

    private final JTextArea logsArea;

    private TextLineNumber tln;

    private SelectionInList commPortSelection;

    private boolean timestampsEnabled;

    private JCheckBox addTimeStamps;

    private JCheckBox logToFile;

    private ValueModel selectedLogFileValueModel;

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

    private final SettingsService settingsService;

    public DebugInterfaceView(final DockingDesktop desktop, final DebugInterfaceControllerListener listener,
        final DebugInterfaceModel debugInterfaceModel, final SettingsService settingsService) {

        this.settingsService = settingsService;
        this.debugInterfaceModel = debugInterfaceModel;

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

        this.dockableStateChangeListener = new DockableStateChangeListener() {

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

                DockableState newState = event.getNewState();
                if (newState.getDockable().equals(DebugInterfaceView.this) && newState.isClosed()) {
                    LOGGER.info("The DebugInterfaceView is closed.");
                    // we are closed
                    try {
                        desktop.removeDockableStateChangeListener(dockableStateChangeListener);
                    }
                    catch (Exception ex) {
                        LOGGER
                            .warn("Remove dockableStateChangeListener from desktop failed: "
                                + dockableStateChangeListener, ex);
                    }

                    if (listener != null) {
                        LOGGER.info("Close the view.");

                        listener.viewClosed();
                    }
                }

            }
        };
        desktop.addDockableStateChangeListener(dockableStateChangeListener);

        LOGGER.info("Create new DebugInterfaceView");

        loadPortIdentifiers(this.debugInterfaceModel);

        // create form builder
        FormBuilder dialogBuilder = null;
        boolean debugDialog = false;
        if (debugDialog) {
            JPanel panel = new FormDebugPanel();
            dialogBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        else {
            JPanel panel = new JPanel(new BorderLayout());
            dialogBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        dialogBuilder.border(Paddings.DIALOG);

        // add some components
        commPortSelection =
            new SelectionInList((ListModel) this.debugInterfaceModel.getCommPortsListModel());

        selectionHolderComPort =
            new PropertyAdapter(this.debugInterfaceModel,
                DebugInterfaceModel.PROPERTY_SELECTED_PORT, true);

        ComboBoxAdapter comboBoxAdapterCommPorts =
            new ComboBoxAdapter(commPortSelection, selectionHolderComPort);
        JComboBox comboCommPorts = new JComboBox();
        comboCommPorts.setModel(comboBoxAdapterCommPorts);
        if (this.debugInterfaceModel.getSelectedPort() != null) {
            comboCommPorts.setSelectedItem(this.debugInterfaceModel.getSelectedPort());
        }

        dialogBuilder.add(Resources.getString(getClass(), "selectedPort")).xy(1, 1);
        dialogBuilder.add(comboCommPorts).xy(3, 1);

        refreshButton.setBorder(new EmptyBorder(5, 5, 5, 5));
        dialogBuilder.add(refreshButton).xy(5, 1);

        List baudRates = new ArrayList<>();
        baudRates.add(Integer.valueOf(19200));
        baudRates.add(Integer.valueOf(115200));
        selectionHolderBaudRate =
            new PropertyAdapter(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_BAUDRATE,
                true);
        ComboBoxAdapter comboBoxAdapter = new ComboBoxAdapter(baudRates, selectionHolderBaudRate);

        JComboBox comboBaudRate = new JComboBox<>();
        comboBaudRate.setModel(comboBoxAdapter);
        comboBaudRate.setSelectedIndex(1);

        dialogBuilder.add(Resources.getString(getClass(), "selectedBaudRate")).xy(7, 1);
        dialogBuilder.add(comboBaudRate).xy(9, 1);

        refreshButton.addActionListener(new ActionListener() {

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

        connectButton.addActionListener(new ActionListener() {

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

            @Override
            public void actionPerformed(ActionEvent e) {
                fireDisconnect();
            }
        });
        disconnectButton.setEnabled(false);

        List lineEndings = new ArrayList<>();
        for (LineEndingEnum lineEnding : LineEndingEnum.values()) {
            lineEndings.add(lineEnding);
        }
        // SelectionInList lineEndingSelection = new SelectionInList<>(lineEndings);
        ValueModel selectionHolderLineEnding =
            new PropertyAdapter(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_LINE_ENDING,
                true);
        ComboBoxAdapter comboAdapterLineEnding =
            new ComboBoxAdapter(lineEndings, selectionHolderLineEnding);

        JComboBox comboLineEnding = new JComboBox<>();
        comboLineEnding.setModel(comboAdapterLineEnding);
        comboLineEnding.setRenderer(new LineEndingCellRenderer());

        // prepare the connect and disconnect button
        JPanel debugInterfaceActionButtons =
            new ButtonBarBuilder().addButton(connectButton).addRelatedGap().addButton(disconnectButton).build();
        dialogBuilder.add(debugInterfaceActionButtons).xy(11, 1);

        addTimeStamps = new JCheckBox(Resources.getString(getClass(), "addTimestamps"));
        dialogBuilder.add(addTimeStamps).xy(13, 1);

        // dialogBuilder.appendRows("p");

        logToFile = new JCheckBox(Resources.getString(getClass(), "logToFile"));
        dialogBuilder.add(logToFile).xy(1, 3);

        selectedLogFileValueModel =
            new PropertyAdapter(this.debugInterfaceModel,
                DebugInterfaceModel.PROPERTY_LOGFILE_NAME, true);
        JTextField selectedLogFileText = WizardComponentFactory.createTextField(selectedLogFileValueModel, true);
        selectedLogFileText.setEditable(false);
        dialogBuilder.add(selectedLogFileText).xy(3, 3);

        // prepare the select and save button

        selectLogFileButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                selectLogFile();
            }
        });
        dialogBuilder.add(selectLogFileButton).xy(5, 3);

        dialogBuilder.add(Resources.getString(getClass(), "lineEnding")).xy(7, 3);
        dialogBuilder.add(comboLineEnding).xy(9, 3);

        // add the log area
        this.logsPane = new JScrollPane() {

            private static final long serialVersionUID = 1L;

            @Override
            protected void processMouseWheelEvent(MouseWheelEvent e) {
                super.processMouseWheelEvent(e);

                if (logsArea.getAutoscrolls()) {
                    LOGGER.info("Clear the flag on the autoScrollsItem and disable autoscrolls.");
                    // clear the flag on the autoScrollsItem
                    autoScrollsItem.setSelected(false);
                    logsArea.setAutoscrolls(false);
                    logsArea.setCaretPosition(logsArea.getCaretPosition() > 0 ? logsArea.getCaretPosition() - 1 : 0);
                }
            }

        };

        this.logsArea = new JTextArea() {

            private static final long serialVersionUID = 1L;

            @Override
            protected void processMouseEvent(MouseEvent e) {
                if (getAutoscrolls() && e.getID() == MouseEvent.MOUSE_RELEASED && !e.isPopupTrigger()) {
                    LOGGER.info("Clear the flag on the autoScrollsItem.");
                    // clear the flag on the autoScrollsItem
                    autoScrollsItem.setSelected(false);
                }
                super.processMouseEvent(e);
            }
        };
        this.logsArea.setEditable(false);
        this.logsArea.setFont(new Font("Monospaced", Font.PLAIN, 13));
        logsPane.getViewport().add(logsArea, null);

        // JScrollPane logsPane = new JScrollPane(this.logsArea);

        tln = new TextLineNumber(this.logsArea);
        logsPane.setRowHeaderView(tln);

        logsPane.setAutoscrolls(true);
        dialogBuilder.add(logsPane).xyw(1, 5, 17);

        // create the textfield for send message to debug
        sendTextValueModel =
            new PropertyAdapter(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_SEND_TEXT,
                true);

        // set default 20 items in history
        HistoryModel.setMax(20);
        sendText = new HistoryTextField("sendText", false, true);
        final Document sendTextDoc = new JTextFieldLimitDocument(128);
        sendText.setDocument(sendTextDoc);
        Bindings.bind(sendText, sendTextValueModel, false);

        sendText.addActionListener(new ActionListener() {

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

        final DefaultOverlayable sendTextOverlayable = new DefaultOverlayable(sendText);
        sendTextOverlayable
            .addOverlayComponent(
                StyledLabelBuilder
                    .createStyledLabel("{" + Resources.getString(getClass(), "transmitText.prompt") + ":f:gray}"),
                SwingConstants.WEST);
        sendTextOverlayable.setOverlayLocationInsets(new Insets(0, -5, 0, 5));

        // final Document sendTextDoc = sendText.getDocument();
        sendTextDoc
            .addDocumentListener(
                new ChangeDocumentListener(doc -> sendTextOverlayable.setOverlayVisible(doc.getLength() < 1)));
        sendTextOverlayable.setOverlayVisible(sendTextDoc.getLength() < 1);

        dialogBuilder.add(Resources.getString(getClass(), "transmitText")).xy(1, 7);
        dialogBuilder.add(sendTextOverlayable).xyw(3, 7, 13);

        dialogBuilder.add(transmitButton).xy(17, 7);
        transmitButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                fireTransmit();
            }
        });
        transmitButton.setEnabled(false);

        // create the textfield for filename
        sendFileValueModel =
            new PropertyAdapter(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_SEND_FILE,
                true);
        final ValueModel sendFileConverterModel =
            new ConverterValueModel(sendFileValueModel, new FileStringConverter());
        sendFile = WizardComponentFactory.createTextField(sendFileConverterModel, true);
        sendFile.setEditable(false);

        final DefaultOverlayable sendFileOverlayable = new DefaultOverlayable(sendFile);
        sendFileOverlayable
            .addOverlayComponent(
                StyledLabelBuilder
                    .createStyledLabel("{" + Resources.getString(getClass(), "transmitFile.prompt") + ":f:gray}"),
                SwingConstants.WEST);
        sendFileOverlayable.setOverlayLocationInsets(new Insets(0, -5, 0, 5));

        final Document sendFileDoc = sendFile.getDocument();
        sendFileDoc
            .addDocumentListener(
                new ChangeDocumentListener(doc -> sendFileOverlayable.setOverlayVisible(doc.getLength() < 1)));
        sendFileOverlayable.setOverlayVisible(sendFileDoc.getLength() < 1);

        dialogBuilder.add(Resources.getString(getClass(), "transmitFile")).xy(1, 9);
        dialogBuilder.add(sendFileOverlayable).xyw(3, 9, 11);

        dialogBuilder.add(selectFileButton).xy(15, 9);
        selectFileButton.addActionListener(new ActionListener() {

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

        dialogBuilder.add(transmitFileButton).xy(17, 9);
        transmitFileButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                fireTransmitFile();
            }
        });
        transmitFileButton.setEnabled(false);

        // add bindings for enable/disable the send button
        PropertyConnector
            .connect(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_TRANSMIT_ENABLED, transmitButton,
                "enabled");
        PropertyConnector
            .connect(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_TRANSMIT_ENABLED, transmitFileButton,
                "enabled");

        PropertyConnector
            .connect(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_DISCONNECTED, connectButton, "enabled");
        PropertyConnector
            .connect(this.debugInterfaceModel, DebugInterfaceModel.PROPERTY_CONNECTED, disconnectButton, "enabled");

        JPanel contentPanelTemp = dialogBuilder.build();

        JideScrollPane scrollPane = new JideScrollPane(contentPanelTemp);
        contentPanel = scrollPane;

        final JPopupMenu popupMenu = new BasicPopupMenu();
        prepareMenuItems(popupMenu);

        logsArea.setComponentPopupMenu(popupMenu);

        addTimeStamps.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                final boolean timestampsEnabled = e.getStateChange() == ItemEvent.SELECTED;

                fireTimestampsEnabledChanged(timestampsEnabled);
            }
        });

        logToFile.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                final boolean logToFileEnabled = e.getStateChange() == ItemEvent.SELECTED;

                DebugInterfaceView.this.debugInterfaceModel.setLogToFile(logToFileEnabled);
            }
        });
    }

    private void prepareMenuItems(JPopupMenu menu) {
        autoScrollsItem = new JCheckBoxMenuItem(Resources.getString(getClass(), "autoScrolls"));
        autoScrollsItem.setSelected(true);

        autoScrollsItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                logsArea.setCaretPosition(logsArea.getDocument().getLength());
                boolean enabled = autoScrollsItem.isSelected();
                LOGGER.info("Set autoscrolls enabled: {}", enabled);
                if (enabled) {
                    logsArea.setAutoscrolls(enabled);
                    logsArea.setCaretPosition(logsArea.getDocument().getLength());
                }
                else {
                    logsArea.setAutoscrolls(enabled);
                    logsArea.setCaretPosition(logsArea.getCaretPosition() > 0 ? logsArea.getCaretPosition() - 1 : 0);
                }
            }
        });
        addMenuItem(menu, autoScrollsItem);

        JMenuItem clearConsole = new JMenuItem(Resources.getString(getClass(), "clear_console"));
        clearConsole.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireClearConsole();
            }
        });
        addMenuItem(menu, clearConsole);

        JMenuItem copyAllToClipboard = new JMenuItem(Resources.getString(getClass(), "copyAllToClipboard"));
        copyAllToClipboard.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireCopyAllToClipboard();
            }
        });
        addMenuItem(menu, copyAllToClipboard);

        JMenuItem saveToFile =
            new JMenuItem(Resources.getString(getClass(), "save_to_file"),
                ImageUtils.createImageIcon(getClass(), "/icons/savetofile.png"));
        saveToFile.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireSaveToFile(false);
            }
        });
        addMenuItem(menu, saveToFile);

        JMenuItem saveSelectedToFile =
            new JMenuItem(Resources.getString(getClass(), "save_selected_to_file"),
                ImageUtils.createImageIcon(getClass(), "/icons/saveselectedtofile.png"));
        saveSelectedToFile.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireSaveToFile(true);
            }
        });
        addMenuItem(menu, saveSelectedToFile);
    }

    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 fireTimestampsEnabledChanged(boolean timestampsEnabled) {
        this.timestampsEnabled = timestampsEnabled;
    }

    private void fireClearConsole() {
        LOGGER.info("clear the console.");

        logsArea.setText(null);
        logsArea.setCaretPosition(logsArea.getDocument().getLength());
        boolean enabled = autoScrollsItem.isSelected();
        logsArea.setAutoscrolls(!enabled);
        logsArea.setAutoscrolls(enabled);
    }

    private void fireCopyAllToClipboard() {
        LOGGER.info("Copy all content to clipboard.");

        ActionEvent copyAll = new ActionEvent(logsArea, 0, CopyAllAction.copyAllAction);
        CopyAllAction action = new CopyAllAction();
        action.actionPerformed(copyAll);
    }

    private static FileFilter logfileFilter;

    private static final String LOGFILE_EXTENSION = "log";

    private static final String WORKING_DIR_DEBUG_INTERFACE_KEY = "debugInterface";

    // description, suffix for node files
    private String savedLogFilesDescription;

    private void fireSaveToFile(final boolean selectedOnly) {
        LOGGER.info("Save the console content to file.");

        savedLogFilesDescription = Resources.getString(getClass(), "savedLogFilesDescription");
        logfileFilter = new FileNameExtensionFilter(savedLogFilesDescription, LOGFILE_EXTENSION);

        final WizardSettingsInterface wizardSettings = settingsService.getWizardSettings();
        String storedWorkingDirectory = wizardSettings.getWorkingDirectory(WORKING_DIR_DEBUG_INTERFACE_KEY);

        FileDialog dialog = new FileDialog(logsArea, FileDialog.SAVE, storedWorkingDirectory, null, logfileFilter) {

            @Override
            public void approve(final String fileName) {
                try {
                    // setWaitCursor();
                    LOGGER.info("Start saving logfile, fileName: {}", fileName);
                    File file = new File(fileName);
                    if (selectedOnly) {
                        FileUtils.write(file, logsArea.getSelectedText(), Charset.forName("UTF-8"));
                    }
                    else {
                        FileUtils.write(file, logsArea.getText(), Charset.forName("UTF-8"));
                    }

                    final String workingDir = Paths.get(fileName).getParent().toString();
                    LOGGER.info("Save current workingDir: {}", workingDir);

                    wizardSettings.setWorkingDirectory(WORKING_DIR_DEBUG_INTERFACE_KEY, workingDir);
                }
                catch (Exception ex) {
                    LOGGER.warn("Save logfile failed.", ex);

                    throw new RuntimeException("Save logfile failed.");
                }
                finally {
                    // setDefaultCursor();
                }
            }
        };
        dialog.showDialog();
    }

    private void fireRefreshComPorts() {
        // Reload the com port identifiers

        loadPortIdentifiers(debugInterfaceModel);
    }

    private void fireConnect() {
        for (DebugInterfaceViewListener listener : listeners) {
            listener.openConnection();
        }
    }

    private void fireDisconnect() {
        for (DebugInterfaceViewListener listener : listeners) {
            listener.closeConnection();
        }
    }

    private void fireTransmit() {

        for (DebugInterfaceViewListener listener : listeners) {
            listener.transmit();
        }
        // clear the text field
        sendText.setText(null);
    }

    private void fireTransmitFile() {

        boolean modal = true;
        // open the progress dialog
        FileTransferProgressDialog progressDialog = new FileTransferProgressDialog(contentPanel, modal, listeners);

    }

    private void fireSelectFile() {
        final WizardSettingsInterface wizardSettings = settingsService.getWizardSettings();
        String storedWorkingDirectory = wizardSettings.getWorkingDirectory(WORKING_DIR_DEBUG_INTERFACE_KEY);

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

                debugInterfaceModel.setSendFile(file);

                final String workingDir = Paths.get(selectedFile).getParent().toString();
                LOGGER.info("Save current workingDir: {}", workingDir);

                wizardSettings.setWorkingDirectory(WORKING_DIR_DEBUG_INTERFACE_KEY, workingDir);
            }
        };
        dialog.showDialog();
    }

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

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

    public void addDebugInterfaceViewListener(DebugInterfaceViewListener l) {
        listeners.add(l);
    }

    private int currentLineLen;

    public void addLog(final String logMessage) {

        SwingUtilities.invokeLater(() -> addLogMessage(logMessage));
    }

    private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");

    public static final String LOGGER_PANE_NAME = "DebugInterfacePane";

    private static final Logger LOGGER_PANE = LoggerFactory.getLogger(LOGGER_PANE_NAME);

    private final StringBuilder sbLogger = new StringBuilder();

    private void addLogMessage(final String logMessage) {

        try {
            int lines = logsArea.getLineCount();
            if (lines > 2000) {
                // remove the first 50 lines
                int end = logsArea.getLineEndOffset(/* lines - */50);
                logsArea.getDocument().remove(0, end);
            }
        }
        catch (BadLocationException ex) {
            LOGGER.warn("Remove some lines from logsArea failed.", ex);
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Received message: {}", ByteUtils.bytesToHex(logMessage.getBytes(StandardCharsets.UTF_8)));
        }

        // ...........\n........
        int beginIndex = 0;
        int index = logMessage.indexOf('\n');
        int lenOfTimestamp = 0;
        if (index > -1) {

            // check if the line contains only leading 00
            if ((index - beginIndex) == 2 && logMessage.charAt(0) == 0x00) {
                LOGGER.warn("Skip empty line with 00 0D 0A.");
                logsArea.append("\n");

                beginIndex = index + 1;

                index = logMessage.indexOf('\n', beginIndex);
            }

            // found line terminator
            while (index > -1) {
                // print the whole line
                String part = logMessage.substring(beginIndex, index);
                // add text to scroll pane
                if (timestampsEnabled && currentLineLen == 0) {
                    // add the timestamp to the line
                    sbLogger.append(sdf.format(new Date()));
                    sbLogger.append(" - ");

                    lenOfTimestamp = 12;

                }
                sbLogger.append(part);

                LOGGER.debug("1. Added part: {}, currentLineLen: {}, sbLogger: {}", part, currentLineLen, sbLogger);

                // the line is complete
                logsArea.append(sbLogger.substring(currentLineLen));
                logsArea.append("\n"/* System.lineSeparator() */);

                LOGGER_PANE.info(sbLogger.toString().replace("\r", ""));

                lenOfTimestamp = 0;
                sbLogger.setLength(0);
                currentLineLen = 0;

                beginIndex = index + 1;

                index = logMessage.indexOf('\n', beginIndex);
            }

            lenOfTimestamp = 0;

            if (beginIndex < logMessage.length()) {
                // add text to scroll pane
                String part = logMessage.substring(beginIndex);
                if (timestampsEnabled) {
                    sbLogger.append(sdf.format(new Date()));
                    sbLogger.append(" - ");

                    lenOfTimestamp = 12;
                }
                sbLogger.append(part);

                LOGGER.debug("2. Added part: {}, currentLineLen: {}, sbLogger: {}", part, currentLineLen, sbLogger);

                logsArea.append(sbLogger.substring(currentLineLen));

                currentLineLen = part.length() + lenOfTimestamp;

                lenOfTimestamp = 0;

                LOGGER.debug("Added last part: {}, currentLineLen: {}", part, currentLineLen);
            }
        }
        else {
            // add text to scroll pane
            if (timestampsEnabled && currentLineLen == 0) {
                LOGGER.debug("Add timestamp, currentLineLen: {}", currentLineLen);

                sbLogger.append(sdf.format(new Date()));
                sbLogger.append(" - ");

                lenOfTimestamp = 12;
                // currentLineLen += lenOfTimestamp;
            }

            String toLog = logMessage;
            // split the received data if necessary
            if (logMessage.length() > (120 - currentLineLen)) {
                int start = 0;
                int end = 0;
                String part = null;
                toLog = null;

                end = start + (120 - currentLineLen);

                while (end < logMessage.length()) {
                    // end = start + (120 - currentLineLen);
                    part = StringUtils.substring(logMessage, start, end);
                    LOGGER.debug("Fetched part, currentLineLen {}, part: '{}'", currentLineLen, part);

                    sbLogger.append(part);

                    logsArea.append(sbLogger.substring(currentLineLen));
                    logsArea.append("\n");

                    currentLineLen = 120;

                    LOGGER.debug("Added logMessage: {}, currentLineLen: {}", logMessage, currentLineLen);

                    currentLineLen = 0;
                    sbLogger.setLength(0);

                    start += 120;
                    end = start + (120 - currentLineLen);
                }

                if (start < logMessage.length()) {
                    LOGGER.debug("Keep the remaining part from logMessage, currentLineLen: {}", currentLineLen, start);
                    toLog = StringUtils.substring(logMessage, start);
                }
            }

            if (StringUtils.isNotBlank(toLog)) {

                if (toLog.charAt(0) == 0x00 && toLog.length() == 1) {
                    LOGGER.warn("Skip empty line with 00.");
                }
                else {
                    sbLogger.append(toLog);

                    LOGGER
                        .debug("3. Added logMessage: {}, currentLineLen: {}, sbLogger: {}", toLog, currentLineLen,
                            sbLogger);

                    logsArea.append(sbLogger.substring(currentLineLen));

                    currentLineLen += toLog.length() + lenOfTimestamp;

                    LOGGER.debug("Added logMessage: {}, currentLineLen: {}", logMessage, currentLineLen);
                }
            }
        }

        // Update and scroll pane to the bottom

        if (currentLineLen > 120) {
            LOGGER.debug("Append new line to logsArea, currentLineLen: {}", currentLineLen);

            // break the line
            logsArea.append("\n");

            LOGGER_PANE.info(sbLogger.toString().replace("\r", ""));

            lenOfTimestamp = 0;
            sbLogger.setLength(0);
            currentLineLen = 0;
        }
        logsArea.invalidate();
    }

    private static final String SUFFIX_HEX = "hex";

    private static final String SUFFIX_EEP = "eep";

    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_HEX)) {
                    result = true;
                }
                else if (FilenameUtils.wildcardMatch(file.getName(), "*." + SUFFIX_EEP)) {
                    result = true;
                }
            }
            return result;
        }

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

    private void loadPortIdentifiers(DebugInterfaceModel model) {
        LOGGER.info("Load the comm ports, model: {}", model);
        Set commPorts = new HashSet();

        try {
            // use PortIdentifierUtils because we must load the RXTX libraries
            List portIdentifiers = null;

            switch (settingsService.getMiscSettings().getSelectedSerialPortProvider()) {
                case "SCM":
                    portIdentifiers = ScmPortIdentifierUtils.getPortIdentifiers();
                    break;

                case "SPSW":
                    portIdentifiers = org.bidib.jbidibc.purejavacomm.PortIdentifierUtils.getPortIdentifiers();
                    break;
                case "JSerialComm":
                    portIdentifiers = org.bidib.jbidibc.jserialcomm.PortIdentifierUtils.getPortIdentifiers();
                    break;
                case "PureJavaComm":
                    portIdentifiers = org.bidib.jbidibc.purejavacomm.PortIdentifierUtils.getPortIdentifiers();
                    break;

                default:
                    portIdentifiers = PortIdentifierUtils.getPortIdentifiers();
                    break;
            }

            if (portIdentifiers != null) {
                for (String id : portIdentifiers) {
                    LOGGER.info("Add new CommPort with id: {}", id);
                    commPorts.add(new CommPort(id));
                }
            }
        }
        catch (InvalidLibraryException ex) {
            LOGGER
                .warn(
                    "Fetch port identifiers failed. This can be caused because the ext/lib directory of the Java installation contains an old RXTXComm.jar!",
                    ex);

            JOptionPane
                .showMessageDialog(contentPanel,
                    Resources
                        .getString(DebugInterfaceView.class, "fetch-port-identifiers-failed",
                            new Object[] { new File(SystemUtils.getJavaHome(), "lib/ext").getPath() }),
                    Resources.getString(DebugInterfaceView.class, "title-error"), JOptionPane.ERROR_MESSAGE);
        }
        catch (Exception ex) {
            LOGGER.warn("Fetch port identifiers failed.", ex);
        }
        model.setCommPorts(Arrays.asList(commPorts.toArray(new CommPort[0])));
    }

    private class LineEndingCellRenderer extends DefaultListCellRenderer {
        private static final long serialVersionUID = 1L;

        private Map labelMap = new HashMap<>();

        public LineEndingCellRenderer() {
            for (LineEndingEnum lineEndingEnum : LineEndingEnum.values()) {
                String label = Resources.getString(LineEndingEnum.class, lineEndingEnum.getKey());
                labelMap.put(lineEndingEnum.getKey(), label);
            }
        }

        @Override
        public Component getListCellRendererComponent(
            JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {

            JLabel renderer = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

            if (value instanceof LineEndingEnum) {
                LineEndingEnum lineEndingEnum = (LineEndingEnum) value;
                renderer.setText(labelMap.get(lineEndingEnum.getKey()));
            }
            else {
                renderer.setText(null);
            }

            return renderer;
        }
    }

    private static final String SUFFIX_LOG = "log";

    private static final String SUFFIX_TXT = "txt";

    private final FileFilter ffLogFile = 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_LOG)) {
                    result = true;
                }
                else if (FilenameUtils.wildcardMatch(file.getName(), "*." + SUFFIX_TXT)) {
                    result = true;
                }
            }
            return result;
        }

        @Override
        public String getDescription() {
            return Resources.getString(DebugInterfaceView.class, "filterLogFile") + " (*." + SUFFIX_LOG + ",*."
                + SUFFIX_TXT + ")";
        }
    };

    private void selectLogFile() {

        final WizardSettingsInterface wizardSettings = settingsService.getWizardSettings();
        String storedWorkingDirectory = wizardSettings.getWorkingDirectory(WORKING_DIR_DEBUG_INTERFACE_KEY);

        final FileDialog dialog =
            new FileDialog(contentPanel, FileDialog.SAVE, storedWorkingDirectory, null, ffLogFile) {
                @Override
                public void approve(final String selectedFile) {
                    File file = new File(selectedFile);

                    debugInterfaceModel.setLogFileName(file.toString());

                    final String workingDir = Paths.get(selectedFile).getParent().toString();
                    LOGGER.info("Save current workingDir: {}", workingDir);

                    wizardSettings.setWorkingDirectory(WORKING_DIR_DEBUG_INTERFACE_KEY, workingDir);
                }
            };
        dialog.showDialog();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy