/*
* InputPanel.java
*
* Copyright (C) 2008 Pei Wang
*
* This file is part of Open-NARS.
*
* Open-NARS 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.
*
* Open-NARS 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 Open-NARS. If not, see <http://www.gnu.org/licenses/>.
*/
package nars.gui.input;
import automenta.vivisect.Video;
import automenta.vivisect.swing.NPanel;
import automenta.vivisect.swing.NWindow;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import nars.NAR;
import nars.gui.FileTreeModel;
import nars.gui.input.TextInputPanel.InputAction;
import nars.gui.input.TextInputPanel.TextInputMode;
import static nars.gui.output.SwingLogPanel.setConsoleFont;
import nars.gui.output.SwingText;
import nars.io.Output.OUT;
import nars.io.TextInput;
import nars.gui.input.NarseseParser;
import org.parboiled.errors.InvalidInputError;
import org.parboiled.parserunners.ReportingParseRunner;
import org.parboiled.support.MatcherPath;
import org.parboiled.support.ParsingResult;
public class TextInputPanel extends NPanel /*implements ActionListener*/ {
private ReactionPanel infoPane;
private final JMenuBar menu;
private JSplitPane mainSplit;
private JButton defaultButton;
public interface InputAction {
public String getLabel();
/** may be null */
public String getDescription();
/** perform the action; the returned String, if not null, replaces the current
* input allowing for actions to transform the input
*/
public String run();
/** between 0..1 used for sorting and affecting displayed prominence of menu option */
public double getStrength();
}
/** each InputMode consists of:
* Interpretation - attempts to describe its interpretation (or lack of) of what is currently entered
* Actions - possible actions the current input enables, each with description. more than one may be invoked (checkboxes)
* Completions - descriptions of possible ways to complete the current input, and their meaning
*
*/
public interface TextInputMode /* extends AbstractInputMode */ {
public void setInputState(NAR nar, String input /* int cursorPosition */);
/** null if none available */
public String getInterpretation();
public void getActions(List<InputAction> actionsCollected);
}
/** provides actions that will be available even if, or only if, input is blank */
public class NullInput implements TextInputMode {
private String input;
private NAR nar;
public final InputAction clear = new InputAction() {
@Override public String getLabel() {
return "Clear";
}
@Override public String getDescription() {
return "Empty the input area";
}
@Override public String run() {
return "";
}
@Override
public double getStrength() {
return 0.25;
}
};
public final InputAction library = new InputAction() {
private JTree fileTree = null;
@Override public String getLabel() {
return "Library";
}
@Override public String getDescription() {
return "Browse the file library for experiences to load";
}
@Override public String run() {
TreeModel model = new FileTreeModel(new File("./nal"));
/*if (fileTree==null)*/ {
fileTree = new JTree(model);
fileTree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
int selRow = fileTree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = fileTree.getPathForLocation(e.getX(), e.getY());
if (selRow != -1) {
if (e.getClickCount() == 1) {
} else if (e.getClickCount() == 2) {
//DoubleClick
File f = (File) selPath.getLastPathComponent();
if (!f.isDirectory()) {
try {
nar.addInput(new TextInput(f));
nar.emit(OUT.class, "Loaded file: " + f.getAbsolutePath());
} catch (IOException ex) {
System.err.println(ex);
}
}
}
}
}
});
new NWindow("Experience Library", new JScrollPane(fileTree)).show(400, 200);
}
return "";
}
@Override
public double getStrength() {
return (input.length() == 0) ? 0.5 : 0.15;
}
};
@Override
public void setInputState(NAR nar, String input) {
this.input = input;
this.nar = nar;
}
@Override
public String getInterpretation() {
return null;
}
@Override
public void getActions(List<InputAction> actionsCollected) {
if (input.length() > 0) {
actionsCollected.add(clear);
//TODO concept search
//TODO operator search
}
actionsCollected.add(library);
}
}
public class NarseseInput implements TextInputMode {
public final NarseseParser p = NarseseParser.newParser();
private String input;
private NAR nar;
@Override
public void setInputState(NAR nar, String input) {
this.input = input;
this.nar = nar;
}
@Override
public String getInterpretation() {
if (input.length() == 0)
return null;
ReportingParseRunner rpr = new ReportingParseRunner(p.Input());
ParsingResult r = rpr.run(input);
String s = "";
boolean valid = (r.parseErrors.isEmpty());
if (!valid) {
for (Object e : r.parseErrors) {
if (e instanceof InvalidInputError) {
InvalidInputError iie = (InvalidInputError) e;
s += iie.getClass().getSimpleName() + " " + iie.getErrorMessage() + "\n";
s += (" at: " + iie.getStartIndex() + " to " + iie.getEndIndex()) + "\n";
for (MatcherPath m : iie.getFailedMatchers()) {
s += (" ?-> " + m + '\n');
}
} else {
s += e.toString();
}
}
} else {
s = "OK. ";
}
return s;
}
public InputAction inputDirect = new InputAction() {
@Override
public String getLabel() {
return "Input";
}
@Override
public String getDescription() {
return "Direct input into NAR";
}
@Override
public String run() {
evaluateSeq(input);
return "";
}
@Override
public double getStrength() {
return 1.5;
}
};
public InputAction step = new InputAction() {
@Override
public String getLabel() {
return "Step";
}
@Override
public String getDescription() {
return "Compute 1 cycle";
}
@Override
public String run() {
nar.step(1);
return input;
}
@Override
public double getStrength() {
return 1.0;
}
};
@Override
public void getActions(List<InputAction> actionsCollected) {
//TODO only allow input if it parses, but while parser is incomplete, allow all
if (input.length() > 0)
actionsCollected.add(inputDirect);
//actionsCollected.add(step);
//TODO reset
/*
Other Actions:
Ask - direct input a question, and create a solution window to watch for answers
Command - direct input a goal, and create a task window to watch progress
Parse Tree - show the parse tree for input (but don't clear it)
*/
}
public void evaluateSeq(String input) {
//TODO make sequential evaluation
nar.addInput(input);
nar.step(1);
}
}
/*
public class EnglishInput implements TextInputMode {
private NAR nar;
private String input = "";
private Englisch englisch;
private List<AbstractTask> nextTasks;
@Override
public void setInputState(NAR nar, String input) {
this.nar = nar;
this.englisch = nar.perception.getText().englisch;
input = input.trim();
if (!this.input.equals(input)) {
this.input = input;
if (input.length() == 0) {
this.nextTasks = null;
return;
}
try {
this.nextTasks = englisch.parse(input, nar.perception.getText().narsese, false);
if (nextTasks.isEmpty())
nextTasks = null;
} catch (Narsese.InvalidInputException ex) {
}
}
}
@Override
public String getInterpretation() {
if (nextTasks!=null) {
return nextTasks.toString();
}
return null;
}
@Override
public void getActions(List<InputAction> actionsCollected) {
//Actions:
// interpret
}
}*/
private final NAR nar;
/**
* Input area
*/
private JTextArea inputText;
// /**
// * Whether the window is ready to accept new addInput (in fact whether the
// * Reasoner will read the content of {@link #inputText} )
// */
// private boolean ready;
private final JPanel centerPanel;
private final JComponent textInput;
public List<TextInputMode> modes = new ArrayList();
/**
* Constructor
*
* @param nar The reasoner
* @param title The title of the window
*/
public TextInputPanel(final NAR nar) {
super(new BorderLayout());
this.nar = nar;
modes.add(new NarseseInput());
//modes.add(new EnglishInput());
modes.add(new NullInput());
centerPanel = new JPanel(new BorderLayout());
menu = new JMenuBar();
menu.setOpaque(false);
menu.setBackground(Color.BLACK);
setBackground(Color.BLACK);
textInput = newTextInput();
centerPanel.add(textInput);
add(centerPanel, BorderLayout.CENTER);
add(menu, BorderLayout.SOUTH);
menu.setVisible(true);
}
// private void updateMode(int selectedIndex) {
// centerPanel.removeAll();
// if (selectedIndex == 0) {
// centerPanel.add(narseseInput, BorderLayout.CENTER);
// } else if (selectedIndex == 1) {
// centerPanel.add(new JScrollPane(fileTree), BorderLayout.CENTER);
// }
// centerPanel.validate();
// repaint();
// }
public class ReactionPanel extends JPanel {
private SwingText comments;
/**
* List with buttons (instant invoke) and checkboxes with 'All' at the top when two or more are selected
*/
//private final JPanel list;
JScrollPane scrollp = new JScrollPane(comments);
Date date = new Date();
public ReactionPanel() {
super(new BorderLayout());
//list = new JPanel(new GridBagLayout());
//add(new JScrollPane(list), BorderLayout.CENTER);
comments = new SwingText();
comments.setEditable(false);
setConsoleFont(comments, 12);
/*JPanel pj = new JPanel(new BorderLayout());
pj.add(j, BorderLayout.CENTER);*/
scrollp = new JScrollPane(comments);
add(scrollp, BorderLayout.CENTER);
scrollp.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
if(Math.abs(date.getSeconds()-new Date().getSeconds()) < 1)
e.getAdjustable().setValue(e.getAdjustable().getMinimum());
}
});
scrollp.getHorizontalScrollBar().addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
if(Math.abs(date.getSeconds()-new Date().getSeconds()) < 1)
e.getAdjustable().setValue(e.getAdjustable().getMinimum());
}
});
}
public void update() {
List<String[]> interpretations = new ArrayList();
List<InputAction> actions = new ArrayList();
String input = inputText.getText();
for (final TextInputMode t : modes) {
t.setInputState(nar, input);
String interp = t.getInterpretation();
if (interp!=null) {
interpretations.add(new String[] { t.getClass().getSimpleName(), interp } );
}
t.getActions(actions);
}
/*
GridBagConstraints gc = new GridBagConstraints();
gc.weightx = 1.0;
gc.weighty = 0.0;
gc.fill = GridBagConstraints.HORIZONTAL;
gc.gridx = 1;
gc.gridy = 1;
*/
menu.removeAll();
comments.setText("");
for (String[] i : interpretations) {
Color c = Color.WHITE; //Video.getColor(i[0], 0.7f, 0.6f);
comments.print(Color.WHITE, c, i[0] + ":\n", null);
Color c2 = Color.DARK_GRAY; //DARK_GRAY; //Video.getColor(i[0], 0.5f, 0.3f);
comments.print(Color.WHITE, c2, i[1] + "\n\n", null);
}
comments.setText(comments.getText().trim());
//comments.setText(""); //syntax panel deactivated here !!!!!
if (comments.getText().length() > 0) {
if (!isVisible()) {
/*int ll = mainSplit.getLastDividerLocation();
if (ll <= 0)
ll = (int)(getWidth() * 0.75);
if (getWidth() == 0) {
//component hasnt been instantiated yet, guessing at size
//TODO use actual planned size
ll = 500;
}*/
mainSplit.setDividerLocation(400);
mainSplit.setLastDividerLocation(400);
setVisible(true);
}
}
else {
if (isVisible()) {
mainSplit.setLastDividerLocation(mainSplit.getDividerLocation());
setVisible(false);
}
}
defaultButton = null;
double maxStrength = 0;
for (InputAction a : actions) {
JButton b = new JButton(a.getLabel());
double strength = a.getStrength();
if (strength > maxStrength) {
defaultButton = b;
maxStrength = strength;
}
// b.setFont(b.getFont().deriveFont((float)(b.getFont().getSize() * (0.5f + 0.5f * strength))));
b.setForeground(Color.WHITE);
b.setBackground(Color.DARK_GRAY);
b.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override public void run() {
//TODO catch error around run() ?
String result = a.run();
if (result!=null) {
inputText.setText(result);
}
}
});
}
});
menu.add(b);
}
menu.validate();
menu.repaint();
validate();
repaint();
date = new Date();
}
}
//TODO move this to its own class
public JComponent newTextInput() {
mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
infoPane = new ReactionPanel();
inputText = new JTextArea("");
inputText.setRows(3);
DocumentListener documentListener = new DocumentListener() {
public void changedUpdate(DocumentEvent documentEvent) {
updated(documentEvent);
}
public void insertUpdate(DocumentEvent documentEvent) {
updated(documentEvent);
}
public void removeUpdate(DocumentEvent documentEvent) {
updated(documentEvent);
}
private void updated(DocumentEvent documentEvent) {
String t = inputText.getText();
boolean valid = true, show;
// if (t.length() > 0) {
// infoPane.update(t);
// show = true; //!valid;
//
// }
// else {
// show = false;
// }
//show = true;
updateContext();
}
};
inputText.getDocument().addDocumentListener(documentListener);
inputText.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
//control-enter evaluates
if (e.isControlDown()) {
if (e.getKeyCode() == 10) {
runDefault();
}
}
}
});
setConsoleFont(inputText, 20);
mainSplit.add(new JScrollPane(inputText), 0);
infoPane.setVisible(false);
mainSplit.add(infoPane, 1);
updateContext();
return mainSplit;
}
protected void updateContext() {
SwingUtilities.invokeLater(new Runnable() {
@Override public void run() {
infoPane.update();
}
});
}
/**
* Initialize the window
*/
public void init() {
inputText.setText("");
}
@Override
protected void onShowing(boolean showing) {
if (showing) {
} else {
}
}
protected void runDefault() {
if (defaultButton!=null)
defaultButton.doClick();
}
private void close() {
setVisible(false);
}
}