package org.openrosa.client.view;
import java.util.List;
import java.util.Vector;
import org.openrosa.client.controller.IConditionController;
import org.openrosa.client.model.Condition;
import org.openrosa.client.model.FormDef;
import org.openrosa.client.model.IFormElement;
import org.openrosa.client.model.OptionDef;
import org.openrosa.client.model.QuestionDef;
import org.openrosa.client.model.SkipRule;
import org.openrosa.client.widget.skiprule.ConditionWidget;
import org.openrosa.client.widget.skiprule.GroupHyperlink;
import org.openrosa.client.xforms.RelevantBuilder;
import org.openrosa.client.xforms.RelevantParser;
import org.openrosa.client.xforms.XformParserUtil;
import org.openrosa.client.controller.QuestionSelectionListener;
import org.openrosa.client.jr.xforms.parse.XFormParser;
import org.openrosa.client.locale.LocaleText;
import org.openrosa.client.model.ModelConstants;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* This widget enables creation of skip rules.
*
* @author daniel
*
*/
public class SkipRulesView extends Composite implements IConditionController, QuestionSelectionListener{
/** The widget horizontal spacing in horizontal panels. */
private static final int HORIZONTAL_SPACING = 5;
/** The widget vertical spacing in vertical panels. */
private static final int VERTICAL_SPACING = 0;
/** The main or root widget. */
private VerticalPanel verticalPanel = new VerticalPanel();
/** Widget for adding new conditions. */
private Hyperlink addConditionLink = new Hyperlink(LocaleText.get("clickToAddNewCondition"),"");
/** Widget for grouping conditions. Has all,any, none, and not all. */
private GroupHyperlink groupHyperlink = new GroupHyperlink(GroupHyperlink.CONDITIONS_OPERATOR_TEXT_ALL,"");
/** The form definition object that this skip rule belongs to. */
private FormDef formDef;
/** The question definition object which is the target of the skip rule.
* As for now, the form designer supports only one skip rule target. But the
* skip rule object supports an un limited number.
*/
private IFormElement questionDef;
/** The skip rule definition object. */
private SkipRule skipRule;
/** Flag determining whether to enable this widget or not. */
private boolean enabled;
/** Widget for the skip rule action to make a question required. */
private CheckBox chkMakeRequired = new CheckBox("Make Required");
/** Widget for Label "for question". */
private Label lblAction = new Label(LocaleText.get("forQuestion"));
/** Widget for Label "and". */
private Label lblAnd = new Label(LocaleText.get("and"));
private Label lblrelevant;
private TextArea txtrelevant;
private CheckBox chkUseAdvancedRelevant;
// private IFormElement selectedObj;
private boolean advancedMode;
/**
* Creates a new instance of the skip logic widget.
*/
private HorizontalPanel p1, p2;
private FlexTable p3;
public SkipRulesView(){
setupWidgets();
}
/**
* Sets up the widgets.
*/
private void setupWidgets(){
FlexTable advanced = new FlexTable();
lblrelevant=new Label("Relevant:");
txtrelevant = new TextArea();
txtrelevant.setEnabled(true);
chkUseAdvancedRelevant = new CheckBox("Use Advanced Skip Logic Text");
txtrelevant.setText("");
advanced.setWidget(0,0,new Label(""));
advanced.setWidget(1, 0, chkUseAdvancedRelevant);
advanced.getFlexCellFormatter().setColSpan(1, 0, 2);
advanced.setWidget(2, 0, lblrelevant);
advanced.setWidget(2, 1, txtrelevant);
p3 = advanced;
advanced.setCellPadding(5);
verticalPanel.add(advanced);
verticalPanel.setSpacing(VERTICAL_SPACING);
HorizontalPanel horizontalPanel = new HorizontalPanel();
p1 = horizontalPanel;
horizontalPanel.setSpacing(HORIZONTAL_SPACING);
Hyperlink hyperlink = new Hyperlink(LocaleText.get("clickForOtherQuestions"),"");
hyperlink.addClickHandler(new ClickHandler(){
public void onClick(ClickEvent event){
showOtherQuestions();
}
});
HorizontalPanel horzPanel = new HorizontalPanel();
p2 = horzPanel;
horzPanel.setSpacing(10);
horzPanel.add(lblAction);
horzPanel.add(lblAnd);
horzPanel.add(hyperlink);
verticalPanel.add(horzPanel);
horizontalPanel.add(new Label("will appear ONLY "));
horizontalPanel.add(new Label(LocaleText.get("when")));
horizontalPanel.add(groupHyperlink);
horizontalPanel.add(new Label(LocaleText.get("ofTheFollowingApply")));
verticalPanel.add(horizontalPanel);
verticalPanel.add(addConditionLink);
addConditionLink.addClickHandler(new ClickHandler(){
public void onClick(ClickEvent event){
addCondition();
}
});
verticalPanel.setSpacing(VERTICAL_SPACING);
chkUseAdvancedRelevant.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
setAdvancedMode(chkUseAdvancedRelevant.getValue());
if(event.getValue()){
fromSimpleToAdvancedRelevant();
}else{
fromAdvancedToSimpleRelevant();
}
setQuestionDef(questionDef);
}
});
txtrelevant.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
questionDef.setAdvancedRelevant(txtrelevant.getText());
//and if we've done that the hasAdvanced() should definitely return true so:
questionDef.setHasAdvancedRelevant(true);
}
});
initWidget(verticalPanel);
setAdvancedMode(false);
}
/**
* Converts a simple relevant (that uses SkipRules)
* and converts to Advanced relevant (if possible).
*
* Returns false if unsuccesful
* Returns true if the succesful.
*
* Leaves the old skipRules intact
*
* If no SkipRules are present, will use
* data stored in AdvancedRelevant in the QuestionDef
* (if possible).
*
* Will OVERWRITE any existing advanced relevant data
* if SkipRules are present
* @return
*/
public boolean fromSimpleToAdvancedRelevant(){
SkipRule currentRule = formDef.getSkipRule(questionDef);
String currentAdvRel = questionDef.getAdvancedRelevant();
questionDef.setHasAdvancedRelevant(true);
boolean currentAdvRelExists = (currentAdvRel != null && !currentAdvRel.isEmpty());
if(currentRule == null){
//start from scratch, use existing AdvRel (if it exists).
currentRule = createNewSkipForQtn(questionDef);
formDef.addSkipRule(currentRule);
return true;
}
//if we get here, it implies we already have a skip rule pointing to this question
//in the FormDef, so no need to worry about it.
String newAdvRel = RelevantBuilder.fromSkipRule2String(currentRule, formDef);
boolean newAdvRelExists = (newAdvRel != null && !newAdvRel.isEmpty());
if(!newAdvRelExists){
if(currentAdvRelExists){
//we already have advanced text that isn't empty, so stick with that and return false to indicate something went wrong with conversion.
return false;
}else{
questionDef.setAdvancedRelevant(""); //we've converted nothing and had nothing, so everything went well.
return true;
}
}else{
questionDef.setAdvancedRelevant(newAdvRel);
return true; //great success.
}
}
public SkipRule createNewSkipForQtn(IFormElement qtn){
SkipRule sr = new SkipRule(formDef.getNextSkipRuleId(), new Vector(), ModelConstants.ACTION_ENABLE, new Vector());
sr.addActionTarget(qtn.getId());
return sr;
}
/**
* Converts from and advanced to simple relevant
* (simple uses SkipRules).
* Returns false if not successfully converted
* Returns true if succesful.
*
* Will keep data in advanced if succesful.
* Will always overwrite existing SkipRules
* if they exist. On return false, will
* blank out existing SkipRules
* @return
*/
public boolean fromAdvancedToSimpleRelevant(){
questionDef.setHasAdvancedRelevant(false);
String currentAdvRel = questionDef.getAdvancedRelevant();
boolean hasAdv = (currentAdvRel != null && !currentAdvRel.isEmpty());
SkipRule currentSkip = formDef.getSkipRule(questionDef);
formDef.removeSkipRule(currentSkip);
if(!hasAdv){
return true; //the 'conversion' was a success, because there was nothing to convert.
}else{
//do the conversion using the existing SR parser.
currentSkip = RelevantParser.buildSkipRule(formDef, questionDef.getId(), currentAdvRel, formDef.getNextSkipRuleId(), ModelConstants.ACTION_ENABLE);
if(currentSkip != null){
formDef.addSkipRule(currentSkip);
return true;
}else{
return false;
}
}
}
/**
* Adds a new condition.
*/
public void addCondition(){
if(formDef != null && enabled){
verticalPanel.remove(addConditionLink);
ConditionWidget conditionWidget = new ConditionWidget(formDef,this,true,questionDef);
verticalPanel.add(conditionWidget);
verticalPanel.add(addConditionLink);
}
}
/**
* Supposed to add a bracket or nested set of related conditions which are
* currently not supported.
*/
public void addBracket(){
}
/**
* Deletes a condition.
*
* @param conditionWidget the widget having the condition to delete.
*/
public void deleteCondition(ConditionWidget conditionWidget){
if(skipRule != null){
Condition condition = conditionWidget.getCondition();
if(condition != null){
if(skipRule.getConditionCount() == 1 && skipRule.getActionTargetCount() > 1)
skipRule.removeActionTarget(questionDef);
else
skipRule.removeCondition(condition);
}
}
verticalPanel.remove(conditionWidget);
}
/**
* Sets or updates the values of the skip rule object from the user's widget selections.
*/
public void updateSkipRule(){
GWT.log("Updating skip rule (SkipRulesView.java)");
if(questionDef == null){
skipRule = null;
return;
}
if(skipRule == null)
skipRule = new SkipRule();
int conditionCount = 0;
int count = verticalPanel.getWidgetCount();
for(int i=0; i<count; i++){
Widget widget = verticalPanel.getWidget(i);
if(widget instanceof ConditionWidget){
Condition condition = ((ConditionWidget)widget).getCondition();
if(condition != null && !skipRule.containsCondition(condition))
skipRule.addCondition(condition);
else if(condition != null && skipRule.containsCondition(condition))
skipRule.updateCondition(condition);
conditionCount++;
}
}
if(skipRule.getConditions() == null || conditionCount == 0)
skipRule = null;
else{
skipRule.setConditionsOperator(groupHyperlink.getConditionsOperator());
skipRule.setAction(getAction());
if(!skipRule.containsActionTarget(questionDef.getId()))
skipRule.addActionTarget(questionDef.getId());
}
if(chkUseAdvancedRelevant.getValue()){
//this means that we're using advanced mode!
if(skipRule == null){
skipRule = new SkipRule(questionDef.getId(),new Vector(),ModelConstants.ACTION_NONE, new Vector());
}
if(!skipRule.containsActionTarget(questionDef.getId())){
skipRule.addActionTarget(questionDef.getId());
}
}
if(skipRule != null && !formDef.containsSkipRule(skipRule))
formDef.addSkipRule(skipRule);
}
/**
* Gets the skip rule action based on the user's widget selections.
*
* @return the skip rule action.
*/
private int getAction(){
int action = 0;
action |= ModelConstants.ACTION_ENABLE;
if(chkMakeRequired.getValue() == true)
action |= ModelConstants.ACTION_MAKE_MANDATORY;
else
action |= ModelConstants.ACTION_MAKE_OPTIONAL;
return action;
}
/**
* Updates the widgets basing on a given skip rule action.
*
* @param action the skip rule action.
*/
private void setAction(int action){
chkMakeRequired.setValue((action & ModelConstants.ACTION_MAKE_MANDATORY) != 0);
}
/**
* Sets the question definition object which is the target of the skip rule.
* For now we support only one target for the skip rule.
*
* @param questionDef the question definition object.
*/
public void setQuestionDef(IFormElement questionDef){
clearConditions();
formDef = questionDef.getFormDef();
if(questionDef != null)
lblAction.setText(LocaleText.get("forQuestion") + questionDef.getQuestionID());
else
lblAction.setText(LocaleText.get("forQuestion"));
this.questionDef = questionDef;
if(questionDef.hasAdvancedRelevant()){
txtrelevant.setText(questionDef.getAdvancedRelevant());
}else{
buildConditionsWidgets();
}
}
private void buildConditionsWidgets(){
skipRule = formDef.getSkipRule(questionDef);
if(skipRule != null){
groupHyperlink.setCondionsOperator(skipRule.getConditionsOperator());
setAction(skipRule.getAction());
verticalPanel.remove(addConditionLink);
Vector conditions = skipRule.getConditions();
Vector lostConditions = new Vector();
for(int i=0; i<conditions.size(); i++){
ConditionWidget conditionWidget = new ConditionWidget(formDef,this,true,questionDef);
if(conditionWidget.setCondition((Condition)conditions.elementAt(i)))
verticalPanel.add(conditionWidget);
else
lostConditions.add((Condition)conditions.elementAt(i));
}
for(int i=0; i<lostConditions.size(); i++)
skipRule.removeCondition((Condition)lostConditions.elementAt(i));
if(skipRule.getConditionCount() == 0){
formDef.removeSkipRule(skipRule);
skipRule = null;
}
verticalPanel.add(addConditionLink);
}
}
/**
* Sets the form definition object to which this skip rule belongs.
*
* @param formDef the form definition object.
*/
public void setFormDef(FormDef formDef){
updateSkipRule();
this.formDef = formDef;
this.questionDef = null;
clearConditions();
}
/**
* Removes all skip rule conditions.
*/
private void clearConditions(){
// if(questionDef != null)
// updateSkipRule();
// questionDef = null;
lblAction.setText(LocaleText.get("forQuestion"));
while(verticalPanel.getWidgetCount() > 4)
verticalPanel.remove(verticalPanel.getWidget(3));
chkMakeRequired.setValue(false);
}
/**
* Sets whether to enable this widget or not.
*
* @param enabled set to true to enable, else false.
*/
public void setEnabled(boolean enabled){
this.enabled = enabled;
groupHyperlink.setEnabled(enabled);
chkMakeRequired.setEnabled(enabled);
if(!enabled)
clearConditions();
}
/**
* Shows a list of other questions that are targets of the current skip rule.
*/
private void showOtherQuestions(){
GWT.log("ShowOtherQuestion() enabled="+enabled);
if(enabled){
SkipQtnsDialog dialog = new SkipQtnsDialog(this);
dialog.setData(formDef,questionDef,skipRule);
dialog.center();
}
}
/**
* @see org.openrosa.client.controller.QuestionSelectionListener#onQuestionsSelected(List)
*/
public void onQuestionsSelected(List<String> questions){
if(skipRule == null)
skipRule = new SkipRule();
//Check if we have any action targets. If we do not, just add all as new.
List<Integer> actnTargets = skipRule.getActionTargets();
if(actnTargets == null){
for(String varName : questions)
skipRule.addActionTarget(formDef.getElement(varName).getId());
return;
}
//Remove any de selected action targets from the skip rule.
for(int index = 0; index < actnTargets.size(); index++){
Integer qtnId = actnTargets.get(index);
QuestionDef qtnDef = formDef.getQuestion(qtnId);
if(qtnDef == questionDef)
continue; //Ignore the question for which we are editing the skip rule.
if(qtnDef == null || !questions.contains(qtnDef.getQuestionID())){
actnTargets.remove(index);
index = index - 1;
}
}
//Add any newly added questions as action targets.
for(String varName : questions){
IFormElement qtnDef = formDef.getElement(varName);
if(!skipRule.containsActionTarget(qtnDef.getId()))
skipRule.addActionTarget(qtnDef.getId());
}
}
/**
* This entire class should not support selecting multiple questions
* @param senderWidget
* @param item
*/
public void onItemSelected(Object senderWidget, Object item){
if(item == null){
clearConditions();
return;
}
this.questionDef = (IFormElement)item; //should always be an IFormElement or we're in trouble.
setQuestionDef(questionDef);
//select the checkboxes according to the flags set in the selected items.
//unless they're of the type that can't have any kind of bind logic
if(questionDef instanceof FormDef || questionDef instanceof OptionDef){
chkUseAdvancedRelevant.setEnabled(false);
chkUseAdvancedRelevant.setValue(false);
this.setAdvancedMode(false);
this.setEnabled(false);
}else{
chkUseAdvancedRelevant.setEnabled(true);
chkUseAdvancedRelevant.setValue(questionDef.hasAdvancedRelevant());
if(chkUseAdvancedRelevant.getValue()){
groupHyperlink.setEnabled(false);
chkMakeRequired.setEnabled(false);
this.setEnabled(false);
txtrelevant.setText(questionDef.getAdvancedRelevant());
setAdvancedMode(true);
}else{
groupHyperlink.setEnabled(true);
chkMakeRequired.setEnabled(true);
this.setEnabled(true);
setAdvancedMode(false);
}
setAdvancedMode(chkUseAdvancedRelevant.getValue());
}
}
public void setAdvancedMode(boolean enabled){
this.advancedMode = enabled;
groupHyperlink.setEnabled(!enabled);
p1.setVisible(!enabled);
p2.setVisible(!enabled);
lblrelevant.setVisible(enabled);
txtrelevant.setVisible(enabled);
this.setEnabled(!enabled);
addConditionLink.setVisible(!enabled);
}
public boolean isAdvancedMode(){
return advancedMode;
}
}