package org.sigmah.shared.dto.element;
/*
* #%L
* Sigmah
* %%
* Copyright (C) 2010 - 2016 URD
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import java.util.ArrayList;
import java.util.List;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.ui.res.icon.project.category.CategoryIconProvider;
import org.sigmah.client.ui.widget.FlexibleGrid;
import org.sigmah.client.ui.widget.HistoryTokenText;
import org.sigmah.shared.command.result.ValueResult;
import org.sigmah.shared.dto.category.CategoryTypeDTO;
import org.sigmah.shared.dto.element.event.RequiredValueEvent;
import org.sigmah.shared.dto.element.event.ValueEvent;
import org.sigmah.shared.dto.history.HistoryTokenListDTO;
import org.sigmah.shared.dto.quality.QualityCriterionDTO;
import org.sigmah.shared.util.ValueResultUtils;
import com.extjs.gxt.ui.client.Style.SelectionMode;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
import com.extjs.gxt.ui.client.event.SelectionChangedListener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.form.ComboBox;
import com.extjs.gxt.ui.client.widget.form.ComboBox.TriggerAction;
import com.extjs.gxt.ui.client.widget.grid.CheckBoxSelectionModel;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnData;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import java.util.Collections;
import java.util.HashSet;
import org.sigmah.shared.dto.referential.ValueEventChangeType;
/**
* QuestionElementDTO.
*
* @author Denis Colliot (dcolliot@ideia.fr) (v2.0)
* @author Renato Almeida (renatoaf.ufcg@gmail.com)
*/
public class QuestionElementDTO extends FlexibleElementDTO {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 8520711106031085130L;
/**
* DTO corresponding entity name.
*/
public static final String ENTITY_NAME = "element.QuestionElement";
// DTO attributes keys.
public static final String CHOICES = "choices";
public static final String MULTIPLE = "multiple";
public static final String CATEGORY_TYPE = "categoryType";
public static final String QUALITY_CRITERION = "qualityCriterion";
/**
* {@inheritDoc}
*/
@Override
public String getEntityName() {
return ENTITY_NAME;
}
// Question choices list
public List<QuestionChoiceElementDTO> getChoices() {
return get(CHOICES);
}
public void setChoices(List<QuestionChoiceElementDTO> choices) {
set(CHOICES, choices);
}
// Question multiple mode
public Boolean getMultiple() {
return get(MULTIPLE);
}
public void setMultiple(Boolean multiple) {
set(MULTIPLE, multiple);
}
// Question category type
public CategoryTypeDTO getCategoryType() {
return get(CATEGORY_TYPE);
}
public void setCategoryType(CategoryTypeDTO categoryType) {
set(CATEGORY_TYPE, categoryType);
}
// Question quality criterion
public QualityCriterionDTO getQualityCriterion() {
return get(QUALITY_CRITERION);
}
public void setQualityCriterion(QualityCriterionDTO qualityCriterion) {
set(QUALITY_CRITERION, qualityCriterion);
}
/**
* {@inheritDoc}
*/
@Override
protected Component getComponent(ValueResult valueResult, boolean enabled) {
final boolean canEdit = enabled && userCanPerformChangeType(ValueEventChangeType.EDIT);
// Question's component.
final Component component;
// Creates choices store.
final ListStore<QuestionChoiceElementDTO> store = new ListStore<QuestionChoiceElementDTO>();
store.add(getChoices());
// Creates the listener of selection changes.
final ComboBoxSelectionListener listener = new ComboBoxSelectionListener();
// Selection.
List<QuestionChoiceElementDTO> selectedChoices = Collections.emptyList();
// Single selection case.
if (!Boolean.TRUE.equals(getMultiple())) {
final ComboBox<QuestionChoiceElementDTO> comboBox = new ComboBox<QuestionChoiceElementDTO>();
comboBox.setEmptyText(I18N.CONSTANTS.flexibleElementQuestionEmptyChoice());
comboBox.setStore(store);
comboBox.setFieldLabel(getLabel());
comboBox.setDisplayField(LABEL);
comboBox.setValueField(ID);
comboBox.setLabelSeparator("");
comboBox.setTriggerAction(TriggerAction.ALL);
comboBox.setEditable(false);
comboBox.setAllowBlank(true);
if (getCategoryType() != null) {
for (final QuestionChoiceElementDTO choiceDTO : store.getModels()) {
if (choiceDTO.getCategoryElement() != null) {
choiceDTO.getCategoryElement().setIconHtml(CategoryIconProvider.getIconHtml(choiceDTO.getCategoryElement(), false));
}
}
comboBox.setTemplate(CategoryIconProvider.getComboboxIconTemplate());
}
if (valueResult != null && valueResult.isValueDefined()) {
final String idChoice = valueResult.getValueObject();
for (QuestionChoiceElementDTO choiceDTO : getChoices()) {
if (idChoice.equals(String.valueOf(choiceDTO.getId()))) {
comboBox.setValue(choiceDTO);
selectedChoices = Collections.singletonList(choiceDTO);
break;
}
}
}
// Listens to the selection changes.
comboBox.addSelectionChangedListener(listener);
comboBox.setEnabled(enabled);
component = comboBox;
}
// Multiple selection case.
else {
// Selection model.
final CheckBoxSelectionModel<QuestionChoiceElementDTO> selectionModel = new CheckBoxSelectionModel<QuestionChoiceElementDTO>();
selectionModel.setSelectionMode(SelectionMode.MULTI);
selectionModel.addListener(Events.SelectionChange, listener);
// Defines grid column model.
final ColumnConfig labelColumn = new ColumnConfig();
labelColumn.setId(LABEL);
labelColumn.setHeaderText(I18N.CONSTANTS.flexibleElementQuestionMutiple());
labelColumn.setWidth(500);
if (getCategoryType() != null) {
labelColumn.setRenderer(new GridCellRenderer<QuestionChoiceElementDTO>() {
@Override
public Object render(QuestionChoiceElementDTO model, String property, ColumnData config, int rowIndex, int colIndex,
ListStore<QuestionChoiceElementDTO> store, Grid<QuestionChoiceElementDTO> grid) {
final com.google.gwt.user.client.ui.Grid panel = new com.google.gwt.user.client.ui.Grid(1, 2);
panel.setCellPadding(0);
panel.setCellSpacing(0);
panel.setWidget(0, 0, CategoryIconProvider.getIcon(model.getCategoryElement()));
panel.setText(0, 1, (String) model.get(property));
final Element element = panel.getCellFormatter().getElement(0, 1);
element.getStyle().setPaddingTop(1, Unit.PX);
element.getStyle().setPaddingLeft(5, Unit.PX);
return panel;
}
});
}
// Visible columns.
final ColumnConfig[] columnConfigs = canEdit ?
new ColumnConfig[] {selectionModel.getColumn(), labelColumn} :
new ColumnConfig[] {labelColumn};
// Grid used as a list box.
final FlexibleGrid<QuestionChoiceElementDTO> multipleQuestion = new FlexibleGrid<QuestionChoiceElementDTO>(store, selectionModel, columnConfigs);
multipleQuestion.setAutoExpandColumn(LABEL);
multipleQuestion.setVisibleElementsCount(5);
final ContentPanel contentPanel = new ContentPanel();
contentPanel.setHeaderVisible(true);
contentPanel.setBorders(true);
contentPanel.setHeadingText(getLabel());
contentPanel.setTopComponent(null);
contentPanel.add(multipleQuestion);
// Selects the already selected choices.
if (valueResult != null && valueResult.isValueDefined()) {
final HashSet<Integer> selectedChoicesId = new HashSet<Integer>(ValueResultUtils.splitValuesAsInteger(valueResult.getValueObject()));
selectedChoices = new ArrayList<QuestionChoiceElementDTO>();
for (final QuestionChoiceElementDTO choiceDTO : getChoices()) {
if (selectedChoicesId.contains(choiceDTO.getId())) {
selectedChoices.add(choiceDTO);
}
}
selectionModel.select(selectedChoices, false);
}
component = contentPanel;
}
// If the component is a category and/or a quality criterion.
if (getCategoryType() != null) {
if (getQualityCriterion() != null) {
component.setToolTip(I18N.MESSAGES.flexibleElementQuestionCategory(getCategoryType().getLabel())
+ "<br/>"
+ I18N.MESSAGES.flexibleElementQuestionQuality(getQualityCriterion().getInfo()));
} else {
component.setToolTip(I18N.MESSAGES.flexibleElementQuestionCategory(getCategoryType().getLabel()));
}
} else if (getQualityCriterion() != null) {
component.setToolTip(I18N.MESSAGES.flexibleElementQuestionQuality(getQualityCriterion().getInfo()));
}
// Remove disabled options (not selected)
for (QuestionChoiceElementDTO choice : getChoices()) {
if (choice.isDisabled() && !selectedChoices.contains(choice)) {
store.remove(choice);
}
}
return component;
}
@Override
public boolean isCorrectRequiredValue(ValueResult result) {
if (result == null || !result.isValueDefined()) {
return false;
}
// Single selection case.
if (!Boolean.TRUE.equals(getMultiple())) {
try {
final String value = result.getValueObject();
return value != null && !"".equals(value);
} catch (ClassCastException e) {
return false;
}
}
// Multiple selection case.
else {
final List<Integer> selectedChoicesId = ValueResultUtils.splitValuesAsInteger(result.getValueObject());
return selectedChoicesId != null && selectedChoicesId.size() > 0;
}
}
/**
* Basic selection changes listener implementation to fire value changes events of the current flexible element.
*
* @author tmi
*/
private class ComboBoxSelectionListener extends SelectionChangedListener<QuestionChoiceElementDTO> {
@Override
public void selectionChanged(SelectionChangedEvent<QuestionChoiceElementDTO> se) {
String value = null;
final boolean isValueOn;
// Single selection case.
if (!Boolean.TRUE.equals(getMultiple())) {
// Gets the selected choice.
final QuestionChoiceElementDTO choice = se.getSelectedItem();
// Checks if the choice isn't the default empty choice.
isValueOn = choice != null && choice.getId() != null && choice.getId() != -1;
if (choice != null) {
value = String.valueOf(choice.getId());
}
}
// Multiple selection case.
else {
// Gets the selected choices.
final List<QuestionChoiceElementDTO> choices = se.getSelection();
isValueOn = choices != null && choices.size() != 0;
if (choices != null) {
value = ValueResultUtils.mergeValues(choices);
}
}
if (value != null) {
// Fires value change event.
handlerManager.fireEvent(new ValueEvent(QuestionElementDTO.this, value));
}
// Required element ?
if (getValidates()) {
handlerManager.fireEvent(new RequiredValueEvent(isValueOn));
}
}
}
private String toLabel(String value) {
QuestionChoiceElementDTO singleChoice;
try {
singleChoice = pickChoice(Integer.valueOf(value));
} catch (NumberFormatException e) {
GWT.log("Value is not an id: '" + value + "'.", e);
// Searching for the element.
singleChoice = pickChoice(value);
}
if (singleChoice != null) {
return singleChoice.getLabel();
} else {
return "";
}
}
private List<String> toLabels(String values) {
final ArrayList<String> labels = new ArrayList<String>();
try {
final List<Integer> selectedChoicesId = ValueResultUtils.splitValuesAsInteger(values);
for (final Integer id : selectedChoicesId) {
final QuestionChoiceElementDTO choice = pickChoice(id.intValue());
if (choice != null) {
labels.add(choice.getLabel());
}
}
} catch (NumberFormatException e) {
GWT.log("Values is not an array of ids:'" + values + "'.", e);
final String valuesAsString = (String) values;
final String[] labelArray = valuesAsString.trim().split("-");
for (final String label : labelArray) {
final String trimmedLabel = label.trim();
if (!trimmedLabel.isEmpty()) {
labels.add(trimmedLabel);
}
}
}
return labels;
}
/**
* {@inheritDoc}
*/
@Override
public Object renderHistoryToken(HistoryTokenListDTO token) {
// Single selection case.
if (!Boolean.TRUE.equals(getMultiple())) {
return new HistoryTokenText(toLabel(token.getTokens().get(0).getValue()));
}
// Multiple selection case.
else {
return new HistoryTokenText(toLabels(token.getTokens().get(0).getValue()));
}
}
@Override
public String toHTML(String value) {
if(value == null || value.length() == 0) {
return "";
}
final StringBuilder htmlBuilder = new StringBuilder();
// Single selection case.
if (!Boolean.TRUE.equals(getMultiple())) {
htmlBuilder.append(toLabel(value));
}
// Multiple selection case.
else {
for(final String entry : toLabels(value)) {
htmlBuilder.append(" -").append(entry).append("<br>");
}
}
return htmlBuilder.toString();
}
/**
* Select a choice among the list of the choices.
*
* @param id
* The wanted choice's id.
* @return The choice if it exists, <code>null</code> otherwise.
*/
private QuestionChoiceElementDTO pickChoice(int id) {
if (getChoices() != null) {
for (final QuestionChoiceElementDTO choice : getChoices()) {
if (choice.getId().equals(id)) {
return choice;
}
}
}
return null;
}
/**
* Select a choice among the list of the choices.
*
* @param label
* The wanted choice's label.
* @return The choice if it exists, <code>null</code> otherwise.
*/
private QuestionChoiceElementDTO pickChoice(final String label) {
if (getChoices() != null) {
for (final QuestionChoiceElementDTO choice : getChoices()) {
if (choice.getLabel().equals(label)) {
return choice;
}
}
}
return null;
}
}