// This file is part of PleoCommand: // Interactively control Pleo with psychobiological parameters // // Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de // // 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 2 // 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, write to the Free Software // Foundation, Inc., 51 Franklin Street, Boston, USA. package pleocmd.itfc.gui.dse; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.StringReader; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextPane; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import javax.swing.filechooser.FileFilter; import javax.swing.text.BadLocationException; import javax.swing.text.StyledDocument; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; import pleocmd.Log; import pleocmd.api.PleoCommunication; import pleocmd.cfg.ConfigItem; import pleocmd.exc.FormatException; import pleocmd.exc.InternalException; import pleocmd.exc.PipeException; import pleocmd.itfc.gui.Layouter; import pleocmd.itfc.gui.Layouter.Button; import pleocmd.itfc.gui.MainFrame; import pleocmd.pipe.data.Data; import pleocmd.pipe.out.ConsoleOutput; import pleocmd.pipe.out.Output; import pleocmd.pipe.out.PleoRXTXOutput; import pleocmd.pipe.out.PrintType; public abstract class DataSequenceEditorPanel extends JPanel implements UpdateErrorInterface { private static final long serialVersionUID = -3019900508373635307L; private final static Timer ERR_LBL_TIMER = new Timer("Error-Label Timer", true); private final JTextPane tpDataSequence; private final UndoManager tpUndoManager; private final JButton btnCopyInput; private final JButton btnAddFile; private final JButton btnPlaySel; private final JButton btnPlayAll; private final JButton btnUndo; private final JButton btnRedo; private List<Output> playOutputList; private final JLabel lblErrorFeedback; private TimerTask errorLabelTimerTask; public DataSequenceEditorPanel() { final Layouter lay = new Layouter(this); tpUndoManager = new UndoManager(); tpDataSequence = new JTextPane(); tpDataSequence.setPreferredSize(new Dimension(0, 10 * tpDataSequence .getFontMetrics(tpDataSequence.getFont()).getHeight())); tpDataSequence.addCaretListener(new CaretListener() { @Override public void caretUpdate(final CaretEvent e) { updateState(); } }); final DataSequenceEditorKit kit = new DataSequenceEditorKit(this); tpDataSequence.setEditorKitForContentType("text/datasequence", kit); tpDataSequence.setContentType("text/datasequence"); tpDataSequence.setFont(tpDataSequence.getFont().deriveFont(Font.BOLD)); tpDataSequence.getDocument().addUndoableEditListener( new UndoableEditListener() { @Override public void undoableEditHappened(final UndoableEditEvent e) { addUndo(e.getEdit()); } }); lay.addWholeLine(new JScrollPane(tpDataSequence), true); lblErrorFeedback = new JLabel(""); lblErrorFeedback.setForeground(Color.RED); lay.addWholeLine(lblErrorFeedback, false); lay.setSpan(2); btnCopyInput = lay.addButton("Copy From Input History", "bookmark-new.png", "Copy all history from console input into this Data list", new Runnable() { @Override public void run() { addFromInputHistory(); } }); btnAddFile = lay.addButton("Add From File ...", "edit-text-frame-update.png", "Copy the contents of a file into this Data list", new Runnable() { @Override public void run() { addFromFile(); } }); lay.newLine(); btnPlaySel = lay.addButton("Play Selected", "unknownapp", "Sends all currently selected data to " + "ConsoleOutput and PleoRXTXOutput", new Runnable() { @Override public void run() { playSelected(); } }); btnPlayAll = lay.addButton("Play All", "unknownapp", "Sends all data in the list to " + "ConsoleOutput and PleoRXTXOutput", new Runnable() { @Override public void run() { playAll(); } }); lay.addSpacer(); btnUndo = lay.addButton(Button.Undo, new Runnable() { @Override public void run() { undo(); } }); btnRedo = lay.addButton(Button.Redo, new Runnable() { @Override public void run() { redo(); } }); } protected final void addFromInputHistory() { try { final StyledDocument doc = tpDataSequence.getStyledDocument(); final int offset = doc.getParagraphElement( tpDataSequence.getCaretPosition()).getEndOffset(); for (final String data : MainFrame.the().getHistory()) doc.insertString(offset, data + "\n", null); } catch (final BadLocationException e) { Log.error(e); } } protected final void addFromFile() { final JFileChooser fc = new JFileChooser(); fc.setAcceptAllFileFilterUsed(false); fc.addChoosableFileFilter(new FileFilter() { @Override public boolean accept(final File f) { final String name = f.getName(); return !name.endsWith(".pbd") && !name.endsWith(".pca"); } @Override public String getDescription() { return "ASCII-Textfile containing Data-List"; } }); if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) addSequenceFromFile(fc.getSelectedFile()); } protected final void addSequenceFromFile(final File fileToAdd) { try { final StyledDocument doc = tpDataSequence.getStyledDocument(); int offset = doc.getParagraphElement( tpDataSequence.getCaretPosition()).getEndOffset(); final BufferedReader in = new BufferedReader(new FileReader( fileToAdd)); try { String line; while ((line = in.readLine()) != null) { line = line.trim() + "\n"; doc.insertString(offset, line, null); offset += line.length(); } } finally { in.close(); } } catch (final IOException e) { Log.error(e); } catch (final BadLocationException e) { Log.error(e); } } protected final void clear() { final StyledDocument doc = tpDataSequence.getStyledDocument(); try { doc.remove(0, doc.getLength()); } catch (final BadLocationException e) { Log.error(e); } } protected final void playSelected() { final String sel = tpDataSequence.getSelectedText(); if (sel != null) for (final String line : sel.split("\n")) try { play(Data.createFromAscii(line)); } catch (final IOException e) { Log.error(e); } catch (final FormatException e) { Log.error(e); } } protected final void playAll() { final String text = tpDataSequence.getText(); if (text != null) for (final String line : text.split("\n")) try { play(Data.createFromAscii(line)); } catch (final IOException e) { Log.error(e); } catch (final FormatException e) { Log.error(e); } } protected final void play(final Data data) { if (playOutputList == null) try { String device = null; for (final Output out : MainFrame.the().getPipe() .getOutputList()) if (out instanceof PleoRXTXOutput) device = ((ConfigItem<?>) out.getGroup().get("Device")) .getContent(); if (device == null) try { device = JOptionPane.showInputDialog(this, "Set Pleo device name for playback:", PleoCommunication.getHighestPort().getName()); } catch (final NoClassDefFoundError e) { Log.error(e, "RXTX not available"); } if (device == null) return; playOutputList = new ArrayList<Output>(2); final Output outC = new ConsoleOutput(PrintType.Ascii); outC.configure(); outC.init(); playOutputList.add(outC); final Output outP = new PleoRXTXOutput(device); outP.configure(); outP.init(); playOutputList.add(outP); } catch (final Throwable e) { // CS_IGNORE catch all here Log.error(e); } try { for (final Output out : playOutputList) out.write(data); } catch (final Throwable e) { // CS_IGNORE catch all here Log.error(e); } } protected final void freeResources() { try { if (playOutputList != null) for (final Output out : playOutputList) out.close(); } catch (final PipeException e) { Log.error(e); } } protected final void addUndo(final UndoableEdit edit) { if (edit.isSignificant()) { tpUndoManager.addEdit(edit); updateState(); } } protected final void undo() { try { tpUndoManager.undo(); } catch (final CannotUndoException e) { Log.error(e); } } protected final void redo() { try { tpUndoManager.redo(); } catch (final CannotRedoException e) { Log.error(e); } } protected final List<Data> writeTextPaneToList() throws IOException, FormatException { final List<Data> res = new ArrayList<Data>(); final BufferedReader in = new BufferedReader(new StringReader( tpDataSequence.getText())); String line; while ((line = in.readLine()) != null) { line = line.trim(); if (!line.isEmpty() && line.charAt(0) != '#') res.add(Data.createFromAscii(line)); } in.close(); return res; } public final void writeTextPaneToWriter(final Writer out) throws IOException { final BufferedReader in = new BufferedReader(new StringReader( tpDataSequence.getText())); String line; while ((line = in.readLine()) != null) { out.write(line); out.write('\n'); } in.close(); out.flush(); } protected final void updateTextPaneFromList(final List<Data> dataList) { clear(); final StyledDocument doc = tpDataSequence.getStyledDocument(); if (dataList != null) try { for (final Data data : dataList) doc.insertString(doc.getLength(), data.asString() + "\n", null); } catch (final BadLocationException e) { throw new InternalException(e); } tpUndoManager.discardAllEdits(); updateState(); } public final void updateTextPaneFromReader(final BufferedReader in) throws IOException { clear(); final StyledDocument doc = tpDataSequence.getStyledDocument(); if (in != null) try { String line; while ((line = in.readLine()) != null) doc.insertString(doc.getLength(), line + "\n", null); } catch (final BadLocationException e) { throw new InternalException(e); } tpUndoManager.discardAllEdits(); updateState(); } @Override public final void updateErrorLabel(final String text) { if (text.equals(lblErrorFeedback.getText())) return; lblErrorFeedback.setText(text); Color.RGBtoHSB(255, 0, 0, null); final Color src = Color.RED; final Color trg = lblErrorFeedback.getBackground(); lblErrorFeedback.setForeground(src); if (errorLabelTimerTask != null) errorLabelTimerTask.cancel(); errorLabelTimerTask = new FadeTimerTask(lblErrorFeedback, src.getRed(), src.getGreen(), src.getBlue(), trg.getRed(), trg.getGreen(), trg.getBlue()); ERR_LBL_TIMER.schedule(errorLabelTimerTask, 1000, 100); } protected abstract void stateChanged(); protected final void updateState() { tpDataSequence.setEnabled(isEnabled()); btnCopyInput.setEnabled(MainFrame.the().getHistory().size() > 0); btnAddFile.setEnabled(true); btnPlaySel.setEnabled(tpDataSequence.getSelectedText() != null); btnPlayAll.setEnabled(tpDataSequence.getDocument().getLength() > 0); btnUndo.setEnabled(tpUndoManager.canUndo()); btnRedo.setEnabled(tpUndoManager.canRedo()); stateChanged(); } protected final JLabel getLblErrorFeedback() { return lblErrorFeedback; } public final UndoManager getTpUndoManager() { return tpUndoManager; } public final JTextPane getTpDataSequence() { return tpDataSequence; } public static final class FadeTimerTask extends TimerTask { private final JLabel label; private double cur0; private double cur1; private double cur2; private final double inc0; private final double inc1; private final double inc2; private int steps; FadeTimerTask(final JLabel label, final int src0, final int src1, final int src2, final int trg0, final int trg1, final int trg2) { this.label = label; cur0 = src0; cur1 = src1; cur2 = src2; inc0 = (trg0 - src0) / 10.0; inc1 = (trg1 - src1) / 10.0; inc2 = (trg2 - src2) / 10.0; } @Override public void run() { cur0 = Math.min(255, Math.max(0, cur0 + inc0)); cur1 = Math.min(255, Math.max(0, cur1 + inc1)); cur2 = Math.min(255, Math.max(0, cur2 + inc2)); label.setForeground(new Color((int) cur0, (int) cur1, (int) cur2)); if (++steps == 10) cancel(); } } }