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

org.bidib.wizard.mvc.netdebug.view.NetDebugView Maven / Gradle / Ivy

There is a newer version: 2.0.29
Show newest version
package org.bidib.wizard.mvc.netdebug.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.io.File;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
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.lang3.StringUtils;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.client.common.component.TextLineNumber;
import org.bidib.wizard.client.common.converter.StringConverter;
import org.bidib.wizard.client.common.text.InputValidationDocument;
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.netdebug.controller.listener.NetDebugControllerListener;
import org.bidib.wizard.mvc.netdebug.model.NetDebugModel;
import org.bidib.wizard.mvc.netdebug.view.listener.NetDebugViewListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.beans.PropertyConnector;
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 NetDebugView implements Dockable {

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

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

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

    private final SettingsService settingsService;

    private final NetDebugModel netDebugModel;

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

    private DockableStateChangeListener dockableStateChangeListener;

    private final JComponent contentPanel;

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

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

    private ValueModel hostValueModel;

    private ValueModel portValueModel;

    private JTextField host;

    private JTextField port;

    private HistoryTextField sendText;

    private ValueModel sendTextValueModel;

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

    private TextLineNumber tln;

    private final JTextArea logsArea = new JTextArea();

    public NetDebugView(final DockingDesktop desktop, final NetDebugControllerListener listener,
        final NetDebugModel netDebugModel, final SettingsService settingsService) {

        this.settingsService = settingsService;
        this.netDebugModel = netDebugModel;

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

        this.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(NetDebugView.this) && newState.isClosed()) {
                    LOGGER.info("The NetDebugView is closed.");
                    // we are closed
                    try {
                        desktop.removeDockableStateChangeListener(dockableStateChangeListener);
                    }
                    catch (Exception ex) {
                        LOGGER
                            .warn("Remove dockableStateChangeListener from desktop failed: "
                                + dockableStateChangeListener, ex);
                    }
                    finally {
                        dockableStateChangeListener = null;
                    }

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

                        listener.viewClosed();
                    }
                }
            }
        };
        desktop.addDockableStateChangeListener(dockableStateChangeListener);

        LOGGER.info("Create new NetDebugView");

        // 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);

        // host
        hostValueModel =
            new PropertyAdapter(this.netDebugModel, NetDebugModel.PROPERTY_SELECTED_HOST, true);

        host = WizardComponentFactory.createTextField(hostValueModel, false);
        dialogBuilder.add(Resources.getString(getClass(), "selectedHost")).xy(1, 1);
        dialogBuilder.add(host).xy(3, 1);

        // port
        portValueModel =
            new PropertyAdapter(this.netDebugModel, NetDebugModel.PROPERTY_SELECTED_PORT, true);
        final ValueModel portConverterModel =
            new ConverterValueModel(portValueModel, new StringConverter(new DecimalFormat("#")));

        // create the textfield for the port
        port = new JTextField();
        InputValidationDocument portDocument = new InputValidationDocument(5, InputValidationDocument.NUMERIC);
        port.setDocument(portDocument);
        port.setColumns(5);

        // bind manually because we changed the document of the textfield
        Bindings.bind(port, portConverterModel, false);
        dialogBuilder.add(Resources.getString(getClass(), "selectedPort")).xy(5, 1);
        dialogBuilder.add(port).xy(7, 1);

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

        // add the log area

        logsArea.setEditable(false);
        logsArea.setFont(new Font("Monospaced", Font.PLAIN, 13));

        JScrollPane logsPane = new JScrollPane(logsArea);

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

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

        // create the textfield for send message to debug
        sendTextValueModel =
            new PropertyAdapter(this.netDebugModel, NetDebugModel.PROPERTY_SEND_TEXT, true);
        // sendText = BasicComponentFactory.createTextField(sendTextValueModel, false);

        // set default 20 items in history
        HistoryModel.setMax(20);
        sendText = new HistoryTextField("sendText", false, true);
        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 textDoc = sendText.getDocument();
        textDoc
            .addDocumentListener(
                new ChangeDocumentListener(doc -> sendTextOverlayable.setOverlayVisible(doc.getLength() < 1)));
        sendTextOverlayable.setOverlayVisible(textDoc.getLength() < 1);

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

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

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

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

        PropertyConnector.connect(this.netDebugModel, NetDebugModel.PROPERTY_DISCONNECTED, connectButton, "enabled");
        PropertyConnector.connect(this.netDebugModel, NetDebugModel.PROPERTY_CONNECTED, disconnectButton, "enabled");

        JPanel contentPanelTemp = dialogBuilder.build();

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

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

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

        JMenuItem copyAllToClipboard = new JMenuItem(Resources.getString(getClass(), "copyAllToClipboard"));
        copyAllToClipboard.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireCopyAllToClipboard();
            }
        });
        popupMenu.add(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);
            }
        });
        popupMenu.add(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);
            }
        });
        popupMenu.add(saveSelectedToFile);

        logsArea.setComponentPopupMenu(popupMenu);

    }

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

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

    public void addNetDebugViewListener(NetDebugViewListener listener) {
        listeners.add(listener);
    }

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

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

    private void fireTransmit() {

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

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

        logsArea.setText(null);
    }

    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_NET_DEBUG_LOG_KEY = "netDebugLog";

    // 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_NET_DEBUG_LOG_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_NET_DEBUG_LOG_KEY, workingDir);
                }
                catch (Exception ex) {
                    LOGGER.warn("Save logfile failed.", ex);

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

    public void addLog(final String logMessage) {

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {

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

    StringBuilder sbLogger = new StringBuilder();

    // int lenOfTimestamp = 0;
    private int currentLineLen;

    private boolean timestampsEnabled;

    private void addLogMessage(final String logMessage) {

        try {
            int lines = logsArea.getLineCount();
            if (lines > 500) {
                // 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);
        }

        // ...........\n........
        int beginIndex = 0;
        int index = logMessage.indexOf('\n');
        int lenOfTimestamp = 0;
        if (index > -1) {
            // 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)) {
                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(sb.toString());
            logsArea.append("\n" /* System.lineSeparator() */);

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

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy