/*
* NARControls.java
*
* Copyright (C) 2008 Pei Wang
*
* This file is part of Open-NARSwing.
*
* Open-NARSwing 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-NARSwing 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-NARSwing. If not, see <http://www.gnu.org/licenses/>.
*/
package nars.gui;
import automenta.vivisect.dimensionalize.FastOrganicLayout;
import automenta.vivisect.graph.AnimatingGraphVis;
import automenta.vivisect.swing.AwesomeButton;
import automenta.vivisect.swing.NSlider;
import automenta.vivisect.swing.NWindow;
import automenta.vivisect.swing.PCanvas;
import java.awt.BorderLayout;
import static java.awt.BorderLayout.NORTH;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import nars.util.EventEmitter.EventObserver;
import nars.util.Events;
import nars.util.Events.FrameEnd;
import nars.storage.Memory;
import nars.NAR;
import nars.gui.input.TextInputPanel;
import nars.gui.input.image.SketchPointCloudPanel;
import nars.gui.output.PluginPanel;
import nars.gui.output.SentenceTablePanel;
import nars.gui.output.SwingLogPanel;
import nars.gui.output.TaskTree;
import nars.gui.output.NARFacePanel;
import nars.gui.output.graph.NARGraphDisplay;
import nars.gui.output.graph.NARGraphPanel;
import nars.io.TextInput;
import nars.io.TextOutput;
public class NARControls extends JPanel implements ActionListener, EventObserver {
final int TICKS_PER_TIMER_LABEL_UPDATE = 4 * 1024; //set to zero for max speed, or a large number to reduce GUI updates
/**
* Reference to the reasoner
*/
public final NAR nar;
/**
* Reference to the memory
*/
private final Memory memory;
/**
* Reference to the experience writer
*/
private final TextOutput experienceWriter;
/**
* Control buttons
*/
private JButton stopButton, walkButton;
/**
* Whether the experience is saving into a file
*/
private boolean savingExp = false;
/**
* To process the next chunk of output data
*
* @param lines The text lines to be displayed
*/
private NSlider speedSlider;
private float currentSpeed = 0f;
private float lastSpeed = 0f;
private final float defaultSpeed = 0.5f;
private final int GUIUpdatePeriodMS = 75;
private NSlider volumeSlider;
private boolean allowFullSpeed = true;
public final InferenceLogger logger;
int chartHistoryLength = 128;
/**
* Constructor
*
* @param nar
* @param title
*/
public NARControls(final NAR nar) {
super(new BorderLayout());
this.nar = nar;
memory = nar.memory;
experienceWriter = new TextOutput(nar);
logger = new InferenceLogger(nar);
logger.setActive(false);
JMenuBar menuBar = new JMenuBar();
JMenu m = new JMenu("Memory");
addJMenuItem(m, "Reset");
m.addSeparator();
addJMenuItem(m, "Load Experience");
addJMenuItem(m, "Save Experience");
/*internalExperienceItem = addJMenuItem(m, "Enable Internal Experience (NAL9)");
fullInternalExp = addJMenuItem(m, "Enable Full Internal Experience");
narsPlusItem = addJMenuItem(m, "Enable NARS+ Ideas");*/
m.addActionListener(this);
menuBar.add(m);
m = new JMenu("Windows");
{
JMenuItem mv3 = new JMenuItem("+ Input");
mv3.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TextInputPanel inputPanel = new TextInputPanel(nar);
NWindow inputWindow = new NWindow("Input", inputPanel);
inputWindow.setSize(800, 200);
inputWindow.setVisible(true);
}
});
m.add(mv3);
//not really relevant for NARS, Im working on a active approach to detecting such patterns
//which will work when conditioning works good
/* JMenuItem cct4 = new JMenuItem("+ Input Drawing");
cct4.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
NWindow w = new NWindow("Sketch", new SketchPointCloudPanel(nar));
w.setSize(500,500);
w.setVisible(true);
}
});
m.add(cct4);*/
JMenuItem ml = new JMenuItem("+ Output");
ml.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new NWindow("Output", new SwingLogPanel(NARControls.this)).show(500, 300);
}
});
m.add(ml);
m.addSeparator();
JMenuItem mv = new JMenuItem("+ Concept Network");
mv.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new NWindow("graphvis", new NARGraphPanel( nar) ).show(800, 800, false);
}
});
m.add(mv);
/*JMenuItem tlp = new JMenuItem("+ Timeline");
tlp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
NWindow outputWindow = new NWindow("Timeline", new TimelinePanel(nar));
outputWindow.show(900, 700);
}
});
m.add(tlp);*/
m.addSeparator();
/* JMenuItem pml = new JMenuItem("+ Planning Log");
pml.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new NWindow("Planning", new SwingLogPanel(NARControls.this,
MultipleExecutionManager.class, Execution.class,
GraphExecutive.ParticlePath.class,
GraphExecutive.ParticlePlan.class))
.show(500, 300);
}
});
m.add(pml); */
/* JMenuItem al = new JMenuItem("+ Activity");
al.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new NWindow("Activity", new MultiOutputPanel(NARControls.this)).show(500, 300); }
});
m.add(al); */
// JMenuItem mv2 = new JMenuItem("+ Concept Graph 2");
// mv2.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
// new NWindow("Concept Graph 2", new ProcessingGraphPanel(nar, new ConceptGraphCanvas2(nar))).show(500, 500);
// }
// });
// m.add(mv2);
//
//
/*JMenuItem imv = new JMenuItem("+ Eternalized Implications Graph");
imv.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//new Window("Implication Graph", new SentenceGraphPanel(nar, nar.memory.executive.graph.implication)).show(500, 500);
new NWindow("Implication Graph",
new PCanvas(
new AnimatingGraphVis(
nar.memory.executive.graph.implication,
new NARGraphDisplay(),
new FastOrganicLayout()
))).show(500, 500);
}
});
m.add(imv); */
//
// JMenuItem sg = new JMenuItem("+ Inheritance / Similarity Graph");
// sg.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
// new NWindow("Inheritance Graph",
// new ProcessingGraphPanel(nar,
// new SentenceGraphCanvas(
// new InheritanceGraph(nar)))).show(500, 500);
// }
// });
// m.add(sg);
// m.addSeparator();
JMenuItem tt = new JMenuItem("+ Task Tree");
tt.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new NWindow("Task Tree", new TaskTree(nar)).show(300, 650, false);
}
});
m.add(tt);
// m.addSeparator();
JMenuItem st = new JMenuItem("+ Sentence Table");
st.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SentenceTablePanel p = new SentenceTablePanel(nar);
NWindow w = new NWindow("Sentence Table", p);
w.setSize(500, 300);
w.setVisible(true);
}
});
m.add(st);
m.addSeparator();
JMenuItem gml = new JMenuItem("+ Concept Forgetting Log");
gml.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new NWindow("Forgot", new SwingLogPanel(NARControls.this,
Events.ConceptForget.class
//, Events.TaskRemove.class, Events.TermLinkRemove.class, Events.TaskLinkRemove.class)
))
.show(500, 300);
}
});
m.add(gml);
JMenuItem gml2 = new JMenuItem("+ Task Forgetting Log");
gml2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new NWindow("Forgot", new SwingLogPanel(NARControls.this,
Events.TaskRemove.class
//, Events.TaskRemove.class, Events.TermLinkRemove.class, Events.TaskLinkRemove.class)
))
.show(500, 300);
}
});
m.add(gml2);
/* not working yet anyway JMenuItem fc = new JMenuItem("+ Freq. vs Confidence");
fc.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
BubbleChart bc = new BubbleChart(nar);
NWindow wbc = new NWindow("Freq vs. Conf", bc);
wbc.setSize(250,250);
wbc.setVisible(true);
}
});
m.add(fc); */
/*JMenuItem hf = new JMenuItem("+ Humanoid Face");
hf.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
NARFacePanel f = new NARFacePanel(nar);
NWindow w = new NWindow("Face", f);
w.setSize(250,400);
w.setVisible(true);
}
});
m.add(hf);*/
/*JMenuItem ct = new JMenuItem("+ Concepts");
ct.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// see design for Bag and {@link BagWindow} in {@link Bag#startPlay(String)}
memory.conceptsStartPlay(new BagWindow<Concept>(), "Active Concepts");
}
});
m.add(ct);
JMenuItem bt = new JMenuItem("+ Buffered Tasks");
bt.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
memory.taskBuffersStartPlay(new BagWindow<Task>(), "Buffered Tasks");
}
});
m.add(bt);*/
/*JMenuItem cct = new JMenuItem("+ Concept Content");
cct.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
conceptWin.setVisible(true);
}
});
m.add(cct);*/
/*
JMenuItem it = new JMenuItem("+ Inference Log");
it.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
record.show();
record.play();
}
});
m.add(it);
*/
}
menuBar.add(m);
// m = new JMenu("Demos");
// {
// JMenuItem cct2 = new JMenuItem("+ Test Chamber");
// cct2.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
//
// chamber.create(nar);
// }
// });
// m.add(cct2);
// }
// menuBar.add(m);
m = new JMenu("Help");
//addJMenuItem(m, "Related Information");
addJMenuItem(m, "About NARS");
m.addActionListener(this);
menuBar.add(m);
JPanel top = new JPanel(new BorderLayout());
top.add(menuBar, BorderLayout.NORTH);
JComponent jp = newParameterPanel();
top.add(jp, BorderLayout.CENTER);
/*CompoundMeter senses = new CompoundMeter(memory.logic, memory.resource) {
@Override
public Chart newDefaultChart(String id, TreeMLData data) {
switch (id) {
case "concept.pri.histo":
return new StackedPercentageChart(data).height(2);
case "concept.pri.mean":
case "task.pri.mean":
return new LineChart(data).range(0, 1f);
case "plan.graph":
case "plan.graph.add":
case "plan.task":
case "concept.belief.mean":
case "task.process":
return new LineChart(data);
}
return new BarChart(data);
}
};
senses.setActive(true);
senses.update(memory); */
add(top, NORTH);
//add(new MeterVis(nar, senses, 128).newPanel(), CENTER);
init();
volumeSlider.setValue(nar.param.noiseLevel.get());
}
/**
* @param m
* @param item
*/
private JMenuItem addJMenuItem(JMenu m, String item) {
JMenuItem menuItem = new JMenuItem(item);
m.add(menuItem);
menuItem.addActionListener(this);
return menuItem;
}
/**
* Open an addInput experience file with a FileDialog
*/
public void openLoadFile() {
FileDialog dialog = new FileDialog((Dialog) null, "Load experience", FileDialog.LOAD);
dialog.setVisible(true);
String directoryName = dialog.getDirectory();
String fileName = dialog.getFile();
String filePath = directoryName + fileName;
try {
nar.addInput(new TextInput(new File(filePath)));
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Initialize the system for a new finish
*/
public void init() {
setSpeed(0);
setSpeed(0); //call twice to make it start as paused
updateGUI();
nar.memory.event.on(FrameEnd.class, this);
}
final Runnable updateGUIRunnable = new Runnable() {
@Override public void run() {
updateGUI();
}
};
/** in ms */
long lastUpdateTime = -1;
/** in memory cycles */
AtomicBoolean updateScheduled = new AtomicBoolean(false);
protected void updateGUI() {
speedSlider.repaint();
updateScheduled.set(false);
}
@Override
public void event(final Class event, final Object... arguments) {
if (event == FrameEnd.class) {
long now = System.currentTimeMillis();
long deltaTime = now - lastUpdateTime;
if ((deltaTime >= GUIUpdatePeriodMS) /*|| (!updateScheduled.get())*/) {
updateScheduled.set(true);
speedSlider.repaint();
SwingUtilities.invokeLater(updateGUIRunnable);
lastUpdateTime = now;
}
}
}
/**
* Handling button click
*
* @param e The ActionEvent
*/
@Override
public void actionPerformed(ActionEvent e) {
Object obj = e.getSource();
if (obj instanceof JButton) {
if (obj == stopButton) {
setSpeed(0);
updateGUI();
} else if (obj == walkButton) {
nar.stop();
nar.step(1);
updateGUI();
}
} else if (obj instanceof JMenuItem) {
String label = e.getActionCommand();
switch (label) {
//case "Enable Full Internal Experience":
//fullInternalExp.setEnabled(false);
//Parameters.INTERNAL_EXPERIENCE_FULL=true;
//Parameters.ENABLE_EXPERIMENTAL_NARS_PLUS=!Parameters.ENABLE_EXPERIMENTAL_NARS_PLUS;
// break;
// case "Enable NARS+ Ideas":
// narsPlusItem.setEnabled(false);
// nar.memory.param.experimentalNarsPlus.set(true);
// break;
// case "Enable Internal Experience (NAL9)":
// internalExperienceItem.setEnabled(false);
// nar.memory.param.internalExperience.set(true);
// break;
case "Load Experience":
openLoadFile();
break;
case "Save Experience":
if (savingExp) {
experienceWriter.closeSaveFile();
} else {
FileDialog dialog = new FileDialog((Dialog) null, "Save experience", FileDialog.SAVE);
dialog.setVisible(true);
String directoryName = dialog.getDirectory();
String fileName = dialog.getFile();
String path = directoryName + fileName;
experienceWriter.openSaveFile(path);
}
savingExp = !savingExp;
break;
case "Reset":
/// TODO mixture of modifier and reporting
//narsPlusItem.setEnabled(true);
//internalExperienceItem.setEnabled(true);
nar.reset();
break;
case "Related Information":
// MessageDialog web =
new MessageDialog(NAR.WEBSITE);
break;
case "About NARS":
// MessageDialog info =
new MessageDialog(NAR.VERSION+"\n\n"+NAR.WEBSITE);
break;
}
}
}
private NSlider newSpeedSlider() {
final StringBuilder sb = new StringBuilder(32);
final NSlider s = new NSlider(0f, 0f, 1.0f) {
@Override
public String getText() {
if (value == null) {
return "";
}
if (sb.length() > 0) sb.setLength(0);
sb.append(memory.time());
if (currentSpeed == 0) {
sb.append(" - pause");
} else if (currentSpeed == 1.0) {
sb.append(" - max speed");
} else {
sb.append(" - ").append(nar.getMinCyclePeriodMS()).append(" ms/step");
}
return sb.toString();
}
@Override
public void onChange(float v) {
setSpeed(v);
}
};
this.speedSlider = s;
return s;
}
private NSlider newVolumeSlider() {
final NSlider s = this.volumeSlider = new NSlider(100f, 0, 100f) {
@Override
public String getText() {
if (value == null) {
return "";
}
float v = value();
String s = "Volume:" + super.getText() + " (";
if (v == 0) {
s += "Silent";
} else if (v < 25) {
s += "Quiet";
} else if (v < 75) {
s += "Normal";
} else {
s += "Loud";
}
s += ")";
return s;
}
@Override
public void setValue(float v) {
super.setValue(Math.round(v));
repaint(); //needed to update when called from outside, as the 'focus' button does
}
@Override
public void onChange(float v) {
int level = (int) v;
(nar.param).noiseLevel.set(level);
}
};
return s;
}
public void setSpeed(float nextSpeed) {
final float maxPeriodMS = 1024.0f;
if (nextSpeed == 0) {
if (currentSpeed == 0) {
if (lastSpeed == 0) {
lastSpeed = defaultSpeed;
}
nextSpeed = lastSpeed;
} else {
}
}
if (currentSpeed == nextSpeed) return;
lastSpeed = currentSpeed;
speedSlider.repaint();
stopButton.setText(String.valueOf(FA_PlayCharacter));
/*if (currentSpeed == s)
return;*/
speedSlider.setValue(nextSpeed);
currentSpeed = nextSpeed;
float logScale = 50f;
if (nextSpeed > 0) {
long ms = (long) ((1.0 - Math.log(1+nextSpeed*logScale)/Math.log(1+logScale)) * maxPeriodMS);
if (ms < 1) {
if (allowFullSpeed)
ms = 0;
else
ms = 1;
}
stopButton.setText(String.valueOf(FA_StopCharacter));
//nar.setThreadYield(true);
nar.start(ms, nar.getCyclesPerFrame());
} else {
stopButton.setText(String.valueOf(FA_PlayCharacter));
nar.stop();
}
}
//http://astronautweb.co/snippet/font-awesome/
private final char FA_PlayCharacter = '\uf04b';
private final char FA_StopCharacter = '\uf04c';
private final char FA_FocusCharacter = '\uf11e';
private final char FA_ControlCharacter = '\uf085';
private JComponent newParameterPanel() {
JPanel p = new JPanel();
JPanel pc = new JPanel();
pc.setLayout(new GridLayout(1, 0));
stopButton = new AwesomeButton(FA_StopCharacter);
stopButton.setBackground(Color.DARK_GRAY);
stopButton.addActionListener(this);
pc.add(stopButton);
walkButton = new AwesomeButton('\uf051');
walkButton.setBackground(Color.DARK_GRAY);
walkButton.setToolTipText("Walk 1 Cycle");
walkButton.addActionListener(this);
pc.add(walkButton);
JButton focusButton = new AwesomeButton(FA_FocusCharacter);
focusButton.setBackground(Color.DARK_GRAY);
focusButton.setToolTipText("Focus");
focusButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setSpeed(1.0f);
volumeSlider.setValue(0.0f);
}
});
pc.add(focusButton);
JButton pluginsButton = new AwesomeButton(FA_ControlCharacter);
pluginsButton.setToolTipText("Plugins");
pluginsButton.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
new NWindow("Plugins", new PluginPanel(nar)).show(350, 600);
}
});
pc.add(pluginsButton);
p.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.NORTH;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.gridx = 0;
c.ipady = 8;
p.add(pc, c);
NSlider vs = newVolumeSlider();
vs.setFont(vs.getFont().deriveFont(Font.BOLD));
p.add(vs, c);
NSlider ss = newSpeedSlider();
ss.setFont(vs.getFont());
p.add(ss, c);
c.ipady = 4;
p.add(new NSlider(memory.param.decisionThreshold, "Decision Threshold", 0.0f, 1.0f), c);
p.add(new NSlider(memory.param.taskLinkForgetDurations, "Task Duration", 0.5f, 20), c);
p.add(new NSlider(memory.param.termLinkForgetDurations, "Belief Duration", 0.5f, 20), c);
p.add(new NSlider(memory.param.conceptForgetDurations, "Concept Duration", 0.5f, 20), c);
p.add(new NSlider(memory.param.novelTaskForgetDurations, "Novel Duration", 0.5f, 20), c);
p.add(new NSlider(memory.param.sequenceForgetDurations, "Sequence Duration", 0.5f, 20), c);
//
// //JPanel chartPanel = new JPanel(new GridLayout(0,1));
// {
// this.chart = new MeterVis(senses, chartHistoryLength);
// //chartPanel.add(chart);
//
// }
//
// c.weighty = 1.0;
// c.fill = GridBagConstraints.BOTH;
// //p.add(new JScrollPane(chartPanel), c);
// p.add(chart, c);
/*c.fill = c.BOTH;
p.add(Box.createVerticalBox(), c);*/
return p;
}
private NSlider newIntSlider(final AtomicInteger x, final String prefix, int min, int max) {
final NSlider s = new NSlider(x.intValue(), min, max) {
@Override
public String getText() {
return prefix + ": " + super.getText();
}
@Override
public void setValue(float v) {
int i = (int) Math.round(v);
super.setValue(i);
x.set(i);
}
@Override
public void onChange(float v) {
}
};
return s;
}
/** if true, then the speed control allows NAR to run() each iteration with 0 delay.
* otherwise, the minimum delay is 1ms */
public void setAllowFullSpeed(boolean allowFullSpeed) {
this.allowFullSpeed = allowFullSpeed;
}
}