/* Author: Jeff Dalton * Updated: Mon Dec 3 20:31:53 2001 by Jeff Dalton * Copyright: (c) 2001, AIAI, University of Edinburgh */ package ix.iview; import java.util.*; import java.awt.Color; import java.awt.Container; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Dimension; import java.awt.event.*; import javax.swing.*; import java.io.*; import ix.icore.IXAgent; import ix.icore.domain.*; import ix.iface.domain.*; import ix.iface.util.RadioButtonBox; import ix.util.*; import ix.util.lisp.*; /** * A simple domain editor. */ public class SimpleDomainEditor implements InternalDomainEditor, ActionListener { JFrame frame; Container contentPane; JMenu schemaMenu; SchemaEditor schemaEditor; File domainLibraryDirectory = DomainParser.getLibraryDirectory(); IXAgent agent; Domain dom; public SimpleDomainEditor(IXAgent agent, Domain dom) { this.agent =agent; this.dom = dom; setUpFrame(); } protected void setUpFrame() { frame = new JFrame(agent.getAgentSymbolName() + " Domain Editor"); frame.setSize(500, 300); frame.setJMenuBar(makeMenuBar()); contentPane = frame.getContentPane(); // contentPane.setLayout(new FlowLayout()); // ensureSchemaEditor(); frame.setVisible(true); } public void setVisible(boolean v) { frame.setVisible(v); } public void setLocation(int x, int y) { frame.setLocation(x, y); } public void saveExpansion(Object data) { Schema s = (Schema)data; ensureSchemaEditor(); schemaEditor.editPartialSchema(s); } protected void ensureSchemaEditor() { if (schemaEditor == null) { schemaEditor = this.new SchemaEditor(); schemaEditor.setBorder( BorderFactory.createTitledBorder("Schema")); // contentPane.add(schemaEditor, BorderLayout.CENTER); contentPane.add(schemaEditor); frame.pack(); } } /* * Menu bar */ protected JMenuBar makeMenuBar() { JMenuBar bar = new JMenuBar(); // File menu JMenu fileMenu = new JMenu("File"); bar.add(fileMenu); fileMenu.add(makeMenuItem("Load Domain")); fileMenu.add(makeMenuItem("Check Domain")); fileMenu.getMenuComponent(fileMenu.getMenuComponentCount()-1) .setEnabled(false); // can't check yet fileMenu.add(makeMenuItem("Clear Domain")); fileMenu.add(makeMenuItem("Save Domain")); fileMenu.addSeparator(); fileMenu.add(makeMenuItem("Close")); // Edit menu JMenu editMenu = new JMenu("Edit"); bar.add(editMenu); schemaMenu = new JMenu("Schema"); populateSchemaMenu(); editMenu.add(schemaMenu); editMenu.add(makeMenuItem("New Schema")); // View menu JMenu viewMenu = new JMenu("View"); bar.add(viewMenu); viewMenu.add(makeMenuItem("Simple")); viewMenu.add(makeMenuItem("Advanced")); viewMenu.getMenuComponent(viewMenu.getMenuComponentCount()-1) .setEnabled(false); // can't go to advanced editor yet return bar; } protected JMenuItem makeMenuItem(String text) { JMenuItem item = new JMenuItem(text); item.addActionListener(this); return item; } protected void populateSchemaMenu() { schemaMenu.setEnabled(false); schemaMenu.removeAll(); LList schemas = dom.getAllSchemas(); // Put schemas in alphabetical order by name. List sorted = new ArrayList(schemas); // sort modifies, so make a copy Collections.sort( sorted, new Comparator() { public int compare(Object a, Object b) { Schema s1 = (Schema)a; Schema s2 = (Schema)b; return s1.name.compareTo(s2.name); } }); if (!sorted.isEmpty()) { for (Iterator i = sorted.iterator(); i.hasNext();) { Schema s = (Schema)i.next(); JMenuItem item = makeMenuItem(s.name); item.setActionCommand("Edit Schema"); schemaMenu.add(item); } schemaMenu.setEnabled(true); } } /* * Action interpreter */ public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); Debug.noteln("DomainEditor action:", command); if (command.equals("Close")) { frame.setVisible(false); } else if (command.equals("Load Domain")) { DomainParser.loadDomain(frame, dom); populateSchemaMenu(); } else if (command.equals("Save Domain")) { DomainWriter.saveDomain(frame, dom); } else if (command.equals("Clear Domain")) { clearDomain(); } else if (command.equals("Edit Schema")) { JMenuItem item = (JMenuItem)e.getSource(); String schemaName = item.getText(); Debug.noteln("Edit schema", schemaName); ensureSchemaEditor(); schemaEditor.editSchema(dom.getNamedSchema(schemaName)); } else if (command.equals("New Schema")) { ensureSchemaEditor(); schemaEditor.editNewSchema(); } else Debug.noteln("Nothing to do for", command); } /* * Clearing a domain */ protected void clearDomain() { if (dom.isEmpty()) { JOptionPane.showMessageDialog(frame, "The domain is already empty.", "Empty Domain", JOptionPane.INFORMATION_MESSAGE); return; } switch(JOptionPane.showConfirmDialog(frame, "Are you sure you want to clear the domain?", "Confirm", JOptionPane.YES_NO_OPTION)) { case JOptionPane.YES_OPTION: // Continue break; case JOptionPane.NO_OPTION: // Return now and don't clear. return; } // Clear the domain. dom.clear(); populateSchemaMenu(); } /** * Used to indicate why an editing command cannot be carried out. */ class EditException extends RuntimeException { EditException(String message) { super(message); } } /* * Some static utilities that would be in the SchemaEditor class * if inner classes could have static declarations. */ public static LList parseNodes(String expansion) { LListCollector nodes = new LListCollector(); long i = 1; // For some reason, it doesn't work on Windows to // use lineSpearator here. while (!expansion.equals("")) { // String[] parts = // Util.breakStringAtFirst(expansion, lineSeparator); String[] parts = Util.breakStringAtFirst(expansion, "\n"); Long index = new Long(i); LList childPattern = Lisp.elementsFromString(parts[0]); if (!childPattern.isNull()) { nodes.addElement(Lisp.list(index, childPattern)); i++; } expansion = parts[1]; } return nodes.contents(); } public static LList sequentialOrderings(LList nodes) { LListCollector orderings = new LListCollector(); for (int i = 1, len = nodes.size(); i < len; i++) { orderings.addElement(Lisp.list(new Long(i), new Long(i+1))); } return orderings.contents(); } public static boolean orderingsAreSequential(LList nodes, LList orderings) { return new HashSet(orderings) .equals(new HashSet(sequentialOrderings(nodes))); } /** * Simple schema-editing panel. */ protected class SchemaEditor extends JPanel implements ActionListener, SchemaSymbols { String lineSeparator = System.getProperty("line.separator"); int textCols = 50; JTextField nameText; JTextField patternText; JTextArea expansionText; JTextArea commentText; TemporalConstraintPanel constraintPanel; JButton saveButton; JButton deleteButton; JButton cancelButton; Schema editingSchema = null; SchemaEditor() { super(); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setUp(); } protected void setUp() { nameText = new JTextField(textCols); patternText = new JTextField(textCols); expansionText = new JTextArea(7, textCols); commentText = new JTextArea(5, textCols); constraintPanel = new TemporalConstraintPanel(); addLeftLabel("Name"); add(nameText); addLeftLabel("Pattern"); add(patternText); addLeftLabel("Expansion"); add(new JScrollPane(expansionText)); addLeftLabel("Constraints"); add(constraintPanel); addLeftLabel("Annotations"); add(new JScrollPane(commentText)); saveButton = makeButton("Define Schema"); deleteButton = makeButton("Delete Schema"); cancelButton = makeButton("Cancel Edit"); setButtonsEnabled(false); Box buttons = Box.createHorizontalBox(); buttons.add(saveButton); buttons.add(deleteButton); buttons.add(cancelButton); add(buttons); } protected void addLeftLabel(String text) { // Might be better to change the AlignmentX of the other // things in the vertical box to be Component.LEFT_ALIGNMENT. Box b = Box.createHorizontalBox(); b.add(new JLabel(text)); b.add(Box.createHorizontalGlue()); add(b); } protected JButton makeButton(String text) { JButton button = new JButton(text); button.addActionListener(this); return button; } protected void setButtonsEnabled(boolean value) { saveButton.setEnabled(value); deleteButton.setEnabled(value); cancelButton.setEnabled(value); } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); Debug.noteln("SchemaEditor action:", command); if (command.equals("Define Schema")) { saveDefinition(); } else if (command.equals("Delete Schema")) { deleteSchema(); } else if (command.equals("Cancel Edit")) { cancelEdit(); } else Debug.noteln("Nothing to do for", command); } public void editNewSchema() { editingSchema = null; nameText.setText(""); patternText.setText(""); expansionText.setText(""); commentText.setText(""); constraintPanel.newOrderings(); setButtonsEnabled(true); deleteButton.setEnabled(false); } public void editSchema(Schema s) { Debug.noteln("Editing schema named", s.name); editingSchema = s; loadFromSchema(s); setButtonsEnabled(true); } public void editPartialSchema(Schema s) { // Get here via saveExpansion // N.B. We do not do editingSchema = s. loadFromSchema(s); setButtonsEnabled(true); deleteButton.setEnabled(false); } protected void cancelEdit() { editNewSchema(); // setButtonsEnabled(false); } /** * Define a schema */ protected void saveDefinition() { if (editingSchema == null) { defineNewSchema(); } else { String oldName = editingSchema.name; String newName = nameText.getText(); if (newName.equals(oldName)) { // If the name hasn't changed, the user probably wants // to redefine the schema. switch(JOptionPane.showConfirmDialog(frame, "Redefine schema " + Util.quote(oldName), "Confirm", JOptionPane.YES_NO_OPTION)) { case JOptionPane.YES_OPTION: // Redefine the schema. redefineSchema(editingSchema); break; case JOptionPane.NO_OPTION: // Do nothing. } } else { // If the name has changed, the user probably wants // to define a new Schema. Object[] message = { "You have changed the name from " + Util.quote(oldName) + ".", "Do you want to define a new schema named " + Util.quote(newName) + "?" }; switch(JOptionPane.showConfirmDialog(frame, message, "Confirm", JOptionPane.YES_NO_OPTION)) { case JOptionPane.YES_OPTION: // Define a new schema based on old one defineRenamedSchema(); break; case JOptionPane.NO_OPTION: // Do nothing. } } } } protected void defineRenamedSchema() { // Warn user about data loss. LinkedList lossage = new LinkedList(); Schema s = editingSchema; if (!s.orderings.isEmpty() && !orderingsAreSequential(s.nodes, s.orderings)) lossage.add(" Nonsequential orderings"); if (!s.effects.isEmpty()) lossage.add(" Effects"); if (!s.properties.isEmpty()) lossage.add(" Properties: " + s.properties); if (!lossage.isEmpty()) { lossage.addFirst ("Some of the original schema will be lost:"); lossage.add("Do you still want to define the new schema?"); switch(JOptionPane.showConfirmDialog(frame, lossage.toArray(), "Confirm", JOptionPane.YES_NO_OPTION)) { case JOptionPane.YES_OPTION: // Go through to act like it was a completely // new definition. break; case JOptionPane.NO_OPTION: // Do not define. return; } } // Define editingSchema = null; // forget old schema saveDefinition(); // and try saving again } protected void defineNewSchema() { Schema s; try { String name = nameText.getText().trim(); LList pattern = Lisp.elementsFromString(patternText.getText()); if (name.equals("")) throw new EditException("No name for schema."); if (pattern.isNull()) throw new EditException("No pattern for schema."); if (SimpleDomainEditor.this.dom.getNamedSchema(name) != null) throw new EditException ("There is already a schema named " + Util.quote(name)); s = new Schema(name, pattern); storeIntoSchema(s); } catch (Exception e) { // Debug.noteException(e); JOptionPane.showMessageDialog(frame, new Object[] {"Cannot define schema.", e.getMessage()}, "Error while defining schema", JOptionPane.ERROR_MESSAGE); return; } editingSchema = s; SimpleDomainEditor.this.dom.addSchema(s); SimpleDomainEditor.this.populateSchemaMenu(); } protected void redefineSchema(Schema s) { Debug.noteln("Redefining schema", s.toLList()); // Can't yet redefine schemas. /\/ JOptionPane.showMessageDialog(frame, "Schema redefinition is not yet supported.", "Redefinition not allowed", JOptionPane.ERROR_MESSAGE); } protected void loadFromSchema(Schema s) { // Name might be null because we might get here via saveExpansion. nameText.setText(s.name != null ? s.name : ""); patternText.setText(Lisp.elementsToString(s.pattern)); expansionText.setText(""); for (LList nodes = s.nodes; nodes != Lisp.NIL; nodes = nodes.cdr()) { LList spec = (LList)nodes.car(); LList childPattern = (LList)spec.elementAt(1); expansionText.append(Lisp.elementsToString(childPattern)); expansionText.append(lineSeparator); } commentText.setText(s.comments); constraintPanel.loadFromOrderings(s.nodes, s.orderings); } protected void storeIntoSchema(Schema s) { Debug.noteln("Changing schema", s.toLList()); Debug.assert(s.name.equals(nameText.getText().trim())); // Nodes String expansion = expansionText.getText().trim(); LList nodes = parseNodes(expansion); s.nodes = nodes; // Orderings String ordChoice = constraintPanel.getOrderingChoice(); LList orderings = ordChoice.equals("Sequential") ? sequentialOrderings(nodes) : Lisp.NIL; // Don't change existing orderings if choice is "Other" if (!ordChoice.equals("Other")) s.orderings = orderings; // Comments s.comments = commentText.getText(); s.checkConsistency(); Debug.noteln("Schema is now", s.toLList()); } /** * Delete a schema. */ protected void deleteSchema() { Debug.assert(editingSchema != null); String oldName = editingSchema.name; String newName = nameText.getText(); if (!newName.equals(oldName)) { // The user has changed the name JOptionPane.showMessageDialog(frame, "You cannot delete the schema after changing the name.", "Name changed", JOptionPane.ERROR_MESSAGE); deleteButton.setEnabled(false); return; } if (SimpleDomainEditor.this.dom.getNamedSchema(newName) == null) { // There's no schema with the name that's in the form. JOptionPane.showMessageDialog(frame, "There is no schema named " + Util.quote(newName), "No such schema", JOptionPane.ERROR_MESSAGE); deleteButton.setEnabled(false); return; } // Make sure the user really wants to delete it. switch (JOptionPane.showConfirmDialog(frame, "Delete schema " + Util.quote(newName), "Confirm", JOptionPane.YES_NO_OPTION)) { case JOptionPane.YES_OPTION: // Delete the schema. SimpleDomainEditor.this.dom.deleteNamedSchema(newName); SimpleDomainEditor.this.populateSchemaMenu(); cancelEdit(); break; case JOptionPane.NO_OPTION: // Do nothing. } } } /** * A subpanel of the schema editor that handles the choice * between parallel and sequential ordering contraints on * a schema's nodes. */ public static class TemporalConstraintPanel extends JPanel { RadioButtonBox box = RadioButtonBox.createHorizontalBox(); JRadioButton ordParallel = new JRadioButton("Parallel", false); JRadioButton ordSequential = new JRadioButton("Sequential", true); JRadioButton ordOther = new JRadioButton("Other", false); public TemporalConstraintPanel() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); // setBorder(BorderFactory.createLineBorder(Color.gray)); // setBackground(Color.white); // Temporal constraints JPanel temporalPanel = new JPanel(); temporalPanel .setLayout(new BoxLayout(temporalPanel, BoxLayout.X_AXIS)); temporalPanel .setBorder(BorderFactory.createTitledBorder("Temporal")); ordOther.setEnabled(false); box.add(new JLabel("Activities are", SwingConstants.LEFT)); box.add(Box.createHorizontalStrut(10)); box.add(ordParallel); box.add(ordSequential); box.add(ordOther); box.add(Box.createHorizontalGlue()); temporalPanel.add(box); add(temporalPanel); } public String getOrderingChoice() { String choice = box.getSelection(); Debug.noteln("Ordering choice", choice); Debug.assert(choice != null); return choice; } public void newOrderings() { ordParallel.setEnabled(true); ordSequential.setEnabled(true); ordOther.setEnabled(false); ordSequential.setSelected(true); } public void loadFromOrderings(LList nodes, LList orderings) { if (orderings.isEmpty() || orderingsAreSequential(nodes, orderings)) { // Allow parallel / sequential choice ordParallel.setEnabled(true); ordSequential.setEnabled(true); ordOther.setEnabled(false); if (orderings.isEmpty() && nodes.size() >= 2) ordParallel.setSelected(true); else ordSequential.setSelected(true); } else { // Schema already has orderings that are not sequential, // but still allow parallel / sequential choice. ordParallel.setEnabled(true); ordSequential.setEnabled(true); ordOther.setEnabled(true); ordOther.setSelected(true); } } } } // Issues: // * Need a way to prevent the JFileChooser from creating new folders.