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.io.File;
007: import java.io.IOException;
008: import java.util.ArrayList;
009: import java.util.HashMap;
010: import java.util.List;
011: import java.util.Map;
012:
013: import org.osbl.agent.logic.AgentSystem;
014: import org.osbl.agent.model.Rule;
015: import org.osbl.client.wings.shell.Client;
016: import org.osbl.client.wings.shell.Window;
017: import org.osbl.client.wings.XButton;
018: import org.wings.LowLevelEventListener;
019: import org.wings.SButton;
020: import org.wings.SCheckBox;
021: import org.wings.SComponent;
022: import org.wings.SDimension;
023: import org.wings.SFileChooser;
024: import org.wings.SFont;
025: import org.wings.SGridBagLayout;
026: import org.wings.SLabel;
027: import org.wings.SOptionPane;
028: import org.wings.SPanel;
029: import org.wings.STable;
030: import org.wings.border.SLineBorder;
031: import org.wings.externalizer.ExternalizeManager;
032: import org.wings.resource.FileResource;
033: import org.wings.script.JavaScriptListener;
034: import org.wings.script.ScriptListener;
035: import org.wings.session.SessionManager;
036: import org.wings.table.STableCellRenderer;
037: import org.wingx.XTable;
038: import org.wingx.table.EditableTableCellRenderer;
039: import org.wingx.table.XTableClickListener;
040: import org.wingx.table.XTableColumn;
041:
042: import javax.swing.*;
043:
044: /**
045: * Provides the sidepanel that lets you browse existing Rules and perform some
046: * operations on them (like add/remove/run/edit/publish...)
047: *
048: * When selecting a Rule for editing, it will open itself in the Editor panel.
049: *
050: * @author Sebastian Nozzi.
051: */
052: public class SidePanel extends SPanel {
053:
054: /** The agent system. */
055: private AgentSystem agentSystem;
056:
057: /** The rule editor panel. */
058: private RuleEditorPanel ruleEditorPanel;
059:
060: /** The rules table. */
061: private XTable rulesTable;
062:
063: /** The SideBar table model. */
064: private SidePanelTableModel sidePanelTableModel;
065:
066: /** The window in which the ruleEditorPanel resides. */
067: private Window ruleEditorWindow;
068:
069: /** The design context. */
070: private DesignContext designContext;
071:
072: /**
073: * Instantiates a new SidePanel in the given DesignContext.
074: *
075: * @param designContext the design context
076: */
077: public SidePanel(DesignContext designContext) {
078: super ();
079:
080: setDesignContext(designContext);
081:
082: // We need THE AgentSystem instance for accessing the Rules repository
083: agentSystem = AgentSystem.getSingletonInstance();
084:
085: // This defers instantiation of the editor panel and window until
086: // it is really needed.
087: ruleEditorWindow = null;
088: ruleEditorPanel = null;
089:
090: populatePanel();
091: }
092:
093: /**
094: * Gets the design context.
095: *
096: * @return the design context
097: */
098: public DesignContext getDesignContext() {
099: return designContext;
100: }
101:
102: /**
103: * Sets the design context.
104: *
105: * @param designContext the new design context
106: */
107: public void setDesignContext(DesignContext designContext) {
108: this .designContext = designContext;
109: }
110:
111: /**
112: * Convenience method to get localized versions of strings/labels
113: *
114: * @param code the String-code
115: * @param args additional, optional String-args
116: *
117: * @return the localized String
118: */
119: protected String msg(String code, Object... args) {
120: return designContext.msg(code, args);
121: }
122:
123: /**
124: * Initially populates the SidePanel with the basic components.
125: * Only called upon construction.
126: *
127: */
128: protected void populatePanel() {
129:
130: // We'll be using SGridBagLayout for our panel...
131: SGridBagLayout layout = new SGridBagLayout();
132: // ... and for it we need the constraints used when adding components.
133: GridBagConstraints constraints = new GridBagConstraints();
134:
135: // Set up visual properties of our panel...
136: layout.setHgap(5);
137: layout.setVgap(5);
138:
139: setLayout(layout);
140: setPreferredSize(new SDimension("250px", null));
141:
142: // The RuleTableModel knows for example how to order Rules by
143: // public-state, etc.
144: sidePanelTableModel = new SidePanelTableModel() {
145: List<Rule> getCurrentRules() {
146: // The model needs "live" access to the current list of Rules
147: // (which is different as soon as something in the
148: // process/activity combos changes)
149: return SidePanel.this .getCurrentRules();
150: }
151:
152: // Whenever the Rules are updated in the Model...
153: void rulesUpdated() {
154: // ... force the AgentSystem to persist the new changes.
155: agentSystem.updateRules();
156: }
157: };
158: // The model also needs to know the current user, to make Rules editable
159: // or not.
160: sidePanelTableModel.setCurrentUser(DesignContext
161: .getCurrentUser());
162:
163: // Create a new XTable using our custom table model.
164: // We use an XTable in order to be able to use a ClickListener (see
165: // below).
166: rulesTable = new XTable(sidePanelTableModel);
167: rulesTable.setBorder(new SLineBorder(
168: new Color(0x99, 0x99, 0x99)));
169:
170: // Make the columns sortable.
171: ((XTableColumn) rulesTable.getColumnModel().getColumn(0))
172: .setSortable(true);
173: ((XTableColumn) rulesTable.getColumnModel().getColumn(1))
174: .setSortable(true);
175:
176: ((XTableColumn) rulesTable.getColumnModel().getColumn(1))
177: .setWidth("30px");
178:
179: // We wanted the name of the Rule being edited to be differently
180: // rendered
181: // (for example in bold letters, and the rest in plain text)
182: rulesTable.getColumnModel().getColumn(0).setCellRenderer(
183: // For this we need to specify a custom renderer...
184: new STableCellRenderer() {
185: // ...which will return a SLabel.
186: public SComponent getTableCellRendererComponent(
187: STable table, Object value,
188: boolean isSelected, int row, int column) {
189: // The SLabel consists of the name of the Rule (gotten
190: // from the model)
191: SLabel result = new SLabel(((String) value));
192:
193: List<Rule> rules = getCurrentRules();
194:
195: // If there are rules to begin with...
196: if (rules.size() > 0) {
197: Rule ruleToRender = rules.get(row);
198: Rule editedRule = null;
199:
200: if (ruleEditorPanel != null)
201: editedRule = ruleEditorPanel.getRule();
202:
203: // ...and if the rule we have to render is the same
204: // that is
205: // being editer in the editor panel...
206: if (editedRule != null
207: && editedRule.equals(ruleToRender))
208: // ...make the SLabel in BOLD letters
209: result.setFont(new SFont(SFont.BOLD));
210: } else
211: // ...otherwise make it normal.
212: result.setFont(new SFont(SFont.PLAIN));
213:
214: return result;
215: }
216: });
217: // The BooleanEditableCellR.. is used to make your Rules public or not.
218: rulesTable.getColumnModel().getColumn(1).setCellRenderer(
219: new BooleanEditableCellRenderer());
220:
221: designContext.addSideFilters(this );
222:
223: // Accordingly, recompute current Rules (table)
224: recomputeCurrentRules();
225: refreshTable();
226:
227: // It's recommended to take a look at the SGridBagLayout documentation
228: // to understand
229: // the settings in the contraints.
230:
231: // Add the rule's table (previously configured, see above)
232: constraints.gridx = 0;
233: constraints.gridy = 2;
234: constraints.gridwidth = GridBagConstraints.REMAINDER;
235: this .add(rulesTable, constraints);
236:
237: // Main buttons
238: XButton addRuleButton = new XButton(new AddRuleAction());
239: XButton removeRuleButton = new XButton(new RemoveRuleAction());
240: XButton runButton = new XButton(new RunRuleAction());
241: XButton importButton = new XButton(new ImportAction());
242: XButton exportButton = new XButton(new ExportAction());
243:
244: // Add the buttons
245: constraints.gridx = GridBagConstraints.RELATIVE;
246: constraints.gridy = 3;
247: constraints.gridwidth = 1;
248:
249: this .add(new SLabel(msg("agents") + ":"), constraints);
250: this .add(addRuleButton, constraints);
251: this .add(removeRuleButton, constraints);
252: this .add(runButton, constraints);
253: this .add(importButton, constraints);
254: constraints.gridwidth = GridBagConstraints.REMAINDER;
255: this .add(exportButton, constraints);
256:
257: // This is used to intercept clicks done on the name of a Rule.
258: rulesTable.addClickListener(0, new XTableClickListener() {
259: public void clickOccured(int row, int column) {
260: Rule clickedRule = getCurrentRules().get(row);
261:
262: // This not only shows the editor window but instantiates the
263: // window/panel if not already done so. Because of this,
264: // this should be called before trying to access
265: // ruleEditorPanel...
266: showEditorWindow();
267:
268: ruleEditorPanel.setRule(clickedRule);
269: // Force a table-refresh so that the Rule appears in BOLD.
270: refreshTable();
271: }
272: });
273:
274: }
275:
276: class AddRuleAction extends AbstractAction {
277: public AddRuleAction() {
278: putValue(SMALL_ICON, AgentIcons.AGENT_NEW);
279: putValue(SHORT_DESCRIPTION, msg("add"));
280: }
281:
282: public void actionPerformed(ActionEvent e) {
283: addRule();
284: recomputeCurrentRules();
285: refreshTable();
286: }
287: }
288:
289: class RemoveRuleAction extends AbstractAction {
290: public RemoveRuleAction() {
291: putValue(SMALL_ICON, AgentIcons.AGENT_DELETE);
292: putValue(SHORT_DESCRIPTION, msg("remove"));
293: }
294:
295: public void actionPerformed(ActionEvent e) {
296: removeRule();
297: recomputeCurrentRules();
298: refreshTable();
299: }
300: }
301:
302: class RunRuleAction extends AbstractAction {
303: public RunRuleAction() {
304: putValue(SMALL_ICON, AgentIcons.AGENT_RUN);
305: putValue(SHORT_DESCRIPTION, msg("run"));
306: }
307:
308: public void actionPerformed(ActionEvent e) {
309: runRules();
310: }
311: }
312:
313: class ImportAction extends AbstractAction {
314: public ImportAction() {
315: putValue(SMALL_ICON, AgentIcons.UPLOAD);
316: putValue(SHORT_DESCRIPTION, msg("import"));
317: }
318:
319: public void actionPerformed(ActionEvent e) {
320: importRule();
321: recomputeCurrentRules();
322: refreshTable();
323: }
324: }
325:
326: class ExportAction extends AbstractAction {
327: public ExportAction() {
328: putValue(SMALL_ICON, AgentIcons.DOWNLOAD);
329: putValue(SHORT_DESCRIPTION, msg("export"));
330: }
331:
332: public void actionPerformed(ActionEvent e) {
333: exportRule();
334: }
335: }
336:
337: /**
338: * Recompute current rules.
339: */
340: void recomputeCurrentRules() {
341: designContext.recomputeCurrentRules();
342: }
343:
344: /**
345: * Run the selected Rule(s), currently only runs one Rule.
346: */
347: protected void runRules() {
348:
349: List<Rule> rules = getCurrentRules();
350: // List<Rule> selectedRules = new ArrayList<Rule>();
351:
352: // For each row in the rule table...
353: for (int idx : rulesTable.getSelectedRows()) {
354:
355: Rule selectedRule = rules.get(idx);
356:
357: designContext.getPreviewController().runRule(selectedRule);
358: break;
359: }
360:
361: }
362:
363: /**
364: * Export the selected Rule, providing an XML file for it.
365: */
366: protected void exportRule() {
367:
368: List<Rule> rules = getCurrentRules();
369:
370: // For each row in the rule table...
371: for (int idx : rulesTable.getSelectedRows()) {
372:
373: Rule selectedRule = rules.get(idx);
374: File tmpRuleFile = agentSystem.tempRuleFile(selectedRule);
375:
376: /*
377: String xmlRule = agentSystem.ruleAsXML(selectedRule);
378:
379: StringResource resource = new StringResource(xmlRule, ".xml",
380: "text/xml", ExternalizeManager.REQUEST);
381: */
382: String name = selectedRule.getName();
383: name = name.replace(' ', '_');
384:
385: FileResource resource = new FileResource(tmpRuleFile);
386: resource.setExternalizerFlags(resource
387: .getExternalizerFlags()
388: | ExternalizeManager.REQUEST);
389:
390: Map headers = new HashMap();
391: headers.put("Content-Disposition", "attachment; filename="
392: + name + ".osbl-agent.xml");
393: resource.setHeaders(headers.entrySet());
394:
395: final ScriptListener listener = new JavaScriptListener(
396: null, null, "location.href='" + resource.getURL()
397: + "'");
398: SessionManager.getSession().getScriptManager()
399: .addScriptListener(listener);
400:
401: // Stop after first selected Rule, in case there are more than one...
402: break;
403: }
404: }
405:
406: /**
407: * Allows the user to import a Rule from an XML file.
408: */
409: protected void importRule() {
410:
411: final SFileChooser fileInputChooser = new SFileChooser();
412:
413: fileInputChooser.setFileNameFilter("text/xml");
414:
415: SOptionPane.showInputDialog(this , msg("xmlImportMessage"),
416: msg("xmlImportTitle"), fileInputChooser,
417: new ActionListener() {
418:
419: public void actionPerformed(ActionEvent e) {
420:
421: // User uploaded a file...
422: if (e.getActionCommand().equals(
423: SOptionPane.OK_ACTION)) {
424:
425: Rule newRule = null;
426: try {
427: newRule = agentSystem
428: .XMLToRule(fileInputChooser
429: .getSelectedFile());
430: } catch (IOException e1) {
431: // TODO Auto-generated catch block
432: e1.printStackTrace();
433: }
434:
435: if (newRule != null) {
436: // Make our current user the owner for the freshly imported
437: // Rule, since exported rules retain their creatorUser in its
438: // fields.
439: newRule.setCreatorUser(DesignContext
440: .getCurrentUser());
441:
442: newRule.setPublic(false);
443:
444: agentSystem.addRule(newRule);
445:
446: recomputeCurrentRules();
447:
448: refreshTable();
449: }
450:
451: }
452:
453: }
454: });
455: }
456:
457: /**
458: * Gets the current Rules.
459: *
460: * @return the current Rules
461: */
462: protected List<Rule> getCurrentRules() {
463: return designContext.getCurrentRules();
464: }
465:
466: /** Used to name the initially unnamed Rules. */
467: private int untitledNr = 0;
468:
469: /**
470: * Adds a new Rule to the system.
471: */
472: protected void addRule() {
473:
474: // Create a Rule and assign a default name to it.
475: Rule newRule = new Rule("untitled" + untitledNr++);
476:
477: newRule.setCreatorUser(DesignContext.getCurrentUser());
478:
479: showEditorWindow();
480:
481: ruleEditorPanel.setRule(newRule);
482: }
483:
484: /**
485: * Refresh the table that shows the Rules according to the filters.
486: */
487: void refreshTable() {
488:
489: ((SidePanelTableModel) rulesTable.getModel()).refresh();
490:
491: }
492:
493: /**
494: * Removes the selected Rule from the system.
495: */
496: protected void removeRule() {
497:
498: List<Rule> rules = getCurrentRules();
499:
500: // Look for selected Rules in the table's rows...
501: for (int idx : rulesTable.getSelectedRows()) {
502:
503: Rule selectedRule = rules.get(idx);
504:
505: // ... but only delete the Rules that this user owns
506: if (selectedRule.getCreatorUser().equals(
507: DesignContext.getCurrentUser()))
508: agentSystem.removeRule(selectedRule);
509: }
510:
511: }
512:
513: /**
514: * Sets the RuleEditor up.
515: */
516: void setupEditor() {
517:
518: // This is to communicate with the editor panel
519: ruleEditorPanel = designContext.getRuleEditorPanel();
520:
521: // The editor panel also needs to know the current user
522: // (to only let the user edit his/her own Rules)
523: ruleEditorPanel.setCurrentUser(DesignContext.getCurrentUser());
524: // The editor panel also needs to communicate with us
525: ruleEditorPanel.setAgentSidebarPanel(this );
526:
527: ruleEditorWindow = new Window();
528: ruleEditorWindow.setContentPane(ruleEditorPanel);
529:
530: // Buttons like "Apply", "Revert", "Close"
531: List<SComponent> buttons = new ArrayList<SComponent>();
532:
533: buttons.add(ruleEditorPanel.closeButton);
534: buttons.add(ruleEditorPanel.revertButton);
535: buttons.add(ruleEditorPanel.applyButton);
536:
537: ruleEditorWindow.setControls(buttons);
538:
539: ruleEditorWindow.setTitle(msg("editorWindowTitle"));
540:
541: ruleEditorPanel.closeButton
542: .addActionListener(new ActionListener() {
543: // On "close", show whatever was underneath
544: public void actionPerformed(ActionEvent e) {
545:
546: if (ruleEditorPanel.isDirty()) {
547: // Show a confirmation, informing the user that by closing
548: // the
549: // window he will lose all changes made...
550: SOptionPane.showYesNoDialog(
551: ruleEditorPanel,
552: msg("closeEditorMessage"),
553: msg("closeEditorTitle"),
554: new ActionListener() {
555:
556: public void actionPerformed(
557: ActionEvent e) {
558:
559: if (e
560: .getActionCommand()
561: .equals(
562: SOptionPane.NO_ACTION)) {
563: } else {
564: Client
565: .getInstance()
566: .popWindow(
567: ruleEditorWindow);
568: }
569: }
570: });
571: // Rule is not dirty, just close the window...
572: } else {
573: Client.getInstance().popWindow(
574: ruleEditorWindow);
575: }
576: }
577: });
578:
579: ruleEditorPanel.revertButton
580: .addActionListener(new ActionListener() {
581: public void actionPerformed(ActionEvent e) {
582: ruleEditorPanel.revertChanges();
583: }
584: });
585:
586: ruleEditorPanel.applyButton
587: .addActionListener(new ActionListener() {
588: public void actionPerformed(ActionEvent e) {
589:
590: ruleEditorPanel.applyChanges();
591: Rule editedRule = ruleEditorPanel.getRule();
592:
593: // Add rule to the system if it was a new Rule.
594: if (agentSystem.getRules().contains(editedRule) == false)
595: agentSystem.addRule(editedRule);
596: else
597: agentSystem.updateRules();
598:
599: // Force refresh to eventually
600: // reflect a name-change.
601: recomputeCurrentRules();
602: refreshTable();
603: }
604: });
605: }
606:
607: /**
608: * Shows the editor window.
609: */
610: void showEditorWindow() {
611:
612: if (ruleEditorWindow == null)
613: setupEditor();
614:
615: Client.getInstance().pushWindow(
616: designContext.getParentWindow(), ruleEditorWindow);
617: }
618:
619: /**
620: * A custom EditableTableCellRenderer, the class BooleanEditableCellRenderer presents
621: * boolean values in a table-cell as a checkbox. It reflects the corresponding boolean
622: * value and updates the model when the checkbox changes.
623: */
624: class BooleanEditableCellRenderer extends SCheckBox implements
625: EditableTableCellRenderer {
626:
627: /** The label. */
628: SLabel label = new SLabel();
629:
630: /*
631: * (non-Javadoc)
632: *
633: * @see org.wingx.table.EditableTableCellRenderer#getValue()
634: */
635: public Object getValue() {
636: return isSelected() ? Boolean.TRUE : Boolean.FALSE;
637: }
638:
639: /*
640: * (non-Javadoc)
641: *
642: * @see org.wings.table.STableCellRenderer#getTableCellRendererComponent(org.wings.STable,
643: * java.lang.Object, boolean, int, int)
644: */
645: public SComponent getTableCellRendererComponent(STable table,
646: Object value, boolean isSelected, int row, int column) {
647:
648: // Remember that we extend SCheckBox, so we ARE a SCheckBox
649:
650: // If "value" is a Boolean (which the TableModel returns in case the
651: // Rule
652: // is an user-editable Rule, this is, an "own" Rule)..
653: if (value instanceof Boolean) {
654: // ... check/uncheck according to the boolean value of "value"
655: this .setSelected(value.equals(Boolean.TRUE));
656: // don't show any text... it's our own Rule, we know we created
657: // it
658: this .setText("");
659: // let the user check/uncheck de SCheckBox, and...
660: this .setEnabled(true);
661: // return ourselves
662: return this ;
663: // That "value" is not a Boolean means that the Rule the
664: // TableModel is
665: // processing is not our own but a foreign public Rule...
666: } else if (value instanceof String) {
667: // Make the checkbox selected (checked).
668: // TODO: Is this necessary? We are returning a SLabel after
669: // all...
670: // Maybe it's necessary, because of how the mechanism of
671: // renderers work...
672: this .setSelected(true);
673:
674: // Instead of returning ourselves, we have a SLabel in which
675: // we'll
676: // set the user-name of the user that the current Rule belong
677: // and
678: // will just return that. We save some space, because the
679: // check-box
680: // would be non-editable anyway (a user can only make his/her
681: // Rules
682: // public/non-public)
683: label.setText((String) value);
684:
685: // Note that because of how the renderers mechanism work, the
686: // right
687: // solution is to instantiate ONE SLabel, set it each time
688: // according
689: // to the parameters and return it, and NOT creating one
690: // instance
691: // each time.
692:
693: // Return the SLabel with the users-id instead of the check-box.
694: return label;
695: }
696:
697: return null;
698: }
699:
700: /*
701: * (non-Javadoc)
702: *
703: * @see org.wingx.table.EditableTableCellRenderer#getLowLevelEventListener(org.wings.STable,
704: * int, int)
705: */
706: public LowLevelEventListener getLowLevelEventListener(
707: STable table, int row, int column) {
708:
709: return this;
710: }
711: }
712:
713: }
|