// 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;
import java.awt.EventQueue;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.filechooser.FileFilter;
import pleocmd.Log;
import pleocmd.StandardInput;
import pleocmd.pipe.in.ConsoleInput;
import pleocmd.pipe.in.Input;
final class MainInputPanel extends JPanel {
private static final long serialVersionUID = 8130292678723649962L;
private static final int INVALID_INDEX = 0x7FFFFF00;
private final HistoryListModel historyListModel;
private final JList historyList;
private final JScrollPane historyScrollPane;
private final JTextField consoleInput;
private final JButton btnSend;
private final JButton btnSendEOS;
private final JButton btnRead;
private final JButton btnClear;
private int historyIndex = INVALID_INDEX;
public MainInputPanel() {
final Layouter lay = new Layouter(this);
historyListModel = new HistoryListModel();
historyList = new JList(historyListModel);
lay.addWholeLine(historyScrollPane = new JScrollPane(historyList,
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), true);
historyList.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
final int idx = getHistoryList().getSelectedIndex();
if (idx != -1) {
setConsoleInput(idx);
if (e.getClickCount() == 2) sendConsoleInput();
}
}
});
consoleInput = new JTextField();
consoleInput.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent e) {
handleConsoleKeys(e.getKeyCode(), e.getModifiersEx());
}
});
lay.addWholeLine(consoleInput, false);
btnSend = lay.addButton("Send", "system-run",
"Send text from input-field to the pipe", new Runnable() {
@Override
public void run() {
sendConsoleInput();
}
});
btnSendEOS = lay.addButton("Send EOS", "media-playback-stop",
"Send end-of-stream signal", new Runnable() {
@Override
public void run() {
closeConsoleInput();
}
});
btnRead = lay.addButton("Read From ...", "document-import",
"Use the whole contents of a file as it was "
+ "entered in the input field", new Runnable() {
@Override
public void run() {
readConsoleInputFromFile();
}
});
lay.addSpacer();
btnClear = lay.addButton("Clear History", "archive-remove",
"Clear the history list of recently entered input",
new Runnable() {
@Override
public void run() {
clearHistory();
}
});
// an old history list may have been loaded from the configuration
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
scrollToBottom();
updateState();
}
});
}
protected void handleConsoleKeys(final int key, final int modifiers) {
switch (key) {
case KeyEvent.VK_ENTER:
sendConsoleInput();
break;
case KeyEvent.VK_UP:
moveInHistory(-1);
break;
case KeyEvent.VK_DOWN:
moveInHistory(1);
break;
case KeyEvent.VK_PAGE_UP:
scrollInHistory(-1);
break;
case KeyEvent.VK_PAGE_DOWN:
scrollInHistory(1);
break;
case KeyEvent.VK_R:
if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0)
searchInHistory();
break;
}
}
/**
* Scrolls in the history just like the scroll bar has been dragged.
*
* @param direction
* -1 to scroll upwards and +1 to scroll downwards
*/
private void scrollInHistory(final int direction) {
final JScrollBar sb = historyScrollPane.getVerticalScrollBar();
sb.setValue(sb.getValue() + sb.getUnitIncrement(direction) * direction);
}
/**
* Moves the current active history line marker and sets the console input
* to the contents of the now active line.
*
* @param direction
* -1 to move upwards and +1 to move downwards
*/
private void moveInHistory(final int direction) {
final int idx = Math.max(0,
Math.min(historyListModel.getSize(), historyIndex));
setConsoleInput(Math.max(0,
Math.min(historyListModel.getSize(), idx + direction)));
}
/**
* Searches for the first line in history which matches the current console
* input and sets the console input to this line.<br>
* Does nothing if no match can be found.
*/
private void searchInHistory() {
final String expr = String.format(".*%s.*", consoleInput.getText());
for (int idx = historyListModel.getSize() - 1; idx >= 0; --idx)
if (historyListModel.getElementAt(idx).matches(expr)) {
setConsoleInput(idx);
return;
}
}
/**
* Sets the console input to the history at the given position.<br>
* If the position equals the size of the history, an empty {@link String}
* will be used.
*
* @param index
* index of the history
*/
protected void setConsoleInput(final int index) {
Log.detail("Setting console input to index %d (history index: %d)",
index, historyIndex);
final String hist = index == historyListModel.getSize() ? ""
: historyListModel.getElementAt(index);
consoleInput.setText(hist);
consoleInput.setCaretPosition(hist.length());
historyIndex = index;
if (index == historyListModel.getSize())
historyList.clearSelection();
else
historyList.setSelectedIndex(index);
}
/**
* Sets the console input to the given {@link String} and resets the active
* history line marker.
*
* @param str
* new text to display in the console input field
*/
protected void setConsoleInput(final String str) {
Log.detail("Setting console input to '%s' (history index: %d)", str,
historyIndex);
consoleInput.setText(str);
consoleInput.setCaretPosition(str.length());
historyIndex = INVALID_INDEX;
historyList.clearSelection();
}
/**
* Sends the current input to the {@link StandardInput}, adds it to the
* history and resets the console input field.<br>
* Does nothing if the console input is empty.
*/
public void sendConsoleInput() {
Log.detail("Sending console input (history index: %d)", historyIndex);
try {
final String input = consoleInput.getText();
if (input.isEmpty()) return;
setConsoleInput("");
StandardInput.the().put((input + "\n").getBytes("ISO-8859-1"));
historyListModel.add(input);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
scrollToBottom();
updateState();
}
});
} catch (final IOException exc) {
Log.error(exc);
}
}
public void closeConsoleInput() {
try {
StandardInput.the().close();
} catch (final IOException exc) {
Log.error(exc);
}
}
public void readConsoleInputFromFile() {
final JFileChooser fc = new JFileChooser();
fc.setAcceptAllFileFilterUsed(false);
fc.addChoosableFileFilter(new FileFilter() {
@Override
public boolean accept(final File f) {
return true;
}
@Override
public String getDescription() {
return "ASCII-Textfile containing Data-List";
}
});
if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
readConsoleInputFromFile(fc.getSelectedFile());
}
public void readConsoleInputFromFile(final File file) {
try {
final BufferedReader in = new BufferedReader(new FileReader(file));
try {
String line;
while ((line = in.readLine()) != null) {
setConsoleInput(line);
sendConsoleInput();
}
} finally {
in.close();
}
} catch (final IOException exc) {
Log.error(exc);
}
}
public void clearHistory() {
historyListModel.clear();
updateState();
}
protected JList getHistoryList() {
return historyList;
}
protected HistoryListModel getHistoryListModel() {
return historyListModel;
}
protected void scrollToBottom() {
final int size = historyListModel.getSize() - 1;
if (size >= 0)
historyList.scrollRectToVisible(historyList.getCellBounds(size,
size));
}
public void updateState() {
boolean ready = MainFrame.the().isPipeRunning()
&& !StandardInput.the().isClosed();
boolean found = false;
if (ready)
for (final Input in : MainFrame.the().getPipe().getInputList())
if (in instanceof ConsoleInput) {
found = true;
break;
}
ready &= found;
consoleInput.setEnabled(ready);
btnSend.setEnabled(ready);
btnSendEOS.setEnabled(ready);
btnRead.setEnabled(ready);
historyList.setEnabled(ready);
btnClear.setEnabled(historyListModel.getSize() > 0);
}
}