ca.phon.app.log.BufferPanel.java Source code

Java tutorial

Introduction

Here is the source code for ca.phon.app.log.BufferPanel.java

Source

/*
 * Phon - An open source tool for research in phonology.
 * Copyright (C) 2005 - 2015, Gregory Hedlund <ghedlund@mun.ca> and Yvan Rose <yrose@mun.ca>
 * Dept of Linguistics, Memorial University <https://phon.ca>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ca.phon.app.log;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
import javax.swing.table.TableModel;

import org.fife.ui.rtextarea.RTextScrollPane;
import org.jdesktop.swingx.JXBusyLabel;
import org.jdesktop.swingx.JXTable;

import au.com.bytecode.opencsv.CSVReader;
import ca.phon.app.modules.EntryPointArgs;
import ca.phon.app.session.editor.SessionEditor;
import ca.phon.app.session.editor.SessionEditorEP;
import ca.phon.extensions.ExtensionSupport;
import ca.phon.extensions.IExtendable;
import ca.phon.functor.Functor;
import ca.phon.plugin.PluginEntryPointRunner;
import ca.phon.plugin.PluginException;
import ca.phon.project.Project;
import ca.phon.query.report.csv.CSVTableDataWriter;
import ca.phon.session.Session;
import ca.phon.session.SessionPath;
import ca.phon.ui.CommonModuleFrame;
import ca.phon.ui.action.PhonUIAction;
import ca.phon.ui.fonts.FontPreferences;
import ca.phon.ui.nativedialogs.FileFilter;
import ca.phon.ui.nativedialogs.NativeDialogs;
import ca.phon.ui.nativedialogs.SaveDialogProperties;
import ca.phon.ui.toast.ToastFactory;
import ca.phon.util.OpenFileLauncher;
import ca.phon.util.PrefHelper;
import ca.phon.util.icons.IconManager;
import ca.phon.util.icons.IconSize;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class BufferPanel extends JPanel implements IExtendable {

    public final static String SHOW_TABLE_CODE = "SHOW_TABLE";

    public final static String PACK_TABLE_COLUMNS = "PACK_COLUMNS";

    public final static String SHOW_BUFFER_CODE = "SHOW_BUFFER";

    public final static String SHOW_BUSY = "SHOW_BUSY";

    public final static String STOP_BUSY = "STOP_BUSY";

    private static final Logger LOGGER = Logger.getLogger(BufferPanel.class.getName());

    private static final long serialVersionUID = -153000974506461908L;

    private final ExtensionSupport extSupport = new ExtensionSupport(BufferPanel.class, this);

    private JScrollPane logScroller;
    private LogBuffer logBuffer;

    private JScrollPane tableScroller;
    private JXTable dataTable;

    private boolean showingBuffer = true;

    private JButton saveButton;

    private BufferPanelButtons buttons;

    private JCheckBox firstRowAsHeaderBox;

    private JCheckBox openFileAfterSavingBox;

    private JXBusyLabel busyLabel = new JXBusyLabel(new Dimension(16, 16));

    public final static String OPEN_AFTER_SAVING_PROP = BufferPanel.class.getName() + ".openFileAfterSaving";

    private boolean openFileAfterSaving = PrefHelper.getBoolean(OPEN_AFTER_SAVING_PROP, Boolean.TRUE);

    public final static String SHOWING_BUFFER_PROP = BufferPanel.class.getName() + ".showingBuffer";

    public BufferPanel(String name) {
        super();

        logBuffer = new LogBuffer(name);
        logBuffer.addEscapeCodeHandler(new LogEscapeCodeHandler() {

            @Override
            public void handleEscapeCode(String code) {
                final Runnable swapBuffer = new Runnable() {

                    @Override
                    public void run() {
                        onSwapBuffer();
                    }

                };
                if (SHOW_TABLE_CODE.equals(code) && isShowingBuffer()) {
                    SwingUtilities.invokeLater(swapBuffer);
                } else if (SHOW_BUFFER_CODE.equals(code) && !isShowingBuffer()) {
                    SwingUtilities.invokeLater(swapBuffer);
                } else if (PACK_TABLE_COLUMNS.equals(code) && !isShowingBuffer()) {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            getDataTable().packAll();
                        }
                    });
                } else if (SHOW_BUSY.equals(code)) {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            setBusy(true);
                        }
                    });
                } else if (STOP_BUSY.equals(code)) {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            setBusy(false);
                        }
                    });
                }
            }

        });

        dataTable = new JXTable();
        dataTable.setColumnControlVisible(true);
        dataTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        dataTable.setFont(FontPreferences.getUIIpaFont());

        init();
        extSupport.initExtensions();
    }

    public boolean isShowingBuffer() {
        return showingBuffer;
    }

    public void clear() {
        getLogBuffer().setText("");
        if (!isShowingBuffer()) {
            onSwapBuffer();
        }
    }

    private void init() {
        setLayout(new BorderLayout());

        final FormLayout topLayout = new FormLayout("pref, pref,3dlu, pref, fill:pref:grow, pref, 3dlu, right:pref",
                "pref");
        final CellConstraints cc = new CellConstraints();
        final JPanel topPanel = new JPanel(topLayout);

        final PhonUIAction saveAct = new PhonUIAction(this, "onSaveBuffer");
        saveAct.putValue(PhonUIAction.SHORT_DESCRIPTION, "Save buffer...");
        saveAct.putValue(PhonUIAction.SMALL_ICON,
                IconManager.getInstance().getIcon("actions/document-save", IconSize.SMALL));
        saveButton = new JButton(saveAct);

        openFileAfterSavingBox = new JCheckBox("Open after saving");
        openFileAfterSavingBox.setSelected(openFileAfterSaving);
        openFileAfterSavingBox.addChangeListener(e -> {
            BufferPanel.this.openFileAfterSaving = openFileAfterSavingBox.isSelected();
            PrefHelper.getUserPreferences().putBoolean(OPEN_AFTER_SAVING_PROP,
                    BufferPanel.this.openFileAfterSaving);
        });

        final PhonUIAction firstRowAsHeaderAct = new PhonUIAction(this, "onToggleFirstRowAsHeader");
        firstRowAsHeaderAct.putValue(PhonUIAction.NAME, "Use first row as column header");
        firstRowAsHeaderAct.putValue(PhonUIAction.SELECTED_KEY, Boolean.TRUE);
        firstRowAsHeaderBox = new JCheckBox(firstRowAsHeaderAct);

        busyLabel.setVisible(false);

        buttons = new BufferPanelButtons(this);

        topPanel.add(firstRowAsHeaderBox, cc.xy(4, 1));
        topPanel.add(saveButton, cc.xy(1, 1));
        topPanel.add(openFileAfterSavingBox, cc.xy(2, 1));
        topPanel.add(busyLabel, cc.xy(6, 1));
        topPanel.add(buttons, cc.xy(8, 1));

        add(topPanel, BorderLayout.NORTH);

        logScroller = new RTextScrollPane(logBuffer, true);
        add(logScroller, BorderLayout.CENTER);

        final ActionMap am = dataTable.getActionMap();
        final InputMap im = dataTable.getInputMap(JComponent.WHEN_FOCUSED);

        final String deleteRowsKey = "__delete_rows__";
        final PhonUIAction deleteRowsAct = new PhonUIAction(this, "deleteSelectedRows");
        am.put(deleteRowsKey, deleteRowsAct);

        final KeyStroke delKs = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
        im.put(delKs, deleteRowsKey);

        final String defaultActKey = "__default_act__";
        final TableAction defaultAct = new TableAction(tableAct);
        am.put(defaultActKey, defaultAct);

        final KeyStroke defKs = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
        im.put(defKs, defaultActKey);

        dataTable.setActionMap(am);
        dataTable.setInputMap(JComponent.WHEN_FOCUSED, im);
        dataTable.addMouseListener(new TableMouseAdapter(tableAct));

        tableScroller = new JScrollPane(dataTable, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    }

    public String getBufferName() {
        return logBuffer.getBufferName();
    }

    public LogBuffer getLogBuffer() {
        return logBuffer;
    }

    public JXTable getDataTable() {
        return dataTable;
    }

    public void setBusy(boolean busy) {
        busyLabel.setBusy(busy);
        busyLabel.setVisible(busy);
    }

    public void setFirstRowIsHeader(boolean firstRowIsColumnHeader) {
        final CSVTableModel model = (CSVTableModel) getDataTable().getModel();
        model.setUseFirstRowAsHeader(firstRowIsColumnHeader);
        model.fireTableStructureChanged();
        firstRowAsHeaderBox.setSelected(firstRowIsColumnHeader);
    }

    public void onToggleFirstRowAsHeader() {
        boolean isFirstRowHeader = firstRowAsHeaderBox.isSelected();
        setFirstRowIsHeader(isFirstRowHeader);
    }

    public void onSwapBuffer() {
        boolean wasShowingBuffer = isShowingBuffer();
        if (showingBuffer) {
            final CSVReader reader = new CSVReader(new StringReader(logBuffer.getText()));
            final CSVTableModel tableModel = new CSVTableModel(reader);
            tableModel.setUseFirstRowAsHeader(firstRowAsHeaderBox.isSelected());
            dataTable.setModel(tableModel);

            remove(logScroller);
            add(tableScroller, BorderLayout.CENTER);

            showingBuffer = false;
        } else {
            remove(tableScroller);

            add(logScroller, BorderLayout.CENTER);
            showingBuffer = true;
        }
        super.firePropertyChange(SHOWING_BUFFER_PROP, wasShowingBuffer, isShowingBuffer());
        revalidate();
        repaint();
    }

    public void onSaveBuffer() {
        final SaveDialogProperties props = new SaveDialogProperties();
        props.setParentWindow(CommonModuleFrame.getCurrentFrame());
        props.setRunAsync(false);
        props.setInitialFile(logBuffer.getBufferName());
        final FileFilter filter = (showingBuffer ? new FileFilter("Text files (*.txt)", "txt")
                : FileFilter.csvFilter);
        props.setFileFilter(filter);
        props.setCanCreateDirectories(true);

        final String saveAs = NativeDialogs.showSaveDialog(props);

        if (saveAs != null && saveAs.length() > 0) {
            if (showingBuffer) {
                // save buffer contents as text
                try {
                    final File f = new File(saveAs);
                    final BufferedWriter out = new BufferedWriter(
                            new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
                    out.write(logBuffer.getText());
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
                    ToastFactory.makeToast(e.getLocalizedMessage()).start(logBuffer);
                }
            } else {
                // save table model as csv 
                final CSVTableDataWriter writer = new CSVTableDataWriter();
                try {
                    writer.writeTableToFile(dataTable, new File(saveAs));
                } catch (IOException e) {
                    LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
                    ToastFactory.makeToast(e.getLocalizedMessage()).start(logBuffer);
                }
            }

            if (openFileAfterSaving) {
                try {
                    OpenFileLauncher.openURL(new File(saveAs).toURI().toURL());
                } catch (MalformedURLException e) {
                    LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
                    Toolkit.getDefaultToolkit().beep();
                }
            }
        }
    }

    public void deleteSelectedRows() {
        final int selected[] = dataTable.getSelectedRows();
        final List<Integer> selectedRows = new ArrayList<Integer>();
        for (int viewSelected : selected) {
            int modelSelected = dataTable.convertRowIndexToModel(viewSelected);
            selectedRows.add(modelSelected);
        }
        Collections.sort(selectedRows);

        for (int rowIdx = selectedRows.size() - 1; rowIdx >= 0; rowIdx--) {
            final int row = selectedRows.get(rowIdx);
            final CSVTableModel model = (CSVTableModel) dataTable.getModel();
            model.deleteRow(row);
        }
    }

    /**
     * Listener action for double clicks in the data table.
     * 
     * Returns: Void
     * Params: Integer (table row index)
     */
    private final Functor<Void, Integer> tableAct = new Functor<Void, Integer>() {

        @Override
        public Void op(Integer row) {
            final JXTable tbl = getDataTable();
            if (tbl == null)
                return null;

            final TableModel tblModel = tbl.getModel();
            if (tblModel == null)
                return null;
            if (row < 0 || row >= tblModel.getRowCount())
                return null;

            // fix row number if sorted
            row = tbl.convertRowIndexToModel(row);

            // get project reference from parent window
            final CommonModuleFrame cmf = (CommonModuleFrame) SwingUtilities
                    .getAncestorOfClass(CommonModuleFrame.class, BufferPanel.this);
            if (cmf == null)
                return null;

            final Project project = cmf.getExtension(Project.class);
            if (project == null)
                return null;

            // look for a session reference, if not found try to find a column
            // with name 'session'
            final Session primarySession = cmf.getExtension(Session.class);
            int sessionColumn = -1;
            int recordColumn = -1;
            int tierColumn = -1;
            int groupColumn = -1;
            int rangeColumn = -1;

            for (int i = 0; i < tblModel.getColumnCount(); i++) {
                final String colName = tblModel.getColumnName(i);

                if (colName.equalsIgnoreCase("session")) {
                    sessionColumn = i;
                } else if (colName.equalsIgnoreCase("record") || colName.equalsIgnoreCase("record #")) {
                    recordColumn = i;
                } else if (colName.equalsIgnoreCase("tier") || colName.equalsIgnoreCase("tier name")) {
                    tierColumn = i;
                } else if (colName.equalsIgnoreCase("group")) {
                    groupColumn = i;
                } else if (colName.equalsIgnoreCase("range")) {
                    rangeColumn = i;
                }
            }

            // check for required items
            if (primarySession == null && sessionColumn == -1)
                return null;
            if (recordColumn == -1)
                return null;

            // load session
            SessionEditor editor = null;
            // get values for each column

            SessionPath sp = new SessionPath();

            if (sessionColumn >= 0 && primarySession == null) {
                String sessionTxt = tblModel.getValueAt(row, sessionColumn).toString();
                if (sessionTxt == null || sessionTxt.length() == 0 || sessionTxt.indexOf('.') < 0)
                    return null;
                String[] sessionPath = sessionTxt.split("\\.");
                if (sessionPath.length != 2)
                    return null;
                sp.setCorpus(sessionPath[0]);
                sp.setSession(sessionPath[1]);
            } else if (primarySession != null) {
                sp.setCorpus(primarySession.getCorpus());
                sp.setSession(primarySession.getName());
            }
            // load session editor (if necessary)
            final EntryPointArgs epArgs = new EntryPointArgs();
            epArgs.put(EntryPointArgs.PROJECT_OBJECT, project);
            epArgs.put(EntryPointArgs.CORPUS_NAME, sp.getCorpus());
            epArgs.put(EntryPointArgs.SESSION_NAME, sp.getSession());
            try {
                PluginEntryPointRunner.executePlugin(SessionEditorEP.EP_NAME, epArgs);
            } catch (PluginException e) {
                LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
                return null;
            }

            // find session editor
            for (CommonModuleFrame openWindow : CommonModuleFrame.getOpenWindows()) {
                if (openWindow instanceof SessionEditor) {
                    final SessionEditor currentEditor = (SessionEditor) openWindow;
                    if (currentEditor.getSession().getCorpus().equals(sp.getCorpus())
                            && currentEditor.getSession().getName().equals(sp.getSession())) {
                        editor = (SessionEditor) openWindow;
                        break;
                    }
                }
            }
            if (editor == null)
                return null;

            // get record index
            String recordTxt = tblModel.getValueAt(row, recordColumn).toString();
            int recordNum = Integer.parseInt(recordTxt) - 1;
            if (recordNum < 0 || recordNum >= editor.getDataModel().getRecordCount())
                return null;

            editor.setCurrentRecordIndex(recordNum);

            if (tierColumn >= 0 && groupColumn >= 0) {
                // TODO attempt to setup highlighting
            }

            return null;
        }

    };

    private final class TableMouseAdapter extends MouseInputAdapter {

        private Functor<Void, Integer> functor;

        public TableMouseAdapter(Functor<Void, Integer> functor) {
            this.functor = functor;
        }

        @Override
        public void mouseClicked(MouseEvent me) {
            if (me.getClickCount() == 2 && me.getButton() == MouseEvent.BUTTON1) {
                functor.op(getDataTable().getSelectedRow());
            }
        }

    }

    private final class TableAction extends AbstractAction {

        private Functor<Void, Integer> functor;

        public TableAction(Functor<Void, Integer> functor) {
            this.functor = functor;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            functor.op(getDataTable().getSelectedRow());
        }

    }

    public void setBufferName(String string) {
        logBuffer.setBufferName(string);
    }

    public Set<Class<?>> getExtensions() {
        return extSupport.getExtensions();
    }

    public <T> T getExtension(Class<T> cap) {
        return extSupport.getExtension(cap);
    }

    public <T> T putExtension(Class<T> cap, T impl) {
        return extSupport.putExtension(cap, impl);
    }

    public <T> T removeExtension(Class<T> cap) {
        return extSupport.removeExtension(cap);
    }

}