/* Author: Jeff Dalton * Updated: Wed Dec 5 00:28:29 2001 by Jeff Dalton * Copyright: (c) 2001, AIAI, University of Edinburgh */ package ix.ideel; import javax.swing.*; import java.awt.Container; import java.awt.Component; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.*; import java.util.*; import ix.ideel.event.IssueListener; import ix.ideel.event.IssueEvent; import ix.iview.SimpleDomainEditor; import ix.icore.domain.Schema; import ix.icore.process.StatusValues; import ix.icore.process.PNode; import ix.icore.process.Variable; import ix.icore.*; import ix.util.*; import ix.util.lisp.*; /** * A simple editor for entering issues. */ class IssueEditor implements ActionListener { protected String title; protected JFrame frame; protected Container contentPane; protected IssueViewer issueViewer; // who we work for protected EditPanel editPanel; protected JMenuItem saveAsSchemaItem; IssueEditor(IssueViewer issueViewer, String title) { this.issueViewer = issueViewer; this.title = title; setUpFrame(); } protected void setUpFrame() { frame = new JFrame(title); frame.setSize(500, 300); frame.setJMenuBar(makeMenuBar()); contentPane = frame.getContentPane(); editPanel = new EditPanel(); editPanel.setBorder(BorderFactory.createTitledBorder("Issue")); contentPane.add(editPanel); frame.pack(); } void setVisible(boolean v) { frame.setVisible(v); } void showIssue(IdeelIssue i) { editPanel.showIssue(i); setVisible(true); } void showNewIssue() { editPanel.showNewIssue(); setVisible(true); } /* * Menu Bar */ protected JMenuBar makeMenuBar() { JMenuBar bar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); bar.add(fileMenu); saveAsSchemaItem = makeMenuItem("Save As Schema"); saveAsSchemaItem.setEnabled(false); fileMenu.add(saveAsSchemaItem); fileMenu.addSeparator(); fileMenu.add(makeMenuItem("Close")); return bar; } protected JMenuItem makeMenuItem(String text) { JMenuItem item = new JMenuItem(text); item.addActionListener(this); return item; } /** * Action interpreter */ public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); Debug.noteln("IssueEditor action:", command); if (command.equals("Save As Schema")) { Schema s = editPanel.expansionPanel.makePartialSchema(); issueViewer.saveIssueExpansion(s); } else if (command.equals("Close")) { frame.setVisible(false); } else Debug.noteln("Nothing to do for", command); } /** * Issue-editing panel */ protected class EditPanel extends JPanel implements ActionListener, IssueListener, StatusValues { int textCols = 50; JTextArea issueText = new JTextArea(3, textCols); JTextArea commentText = new JTextArea(6, textCols); // Buttons that can be disabled or have their text changed. JButton modifyButton = makeButton("Modify Issue"); JButton expansionButton = makeButton("Edit Subissues"); Box newIssueButtons = Box.createHorizontalBox(); Box editIssueButtons = Box.createHorizontalBox(); ExpansionPanel expansionPanel = new ExpansionPanel(); IdeelIssue editingIssue = null; EditPanel() { super(); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setUp(); } protected void setUp() { add(makeLeftLabel("Issue Text")); add(new JScrollPane(issueText)); add(makeLeftLabel("Annotations")); add(new JScrollPane(commentText)); newIssueButtons.add(makeButton("Add Issue")); newIssueButtons.add(makeButton("Cancel")); editIssueButtons.add(modifyButton); editIssueButtons.add(makeButton("Copy Issue")); editIssueButtons.add(expansionButton); editIssueButtons.add(makeButton("Cancel")); add(newIssueButtons); } protected Box makeLeftLabel(String text) { Box b = Box.createHorizontalBox(); b.add(new JLabel(text)); b.add(Box.createHorizontalGlue()); return b; } protected JButton makeButton(String text) { JButton button = new JButton(text); button.addActionListener(this); return button; } /** * Sets up to edit an existing issue. */ void showIssue(IdeelIssue i) { editingIssue = i; i.addIssueListener(this); issueText.setText(i.shortDescription); issueText.setEnabled(false); commentText.setText(i.comments); commentText.setEnabled(true); ensureNoExpansionPanel(); ensureButtons(editIssueButtons); int status = i.getStatus(); if (status == STATUS_BLANK || status == STATUS_POSSIBLE) { modifyButton.setEnabled(true); expansionButton.setEnabled(true); } else { // /\/: Instead allow modification of e.g. comments? modifyButton.setEnabled(false); commentText.setEnabled(false); expansionButton.setEnabled(true); } frame.validate(); } /** * Sets up to edit a new, initially empty issue */ void showNewIssue() { editingIssue = null; issueText.setText(""); issueText.setEnabled(true); commentText.setText(""); commentText.setEnabled(true); ensureNoExpansionPanel(); ensureButtons(newIssueButtons); frame.validate(); } void ensureButtons(Box buttons) { Component lastComponent = getComponent(getComponentCount() - 1); Debug.assert(lastComponent == newIssueButtons || lastComponent == editIssueButtons); if (lastComponent != buttons) { remove(lastComponent); add(buttons); repaint(); } } /** * Action interpreter */ public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); Debug.noteln("Issue action:", command); if (command.equals("Add Issue")) { addIssue(); } else if (command.equals("Modify Issue")) { modifyIssue(); } else if (command.equals("Copy Issue")) { copyIssue(); } else if (command.equals("Edit Subissues")) { addExpansionPanel(); } else if (command.equals("Add Subissues")) { addSubissues(); } else if (command.equals("Modify Subissues")) { throw new Error("Modify Subissues button should be disabled"); } else if (command.equals("Cancel")) { showNewIssue(); } else Debug.noteln("Nothing to do for", command); } /** * Tells the issue viewer to add a new issue */ void addIssue() { Debug.assert(editingIssue == null); String text = issueText.getText().trim(); String comments = commentText.getText(); if (text.length() == 0) { JOptionPane.showMessageDialog(frame, "Empty issue text", "Invalid issue", JOptionPane.ERROR_MESSAGE); return; } try { LList pattern = Lisp.elementsFromString(text); if (pattern.length() < 1) throw new RuntimeException("Empty issue pattern"); IdeelIssue i = new IdeelIssue(pattern); i.comments = comments; issueViewer.addIssue(i); showIssue(i); // now editing that issue } catch (Exception e) { Debug.noteException(e); JOptionPane.showMessageDialog(this, new Object[] {"Cannot define issue.", e.getMessage()}, "Error while defining issue", JOptionPane.ERROR_MESSAGE); return; } } /** * Modifies a existing issue. */ void modifyIssue() { Debug.assert(editingIssue != null); editingIssue.comments = commentText.getText(); editingIssue.fireIssueEdited(); } /** * Starts editing a new issue based on an existing one. */ void copyIssue() { Debug.assert(editingIssue != null); if (!editingIssue.patternVars.isEmpty()) { // Go back from Variables to ?-vars. LList pat = (LList)Variable.removeVars(editingIssue.pattern); issueText.setText(Lisp.elementsToString(pat)); } editingIssue = null; issueText.setEnabled(true); commentText.setEnabled(true); ensureNoExpansionPanel(); ensureButtons(newIssueButtons); // can't edit subactions frame.validate(); } /** * Adds subissues to an existing issue. */ void addSubissues() { Schema s = expansionPanel.makePartialSchema(); issueViewer.expandIssue(editingIssue, s); } /* * IssueListener methods - in case an Issue changes while * we're editing it. */ public void statusChanged(IssueEvent e) { IdeelIssue i = (IdeelIssue)e.getSource(); if (i != editingIssue) return; Debug.noteln("Status change while editing issue", i); // Since it only gets more restrictive, and always in the // same way, we only have to check that we're not still in // the most permissive state. int status = i.getStatus(); if (status != STATUS_BLANK && status != STATUS_POSSIBLE) { modifyButton.setEnabled(false); // User can look at expansion if not already doing so // but can't change it. expansionButton .setEnabled(expansionButton.getText() .equals("Edit Subissues")); } } public void priorityChanged(IssueEvent e) { IdeelIssue i = (IdeelIssue)e.getSource(); if (i != editingIssue) return; Debug.noteln("Priority change while editing issue", i); } public void newOption(IssueEvent e, IssueOption opt) { IdeelIssue i = (IdeelIssue)e.getSource(); if (i != editingIssue) return; Debug.noteln("New option while editing issue", i); } public void newReport(IssueEvent e, Report report) { IdeelIssue i = (IdeelIssue)e.getSource(); if (i != editingIssue) return; Debug.noteln("New report while editing issue", i); commentText.setText(i.comments); // Try to get it to scoll to the end commentText.setCaretPosition(i.comments.length()); frame.validate(); } public void issueEdited(IssueEvent e) { IdeelIssue i = (IdeelIssue)e.getSource(); if (i != editingIssue) return; // Probably we did the editing ... Debug.noteln("Issue edited while editing", i); } /* * Expansion / subissue panel */ /** * Adds a panel for editing subissues and their ordering constraints. */ void addExpansionPanel() { // Panel shouldn't already be there. Debug.assert(!expansionPanelIsShown()); // The panel is only for editing existing issues. Debug.assert(editingIssue != null, "can't edit new issue subissues"); expansionPanel.loadFromIssue(editingIssue); // Allow the user to turn the issue's expansion into a schema. saveAsSchemaItem.setEnabled(true); if (editingIssue.children.isEmpty()) { // No existing subissues. expansionButton.setText("Add Subissues"); int status = editingIssue.getStatus(); if (status == STATUS_BLANK || status == STATUS_POSSIBLE) expansionButton.setEnabled(true); else expansionButton.setEnabled(false); } else { // We don't yet allow existing subissues to be modified. /\/ expansionButton.setText("Modify Subissues"); expansionButton.setEnabled(false); } // Add the panel just above the buttons. add(expansionPanel, getComponentCount() - 1); frame.pack(); } /** * Removes the panel for editing subissues if it is being displayed. */ void ensureNoExpansionPanel() { if (expansionPanelIsShown()) { saveAsSchemaItem.setEnabled(false); expansionButton.setText("Edit Subissues"); // button can bring expansionButton.setEnabled(true); // panel back remove(expansionPanel); frame.pack(); } } /** * Determines whether the expansion panel is being displayed. */ boolean expansionPanelIsShown() { return getComponent(getComponentCount() - 2) == expansionPanel; } /** * A panel for editing subissues and their ordering constraints. */ class ExpansionPanel extends JPanel { JTextArea subissueText = new JTextArea(5, textCols); SimpleDomainEditor.TemporalConstraintPanel constraintPanel = new SimpleDomainEditor.TemporalConstraintPanel(); ExpansionPanel() { super(); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); // setBorder(BorderFactory.createTitledBorder("Expansion")); setUp(); } void setUp() { add(makeLeftLabel("Subissues")); add(new JScrollPane(subissueText)); add(makeLeftLabel("Constraints")); add(constraintPanel); } Schema makePartialSchema() { // Pattern // LList pattern = editingIssue != null // ? editingIssue.pattern // : null; LList pattern = Lisp.elementsFromString(issueText.getText().trim()); // Nodes String expansion = subissueText.getText().trim(); LList nodes = SimpleDomainEditor.parseNodes(expansion); // Orderings String ordChoice = constraintPanel.getOrderingChoice(); LList orderings = ordChoice.equals("Sequential") ? SimpleDomainEditor.sequentialOrderings(nodes) : Lisp.NIL; // Schema Schema partial = new Schema(); partial.pattern = pattern; partial.nodes = nodes; partial.orderings = orderings; partial.comments = commentText.getText(); return partial; } void loadFromIssue(IdeelIssue issue) { Debug.noteln("Editing orderings of", issue); LList subnodes = issue.children; LList orderings = issueOrderings(issue); Debug.noteln("Orderings = ", orderings); subissueText.setText(nodesToText(subnodes)); constraintPanel.loadFromOrderings(subnodes, orderings); } String nodesToText(LList nodes) { String lineSeparator = System.getProperty("line.separator"); StringBuffer result = new StringBuffer(); for (Iterator n = nodes.iterator(); n.hasNext();) { PNode node = (PNode)n.next(); result.append(Lisp.elementsToString(node.pattern)); result.append(lineSeparator); } return result.toString(); } LList issueOrderings(IdeelIssue issue) { // We have to turn the graph of the issue's children // back into a list of orderings. This is tedious // but straightforward. Fortunately, we know there // are not links except to siblings. LList nodes = issue.children; HashMap nodeToNumber = new HashMap(nodes.size()); LListCollector orderings = new LListCollector(); // Give each subnode a number. int count = 1; for (Iterator n = nodes.iterator(); n.hasNext();) { nodeToNumber.put(n.next(), new Long(count)); count++; } // Look at the direct successors of each subnode // and record a (subnode successor) ordering using // the corresponding node-numbers. for (Iterator n = nodes.iterator(); n.hasNext();) { PNode node = (PNode)n.next(); for(Iterator p = node.postNodes.iterator(); p.hasNext();) { PNode post = (PNode)p.next(); Long node_n = (Long)nodeToNumber.get(node); Long post_n = (Long)nodeToNumber.get(post); orderings.add(Lisp.list(node_n, post_n)); } } // Return list of orderings. return orderings.contents(); } } } }