001: package org.drools.brms.client.modeldriven.ui;
002:
003: /*
004: * Copyright 2005 JBoss Inc
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.util.Iterator;
020: import java.util.List;
021:
022: import org.drools.brms.client.common.DirtyableComposite;
023: import org.drools.brms.client.common.DirtyableFlexTable;
024: import org.drools.brms.client.common.DirtyableHorizontalPane;
025: import org.drools.brms.client.common.DirtyableVerticalPane;
026: import org.drools.brms.client.common.ErrorPopup;
027: import org.drools.brms.client.common.FormStylePopup;
028: import org.drools.brms.client.common.ImageButton;
029: import org.drools.brms.client.common.YesNoDialog;
030: import org.drools.brms.client.modeldriven.HumanReadable;
031: import org.drools.brms.client.modeldriven.SuggestionCompletionEngine;
032: import org.drools.brms.client.modeldriven.brl.ActionInsertFact;
033: import org.drools.brms.client.modeldriven.brl.ActionInsertLogicalFact;
034: import org.drools.brms.client.modeldriven.brl.ActionRetractFact;
035: import org.drools.brms.client.modeldriven.brl.ActionSetField;
036: import org.drools.brms.client.modeldriven.brl.ActionUpdateField;
037: import org.drools.brms.client.modeldriven.brl.CompositeFactPattern;
038: import org.drools.brms.client.modeldriven.brl.DSLSentence;
039: import org.drools.brms.client.modeldriven.brl.FactPattern;
040: import org.drools.brms.client.modeldriven.brl.IAction;
041: import org.drools.brms.client.modeldriven.brl.IPattern;
042: import org.drools.brms.client.modeldriven.brl.RuleAttribute;
043: import org.drools.brms.client.modeldriven.brl.RuleModel;
044: import org.drools.brms.client.packages.SuggestionCompletionCache;
045: import org.drools.brms.client.rpc.RuleAsset;
046:
047: import com.google.gwt.user.client.Command;
048: import com.google.gwt.user.client.Window;
049: import com.google.gwt.user.client.ui.ChangeListener;
050: import com.google.gwt.user.client.ui.ClickListener;
051: import com.google.gwt.user.client.ui.HTML;
052: import com.google.gwt.user.client.ui.HorizontalPanel;
053: import com.google.gwt.user.client.ui.Image;
054: import com.google.gwt.user.client.ui.Label;
055: import com.google.gwt.user.client.ui.ListBox;
056: import com.google.gwt.user.client.ui.Widget;
057:
058: /**
059: * This is the parent widget that contains the model based rule builder.
060: *
061: * @author Michael Neale
062: *
063: */
064: public class RuleModeller extends DirtyableComposite {
065:
066: private DirtyableFlexTable layout;
067: private SuggestionCompletionEngine completions;
068: private RuleModel model;
069:
070: public RuleModeller(RuleAsset asset) {
071: this .model = (RuleModel) asset.content;
072:
073: this .completions = SuggestionCompletionCache.getInstance()
074: .getEngineFromCache(asset.metaData.packageName);
075:
076: layout = new DirtyableFlexTable();
077:
078: initWidget();
079:
080: layout.setStyleName("model-builder-Background");
081: initWidget(layout);
082: setWidth("100%");
083: setHeight("100%");
084: }
085:
086: /**
087: * This updates the widget to reflect the state of the model.
088: */
089: public void initWidget() {
090: layout.clear();
091:
092: Image addPattern = new ImageButton("images/new_item.gif");
093: addPattern.setTitle("Add a condition to this rule.");
094: addPattern.addClickListener(new ClickListener() {
095: public void onClick(Widget w) {
096: showConditionSelector(w);
097: }
098: });
099:
100: layout.setWidget(0, 0, new Label("WHEN"));
101: layout.setWidget(0, 2, addPattern);
102:
103: layout.setWidget(1, 1, renderLhs(this .model));
104: layout.setWidget(2, 0, new Label("THEN"));
105:
106: Image addAction = new ImageButton("images/new_item.gif");
107: addAction.setTitle("Add an action to this rule.");
108: addAction.addClickListener(new ClickListener() {
109: public void onClick(Widget w) {
110: showActionSelector(w);
111: }
112: });
113: layout.setWidget(2, 2, addAction);
114:
115: layout.setWidget(3, 1, renderRhs(this .model));
116:
117: layout.setWidget(4, 0, new Label("(options)"));
118: layout.setWidget(4, 2, getAddAttribute());
119: layout.setWidget(5, 1,
120: new RuleAttributeWidget(this , this .model));
121: }
122:
123: public void refreshWidget() {
124: initWidget();
125: makeDirty();
126: }
127:
128: private Widget getAddAttribute() {
129: Image add = new ImageButton("images/new_item.gif");
130: add
131: .setTitle("Add an option to the rule, to modify its behavior when evaluated or executed.");
132:
133: add.addClickListener(new ClickListener() {
134: public void onClick(Widget w) {
135: showAttributeSelector(w);
136: }
137: });
138: return add;
139: }
140:
141: protected void showAttributeSelector(Widget w) {
142: final FormStylePopup pop = new FormStylePopup(
143: "images/config.png", "Add an option to the rule");
144: final ListBox list = RuleAttributeWidget.getAttributeList();
145:
146: list.setSelectedIndex(0);
147:
148: list.addChangeListener(new ChangeListener() {
149: public void onChange(Widget w) {
150: model.addAttribute(new RuleAttribute(list
151: .getItemText(list.getSelectedIndex()), ""));
152: refreshWidget();
153: pop.hide();
154: }
155: });
156:
157: pop.setStyleName("ks-popups-Popup");
158:
159: pop.addAttribute("Attribute", list);
160: pop.setPopupPosition(w.getAbsoluteLeft() - 400, w
161: .getAbsoluteTop());
162: pop.show();
163: }
164:
165: /**
166: * Do all the widgets for the RHS.
167: */
168: /*
169: * TODO STILL NEED TO BE CHECKED
170: */
171: private Widget renderRhs(final RuleModel model) {
172: DirtyableVerticalPane vert = new DirtyableVerticalPane();
173:
174: for (int i = 0; i < model.rhs.length; i++) {
175: IAction action = model.rhs[i];
176:
177: Widget w = null;
178: if (action instanceof ActionSetField) {
179: w = new ActionSetFieldWidget(this ,
180: (ActionSetField) action, completions);
181: } else if (action instanceof ActionInsertFact) {
182: w = new ActionInsertFactWidget(this ,
183: (ActionInsertFact) action, completions);
184: } else if (action instanceof ActionRetractFact) {
185: w = new ActionRetractFactWidget(this .completions,
186: (ActionRetractFact) action);
187: } else if (action instanceof DSLSentence) {
188: w = new DSLSentenceWidget((DSLSentence) action);
189: w.setStyleName("model-builderInner-Background");
190: }
191:
192: //w.setWidth( "100%" );
193: vert.add(spacerWidget());
194: //vert.setWidth( "100%" );
195:
196: DirtyableHorizontalPane horiz = new DirtyableHorizontalPane();
197:
198: Image remove = new ImageButton(
199: "images/delete_item_small.gif");
200: remove.setTitle("Remove this action.");
201: final int idx = i;
202: remove.addClickListener(new ClickListener() {
203: public void onClick(Widget w) {
204: YesNoDialog diag = new YesNoDialog(
205: "Remove this item?", new Command() {
206: public void execute() {
207: model.removeRhsItem(idx);
208: refreshWidget();
209: }
210: });
211: diag.setPopupPosition(w.getAbsoluteLeft(), w
212: .getAbsoluteTop());
213: diag.show();
214: }
215: });
216: horiz.add(w);
217: if (!(w instanceof ActionRetractFactWidget)) {
218: w.setWidth("100%");
219: horiz.setWidth("100%");
220: }
221:
222: horiz.add(remove);
223: vert.add(horiz);
224:
225: }
226:
227: return vert;
228: }
229:
230: /**
231: * Pops up the fact selector.
232: */
233: protected void showConditionSelector(final Widget w) {
234: final FormStylePopup popup = new FormStylePopup(
235: "images/new_fact.gif", "Add a condition to the rule...");
236:
237: //
238: // The list of facts
239: //
240: String[] facts = completions.getFactTypes();
241: final ListBox factTypeBox = new ListBox();
242: factTypeBox.addItem("Choose fact type...", "IGNORE");
243: for (int i = 0; i < facts.length; i++) {
244: factTypeBox.addItem(facts[i]);
245: }
246: factTypeBox.setSelectedIndex(0);
247: if (facts.length > 0)
248: popup.addAttribute("Fact", factTypeBox);
249: factTypeBox.addChangeListener(new ChangeListener() {
250: public void onChange(Widget w) {
251: String s = factTypeBox.getItemText(factTypeBox
252: .getSelectedIndex());
253: if (!s.equals("IGNORE")) {
254: addNewFact(s);
255: popup.hide();
256: }
257: }
258: });
259: popup.setStyleName("ks-popups-Popup");
260:
261: //
262: // The list of top level CEs
263: //
264: String ces[] = HumanReadable.CONDITIONAL_ELEMENTS;
265: final ListBox ceBox = new ListBox();
266: ceBox.addItem("Choose condition type...", "IGNORE");
267: for (int i = 0; i < ces.length; i++) {
268: String ce = ces[i];
269: ceBox.addItem(HumanReadable.getCEDisplayName(ce), ce);
270: }
271: ceBox.setSelectedIndex(0);
272:
273: if (facts.length > 0)
274: popup.addAttribute("Condition type", ceBox);
275: ceBox.addChangeListener(new ChangeListener() {
276: public void onChange(Widget w) {
277: String s = ceBox.getValue(ceBox.getSelectedIndex());
278: if (!s.equals("IGNORE")) {
279: addNewCE(s);
280: popup.hide();
281: }
282: }
283: });
284:
285: //
286: // The list of DSL sentences
287: //
288: if (completions.getDSLConditions().length > 0) {
289: final ListBox dsls = new ListBox();
290: dsls.addItem("Choose...");
291: for (int i = 0; i < completions.getDSLConditions().length; i++) {
292: DSLSentence sen = completions.getDSLConditions()[i];
293: dsls.addItem(sen.toString(), Integer.toString(i));
294: }
295:
296: dsls.addChangeListener(new ChangeListener() {
297: public void onChange(Widget w) {
298: int idx = Integer.parseInt(dsls.getValue(dsls
299: .getSelectedIndex()));
300: addNewDSLLhs((DSLSentence) completions
301: .getDSLConditions()[idx]);
302: popup.hide();
303: }
304: });
305: popup.addAttribute("DSL sentence", dsls);
306: }
307:
308: popup.setPopupPosition(w.getAbsoluteLeft() - 400, w
309: .getAbsoluteTop());
310: popup.show();
311:
312: }
313:
314: protected void addNewDSLLhs(DSLSentence sentence) {
315: model.addLhsItem(sentence.copy());
316: refreshWidget();
317:
318: }
319:
320: protected void showActionSelector(Widget w) {
321: final FormStylePopup popup = new FormStylePopup(
322: "images/new_fact.gif", "Add a new action...");
323:
324: popup.setStyleName("ks-popups-Popup");
325:
326: //
327: // First load up the stuff to do with bound variables or globals
328: //
329: List vars = model.getBoundFacts();
330: final ListBox varBox = new ListBox();
331: final ListBox retractBox = new ListBox();
332: final ListBox modifyBox = new ListBox();
333: varBox.addItem("Choose ...");
334: retractBox.addItem("Choose ...");
335: modifyBox.addItem("Choose ...");
336: for (Iterator iter = vars.iterator(); iter.hasNext();) {
337: String v = (String) iter.next();
338: varBox.addItem(v);
339: retractBox.addItem(v);
340: modifyBox.addItem(v);
341: }
342: String[] globals = this .completions.getGlobalVariables();
343: for (int i = 0; i < globals.length; i++) {
344: varBox.addItem(globals[i]);
345: }
346:
347: varBox.setSelectedIndex(0);
348: varBox.addChangeListener(new ChangeListener() {
349: public void onChange(Widget w) {
350: addActionSetField(varBox.getItemText(varBox
351: .getSelectedIndex()));
352: popup.hide();
353: }
354: });
355:
356: retractBox.addChangeListener(new ChangeListener() {
357: public void onChange(Widget w) {
358: addRetract(retractBox.getItemText(retractBox
359: .getSelectedIndex()));
360: popup.hide();
361: }
362: });
363:
364: modifyBox.addChangeListener(new ChangeListener() {
365: public void onChange(Widget w) {
366: addModify(modifyBox.getItemText(modifyBox
367: .getSelectedIndex()));
368: popup.hide();
369: }
370: });
371:
372: if (varBox.getItemCount() > 1) {
373: popup.addAttribute("Set the values of a field on", varBox);
374: }
375:
376: if (modifyBox.getItemCount() > 1) {
377: HorizontalPanel horiz = new HorizontalPanel();
378: horiz.add(modifyBox);
379: Image img = new Image("images/information.gif");
380: img
381: .setTitle("Modify a field on a fact, and notify the engine to re-evaluate rules.");
382: horiz.add(img);
383: popup.addAttribute("Modify a fact", horiz);
384: }
385:
386: popup.addRow(new HTML("<hr/>"));
387:
388: if (retractBox.getItemCount() > 1) {
389: popup.addAttribute("Retract the fact", retractBox);
390: }
391:
392: popup.addRow(new HTML("<hr/>"));
393:
394: final ListBox factsToAssert = new ListBox();
395: final ListBox factsToLogicallyAssert = new ListBox();
396: factsToAssert.addItem("Choose ...");
397: factsToLogicallyAssert.addItem("Choose ...");
398: for (int i = 0; i < completions.getFactTypes().length; i++) {
399: String item = completions.getFactTypes()[i];
400: factsToAssert.addItem(item);
401: factsToLogicallyAssert.addItem(item);
402: }
403:
404: factsToAssert.addChangeListener(new ChangeListener() {
405: public void onChange(Widget w) {
406: String fact = factsToAssert.getItemText(factsToAssert
407: .getSelectedIndex());
408: model.addRhsItem(new ActionInsertFact(fact));
409: refreshWidget();
410: popup.hide();
411:
412: }
413: });
414:
415: factsToLogicallyAssert.addChangeListener(new ChangeListener() {
416: public void onChange(Widget w) {
417: String fact = factsToLogicallyAssert
418: .getItemText(factsToLogicallyAssert
419: .getSelectedIndex());
420: model.addRhsItem(new ActionInsertLogicalFact(fact));
421: refreshWidget();
422: popup.hide();
423:
424: }
425: });
426:
427: if (factsToAssert.getItemCount() > 1) {
428: popup.addAttribute("Insert a new fact", factsToAssert);
429: HorizontalPanel horiz = new HorizontalPanel();
430: horiz.add(factsToLogicallyAssert);
431: Image img = new Image("images/information.gif");
432: img
433: .setTitle("Logically assert a fact - the fact will be retracted when the supporting evidence is removed.");
434: horiz.add(img);
435: popup.addAttribute("Logically assert a new fact", horiz);
436: }
437:
438: //
439: // The list of DSL sentences
440: //
441: if (completions.getDSLActions().length > 0) {
442: final ListBox dsls = new ListBox();
443: dsls.addItem("Choose...");
444: for (int i = 0; i < completions.getDSLActions().length; i++) {
445: DSLSentence sen = (DSLSentence) completions
446: .getDSLActions()[i];
447: dsls.addItem(sen.toString(), Integer.toString(i));
448: }
449:
450: dsls.addChangeListener(new ChangeListener() {
451: public void onChange(Widget w) {
452: int idx = Integer.parseInt(dsls.getValue(dsls
453: .getSelectedIndex()));
454: addNewDSLRhs((DSLSentence) completions
455: .getDSLActions()[idx]);
456: popup.hide();
457: }
458: });
459: popup.addAttribute("DSL sentence", dsls);
460: }
461:
462: popup.setPopupPosition(Window.getClientWidth() / 3, Window
463: .getClientHeight() / 3);
464: popup.show();
465: }
466:
467: protected void addModify(String itemText) {
468: this .model.addRhsItem(new ActionUpdateField(itemText));
469: refreshWidget();
470:
471: }
472:
473: protected void addNewDSLRhs(DSLSentence sentence) {
474: this .model.addRhsItem(sentence.copy());
475: refreshWidget();
476: }
477:
478: protected void addRetract(String var) {
479: this .model.addRhsItem(new ActionRetractFact(var));
480: refreshWidget();
481: }
482:
483: protected void addActionSetField(String itemText) {
484: this .model.addRhsItem(new ActionSetField(itemText));
485: refreshWidget();
486: }
487:
488: protected void addNewCE(String s) {
489: this .model.addLhsItem(new CompositeFactPattern(s));
490: refreshWidget();
491: }
492:
493: /**
494: * Adds a fact to the model, and then refreshes the display.
495: */
496: protected void addNewFact(String itemText) {
497: this .model.addLhsItem(new FactPattern(itemText));
498: refreshWidget();
499: }
500:
501: /**
502: * Builds all the condition widgets.
503: */
504: private Widget renderLhs(final RuleModel model) {
505: DirtyableVerticalPane vert = new DirtyableVerticalPane();
506:
507: for (int i = 0; i < model.lhs.length; i++) {
508: IPattern pattern = model.lhs[i];
509: Widget w = null;
510: if (pattern instanceof FactPattern) {
511: w = new FactPatternWidget(this , pattern, completions,
512: true);
513: vert.add(wrapLHSWidget(model, i, w));
514: vert.add(spacerWidget());
515: } else if (pattern instanceof CompositeFactPattern) {
516: w = new CompositeFactPatternWidget(this ,
517: (CompositeFactPattern) pattern, completions);
518: vert.add(wrapLHSWidget(model, i, w));
519: vert.add(spacerWidget());
520: } else if (pattern instanceof DSLSentence) {
521: //ignore this time
522: } else {
523: throw new RuntimeException(
524: "I don't know what type of pattern that is.");
525: }
526:
527: }
528:
529: DirtyableVerticalPane dsls = new DirtyableVerticalPane();
530: for (int i = 0; i < model.lhs.length; i++) {
531: IPattern pattern = model.lhs[i];
532: Widget w = null;
533:
534: if (pattern instanceof DSLSentence) {
535: w = new DSLSentenceWidget((DSLSentence) pattern);
536:
537: dsls.add(wrapLHSWidget(model, i, w));
538: dsls.setStyleName("model-builderInner-Background");
539: }
540: }
541: vert.add(dsls);
542:
543: return vert;
544: }
545:
546: private HTML spacerWidget() {
547: HTML h = new HTML(" ");
548: h.setHeight("2px");
549: return h;
550: }
551:
552: /**
553: * This adds the widget to the UI, also adding the remove icon.
554: */
555: private Widget wrapLHSWidget(final RuleModel model, int i, Widget w) {
556: DirtyableHorizontalPane horiz = new DirtyableHorizontalPane();
557:
558: Image remove = new ImageButton("images/delete_item_small.gif");
559: remove
560: .setTitle("Remove this ENTIRE condition, and all the field constraints that belong to it.");
561: final int idx = i;
562: remove.addClickListener(new ClickListener() {
563: public void onClick(Widget w) {
564: YesNoDialog diag = new YesNoDialog(
565: "Remove this entire condition?", new Command() {
566: public void execute() {
567: if (model.removeLhsItem(idx)) {
568: refreshWidget();
569: } else {
570: ErrorPopup
571: .showMessage("Can't remove that item as it is used in the action part of the rule.");
572: }
573: }
574: });
575: diag.setPopupPosition(w.getAbsoluteLeft(), w
576: .getAbsoluteTop());
577: diag.show();
578: }
579: });
580:
581: horiz.setWidth("100%");
582: w.setWidth("100%");
583:
584: horiz.add(w);
585: horiz.add(remove);
586:
587: return horiz;
588: }
589:
590: public RuleModel getModel() {
591: return model;
592: }
593:
594: /**
595: * Returns true is a var name has already been used
596: * either by the rule, or as a global.
597: */
598:
599: public boolean isVariableNameUsed(String name) {
600:
601: return model.isVariableNameUsed(name)
602: || completions.isGlobalVariable(name);
603: }
604:
605: public boolean isDirty() {
606: return (layout.hasDirty() || dirtyflag);
607: }
608:
609: public SuggestionCompletionEngine getSuggestionCompletions() {
610: return this.completions;
611: }
612:
613: }
|