package infosapient.ui;
/*
* Copyright (c) 2001, Workplace Performance Tools, All Rights Reserved.
* License to use this program is provided under the COMMON PUBLIC LICENSE 0.5
* THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE
* ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES
* RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
*/
import infosapient.system.FzySystemComponent;
import infosapient.system.FSEvent;
import infosapient.system.FzySet;
import infosapient.system.FzyKnowledgebase;
import infosapient.system.FzyAttribute;
import infosapient.system.FzyRule;
import infosapient.control.InfoSapientController;
import infosapient.xml.*;
import infosapient.rulecompiler.*;
import infosapient.system.ObservableImpl;
import infosapient.system.Observer;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.net.URL;
import java.util.Hashtable;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import javax.swing.event.*;
/**
* InfoSapientRuleEditor is the GUI for fzy rule creation, modification.
* @author:
* @version $Revision: 1.1.1.1 $
* @package infosapient.ui
*/
public class InfoSapientRuleEditor
extends JPanel
implements UndoableEditListener, infosapient.system.Observer {
private static ResourceBundle resources;
static boolean FOUND_PROPERTIES;
static {
try {
resources = ResourceBundle.getBundle("resources.RuleEditor", Locale.getDefault());
FOUND_PROPERTIES = true;
} catch (MissingResourceException mre) {
}
}
private RuleEditorObservable reo;
private FzyKnowledgebase theKB;
private FzyRule theRule;
private infosapient.rulecompiler.RuleCompiler ruleCompiler = null;
private InfoSapientController controller;
private JFrame frame;
private JTextComponent editor;
private JPopupMenu pMenu;
private JPopupMenu editMenu;
private JPopupMenu ruleMenu;
/**
* Suffix applied to the key used in resource file
* lookups for an image.
*/
public static final String imageSuffix = "Image";
/**
* Suffix applied to the key used in resource file
* lookups for a label.
*/
public static final String labelSuffix = "Label";
/**
* Suffix applied to the key used in resource file
* lookups for an action.
*/
public static final String actionSuffix = "Action";
/**
* Suffix applied to the key used in resource file
* lookups for tooltip text.
*/
public static final String tipSuffix = "Tooltip";
public static final String DELETE = "delete";
public static final String NEW = "new";
public static final String SAVE = "save";
public static final String EXIT = "exit";
/**
* Rule template string
*/
public static final String NEW_RULE =
"RULE_NAME\n\n if \n then ; ";
private Hashtable commands;
private Hashtable menuItems;
private JMenuBar menubar;
private JToolBar toolbar;
private JComponent status;
private FileDialog fileDialog;
/** UndoManager that we add edits to. */
private UndoManager undoMgr = new UndoManager();
// --- action implementations -----------------------------------
/**
* Actions defined by InfoSapientRuleEditor
*/
private UndoAction undoAction = new UndoAction();
private RedoAction redoAction = new RedoAction();
private NewAction newAction = new NewAction(NEW);
private SaveAction saveAction = new SaveAction(SAVE);
private DeleteAction deleteAction = new DeleteAction(DELETE);
private ExitAction exitAction = new ExitAction(EXIT);
private Action[] actionsList =
{newAction, deleteAction, saveAction, exitAction, undoAction, redoAction};
// Yarked from JMenu, ideally this would be public.
private class ActionChangedListener implements PropertyChangeListener {
JMenuItem menuItem;
ActionChangedListener(JMenuItem mi) {
super();
this.menuItem = mi;
}
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (e.getPropertyName().equals(Action.NAME)) {
String text = (String) e.getNewValue();
menuItem.setText(text);
} else
if (propertyName.equals("enabled")) {
Boolean enabledState = (Boolean) e.getNewValue();
menuItem.setEnabled(enabledState.booleanValue());
}
}
}
/**
* FIXME - I'm not very useful yet
*/
class StatusBar extends JComponent {
public StatusBar() {
super();
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
}
public void paint(Graphics g) {
super.paint(g);
}
}
class UndoAction extends AbstractAction {
public UndoAction() {
super("Undo");
setEnabled(false);
}
public void actionPerformed(ActionEvent e) {
try {
undoMgr.undo();
} catch (CannotUndoException ex) {
JOptionPane.showMessageDialog(
frame,
("Unable to undo: " + ex.getMessage()),
"InfoSapient ERROR",
JOptionPane.ERROR_MESSAGE);
}
update();
redoAction.update();
}
private void update() {
if (undoMgr.canUndo()) {
setEnabled(true);
newAction.setEnabled(false);
saveAction.setEnabled(true);
putValue(Action.NAME, undoMgr.getUndoPresentationName());
} else {
setEnabled(false);
putValue(Action.NAME, "Undo");
}
}
}
class RedoAction extends AbstractAction {
public RedoAction() {
super("Redo");
setEnabled(false);
}
public void actionPerformed(ActionEvent e) {
try {
undoMgr.redo();
} catch (CannotRedoException ex) {
JOptionPane.showMessageDialog(
frame,
("Unable to redo: " + ex.getMessage()),
"InfoSapientRuleEditor ERROR",
JOptionPane.ERROR_MESSAGE);
}
update();
undoAction.update();
}
private void update() {
if (undoMgr.canRedo()) {
setEnabled(true);
putValue(Action.NAME, undoMgr.getRedoPresentationName());
} else {
setEnabled(false);
putValue(Action.NAME, "Redo");
}
}
}
class NewAction extends AbstractAction {
NewAction() {
super(NEW);
}
NewAction(String nm) {
super(nm);
}
public void actionPerformed(ActionEvent e) {
setRuleText(NEW_RULE);
setEnabled(true);
saveAction.setEnabled(true);
}
}
class SaveAction extends AbstractAction {
SaveAction() {
this(SAVE);
}
SaveAction(String nm) {
super(nm);
setEnabled(true);
}
public void actionPerformed(ActionEvent e) {
if (this.isEnabled()) {
String ruleText = ((JTextComponent) getEditor()).getText();
theRule = null;
try {
ruleCompiler =
infosapient.rulecompiler.RuleCompiler.newCompiler(
theKB,
ruleText);
theRule = ruleCompiler.compileRule();
if (getController().getKB().containsRule(theRule.getName()))
getController().getKB().removeRule(theRule);
FSEvent fse = new FSEvent(this, "RULE:created", theRule);
theRule.addObserver(controller.getKB());
reo.notify(fse);
this.setEnabled(false);
newAction.setEnabled(true);
} catch (Exception pe) {
String msg = pe.getMessage();
if (pe instanceof NullPointerException)
msg =
"Incorrect rule syntax.\nThis rule is either lacking a premise or a consequent. ";
JOptionPane.showMessageDialog(
getFrame(),
msg,
"InfoSapient ERROR",
JOptionPane.ERROR_MESSAGE);
setEnabled(false);
}
}
}
}
class DeleteAction extends AbstractAction {
DeleteAction() {
this(DELETE);
}
DeleteAction(String nm) {
super(nm);
setEnabled(false);
}
public void actionPerformed(ActionEvent ae) {
if (this.isEnabled()) {
int result =
JOptionPane.showConfirmDialog(
getFrame(),
" You are about to delete rule "
+ getRule().getName()
+ ". Click OK to continue",
"InfoSapient WARNING",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
try {
if (getRule() instanceof FzyRule)
getController().getKB().removeRule(getRule());
setRuleText(null);
setEnabled(false);
} catch (Exception iae) {
JOptionPane.showMessageDialog(
getFrame(),
iae.getMessage(),
"InfoSapient ERROR",
JOptionPane.ERROR_MESSAGE);
}
}
}
}
}
/**
*
*/
class ExitAction extends AbstractAction {
ExitAction() {
this("close-editor");
}
ExitAction(String aCmd) {
super(aCmd);
}
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
}
class RuleDocListener implements DocumentListener {
/**Gives notification that an attribute or set of attributes changed.*/
public void changedUpdate(DocumentEvent de) {
}
/**Gives notification that there was an insert into the document. */
public void insertUpdate(DocumentEvent de) {
}
/**Gives notification that a portion of the document has been removed.*/
public void removeUpdate(DocumentEvent de) {
}
}
class RuleEditorObservable extends infosapient.system.ObservableImpl {
InfoSapientRuleEditor owner;
public RuleEditorObservable(InfoSapientRuleEditor aRE) {
owner = aRE;
}
void notify(Object obj) {
super.setChanged();
notifyObservers(obj);
}
}
class RuleEditorCloser extends WindowAdapter implements java.io.Serializable {
public void windowClosing(WindowEvent wevnt) {
Window w = (Window) wevnt.getSource();
setVisible(false);
}
}
public InfoSapientRuleEditor(InfoSapientController theControl) {
super(new BorderLayout());
if (FOUND_PROPERTIES) {
controller = theControl;
initialize();
reo = this.new RuleEditorObservable(this);
reo.addObserver(controller);
} else {
JOptionPane.showMessageDialog(
this,
"Unable to open rule editor because no window properties found!",
"InfoSapient ERROR",
JOptionPane.ERROR_MESSAGE);
}
}
public InfoSapientRuleEditor(InfoSapientController theControl, FzyRule aRule) {
this(theControl);
setRule(aRule);
}
// Yarked from JMenu, ideally this would be public.
private PropertyChangeListener newActionChangeListener(JMenuItem b) {
return new ActionChangedListener(b);
}
/**
* Create a menu for the app. By default this pulls the
* definition of the menu from the associated resource file.
*/
private JMenu createMenu(String key) {
String[] itemKeys = tokenize(getResourceString(key));
JMenu menu = new JMenu(getResourceString(key + "Label"));
for (int i = 0; i < itemKeys.length; i++) {
if (itemKeys[i].equals("-")) {
menu.addSeparator();
} else {
JMenuItem mi = createMenuItem(itemKeys[i]);
mi.enable(true);
menu.add(mi);
}
}
return menu;
}
/**
* Create the menubar for the app. By default this pulls the
* definition of the menu from the associated resource file.
*/
private JMenuBar createMenubar() {
JMenuItem mi;
JMenuBar mb = new JMenuBar();
String[] menuKeys = tokenize(getResourceString("menubar"));
for (int i = 0; i < menuKeys.length; i++) {
JMenu m = createMenu(menuKeys[i]);
if (m != null) {
mb.add(m);
}
}
return mb;
}
/**
* This is the hook through which all menu items are
* created. It registers the result with the menuitem
* hashtable so that it can be fetched with getMenuItem().
* @see #getMenuItem
* @param String -- the menu item name being created
* @returns JMenuItem -- the new menuItem.
*/
private JMenuItem createMenuItem(String cmd) {
JMenuItem mi = new JMenuItem(getResourceString(cmd + labelSuffix));
URL url = getResource(cmd + imageSuffix);
if (url != null) {
mi.setHorizontalTextPosition(JButton.RIGHT);
mi.setIcon(new ImageIcon(url));
}
String astr = getResourceString(cmd + actionSuffix);
if (astr == null) {
astr = cmd;
}
mi.setActionCommand(astr);
Action a = getAction(astr);
if (a != null) {
mi.addActionListener(a);
a.addPropertyChangeListener(newActionChangeListener(mi));
mi.setEnabled(true);
} else {
mi.setEnabled(false);
}
menuItems.put(cmd, mi);
return mi;
}
/**
* Create a status bar
*/
private Component createStatusbar() {
// need to do something reasonable here
status = new StatusBar();
return status;
}
/**
* Hook through which every toolbar item is created.
*/
private Component createTool(String key) {
return createToolbarButton(key);
}
/**
* Create the toolbar. By default this reads the
* resource file for the definition of the toolbar.
*/
private Component createToolbar() {
toolbar = new JToolBar();
String[] toolKeys = tokenize(getResourceString("toolbar"));
for (int i = 0; i < toolKeys.length; i++) {
if (toolKeys[i].equals("-")) {
toolbar.add(Box.createHorizontalStrut(5));
} else {
toolbar.add(createTool(toolKeys[i]));
}
}
toolbar.add(Box.createHorizontalGlue());
return toolbar;
}
/**
* Create a button to go inside of the toolbar. By default this
* will load an image resource. The image filename is relative to
* the classpath (including the '.' directory if its a part of the
* classpath), and may either be in a JAR file or a separate file.
*
* @param key The key in the resource file to serve as the basis
* of lookups.
*/
private JButton createToolbarButton(String key) {
URL url = getResource(key + imageSuffix);
JButton b = new JButton(new ImageIcon(url)) {
public float getAlignmentY() { return 0.5f; }
};
b.setRequestFocusEnabled(false);
b.setMargin(new Insets(1,1,1,1));
String astr = getResourceString(key + actionSuffix);
if (astr == null) {
astr = key;
}
Action a = getAction(astr);
if (a != null) {
b.setActionCommand(astr);
b.addActionListener(a);
b.setEnabled(true);
} else {
b.setEnabled(false);
}
String tip = getResourceString(key + tipSuffix);
if (tip != null) {
b.setToolTipText(tip);
}
return b;
}
private Action getAction(String cmd) {
return (Action) commands.get(cmd);
}
/**
* Fetch the list of actions supported by this
* editor. It is implemented to return the list
* of actions supported by the embedded JTextComponent
* augmented with the actions defined locally.
* @returns actionArray[] -- the Action[] of defined actions.
*/
public Action[] getActions() {
return actionsList;
}
private InfoSapientController getController() {
return controller;
}
/**
* Fetch the editor contained in this panel
*/
private JTextComponent getEditor() {
if (editor == null) editor = new JTextArea();
return editor;
}
/**
* Find the hosting frame, for the file-chooser dialog.
*/
private Frame getFrame() {
for (Container p = getParent(); p != null; p = p.getParent()) {
if (p instanceof Frame) {
return (Frame) p;
}
}
return null;
}
private JMenuBar getMenubar() {
return menubar;
}
/**
* Fetch the menu item that was created for the given
* command.
* @param cmd Name of the action.
* @returns item -- JMenuItem created for the given command or null
* if one wasn't created.
*/
private JMenuItem getMenuItem(String cmd) {
return (JMenuItem) menuItems.get(cmd);
}
private URL getResource(String key) {
String name = getResourceString(key);
if (name != null) {
URL url = this.getClass().getResource(name);
return url;
}
return null;
}
private String getResourceString(String nm) {
String str;
try {
str = resources.getString(nm);
} catch (MissingResourceException mre) {
str = null;
/*
javax.swing.JOptionPane.showMessageDialog(frame,
"Missing resource for name: "+ nm,
"InfoSapientRuleEditor ERROR",
javax.swing.JOptionPane.ERROR_MESSAGE);
*/
}
return str;
}
public FzyRule getRule() {
return theRule;
}
private Container getToolbar() {
return toolbar;
}
public void handleFSEvent(FSEvent anEvent) {
if (anEvent.getCmd().equalsIgnoreCase("ParseError")) {
String e = (String) anEvent.getPackage();
JOptionPane.showMessageDialog(frame,
e,
"InfoSapient ERROR",
JOptionPane.NO_OPTION);
}
}
private void initialize() {
try {
frame = new JFrame();
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().setFont(new Font("Helvetica",Font.PLAIN,12));
frame.setTitle("InfoSapient Rule Editor");
//
this.setBorder(BorderFactory.createEtchedBorder());
// create the embedded JTextComponent
editor = getEditor();
editor.setFont(new Font("Helvetica", Font.PLAIN, 12));
// Add this as a listener for undoable edits.
editor.getDocument().addUndoableEditListener(this);
// install the command table
commands = new Hashtable();
Action[] actions = getActions();
for (int i = 0; i < actions.length; i++) {
Action a = actions[i];
commands.put(a.getValue(Action.NAME), a);
}
JScrollPane scroller = new JScrollPane();
JViewport port = scroller.getViewport();
port.add(editor);
this.setRuleText(NEW_RULE);
try {
String vpFlag = resources.getString("ViewportBackingStore");
Boolean bs = new Boolean(vpFlag);
port.setBackingStoreEnabled(bs.booleanValue());
} catch (MissingResourceException mre) {
// just use the viewport default
}
menuItems = new Hashtable();
menubar = createMenubar();
JPanel panel = new JPanel(new BorderLayout());
panel.add("North", menubar);
this.add("North",createToolbar());
this.add("Center", scroller);
this.add("South", createStatusbar());
panel.add("Center", this);
frame.getContentPane().add("Center", panel);
frame.pack();
this.setVisible(true);
} catch (Exception initExcep) {
initExcep.printStackTrace();
}
}
public void setRule(FzyRule aRule) {
deleteAction.setEnabled(true);
setRuleText(aRule.getName()+"\n\n"+aRule.getText());
theRule = aRule;
}
public void setRuleText(String rText) {
try {
Document oldDoc = getEditor().getDocument();
if(oldDoc != null)
oldDoc.removeUndoableEditListener(InfoSapientRuleEditor.this);
Document doc = new PlainDocument();
getEditor().setDocument(doc);
doc.insertString(doc.getLength(), rText, null);
doc.addUndoableEditListener(InfoSapientRuleEditor.this);
revalidate();
} catch (Exception exp) {}
}
public void setVisible(boolean f) {
if (FOUND_PROPERTIES) {
if (f) {
frame.setSize(frame.getInsets().left + frame.getInsets().right + 620,
frame.getInsets().top + frame.getInsets().bottom + 420);
frame.addWindowListener(new RuleEditorCloser());
frame.setLocation(250, 150);
frame.setVisible(f);
}
else {
//discard all edits
//getController().setALRuleEditor(null);
frame.setVisible(f);
frame.dispose();
}
}
}
/**
* Take the given string and chop it up into a series
* of strings on whitespace boundries. This is useful
* for trying to get an array of strings out of the
* resource file.
* @returns tokenArray -- the array of words in the string.
*/
private String[] tokenize(String input) {
Vector v = new Vector();
StringTokenizer t = new StringTokenizer(input);
String cmd[];
while (t.hasMoreTokens())
v.addElement(t.nextToken());
cmd = new String[v.size()];
for (int i = 0; i < cmd.length; i++)
cmd[i] = (String) v.elementAt(i);
return cmd;
}
/**
* Messaged when the Document has created an edit, the edit is
* added to <code>undo</code>, an instance of UndoManager.
*/
public void undoableEditHappened(UndoableEditEvent e) {
undoMgr.addEdit(e.getEdit());
undoAction.update();
redoAction.update();
}
public void update(infosapient.system.Observable observ, Object obj) {}
}