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 org.drools.brms.client.common.DirtyableComposite;
020: import org.drools.brms.client.common.DirtyableFlexTable;
021: import org.drools.brms.client.common.DirtyableHorizontalPane;
022: import org.drools.brms.client.common.FormStylePopup;
023: import org.drools.brms.client.common.ImageButton;
024: import org.drools.brms.client.common.InfoPopup;
025: import org.drools.brms.client.common.Lbl;
026: import org.drools.brms.client.modeldriven.HumanReadable;
027: import org.drools.brms.client.modeldriven.SuggestionCompletionEngine;
028: import org.drools.brms.client.modeldriven.brl.CompositeFieldConstraint;
029: import org.drools.brms.client.modeldriven.brl.ConnectiveConstraint;
030: import org.drools.brms.client.modeldriven.brl.FactPattern;
031: import org.drools.brms.client.modeldriven.brl.FieldConstraint;
032: import org.drools.brms.client.modeldriven.brl.IPattern;
033: import org.drools.brms.client.modeldriven.brl.ISingleFieldConstraint;
034: import org.drools.brms.client.modeldriven.brl.SingleFieldConstraint;
035:
036: import com.google.gwt.user.client.Window;
037: import com.google.gwt.user.client.ui.AbsolutePanel;
038: import com.google.gwt.user.client.ui.Button;
039: import com.google.gwt.user.client.ui.ChangeListener;
040: import com.google.gwt.user.client.ui.ClickListener;
041: import com.google.gwt.user.client.ui.HTML;
042: import com.google.gwt.user.client.ui.HasHorizontalAlignment;
043: import com.google.gwt.user.client.ui.HasVerticalAlignment;
044: import com.google.gwt.user.client.ui.HorizontalPanel;
045: import com.google.gwt.user.client.ui.Image;
046: import com.google.gwt.user.client.ui.Label;
047: import com.google.gwt.user.client.ui.ListBox;
048: import com.google.gwt.user.client.ui.TextBox;
049: import com.google.gwt.user.client.ui.Widget;
050: import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
051:
052: /**
053: * This is the new smart widget that works off the model.
054: * @author Michael Neale
055: *
056: */
057: public class FactPatternWidget extends DirtyableComposite {
058:
059: private FactPattern pattern;
060: private DirtyableFlexTable layout = new DirtyableFlexTable();
061: private SuggestionCompletionEngine completions;
062: private RuleModeller modeller;
063: private boolean bindable;
064:
065: public FactPatternWidget(RuleModeller mod, IPattern p,
066: SuggestionCompletionEngine com, boolean canBind) {
067: this .pattern = (FactPattern) p;
068: this .completions = com;
069: this .modeller = mod;
070: this .bindable = canBind;
071:
072: layout.setWidget(0, 0, getPatternLabel());
073: FlexCellFormatter formatter = layout.getFlexCellFormatter();
074: formatter.setAlignment(0, 0,
075: HasHorizontalAlignment.ALIGN_CENTER,
076: HasVerticalAlignment.ALIGN_MIDDLE);
077: formatter.setStyleName(0, 0, "modeller-fact-TypeHeader");
078:
079: final DirtyableFlexTable inner = new DirtyableFlexTable();
080:
081: layout.setWidget(1, 0, inner);
082:
083: for (int row = 0; row < pattern.getFieldConstraints().length; row++) {
084:
085: FieldConstraint constraint = pattern.getFieldConstraints()[row];
086:
087: final int currentRow = row;
088:
089: renderFieldConstraint(inner, row, constraint, true);
090:
091: //now the clear icon
092: Image clear = new ImageButton(
093: "images/delete_item_small.gif");
094: clear.setTitle("Remove this whole restriction");
095: clear.addClickListener(new ClickListener() {
096: public void onClick(Widget w) {
097: if (Window.confirm("Remove this item?")) {
098: pattern.removeConstraint(currentRow);
099: modeller.refreshWidget();
100: }
101: }
102: });
103:
104: inner.setWidget(row, 5, clear);
105:
106: }
107: if (bindable)
108: layout.setStyleName("modeller-fact-pattern-Widget");
109: initWidget(layout);
110:
111: }
112:
113: /**
114: * This will render a field constraint into the given table.
115: * The row is the row number to stick it into.
116: */
117: private void renderFieldConstraint(final DirtyableFlexTable inner,
118: int row, FieldConstraint constraint, boolean showBinding) {
119: //if nesting, or predicate, then it will need to span 5 cols.
120: if (constraint instanceof SingleFieldConstraint) {
121: renderSingleFieldConstraint(modeller, inner, row,
122: constraint, showBinding);
123: } else if (constraint instanceof CompositeFieldConstraint) {
124: inner
125: .setWidget(
126: row,
127: 0,
128: compositeFieldConstraintEditor((CompositeFieldConstraint) constraint));
129: inner.getFlexCellFormatter().setColSpan(row, 0, 5);
130: }
131: }
132:
133: /**
134: * This will show the constraint editor - allowing field constraints to be nested etc.
135: */
136: private Widget compositeFieldConstraintEditor(
137: final CompositeFieldConstraint constraint) {
138: HorizontalPanel horiz = new HorizontalPanel();
139: String desc = null;
140:
141: Image edit = new ImageButton("images/add_field_to_fact.gif");
142: edit.setTitle("Add a field to this nested constraint.");
143:
144: edit.addClickListener(new ClickListener() {
145: public void onClick(Widget w) {
146: showPatternPopupForComposite(w, constraint);
147: }
148:
149: });
150:
151: if (constraint.compositeJunctionType
152: .equals(CompositeFieldConstraint.COMPOSITE_TYPE_AND)) {
153: desc = "All of:";
154: } else {
155: desc = "Any of:";
156: }
157:
158: //HorizontalPanel ab = new HorizontalPanel();
159: //ab.setStyleName( "composite-fact-pattern" );
160: horiz.add(edit);
161: horiz.add(new HTML("<i>" + desc + " </i>"));
162:
163: //horiz.add( ab );
164:
165: FieldConstraint[] nested = constraint.constraints;
166: DirtyableFlexTable inner = new DirtyableFlexTable();
167: inner.setStyleName("modeller-inner-nested-Constraints");
168: if (nested != null) {
169: for (int i = 0; i < nested.length; i++) {
170: this .renderFieldConstraint(inner, i, nested[i], false);
171: //add in remove icon here...
172: final int currentRow = i;
173: Image clear = new ImageButton(
174: "images/delete_item_small.gif");
175: clear.setTitle("Remove this (nested) restriction");
176:
177: clear.addClickListener(new ClickListener() {
178: public void onClick(Widget w) {
179: if (Window
180: .confirm("Remove this item from nested constraint?")) {
181: constraint.removeConstraint(currentRow);
182: modeller.refreshWidget();
183: }
184: }
185: });
186: inner.setWidget(i, 5, clear);
187: }
188: }
189:
190: horiz.add(inner);
191: return horiz;
192: }
193:
194: /**
195: * Applies a single field constraint to the given table, and start row.
196: */
197: private void renderSingleFieldConstraint(
198: final RuleModeller modeller,
199: final DirtyableFlexTable inner, int row,
200: FieldConstraint constraint, boolean showBinding) {
201: final SingleFieldConstraint c = (SingleFieldConstraint) constraint;
202: if (c.constraintValueType != SingleFieldConstraint.TYPE_PREDICATE) {
203: inner.setWidget(row, 0, fieldLabel(c, showBinding));
204:
205: inner.setWidget(row, 1, operatorDropDown(c));
206: inner.setWidget(row, 2, valueEditor(c,
207: this .pattern.factType));
208: inner.setWidget(row, 3, connectives(c,
209: this .pattern.factType));
210:
211: Image addConnective = new ImageButton(
212: "images/add_connective.gif");
213: addConnective
214: .setTitle("Add more options to this fields values.");
215: addConnective.addClickListener(new ClickListener() {
216: public void onClick(Widget w) {
217: c.addNewConnective();
218: modeller.refreshWidget();
219: }
220: });
221:
222: inner.setWidget(row, 4, addConnective);
223: } else if (c.constraintValueType == SingleFieldConstraint.TYPE_PREDICATE) {
224: inner.setWidget(row, 0, predicateEditor(c));
225: inner.getFlexCellFormatter().setColSpan(row, 0, 5);
226: }
227: }
228:
229: /**
230: * This provides an inline formula editor, not unlike a spreadsheet does.
231: */
232: private Widget predicateEditor(final SingleFieldConstraint c) {
233:
234: HorizontalPanel pred = new HorizontalPanel();
235: pred.setWidth("100%");
236: Image img = new Image("images/function_assets.gif");
237: img
238: .setTitle("This is a formula expression that is evaluated to be true or false.");
239:
240: pred.add(img);
241: final TextBox box = new TextBox();
242: box.setText(c.value);
243: box.addChangeListener(new ChangeListener() {
244: public void onChange(Widget w) {
245: c.value = box.getText();
246: modeller.makeDirty();
247: }
248: });
249:
250: box.setWidth("100%");
251: pred.add(box);
252: return pred;
253: }
254:
255: /**
256: * This returns the pattern label.
257: */
258: private Widget getPatternLabel() {
259: HorizontalPanel horiz = new HorizontalPanel();
260:
261: Image edit = new ImageButton("images/add_field_to_fact.gif");
262: edit
263: .setTitle("Add a field to this condition, or bind a varible to this fact.");
264:
265: edit.addClickListener(new ClickListener() {
266: public void onClick(Widget w) {
267: showPatternPopup(w);
268: }
269: });
270:
271: if (pattern.boundName != null) {
272: horiz.add(new Label("[" + pattern.boundName + "] "
273: + pattern.factType));
274: } else {
275: horiz.add(new Label(pattern.factType));
276: }
277: horiz.add(edit);
278:
279: return horiz;
280:
281: }
282:
283: /**
284: * This shows a popup for adding fields to a composite
285: */
286: private void showPatternPopupForComposite(Widget w,
287: final CompositeFieldConstraint composite) {
288: final FormStylePopup popup = new FormStylePopup(
289: "images/newex_wiz.gif", "Add fields to this constraint");
290: popup.setStyleName("ks-popups-Popup");
291: final ListBox box = new ListBox();
292: box.addItem("...");
293: String[] fields = this .completions
294: .getFieldCompletions(this .pattern.factType);
295: for (int i = 0; i < fields.length; i++) {
296: box.addItem(fields[i]);
297: }
298:
299: box.setSelectedIndex(0);
300:
301: box.addChangeListener(new ChangeListener() {
302: public void onChange(Widget w) {
303: composite.addConstraint(new SingleFieldConstraint(box
304: .getItemText(box.getSelectedIndex())));
305: modeller.refreshWidget();
306: popup.hide();
307: }
308: });
309: popup.addAttribute("Add a restriction on a field", box);
310:
311: final ListBox composites = new ListBox();
312: composites.addItem("...");
313: composites.addItem("All of (And)",
314: CompositeFieldConstraint.COMPOSITE_TYPE_AND);
315: composites.addItem("Any of (Or)",
316: CompositeFieldConstraint.COMPOSITE_TYPE_OR);
317: composites.setSelectedIndex(0);
318:
319: composites.addChangeListener(new ChangeListener() {
320: public void onChange(Widget w) {
321: CompositeFieldConstraint comp = new CompositeFieldConstraint();
322: comp.compositeJunctionType = composites
323: .getValue(composites.getSelectedIndex());
324: composite.addConstraint(comp);
325: modeller.refreshWidget();
326: popup.hide();
327: }
328: });
329:
330: InfoPopup infoComp = new InfoPopup(
331: "Multiple field constraints",
332: "You can specify constraints that span multiple fields (and more). The results of all these constraints can be combined with a 'and' or an 'or' logically."
333: + "You can also have other multiple field constraints nested inside these restrictions.");
334:
335: HorizontalPanel horiz = new HorizontalPanel();
336: horiz.add(composites);
337: horiz.add(infoComp);
338: popup.addAttribute("Multiple field constraint", horiz);
339:
340: popup.setPopupPosition(w.getAbsoluteLeft(), w.getAbsoluteTop());
341: popup.show();
342:
343: }
344:
345: /**
346: * This shows a popup allowing you to add field constraints to a pattern (its a popup).
347: */
348: private void showPatternPopup(Widget w) {
349: final FormStylePopup popup = new FormStylePopup(
350: "images/newex_wiz.gif", "Modify constraints for "
351: + pattern.factType);
352: popup.setStyleName("ks-popups-Popup");
353: final ListBox box = new ListBox();
354: box.addItem("...");
355: String[] fields = this .completions
356: .getFieldCompletions(this .pattern.factType);
357: for (int i = 0; i < fields.length; i++) {
358: box.addItem(fields[i]);
359: }
360:
361: box.setSelectedIndex(0);
362:
363: box.addChangeListener(new ChangeListener() {
364: public void onChange(Widget w) {
365: pattern.addConstraint(new SingleFieldConstraint(box
366: .getItemText(box.getSelectedIndex())));
367: modeller.refreshWidget();
368: popup.hide();
369: }
370: });
371: popup.addAttribute("Add a restriction on a field", box);
372:
373: final ListBox composites = new ListBox();
374: composites.addItem("...");
375: composites.addItem("All of (And)",
376: CompositeFieldConstraint.COMPOSITE_TYPE_AND);
377: composites.addItem("Any of (Or)",
378: CompositeFieldConstraint.COMPOSITE_TYPE_OR);
379: composites.setSelectedIndex(0);
380:
381: composites.addChangeListener(new ChangeListener() {
382: public void onChange(Widget w) {
383: CompositeFieldConstraint comp = new CompositeFieldConstraint();
384: comp.compositeJunctionType = composites
385: .getValue(composites.getSelectedIndex());
386: pattern.addConstraint(comp);
387: modeller.refreshWidget();
388: popup.hide();
389: }
390: });
391:
392: InfoPopup infoComp = new InfoPopup(
393: "Multiple field constraints",
394: "You can specify constraints that span multiple fields (and more). The results of all these constraints can be combined with a 'and' or an 'or' logically."
395: + "You can also have other multiple field constraints nested inside these restrictions.");
396:
397: HorizontalPanel horiz = new HorizontalPanel();
398: horiz.add(composites);
399: horiz.add(infoComp);
400: popup.addAttribute("Multiple field constraint", horiz);
401:
402: popup.addRow(new HTML("<hr/>"));
403:
404: popup.addRow(new Lbl("Advanced options", "weak-Text"));
405: final Button predicate = new Button("New formula");
406: predicate.addClickListener(new ClickListener() {
407: public void onClick(Widget w) {
408: SingleFieldConstraint con = new SingleFieldConstraint();
409: con.constraintValueType = SingleFieldConstraint.TYPE_PREDICATE;
410: pattern.addConstraint(con);
411: modeller.refreshWidget();
412: popup.hide();
413: }
414: });
415: popup.addAttribute("Add a new formula style expression",
416: predicate);
417:
418: doBindingEditor(popup);
419:
420: popup.setPopupPosition(w.getAbsoluteLeft(), w.getAbsoluteTop());
421: popup.show();
422: }
423:
424: /**
425: * This adds in (optionally) the editor for changing the bound variable name.
426: * If its a bindable pattern, it will show the editor,
427: * if it is already bound, and the name is used, it should
428: * not be editable.
429: */
430: private void doBindingEditor(final FormStylePopup popup) {
431: if (bindable
432: && !(modeller.getModel()
433: .isBoundFactUsed(pattern.boundName))) {
434: HorizontalPanel varName = new HorizontalPanel();
435: final TextBox varTxt = new TextBox();
436: varTxt.setText(pattern.boundName);
437: varTxt.setVisibleLength(3);
438: varName.add(varTxt);
439:
440: Button bindVar = new Button("Set");
441: bindVar.addClickListener(new ClickListener() {
442: public void onClick(Widget w) {
443: String var = varTxt.getText();
444: if (modeller.isVariableNameUsed(var)) {
445: Window.alert("The variable name [" + var
446: + "] is already taken.");
447: return;
448: }
449: pattern.boundName = varTxt.getText();
450: modeller.refreshWidget();
451: popup.hide();
452: }
453: });
454:
455: varName.add(bindVar);
456: popup.addAttribute("Variable name", varName);
457:
458: }
459: }
460:
461: private Widget connectives(SingleFieldConstraint c, String factClass) {
462: if (c.connectives != null && c.connectives.length > 0) {
463: DirtyableHorizontalPane horiz = new DirtyableHorizontalPane();
464: for (int i = 0; i < c.connectives.length; i++) {
465: ConnectiveConstraint con = c.connectives[i];
466: horiz.add(connectiveOperatorDropDown(con, c.fieldName));
467: horiz.add(connectiveValueEditor(con, factClass,
468: c.fieldName));
469: }
470: return horiz;
471: } else {
472: //nothing to do
473: return null;
474: }
475:
476: }
477:
478: private Widget connectiveValueEditor(
479: final ISingleFieldConstraint con, String factClass,
480: String fieldName) {
481: String typeNumeric = this .modeller.getSuggestionCompletions()
482: .getFieldType(factClass, fieldName);
483: return new ConstraintValueEditor(pattern, fieldName, con,
484: this .modeller, typeNumeric);
485: }
486:
487: private Widget connectiveOperatorDropDown(
488: final ConnectiveConstraint con, String fieldName) {
489: String[] ops = completions.getConnectiveOperatorCompletions(
490: pattern.factType, fieldName);
491: final ListBox box = new ListBox();
492: box.addItem("--- please choose ---");
493: for (int i = 0; i < ops.length; i++) {
494: String op = ops[i];
495: box.addItem(HumanReadable.getOperatorDisplayName(op), op);
496: if (op.equals(con.operator)) {
497: box.setSelectedIndex(i + 1);
498: }
499:
500: }
501:
502: box.addChangeListener(new ChangeListener() {
503: public void onChange(Widget w) {
504: con.operator = box.getValue(box.getSelectedIndex());
505: }
506: });
507:
508: return box;
509: }
510:
511: private Widget valueEditor(final SingleFieldConstraint c,
512: String factType) {
513: String type = this .modeller.getSuggestionCompletions()
514: .getFieldType(factType, c.fieldName);
515: return new ConstraintValueEditor(pattern, c.fieldName, c,
516: this .modeller, type);
517: }
518:
519: private Widget operatorDropDown(final SingleFieldConstraint c) {
520: String[] ops = completions.getOperatorCompletions(
521: pattern.factType, c.fieldName);
522: final ListBox box = new ListBox();
523: box.addItem("--- please choose ---");
524: for (int i = 0; i < ops.length; i++) {
525: String op = ops[i];
526: box.addItem(HumanReadable.getOperatorDisplayName(op), op);
527: if (op.equals(c.operator)) {
528: box.setSelectedIndex(i + 1);
529: }
530:
531: }
532:
533: box.addChangeListener(new ChangeListener() {
534: public void onChange(Widget w) {
535: c.operator = box.getValue(box.getSelectedIndex());
536: modeller.makeDirty();
537: System.out.println("Set operator to :" + c.operator);
538: }
539: });
540:
541: return box;
542: }
543:
544: /**
545: * get the field widget. This may be a simple label, or it may
546: * be bound (and show the var name) or a icon to create a binding.
547: * It will only show the binding option of showBinding is true.
548: */
549: private Widget fieldLabel(final SingleFieldConstraint con,
550: boolean showBinding) {//, final Command onChange) {
551: HorizontalPanel ab = new HorizontalPanel();
552: ab.setStyleName("modeller-field-Label");
553: if (!con.isBound()) {
554: if (bindable && showBinding) {
555: Image bind = new ImageButton(
556: "images/add_field_to_fact.gif",
557: "Give this field a variable name that can be used elsewhere.");
558: bind.addClickListener(new ClickListener() {
559: public void onClick(Widget w) {
560: showBindFieldPopup(w, con);
561: }
562: });
563: ab.add(bind);
564: }
565: } else {
566: ab.add(new Label("[" + con.fieldBinding + "]"));
567: }
568:
569: ab.add(new Label(con.fieldName));
570: return ab;
571: }
572:
573: /**
574: * Display a little editor for field bindings.
575: */
576: private void showBindFieldPopup(final Widget w,
577: final SingleFieldConstraint con) {
578: final FormStylePopup popup = new FormStylePopup(
579: "images/newex_wiz.gif", "Bind the field called ["
580: + con.fieldName + "] to a variable.");
581: final AbsolutePanel vn = new AbsolutePanel();
582: final TextBox varName = new TextBox();
583: final Button ok = new Button("Set");
584: vn.add(varName);
585: vn.add(ok);
586:
587: ok.addClickListener(new ClickListener() {
588: public void onClick(Widget w) {
589: String var = varName.getText();
590: if (modeller.isVariableNameUsed(var)) {
591: Window.alert("The variable name [" + var
592: + "] is already taken.");
593: return;
594: }
595: con.fieldBinding = var;
596: modeller.refreshWidget();
597: popup.hide();
598: }
599: });
600: popup.addAttribute("Variable name", vn);
601: popup.setPopupPosition(w.getAbsoluteLeft(), w.getAbsoluteTop());
602: popup.show();
603: }
604:
605: public boolean isDirty() {
606: return layout.hasDirty();
607: }
608:
609: }
|