001: package org.osbl.agent.gui;
002:
003: import java.awt.*;
004: import java.awt.event.ActionEvent;
005: import java.awt.event.ActionListener;
006: import java.util.List;
007:
008: import org.osbl.agent.model.Action;
009: import org.osbl.agent.model.Condition;
010: import org.osbl.agent.model.Rule;
011: import org.osbl.client.wings.XButton;
012: import org.wings.*;
013: import org.wingx.XDivision;
014:
015: import javax.swing.*;
016:
017: /**
018: * Provides the Editor panel and controls its operation.
019: *
020: * @author Sebastian Nozzi.
021: */
022: public class RuleEditorPanel extends SPanel {
023:
024: SPanel contentPane = new SPanel(new SGridBagLayout());
025:
026: /** The current user id. */
027: private String currentUser;
028:
029: /** The Rule being edited. */
030: private Rule rule;
031:
032: /** The SidePanel. */
033: private SidePanel sidePanel;
034:
035: /** The name field. */
036: private STextField nameField;
037:
038: /** The "add condition" button. */
039: private SButton addConditionButton;
040:
041: /** The "add action" button. */
042: private SButton addActionButton;
043:
044: /** The conditions division, all ConditionPanels go here. */
045: private XDivision conditionsDivision;
046:
047: /** The actions division, all ActionPanels go here. */
048: private XDivision actionsDivision;
049:
050: /** The action panel count. */
051: private int actionPanelCount;
052:
053: /** The condition panel count. */
054: private int conditionPanelCount;
055:
056: /** The design context. */
057: private DesignContext designContext;
058:
059: /** The close button. */
060: protected SButton closeButton;
061:
062: /** The revert button. */
063: protected SButton revertButton;
064:
065: /** The apply button. */
066: protected SButton applyButton;
067:
068: /**
069: * Instantiates a new rule controller for the given DesignContext.
070: *
071: * @param designContext the design context
072: */
073: public RuleEditorPanel(DesignContext designContext) {
074: super (new SBorderLayout());
075: contentPane.setVerticalAlignment(SConstants.TOP_ALIGN);
076: ((SGridBagLayout) contentPane.getLayout()).setHgap(8);
077: ((SGridBagLayout) contentPane.getLayout()).setVgap(8);
078: add(contentPane, SBorderLayout.CENTER);
079:
080: setDesignContext(designContext);
081:
082: // Panel starts up empty (no action/condtion panels).
083: actionPanelCount = 0;
084: conditionPanelCount = 0;
085:
086: // Build up the visual elements of our panel.
087: populateRulePanel();
088:
089: closeButton = new XButton(msg("close"));
090: revertButton = new XButton(msg("revert"));
091: applyButton = new XButton(msg("applyChanges"));
092:
093: // Initialize some visual aspects.
094: setPreferredSize(SDimension.FULLAREA);
095: setStyle("XForm");
096:
097: changeEditingMode();
098: }
099:
100: /**
101: * Gets the SidePanel.
102: *
103: * @return the SidePanel object.
104: */
105: public SidePanel getAgentSidebarPanel() {
106: return sidePanel;
107: }
108:
109: /**
110: * Sets the SidePanel.
111: *
112: * @param sidePanel the SidePanel object.
113: */
114: public void setAgentSidebarPanel(SidePanel sidePanel) {
115: this .sidePanel = sidePanel;
116: }
117:
118: /**
119: * Gets the design context.
120: *
121: * @return the design context
122: */
123: public DesignContext getDesignContext() {
124: return designContext;
125: }
126:
127: /**
128: * Sets the design context.
129: *
130: * @param designContext the design context
131: */
132: public void setDesignContext(DesignContext designContext) {
133: this .designContext = designContext;
134: }
135:
136: /**
137: * Convenience method to get localized versions of strings/labels
138: *
139: * @param code the String-code
140: * @param args the String-args
141: *
142: * @return the localized string
143: */
144: protected String msg(String code, Object... args) {
145: return designContext.msg(code, args);
146: }
147:
148: /**
149: * Checks if this panel is editable.
150: * The panel is not editable for non-editable Rules (for which the user
151: * lacks the necessary permissions).
152: *
153: * @return true, if it is editable
154: */
155: protected boolean isEditable() {
156:
157: // Some elements are only enabled (as in SComponent's setEnabled) only
158: // if a Rule and a Process are set, and the user owns the Rule being
159: // viewed (and potentially edited).
160: return (rule != null && rule.getCreatorUser().equals(
161: currentUser));
162: }
163:
164: /**
165: * Change editing mode, according to the current Rule.
166: */
167: protected void changeEditingMode() {
168:
169: setEnabled(isEditable());
170: }
171:
172: /**
173: * Enables/disables all editable components in the editor.
174: * This either turns this panel to the normal editor or just a Rule viewer.
175: *
176: * @see org.wings.SComponent#setEnabled(boolean)
177: */
178: public void setEnabled(boolean enabled) {
179:
180: super .setEnabled(enabled);
181:
182: // Handle buttons and fields
183: addActionButton.setEnabled(enabled);
184: addConditionButton.setEnabled(enabled);
185: nameField.setEnabled(enabled);
186: applyButton.setEnabled(enabled);
187: revertButton.setEnabled(enabled);
188:
189: // ...make all condition panels non-editable...
190: for (SComponent comp : conditionsDivision.getComponents()) {
191: comp.setEnabled(enabled);
192: }
193:
194: // ...and so for the action panels.
195: for (SComponent comp : actionsDivision.getComponents()) {
196: comp.setEnabled(enabled);
197: }
198:
199: // Handle specifier parameters
200: designContext.enableSpecifierComponents(enabled);
201: }
202:
203: /**
204: * By setting the Rule, the editor re-configures itself for the given Rule.
205: *
206: * @param newRule the Rule that will be shown/edited in this panel.
207: */
208: public void setRule(Rule newRule) {
209:
210: // Return immediately if the newRule is the one we already know.
211: if (newRule == this .rule) {
212: return;
213: }
214:
215: // From this point on, newRule is really NEW.
216:
217: removeAllConditionPanels();
218:
219: removeAllActionPanels();
220:
221: // Acquire newRule.
222: this .rule = newRule;
223:
224: // If the newRule is not null...
225: if (rule != null) {
226: // Show the Rule's name in the UI...
227: nameField.setText(rule.getName());
228:
229: designContext.changeSpecifiers(rule);
230:
231: // Auto-generate condition/action panels according to Rule's contents.
232: populateOperationPanels();
233: } else {
234: // If it was null, do not populate and also clear up the nameField.
235: nameField.setText("");
236: }
237:
238: // Update editability of components for the new rule.
239: changeEditingMode();
240:
241: // If it is a freshly created, editable, Rule then it that lacks any meta-information.
242: // We'll provide the corresponding meta-information, since if we don't the
243: // comparison at isDirty() will fail... (at the point of comparing the meta-informations
244: // of the two Rules... see Rule#equals() ).
245: if (isEditable() && rule.getMetaInformation().size() == 0) {
246: designContext.populateRule(rule);
247: }
248:
249: }
250:
251: /**
252: * Removes all action panels.
253: */
254: public void removeAllActionPanels() {
255: // Remove all action-panels...
256: while (actionPanelCount > 0) {
257: actionsDivision.remove(0); // remove the panel itself
258: actionsDivision.remove(0); // remove the "Remove" button
259: actionPanelCount--;
260: }
261: }
262:
263: /**
264: * Removes all condition panels.
265: */
266: public void removeAllConditionPanels() {
267: // Remove all condition-panels...
268: while (conditionPanelCount > 0) {
269: conditionsDivision.remove(0); // remove the panel itself
270: conditionsDivision.remove(0); // remove the "Remove" button
271: conditionPanelCount--;
272: }
273: }
274:
275: /**
276: * Gets the action panel count.
277: *
278: * @return the action panel count
279: */
280: public int getActionPanelCount() {
281: return actionPanelCount;
282: }
283:
284: /**
285: * Gets the condition panel count.
286: *
287: * @return the condition panel count
288: */
289: public int getConditionPanelCount() {
290: return conditionPanelCount;
291: }
292:
293: /**
294: * Gets the current Rule.
295: *
296: * @return the current Rule
297: */
298: public Rule getRule() {
299: return rule;
300: }
301:
302: /**
303: * Gets the current user-id.
304: *
305: * @return the current user-id
306: */
307: public String getCurrentUser() {
308: return currentUser;
309: }
310:
311: /**
312: * Sets the current user-id.
313: *
314: * @param currentUser the new current user
315: */
316: public void setCurrentUser(String currentUser) {
317: this .currentUser = currentUser;
318: }
319:
320: /**
321: * Gets the name field.
322: *
323: * @return the name field
324: */
325: public STextField getNameField() {
326: return nameField;
327: }
328:
329: /**
330: * Populates this panel with the basic UI components.
331: * This method is only invoked once, upon initial construction.
332: * For setting up the editor for the given Rule see {@link #populateOperationPanels()}.
333: *
334: */
335: protected void populateRulePanel() {
336:
337: // Instantiate the components that will make up our panel...
338: nameField = new STextField("");
339: addConditionButton = new SButton(msg("addCondition"));
340: addActionButton = new SButton(msg("addAction"));
341: conditionsDivision = new XDivision();
342: actionsDivision = new XDivision();
343:
344: designContext.addSpecifierComponents(this );
345:
346: GridBagConstraints constraints = new GridBagConstraints();
347: // Add the label for the Rule's name...
348: constraints.gridwidth = 1;
349: constraints.weightx = 0.2;
350: contentPane.add(new SLabel("Name:"));
351:
352: // Add the input-field for the Rule's name.
353: constraints.weightx = 0.8;
354: constraints.gridwidth = GridBagConstraints.REMAINDER;
355: nameField.setHorizontalAlignment(SConstants.LEFT_ALIGN);
356: nameField.setAttribute("width", "80%");
357: contentPane.add(nameField, constraints);
358:
359: // Add the conditions/actions divisions...
360: // Each of these will contain the condition/action panels.
361:
362: constraints.weightx = 1;
363: constraints.fill = GridBagConstraints.VERTICAL;
364:
365: conditionsDivision.setTitle(msg("conditions"));
366: conditionsDivision.setAttribute("width", "100%");
367:
368: actionsDivision.setTitle(msg("actions"));
369: actionsDivision.setAttribute("width", "100%");
370:
371: contentPane.add(conditionsDivision, constraints);
372: contentPane.add(actionsDivision, constraints);
373:
374: // Add the "Add Condition/Action" buttons to the corresponding division...
375:
376: conditionsDivision.setLayout(new SGridLayout(0, 2));
377: actionsDivision.setLayout(new SGridLayout(0, 2));
378:
379: addConditionButton
380: .setHorizontalAlignment(SConstants.LEFT_ALIGN);
381: addConditionButton
382: .setVerticalAlignment(SConstants.BOTTOM_ALIGN);
383:
384: addActionButton.setHorizontalAlignment(SConstants.LEFT_ALIGN);
385: addActionButton.setVerticalAlignment(SConstants.BOTTOM_ALIGN);
386:
387: conditionsDivision.add(addConditionButton);
388: actionsDivision.add(addActionButton);
389:
390: // Now, add all the ActionListeners...
391:
392: addConditionButton.addActionListener(new ActionListener() {
393: public void actionPerformed(ActionEvent e) {
394: addConditionClicked();
395: }
396: });
397:
398: addActionButton.addActionListener(new ActionListener() {
399: public void actionPerformed(ActionEvent e) {
400: addActionClicked();
401: }
402: });
403:
404: }
405:
406: /**
407: * Populate the Condition- and ActionDivisions with the Condition- and ActionPanels
408: * that correspond to the current Rule.
409: */
410: private void populateOperationPanels() {
411:
412: // For each condition in the current Rule...
413: for (Condition condition : rule.getConditions()) {
414: ConditionController controller;
415: try {
416: // ... ask for the corresponding ConditionController...
417: controller = ConditionController.newInstance(condition);
418: // ... and add it to the panel.
419: addConditionPanel(new ConditionPanel(
420: RuleEditorPanel.this , controller));
421: } catch (InstantiationException e) {
422: // TODO Auto-generated catch block
423: e.printStackTrace();
424: } catch (IllegalAccessException e) {
425: // TODO Auto-generated catch block
426: e.printStackTrace();
427: } catch (ClassNotFoundException e) {
428: // TODO Auto-generated catch block
429: e.printStackTrace();
430: }
431: }
432:
433: // Same principle applies for actions in the Rule...
434:
435: for (Action action : rule.getActions()) {
436: ActionController controller;
437: try {
438: controller = ActionController.newInstance(action);
439: addActionPanel(new ActionPanel(RuleEditorPanel.this ,
440: controller));
441: } catch (InstantiationException e) {
442: // TODO Auto-generated catch block
443: e.printStackTrace();
444: } catch (IllegalAccessException e) {
445: // TODO Auto-generated catch block
446: e.printStackTrace();
447: } catch (ClassNotFoundException e) {
448: // TODO Auto-generated catch block
449: e.printStackTrace();
450: }
451: }
452:
453: }
454:
455: /**
456: * Called when "new action" was clicked.
457: */
458: protected void addActionClicked() {
459:
460: // Create a new ActionPanel attached to us (RuleEditorPanel) and...
461: final ActionPanel actionPanel = new ActionPanel(
462: RuleEditorPanel.this );
463:
464: // ...add it to the UI.
465: addActionPanel(actionPanel);
466: }
467:
468: /**
469: * Adds an action panel to the ActionDivision-
470: *
471: * @param panel the panel to be added.
472: */
473: protected void addActionPanel(final ActionPanel panel) {
474:
475: // Instantiate an ActionListener that will remove this ActionPanel when
476: // pressed on its corresponding "Remove" button...
477: ActionListener removeButtonActionListener = new ActionListener() {
478: public void actionPerformed(ActionEvent e) {
479: actionsDivision.remove(panel);
480: actionsDivision.remove((SComponent) e.getSource());
481: actionPanelCount--;
482: }
483: };
484:
485: // Add this ActionPanel to the actionsDivision.
486: addOperationPanel(actionsDivision, panel,
487: removeButtonActionListener);
488:
489: // Update this; important when later removing the panels (see setRule()).
490: actionPanelCount++;
491: }
492:
493: /**
494: * Called when "new condition" is clicked.
495: */
496: protected void addConditionClicked() {
497:
498: // Create a new ConditionPanel attached to us (RuleEditorPanel) and...
499: final ConditionPanel conditionPanel = new ConditionPanel(
500: RuleEditorPanel.this );
501:
502: // ...add it to the UI.
503: addConditionPanel(conditionPanel);
504: }
505:
506: /**
507: * Adds a ConditionPanel to the ConditionDivision.
508: *
509: * @param panel the panel to be added.
510: */
511: protected void addConditionPanel(final ConditionPanel panel) {
512:
513: // Instantiate an ActionListener that will remove this ConditionPanel when
514: // pressed on its corresponding "Remove" button...
515: ActionListener removeButtonActionListener = new ActionListener() {
516: public void actionPerformed(ActionEvent e) {
517: conditionsDivision.remove(panel);
518: conditionsDivision.remove((SComponent) e.getSource());
519: conditionPanelCount--;
520: }
521: };
522:
523: // Add this ConditionPanel to the conditionsDivision.
524: addOperationPanel(conditionsDivision, panel,
525: removeButtonActionListener);
526: // Update this; important when later removing the panels (see setRule()).
527: conditionPanelCount++;
528: }
529:
530: /**
531: * Adds an OperationPanel to a XDivision.
532: *
533: * @param operationsDivision the XDivision for the panel
534: * @param operationPanel the operation panel
535: * @param removeButtonActionListener the "remove button" handler
536: */
537: protected void addOperationPanel(XDivision operationsDivision,
538: OperationPanel operationPanel,
539: ActionListener removeButtonActionListener) {
540:
541: // Create a "Remove" button for this OperationPanel.
542: XButton removeButton = new XButton(new RemoveAction(
543: removeButtonActionListener));
544:
545: operationPanel.setAttribute("width", "100%");
546:
547: // Add the operationPanel at the end, just before the
548: // "Add XXX" (condition/action) button...
549: operationsDivision.add(operationPanel, operationsDivision
550: .getComponentCount() - 1);
551: // ... followed by its corresponding "Remove" button...
552: operationsDivision.add(removeButton, operationsDivision
553: .getComponentCount() - 1);
554: // ... which is attached the given ActionListener.
555: removeButton.addActionListener(removeButtonActionListener);
556: }
557:
558: class RemoveAction extends AbstractAction {
559: private ActionListener forward;
560:
561: public RemoveAction(ActionListener removeButtonActionListener) {
562: this .forward = removeButtonActionListener;
563: putValue(SMALL_ICON, AgentIcons.REMOVE);
564: putValue(SHORT_DESCRIPTION, msg("remove"));
565: }
566:
567: public void actionPerformed(ActionEvent e) {
568: forward.actionPerformed(e);
569: }
570: }
571:
572: /**
573: * Applies changes to the current Rule.
574: */
575: public void applyChanges() {
576:
577: // Generate a new Rule from the current UI state of the panel.
578: Rule generatedRule = generateRuleFromPanel();
579:
580: // Transfer the contents of the generated rule to our "working" Rule.
581: rule.setActions(generatedRule.getActions());
582: rule.setConditions(generatedRule.getConditions());
583: rule.setCreatorUser(generatedRule.getCreatorUser());
584: rule.setName(generatedRule.getName());
585: rule.setMetaInformation(generatedRule.getMetaInformation());
586: }
587:
588: /**
589: * From the whole visual state of the editor, it generates a new Rule instance.
590: *
591: * @return the generated Rule object.
592: */
593: protected Rule generateRuleFromPanel() {
594:
595: Rule newRule = new Rule();
596:
597: // Set the creator to the currentUser.
598: newRule.setCreatorUser(currentUser);
599: // Set the public state to the working rule's state.
600: newRule.setPublic(rule.isPublic());
601: // Get the Rule's name from the UI.
602: newRule.setName(nameField.getText());
603:
604: // Get the placeholder lists.
605: List<Condition> conditions = newRule.getConditions();
606: List<Action> actions = newRule.getActions();
607:
608: // Ask each Action/ConditionController to return an Action/Condition that
609: // reflects their current UI contents, and add that Action/Condition to
610: // the newRule.
611:
612: for (SComponent comp : conditionsDivision.getComponents()) {
613: if (comp instanceof ConditionPanel) {
614: conditions.add(((ConditionPanel) comp).getCondition());
615: }
616: }
617:
618: for (SComponent comp : actionsDivision.getComponents()) {
619: if (comp instanceof ActionPanel) {
620: actions.add(((ActionPanel) comp).getAction());
621: }
622: }
623:
624: designContext.populateRule(newRule);
625:
626: // Return the "populated" newRule.
627: return newRule;
628:
629: }
630:
631: /**
632: * Reverts the changes made in the UI, making it show the original (unmodified) Rule.
633: */
634: public void revertChanges() {
635:
636: // This is because setRule() also assigns to this.rule and
637: // the reference would be lost below (in setRule(null)).
638: Rule tmpRule = rule;
639:
640: // This sets the editor-panel to an empty state.
641: setRule(null);
642:
643: // And this populates it with the contents of our
644: // original Rule.
645: setRule(tmpRule);
646:
647: }
648:
649: /**
650: * Checks if the editor is "dirty". It is dirty if the visual state differs from the
651: * model (the Rule object being edited).
652: *
653: * @return true, if editor is dirty
654: */
655: public boolean isDirty() {
656:
657: if (isEditable()) {
658:
659: Rule generatedRule = generateRuleFromPanel();
660:
661: return (rule.equals(generatedRule)) == false;
662: } else
663: // Non-editable Rules are per-definition non-dirty...
664: return false;
665: }
666:
667: }
|