001    package org.kuali.student.common.ui.client.widgets.rules;
002    
003    import java.util.ArrayList;
004    import java.util.List;
005    import java.util.Map;
006    
007    import org.kuali.student.common.ui.client.configurable.mvc.SectionTitle;
008    import org.kuali.student.common.ui.client.mvc.Callback;
009    import org.kuali.student.common.ui.client.widgets.KSButton;
010    import org.kuali.student.common.ui.client.widgets.KSButtonAbstract;
011    import org.kuali.student.common.ui.client.widgets.KSLabel;
012    import org.kuali.student.common.ui.client.widgets.dialog.ConfirmationDialog;
013    import org.kuali.student.common.ui.client.widgets.field.layout.element.SpanPanel;
014    import org.kuali.student.core.statement.dto.StatementOperatorTypeKey;
015    import org.kuali.student.core.statement.dto.StatementTreeViewInfo;
016    
017    import com.google.gwt.event.dom.client.ClickEvent;
018    import com.google.gwt.event.dom.client.ClickHandler;
019    import com.google.gwt.user.client.ui.FlowPanel;
020    import com.google.gwt.user.client.ui.Widget;
021    
022    public class RulePreviewWidget extends FlowPanel {
023    
024        //Widgets
025        private SpanPanel rulePanel = new SpanPanel();
026        private KSButton editButton = new KSButton("Edit", KSButtonAbstract.ButtonStyle.DEFAULT_ANCHOR);
027        private SpanPanel separator = new SpanPanel(" | ");
028        private KSButton deleteButton = new KSButton("Delete", KSButtonAbstract.ButtonStyle.DEFAULT_ANCHOR);
029        private KSButton addSubRuleBtn = new KSButton("Add a Rule", KSButtonAbstract.ButtonStyle.FORM_SMALL);
030        private List<SubrulePreviewWidget> subRulePreviewWidgets = new ArrayList<SubrulePreviewWidget>();
031        private Map<String, Widget> clusetWidgets;
032        private SectionTitle progReqHeader = SectionTitle.generateH4Title("");
033        private KSLabel ruleDescLabel = new KSLabel();
034        private SpanPanel actions = new SpanPanel();
035    
036        //data
037        private StatementTreeViewInfo stmtTreeInfo;     //program rule e.g. program completion rule
038        private boolean isReadOnly;
039        private boolean addRuleOperator = false;        //first subrule does not have operator following it
040        private Integer internalProgReqID;
041    
042        private Callback<SubRuleInfo> editSubRuleCallback;
043        private Callback<Integer> deleteSubRuleCallback;
044    
045        public RulePreviewWidget(Integer internalProgReqID, String ruleName, String ruleCreditsText, String ruleDesc, StatementTreeViewInfo stmtTreeInfo,
046                                 Boolean isReadOnly,  Map<String, Widget> clusetWidgets) {
047            super();
048            this.internalProgReqID = internalProgReqID;
049            this.stmtTreeInfo = stmtTreeInfo;
050            this.isReadOnly = isReadOnly;
051            this.clusetWidgets = clusetWidgets;
052    
053            //prepare widgets
054            this.addStyleName("KS-Rule-Preview-Box");
055            progReqHeader.setStyleName("KS-Rule-Preview-header");
056            ruleDescLabel.addStyleName("KS-Rule-Preview-Desc");
057            if (!isReadOnly) {
058                buildEditActionsWidget();
059            }
060            updateProgInfoFields(ruleName, ruleCreditsText, ruleDesc);
061    
062            displayRule();
063        }
064    
065        public void updateProgInfoFields(String ruleName, String ruleCreditsText, String ruleDesc) {
066            progReqHeader.clear();
067            progReqHeader.setHTML("<b>" + ruleName + "</b>" + "  " + ruleCreditsText);
068            //do not show edit,delete etc. if user is only viewing the rule in non-edit mode
069            if (!isReadOnly) {
070                progReqHeader.add(actions);
071            }
072            ruleDescLabel.setText(ruleDesc);
073        }
074    
075        private void displayRule() {
076            //start with a header for the entire rule
077            buildRuleHeader();
078    
079            //show sub-rules if we have any
080            if (stmtTreeInfo == null) {
081                displayNoRule();
082            }
083    
084            if ((stmtTreeInfo.getStatements() == null) || (stmtTreeInfo.getStatements().isEmpty())) {
085                if ((stmtTreeInfo.getReqComponents() == null) || (stmtTreeInfo.getReqComponents().isEmpty())) {
086                    displayNoRule();
087                } else {
088                    addSubRule(stmtTreeInfo);
089                }
090            } else {
091                for (final StatementTreeViewInfo subTree : stmtTreeInfo.getStatements()) {
092                    addSubRule(subTree);
093                }
094            }
095    
096            //button below has to be spaced
097            SpanPanel spacer = new SpanPanel();
098            spacer.addStyleName("KS-Rule-Last-Preview-Spacer");
099            rulePanel.add(spacer);
100    
101            //add a button for user to add additional subrule
102            if (!isReadOnly) {
103                addSubRuleBtn.addStyleName("KS-Rule-Preview-Add-Subrule");
104                rulePanel.add(addSubRuleBtn);
105            }
106    
107            this.add(rulePanel);
108        }
109    
110        private void displayNoRule() {
111            KSLabel noRulesAddedYet = new KSLabel("No rules have been added yet.");
112            noRulesAddedYet.addStyleName("KS-Rule-Preview-NoRule");
113            rulePanel.add(noRulesAddedYet);
114        }
115    
116        private void addSubRule(final StatementTreeViewInfo subTree) {
117            //display AND/OR operator between subrules
118            if (addRuleOperator) {
119                buildANDOperator(stmtTreeInfo.getOperator());
120            }
121    
122            final SubrulePreviewWidget newSubRuleWidget = new SubrulePreviewWidget(subTree, isReadOnly, clusetWidgets);        
123            subRulePreviewWidgets.add(newSubRuleWidget);
124    
125            newSubRuleWidget.addEditButtonClickHandler(new ClickHandler(){
126                public void onClick(ClickEvent event) {
127                    SubRuleInfo subRuleInfo = new SubRuleInfo();
128                    subRuleInfo.setSubrule(subTree);
129                    subRuleInfo.setInternalProgReqID(internalProgReqID);
130                    editSubRuleCallback.exec(subRuleInfo);
131                }
132            });
133    
134            newSubRuleWidget.addDeleteButtonClickHandler(new ClickHandler(){
135                public void onClick(ClickEvent event) {
136                    final ConfirmationDialog dialog = new ConfirmationDialog("Delete Rule", "Are you sure you want to delete this rule?"); //TODO app context for labels
137                    dialog.getConfirmButton().addClickHandler(new ClickHandler(){
138                        @Override
139                        public void onClick(ClickEvent event) {
140                            removeSubRule(newSubRuleWidget, subTree);
141                            dialog.hide();
142                            deleteSubRuleCallback.exec(internalProgReqID);
143                        }
144                    });
145                    dialog.show();
146                }
147            });
148    
149            rulePanel.add(newSubRuleWidget);
150            addRuleOperator = true;
151        }
152    
153        //user selected to delete this particular requirement
154        public void removeSubRule(SubrulePreviewWidget subRuleWidget, StatementTreeViewInfo subTree) {
155            //update data
156            List<StatementTreeViewInfo> statements = stmtTreeInfo.getStatements();
157            statements.remove(subTree);
158            subRulePreviewWidgets.remove(subRuleWidget);
159    
160            //update display
161            rulePanel.remove(subRuleWidget);
162    
163            //remove unnecessary operators
164            int numberOfOperators = 0;
165            boolean lastWidgetIsSubRule = true;
166            boolean foundSubRule = false;
167            int counter = 0;
168            Widget widget = null;
169            Widget widgetToRemove = null;
170            Widget lastOperatorWidget = null;
171            for (Object aRulePanel : rulePanel) {
172                counter++;
173                widget = (Widget) aRulePanel;
174                if (widget instanceof KSLabel) {
175                    if (((KSLabel) widget).getText().equals("AND") || ((KSLabel) widget).getText().equals("OR")) {
176                        lastWidgetIsSubRule = false;
177                        lastOperatorWidget = widget;
178                        numberOfOperators++;
179                        //remove 'AND' above the first sub-rule or two repeated instances
180                        if ((numberOfOperators > 1) || (counter == 1) || !foundSubRule) {
181                            widgetToRemove = widget;
182                            numberOfOperators--;
183                        }
184                    }
185                } else if (widget instanceof SubrulePreviewWidget) {
186                    foundSubRule = true;
187                    lastWidgetIsSubRule = true;
188                    numberOfOperators = 0;
189                }
190            }
191            
192            if (!lastWidgetIsSubRule) {
193                rulePanel.remove(lastOperatorWidget);
194            }
195            if (widgetToRemove != null) {
196                rulePanel.remove(widgetToRemove);
197            }
198        }
199    
200        private void buildRuleHeader() {
201            rulePanel.clear();
202            rulePanel.addStyleName("KS-Rule-Preview-Box1");
203            rulePanel.add(progReqHeader);
204            rulePanel.add(ruleDescLabel);
205    
206            //build subheading
207            SectionTitle header = SectionTitle.generateH6Title("");
208            header.setHTML("Must meet <b>all of the following</b> rules");
209            header.setStyleName("KS-Rule-Preview-header-Subrule");
210            header.getElement().setAttribute("style", "font-weight: normal");        
211            rulePanel.add(header);
212        }
213    
214        private void buildEditActionsWidget() {       
215            actions.add(editButton);
216            actions.add(separator);
217            actions.add(deleteButton);
218            actions.addStyleName("KS-Rule-Preview-header-action");
219        }
220    
221        private void buildANDOperator(StatementOperatorTypeKey operator) {
222            KSLabel andLabel = new KSLabel((operator == StatementOperatorTypeKey.AND ? "AND" : "OR"));
223            andLabel.addStyleName("KS-Rule-Preview-Operator");
224            rulePanel.add(andLabel);        
225        }
226    
227        public void addProgReqEditButtonClickHandler(ClickHandler handler) {
228            editButton.addClickHandler(handler);
229        }
230    
231        public void addProgReqDeleteButtonClickHandler(ClickHandler handler) {
232            deleteButton.addClickHandler(handler);
233        }
234    
235        public void addSubRuleAddButtonClickHandler(ClickHandler handler) {
236            addSubRuleBtn.addClickHandler(handler);
237        }
238    
239        public void addSubRuleEditButtonClickHandler(Callback<SubRuleInfo> callback) {
240            this.editSubRuleCallback = callback;
241        }
242    
243        public void addSubRuleDeleteCallback(Callback<Integer> callback) {
244            this.deleteSubRuleCallback = callback;
245        }
246    
247        public StatementTreeViewInfo getStatementTreeViewInfo() {
248            return  stmtTreeInfo;
249        }
250    
251        public Integer getInternalProgReqID() {
252            return internalProgReqID;
253        }
254    
255        public class SubRuleInfo {
256            private StatementTreeViewInfo subrule;
257    
258            private Integer internalProgReqID;
259    
260            public StatementTreeViewInfo getSubrule() {
261                return subrule;
262            }
263    
264            public void setSubrule(StatementTreeViewInfo subrule) {
265                this.subrule = subrule;
266            }
267    
268            public Integer getInternalProgReqID() {
269                return internalProgReqID;
270            }
271    
272            public void setInternalProgReqID(Integer internalProgReqID) {
273                this.internalProgReqID = internalProgReqID;
274            }
275        }
276    }