/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.ims.qti21.ui.editor.interactions;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
import org.olat.core.gui.components.form.flexible.elements.RichTextElement;
import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.ims.qti21.QTI21Constants;
import org.olat.ims.qti21.model.QTI21QuestionType;
import org.olat.ims.qti21.model.xml.interactions.KPrimAssessmentItemBuilder;
import org.olat.ims.qti21.ui.editor.AssessmentTestEditorController;
import org.olat.ims.qti21.ui.editor.events.AssessmentItemEvent;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.MatchInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.choice.SimpleAssociableChoice;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
/**
* KPrim is 8 simple choice, but 2 choices are paired together.
*
* Initial date: 06.01.2016<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class KPrimEditorController extends FormBasicController {
private static final String[] yesnoKeys = new String[]{ "y", "n"};
private static final String[] alignmentKeys = new String[]{ "left", "right"};
private TextElement titleEl;
private RichTextElement textEl;
private SingleSelection shuffleEl, alignmentEl;
private FormLayoutContainer answersCont;
private final List<KprimWrapper> choiceWrappers = new ArrayList<>();
private int count = 0;
private final VFSContainer itemContainer;
private final boolean restrictedEdit;
private final KPrimAssessmentItemBuilder itemBuilder;
public KPrimEditorController(UserRequest ureq, WindowControl wControl, KPrimAssessmentItemBuilder itemBuilder,
File rootDirectory, VFSContainer rootContainer, File itemFile, boolean restrictedEdit) {
super(ureq, wControl, "simple_choices_editor");
setTranslator(Util.createPackageTranslator(AssessmentTestEditorController.class, getLocale()));
this.itemBuilder = itemBuilder;
this.restrictedEdit = restrictedEdit;
String relativePath = rootDirectory.toPath().relativize(itemFile.toPath().getParent()).toString();
itemContainer = (VFSContainer)rootContainer.resolve(relativePath);
initForm(ureq);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
FormLayoutContainer metadata = FormLayoutContainer.createDefaultFormLayout("metadata", getTranslator());
metadata.setFormContextHelp("Test editor QTI 2.1 in detail#details_testeditor_fragetypen_kprim");
metadata.setRootForm(mainForm);
formLayout.add(metadata);
formLayout.add("metadata", metadata);
titleEl = uifactory.addTextElement("title", "form.imd.title", -1, itemBuilder.getTitle(), metadata);
titleEl.setElementCssClass("o_sel_assessment_item_title");
titleEl.setMandatory(true);
String description = itemBuilder.getQuestion();
textEl = uifactory.addRichTextElementForQTI21("desc", "form.imd.descr", description, 8, -1, itemContainer,
metadata, ureq.getUserSession(), getWindowControl());
//shuffle
String[] yesnoValues = new String[]{ translate("yes"), translate("no") };
shuffleEl = uifactory.addRadiosHorizontal("shuffle", "form.imd.shuffle", metadata, yesnoKeys, yesnoValues);
shuffleEl.setEnabled(!restrictedEdit);
if (itemBuilder.isShuffle()) {
shuffleEl.select("y", true);
} else {
shuffleEl.select("n", true);
}
//layout
String[] alignmentValues = new String[]{ translate("form.imd.alignment.left"), translate("form.imd.alignment.right") };
alignmentEl = uifactory.addRadiosHorizontal("alignment", "form.imd.alignment", metadata, alignmentKeys, alignmentValues);
alignmentEl.setEnabled(!restrictedEdit);
if (itemBuilder.hasClassAttr(QTI21Constants.CHOICE_ALIGN_RIGHT)) {
alignmentEl.select(alignmentKeys[1], true);
} else {
alignmentEl.select(alignmentKeys[0], true);
}
//responses
String page = velocity_root + "/kprim_choices.html";
answersCont = FormLayoutContainer.createCustomFormLayout("answers", getTranslator(), page);
answersCont.setRootForm(mainForm);
formLayout.add(answersCont);
formLayout.add("answers", answersCont);
MatchInteraction interaction = itemBuilder.getMatchInteraction();
if(interaction != null) {
List<SimpleAssociableChoice> choices = itemBuilder.getKprimChoices();
for(SimpleAssociableChoice choice:choices) {
wrapAnswer(ureq, choice);
}
}
answersCont.contextPut("choices", choiceWrappers);
answersCont.contextPut("restrictedEdit", restrictedEdit);
recalculateUpDownLinks();
// Submit Button
FormLayoutContainer buttonsContainer = FormLayoutContainer.createDefaultFormLayout("buttons", getTranslator());
buttonsContainer.setElementCssClass("o_sel_choices_save");
buttonsContainer.setRootForm(mainForm);
formLayout.add(buttonsContainer);
formLayout.add("buttons", buttonsContainer);
uifactory.addFormSubmitButton("submit", buttonsContainer);
}
private void wrapAnswer(UserRequest ureq, SimpleAssociableChoice choice) {
String choiceContent = itemBuilder.getHtmlHelper().flowStaticString(choice.getFlowStatics());
String choiceId = "answer" + count++;
RichTextElement choiceEl = uifactory.addRichTextElementForQTI21(choiceId, "form.imd.answer", choiceContent, 8, -1, itemContainer,
answersCont, ureq.getUserSession(), getWindowControl());
choiceEl.setUserObject(choice);
answersCont.add("choiceId", choiceEl);
FormLink upLink = uifactory.addFormLink("up-".concat(choiceId), "up", "", null, answersCont, Link.NONTRANSLATED);
upLink.setIconLeftCSS("o_icon o_icon-lg o_icon_move_up");
upLink.setEnabled(!restrictedEdit);
answersCont.add(upLink);
answersCont.add("up-".concat(choiceId), upLink);
FormLink downLink = uifactory.addFormLink("down-".concat(choiceId), "down", "", null, answersCont, Link.NONTRANSLATED);
downLink.setIconLeftCSS("o_icon o_icon-lg o_icon_move_down");
downLink.setEnabled(!restrictedEdit);
answersCont.add(downLink);
answersCont.add("down-".concat(choiceId), downLink);
choiceWrappers.add(new KprimWrapper(choice, choiceEl, upLink, downLink));
}
@Override
protected void doDispose() {
//
}
@Override
protected void formOK(UserRequest ureq) {
//title
itemBuilder.setTitle(titleEl.getValue());
//question
String questionText = textEl.getRawValue();
itemBuilder.setQuestion(questionText);
//shuffle
if(!restrictedEdit) {
itemBuilder.setShuffle(shuffleEl.isOneSelected() && shuffleEl.isSelected(0));
//alignment
if(alignmentEl.isOneSelected() && alignmentEl.isSelected(1)) {
itemBuilder.addClass(QTI21Constants.CHOICE_ALIGN_RIGHT);
} else {
itemBuilder.removeClass(QTI21Constants.CHOICE_ALIGN_RIGHT);
}
}
//update kprims
for(KprimWrapper choiceWrapper:choiceWrappers) {
SimpleAssociableChoice choice = choiceWrapper.getSimpleChoice();
String answer = choiceWrapper.getAnswer().getRawValue();
itemBuilder.getHtmlHelper().appendHtml(choice, answer);
}
//set associations
if(!restrictedEdit) {
for(KprimWrapper choiceWrapper:choiceWrappers) {
SimpleAssociableChoice choice = choiceWrapper.getSimpleChoice();
Identifier choiceIdentifier = choice.getIdentifier();
String association = ureq.getHttpReq().getParameter(choiceIdentifier.toString());
if("correct".equals(association)) {
itemBuilder.setAssociation(choiceIdentifier, QTI21Constants.CORRECT_IDENTIFIER);
} else if("wrong".equals(association)) {
itemBuilder.setAssociation(choiceIdentifier, QTI21Constants.WRONG_IDENTIFIER);
}
}
}
fireEvent(ureq, new AssessmentItemEvent(AssessmentItemEvent.ASSESSMENT_ITEM_CHANGED, itemBuilder.getAssessmentItem(), QTI21QuestionType.kprim));
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(source instanceof FormLink) {
FormLink button = (FormLink)source;
String cmd = button.getCmd();
if("up".equals(cmd)) {
updateMatch(ureq);
doMoveSimpleChoiceUp((KprimWrapper)button.getUserObject());
} else if("down".equals(cmd)) {
updateMatch(ureq);
doMoveSimpleChoiceDown((KprimWrapper)button.getUserObject());
}
}
super.formInnerEvent(ureq, source, event);
}
private void updateMatch(UserRequest ureq) {
for(KprimWrapper choiceWrapper:choiceWrappers) {
SimpleAssociableChoice choice = choiceWrapper.getSimpleChoice();
Identifier choiceIdentifier = choice.getIdentifier();
String association = ureq.getHttpReq().getParameter(choiceIdentifier.toString());
if(StringHelper.containsNonWhitespace(association)) {
if("correct".equals(association)) {
itemBuilder.setAssociation(choiceIdentifier, QTI21Constants.CORRECT_IDENTIFIER);
} else if("wrong".equals(association)) {
itemBuilder.setAssociation(choiceIdentifier, QTI21Constants.WRONG_IDENTIFIER);
}
}
}
}
private void doMoveSimpleChoiceUp(KprimWrapper choiceWrapper) {
int index = choiceWrappers.indexOf(choiceWrapper) - 1;
if(index >= 0 && index < choiceWrappers.size()) {
choiceWrappers.remove(choiceWrapper);
choiceWrappers.add(index, choiceWrapper);
}
recalculateUpDownLinks();
flc.setDirty(true);
}
private void doMoveSimpleChoiceDown(KprimWrapper choiceWrapper) {
int index = choiceWrappers.indexOf(choiceWrapper) + 1;
if(index > 0 && index < choiceWrappers.size()) {
choiceWrappers.remove(choiceWrapper);
choiceWrappers.add(index, choiceWrapper);
}
recalculateUpDownLinks();
flc.setDirty(true);
}
private void recalculateUpDownLinks() {
int numOfChoices = choiceWrappers.size();
for(int i=0; i<numOfChoices; i++) {
KprimWrapper choiceWrapper = choiceWrappers.get(i);
choiceWrapper.getUp().setEnabled(i != 0 && !restrictedEdit);
choiceWrapper.getDown().setEnabled(i < (numOfChoices - 1) && !restrictedEdit);
}
}
public final class KprimWrapper {
private final SimpleAssociableChoice choice;
private final RichTextElement answerEl;
private final FormLink upLink, downLink;
private final Identifier choiceIdentifier;
public KprimWrapper(SimpleAssociableChoice choice, RichTextElement answerEl,
FormLink upLink, FormLink downLink) {
this.choice = choice;
this.choiceIdentifier = choice.getIdentifier();
this.answerEl = answerEl;
answerEl.setUserObject(this);
this.upLink = upLink;
upLink.setUserObject(this);
this.downLink = downLink;
downLink.setUserObject(this);
}
public Identifier getIdentifier() {
return choiceIdentifier;
}
public String getIdentifierString() {
return choiceIdentifier.toString();
}
public boolean isCorrect() {
return itemBuilder.isCorrect(choiceIdentifier);
}
public boolean isWrong() {
return itemBuilder.isWrong(choiceIdentifier);
}
public SimpleAssociableChoice getSimpleChoice() {
return choice;
}
public RichTextElement getAnswer() {
return answerEl;
}
public FormLink getUp() {
return upLink;
}
public FormLink getDown() {
return downLink;
}
}
}