001: /*
002: * <copyright>
003: *
004: * Copyright 2001-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026: package org.cougaar.tools.csmart.ui.experiment;
027:
028: import org.cougaar.tools.csmart.experiment.DBExperiment;
029: import org.cougaar.tools.csmart.experiment.Experiment;
030: import org.cougaar.tools.csmart.recipe.RecipeComponent;
031: import org.cougaar.tools.csmart.society.SocietyComponent;
032: import org.cougaar.tools.csmart.ui.viewer.CSMART;
033: import org.cougaar.util.log.Logger;
034:
035: import javax.swing.*;
036: import javax.swing.event.TreeModelEvent;
037: import javax.swing.event.TreeModelListener;
038: import javax.swing.tree.DefaultMutableTreeNode;
039: import javax.swing.tree.DefaultTreeCellRenderer;
040: import javax.swing.tree.DefaultTreeModel;
041: import javax.swing.tree.TreePath;
042: import java.awt.*;
043: import java.awt.event.ActionEvent;
044: import java.awt.event.MouseAdapter;
045: import java.awt.event.MouseEvent;
046: import java.awt.event.MouseListener;
047: import java.io.IOException;
048: import java.io.ObjectInputStream;
049: import java.util.EventObject;
050:
051: /**
052: * Properties panel in experiment builder.<br>
053: * Use this panel to add a society or recipes to your experiment.
054: * This class manages the manipulation of these components.
055: */
056: public class UnboundPropertyBuilder extends JPanel {
057: private static final String REMOVE_MENU_ITEM = "Remove";
058: private ExperimentBuilder experimentBuilder;
059: private DefaultTreeModel model;
060: private ExperimentTree tree;
061: private Experiment experiment;
062: private boolean isEditable;
063: private DefaultMutableTreeNode root;
064: private DefaultMutableTreeNode societies;
065: private DefaultMutableTreeNode recipes;
066:
067: // FIXME: right panel is empty. Get rid of it?
068: // Do something to make this panel useful?
069: // Drop it altogether?
070: private JPanel rightPanel = new JPanel(new BorderLayout());
071: private JSplitPane splitPane = new JSplitPane(
072: JSplitPane.HORIZONTAL_SPLIT);
073: private JPopupMenu popupMenu = new JPopupMenu();
074: private JPopupMenu societiesMenu = new JPopupMenu();
075: private JPopupMenu recipesMenu = new JPopupMenu();
076:
077: private transient Logger log;
078:
079: /**
080: * Define actions for pop-up menus on societies and recipes.
081: */
082: private Action removeAction = new AbstractAction(REMOVE_MENU_ITEM) {
083: public void actionPerformed(ActionEvent e) {
084: removeSelectedItems();
085: }
086: };
087:
088: private Action[] popupActions = { removeAction };
089:
090: private Action[] societiesActions = {
091: // FIXME: Why do we allow doing this?
092: new AbstractAction(REMOVE_MENU_ITEM + " Society") {
093: public void actionPerformed(ActionEvent e) {
094: removeAllChildren(societies);
095: }
096: } };
097:
098: private Action[] recipesActions = { new AbstractAction(
099: REMOVE_MENU_ITEM + " All Recipes") {
100: public void actionPerformed(ActionEvent e) {
101: removeAllChildren(recipes);
102: }
103: } };
104:
105: /**
106: * Define mouse listener to pop-up menus.
107: */
108: private MouseListener mouseListener = new MouseAdapter() {
109: public void mouseClicked(MouseEvent e) {
110: if (!isEditable)
111: return;
112: if (e.isPopupTrigger())
113: doPopup(e);
114: }
115:
116: public void mousePressed(MouseEvent e) {
117: if (!isEditable)
118: return;
119: if (e.isPopupTrigger())
120: doPopup(e);
121: }
122:
123: public void mouseReleased(MouseEvent e) {
124: if (!isEditable)
125: return;
126: if (e.isPopupTrigger())
127: doPopup(e);
128: }
129: };
130:
131: /**
132: * Define tree model listener to update the societies and recipes
133: * in the experiment when the user modifies the tree.
134: */
135:
136: private TreeModelListener myTreeModelListener = new TreeModelListener() {
137: public void treeNodesChanged(TreeModelEvent e) {
138: reconcileExperimentNodes();
139: }
140:
141: public void treeNodesInserted(TreeModelEvent e) {
142: reconcileExperimentNodes();
143: }
144:
145: public void treeNodesRemoved(TreeModelEvent e) {
146: reconcileExperimentNodes();
147: }
148:
149: public void treeStructureChanged(TreeModelEvent e) {
150: reconcileExperimentNodes();
151: }
152: };
153:
154: /**
155: * Constructs user interface for specifying values of
156: * properties of configurable components to use in experiments.
157: * @param experiment the experiment to edit
158: * @param experimentBuilder the <code>ExperimentBuilder</code> that this user interface was invoked from
159: */
160: public UnboundPropertyBuilder(Experiment experiment,
161: ExperimentBuilder experimentBuilder) {
162: this .experiment = experiment;
163: this .experimentBuilder = experimentBuilder;
164: createLogger();
165: isEditable = experiment.isEditable();
166: root = new DefaultMutableTreeNode();
167: model = new DefaultTreeModel(root);
168: societies = new DefaultMutableTreeNode(ExperimentTree.SOCIETIES);
169: recipes = new DefaultMutableTreeNode(ExperimentTree.RECIPES);
170: societies.setAllowsChildren(true);
171: recipes.setAllowsChildren(true);
172: model.insertNodeInto(societies, root, 0);
173: model.insertNodeInto(recipes, root, 1);
174: model.setAsksAllowsChildren(true);
175: tree = new ExperimentTree(model, experiment);
176: // cell editor always returns false so that user can't edit cell names
177: // using tree.setCellEditor(null) doesn't work
178: DefaultCellEditor myEditor = new DefaultCellEditor(
179: new JTextField()) {
180: public boolean isCellEditable(EventObject e) {
181: return false;
182: }
183: };
184: tree.setCellEditor(myEditor);
185:
186: tree.setExpandsSelectedPaths(true);
187: tree.setRootVisible(false);
188: tree.expandNode(societies);
189: tree.expandNode(recipes);
190: tree.addMouseListener(mouseListener);
191: // tree.setPreferredSize(new Dimension(250, 200));
192: model.addTreeModelListener(myTreeModelListener);
193:
194: splitPane.setRightComponent(rightPanel);
195: splitPane.setLeftComponent(new JScrollPane(tree));
196: setLayout(new BorderLayout());
197: add(splitPane, BorderLayout.CENTER);
198: splitPane.setDividerLocation(100);
199: for (int i = 0; i < popupActions.length; i++) {
200: popupMenu.add(popupActions[i]);
201: }
202: // Actions on societies - this allows removing all societies
203: // from an Experiment. Why do we permit this?
204: // This possibility is not documented.
205: // for (int i = 0; i < societiesActions.length; i++) {
206: // societiesMenu.add(societiesActions[i]);
207: // }
208: for (int i = 0; i < recipesActions.length; i++) {
209: recipesMenu.add(recipesActions[i]);
210: }
211: initDisplay();
212: }
213:
214: private void createLogger() {
215: log = CSMART.createLogger(this .getClass().getName());
216: }
217:
218: /**
219: * Display information about the experiment. Called to
220: * re-use this interface.
221: * @param newExperiment the new experiment to edit
222: */
223: public void reinit(Experiment newExperiment) {
224: experiment = newExperiment;
225: isEditable = newExperiment.isEditable();
226: initDisplay();
227: }
228:
229: private void initDisplay() {
230: model.removeTreeModelListener(myTreeModelListener);
231: societies.removeAllChildren();
232: recipes.removeAllChildren();
233: model.nodeStructureChanged(root);
234: DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
235: if (!isEditable) {
236: renderer.setTextNonSelectionColor(Color.gray);
237: renderer.setTextSelectionColor(Color.gray);
238: }
239: tree.setCellRenderer(renderer);
240: tree.setEditable(isEditable);
241: SocietyComponent society = experiment.getSocietyComponent();
242: if (society != null) {
243: addSocietyComponent(society);
244: }
245: for (int i = 0, n = experiment.getRecipeComponentCount(); i < n; i++) {
246: addRecipe(experiment.getRecipeComponent(i));
247: }
248: tree.expandNode(societies);
249: tree.expandNode(recipes);
250: model.addTreeModelListener(myTreeModelListener);
251: }
252:
253: /**
254: * Update society and recipes in the experiment when the user
255: * modifies the tree.
256: */
257: private void reconcileExperimentNodes() {
258: int nSocieties = societies.getChildCount();
259: if (nSocieties > 1) {
260: if (log.isErrorEnabled())
261: log.error("More than one society in experiment.");
262: } else if (nSocieties == 1) {
263: SocietyComponent newSociety = (SocietyComponent) ((DefaultMutableTreeNode) societies
264: .getChildAt(0)).getUserObject();
265: // society.setEditable(false); // so society editability tracks experiment editability
266: SocietyComponent society = experiment.getSocietyComponent();
267: if (society == null)
268: experiment.addSocietyComponent(newSociety);
269: else if (!society.equals(newSociety))
270: if (log.isErrorEnabled())
271: log
272: .error("Attempted to add society to experiment that has a society.");
273: } else if (nSocieties == 0) {
274: experiment.removeSocietyComponent();
275: }
276: int nRecipes = recipes.getChildCount();
277: RecipeComponent[] recipeAry = new RecipeComponent[nRecipes];
278: for (int i = 0; i < nRecipes; i++) {
279: recipeAry[i] = (RecipeComponent) ((DefaultMutableTreeNode) recipes
280: .getChildAt(i)).getUserObject();
281: }
282: experiment.setRecipeComponents(recipeAry);
283: }
284:
285: /**
286: * Display the correct popup menu for "Societies", "Recipes" or
287: * one or more recipes.
288: * If multiple objects are selected,
289: * then, if they're not all recipes, then select
290: * the one the mouse is pointing at.
291: */
292: private void doPopup(MouseEvent e) {
293: TreePath[] selectionPaths = tree.getSelectionPaths();
294: if (selectionPaths == null)
295: return;
296: boolean haveRecipes = false;
297: for (int i = 0; i < selectionPaths.length; i++) {
298: DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectionPaths[i]
299: .getLastPathComponent();
300: if (node.getUserObject() instanceof RecipeComponent)
301: haveRecipes = true;
302: else {
303: haveRecipes = false;
304: break;
305: }
306: }
307: if (!haveRecipes) {
308: TreePath selPath = tree.getPathForLocation(e.getX(), e
309: .getY());
310: // set the selected node to be the node the mouse is pointing at
311: tree.setSelectionPath(selPath);
312: DefaultMutableTreeNode popupNode = (DefaultMutableTreeNode) selPath
313: .getLastPathComponent();
314: if (popupNode == societies)
315: societiesMenu.show(tree, e.getX(), e.getY());
316: else if (popupNode == recipes)
317: recipesMenu.show(tree, e.getX(), e.getY());
318: } else
319: popupMenu.show(tree, e.getX(), e.getY());
320: }
321:
322: /**
323: * Remove all children of the specified parent from the tree.
324: */
325: private void removeAllChildren(DefaultMutableTreeNode parent) {
326: for (int i = 0; i < parent.getChildCount(); i++) {
327: // DefaultMutableTreeNode node =
328: // (DefaultMutableTreeNode)parent.getChildAt(i);
329: // make removed society component editable again
330: // Object userObject = node.getUserObject();
331: // if (userObject != null &&
332: // userObject instanceof ModifiableComponent)
333: // ((ModifiableComponent)userObject).setEditable(true);
334: }
335: parent.removeAllChildren();
336: model.nodeStructureChanged(parent);
337: }
338:
339: /**
340: * Remove selected items from the tree.
341: */
342: private void removeSelectedItems() {
343: TreePath[] selectionPaths = tree.getSelectionPaths();
344: if (selectionPaths == null)
345: return;
346: DefaultMutableTreeNode[] nodes = new DefaultMutableTreeNode[selectionPaths.length];
347: for (int i = 0; i < nodes.length; i++) {
348: nodes[i] = (DefaultMutableTreeNode) selectionPaths[i]
349: .getLastPathComponent();
350: }
351: for (int i = 0; i < nodes.length; i++) {
352: model.removeNodeFromParent(nodes[i]);
353: // make removed society component editable again
354: // Object userObject = nodes[i].getUserObject();
355: // if (userObject != null &&
356: // userObject instanceof ModifiableComponent)
357: // ((ModifiableComponent)userObject).setEditable(true);
358: }
359: }
360:
361: /**
362: * Add society component to tree.
363: */
364: private void addSocietyComponent(SocietyComponent sc) {
365: if (societies.getChildCount() != 0) {
366: // Only 1 society supposed to be allowed
367: System.err
368: .println("attempt to add second society to an experiment");
369: return;
370: }
371: DefaultMutableTreeNode node = new DefaultMutableTreeNode(sc,
372: false);
373: model
374: .insertNodeInto(node, societies, societies
375: .getChildCount());
376: }
377:
378: /**
379: * Add recipe component to tree.
380: */
381: private void addRecipe(RecipeComponent recipe) {
382: DefaultMutableTreeNode node = new DefaultMutableTreeNode(
383: recipe, false);
384: model.insertNodeInto(node, recipes, recipes.getChildCount());
385: }
386:
387: private void readObject(ObjectInputStream ois) throws IOException,
388: ClassNotFoundException {
389: ois.defaultReadObject();
390: createLogger();
391: }
392:
393: }
|