diff --git a/src/main/java/org/olat/ims/qti21/QTI21Constants.java b/src/main/java/org/olat/ims/qti21/QTI21Constants.java index e73a308df2438f6af62a7a16446ae33b9f1c7e34..1faeade286774a3f0ce3dca63be88ed0d4108cc5 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21Constants.java +++ b/src/main/java/org/olat/ims/qti21/QTI21Constants.java @@ -146,6 +146,25 @@ public class QTI21Constants { public static final String CSS_MATCH_SOURCE_BOTTOM = "source-bottom"; - + public static final String CSS_HOTSPOT_DISABLE_SHADOW = "hotspot-noshadow"; + + public enum HotspotLayouts { + + standard(""), + light("hotspot-light"), + inverted("hotspot-inverted"), + green("hotspot-green"), + purple("hotspot-purple"); + + private final String cssClass; + + private HotspotLayouts(String cssClass) { + this.cssClass = cssClass; + } + + public String cssClass() { + return cssClass; + } + } -} +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/HotspotAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/HotspotAssessmentItemBuilder.java index f655816d9a4437efcdc317d4cdaafe6093e09193..84882de3c548b9e67482307961415c0f53bd3a86 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/HotspotAssessmentItemBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/HotspotAssessmentItemBuilder.java @@ -29,12 +29,14 @@ import static org.olat.ims.qti21.model.xml.QtiNodesExtractor.extractIdentifiersF import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.transform.stream.StreamResult; import org.olat.core.gui.render.StringOutput; +import org.olat.core.util.StringHelper; import org.olat.ims.qti21.QTI21Constants; import org.olat.ims.qti21.model.IdentifierGenerator; import org.olat.ims.qti21.model.QTI21QuestionType; @@ -223,6 +225,32 @@ public class HotspotAssessmentItemBuilder extends AssessmentItemBuilder { } } + public boolean hasHotspotInteractionClass(String cssClass) { + List<String> cssClassses = hotspotInteraction.getClassAttr(); + return cssClassses != null && cssClassses.contains(cssClass); + } + + public void addHotspotInteractionClass(String cssClass) { + if(!StringHelper.containsNonWhitespace(cssClass)) return; + + List<String> cssClassses = hotspotInteraction.getClassAttr(); + cssClassses = cssClassses == null ? new ArrayList<>() : new ArrayList<>(cssClassses); + cssClassses.add(cssClass); + hotspotInteraction.setClassAttr(cssClassses); + } + + public void removeHotspotInteractionClass(String cssClass) { + if(cssClass == null || hotspotInteraction.getClassAttr() == null) return; + + List<String> cssClassList = new ArrayList<>(hotspotInteraction.getClassAttr()); + for(Iterator<String> cssClassIt= cssClassList.iterator(); cssClassIt.hasNext(); ) { + if(cssClass.equals(cssClassIt.next())) { + cssClassIt.remove(); + } + } + hotspotInteraction.setClassAttr(cssClassList); + } + public boolean isCorrect(HotspotChoice choice) { if(correctAnswers == null) { correctAnswers = new ArrayList<>(); diff --git a/src/main/java/org/olat/ims/qti21/ui/components/_content/hotspotInteraction.html b/src/main/java/org/olat/ims/qti21/ui/components/_content/hotspotInteraction.html index 2de67d60f4ef2e31df3f089bde67280fc2818a3f..f339795d842dafdec2d631e1e48ddc6c703ae34e 100644 --- a/src/main/java/org/olat/ims/qti21/ui/components/_content/hotspotInteraction.html +++ b/src/main/java/org/olat/ims/qti21/ui/components/_content/hotspotInteraction.html @@ -24,15 +24,83 @@ </map> </div> <script type="text/javascript"> - jQuery(function() { - jQuery('#${qtiContainerId}_img').maphilight({ + /* <![CDATA[ */ + var maphilightSettings; + #if($r.hasCssClass($interaction, "hotspot-light")) + maphilightSettings = { + fillColor: 'dddddd', + fillOpacity: 0, + strokeColor: '7E7E7E', + strokeOpacity: 1.0, + + selectedFillColor: '337ab7', + selectedFillOpacity: 0.05, + selectedStrokeColor: '337ab7', + selectedStrokeOpacity: 1.0 + }; + #elseif($r.hasCssClass($interaction, "hotspot-inverted")) + maphilightSettings = { + fillColor: '6E6E6E',//'bbbbbb', + fillOpacity: 0.5,// 0.5, + strokeColor: '3E3E3E', //'666666', + strokeOpacity: 1.0,//0.8, + + selectedFillColor: 'DEDEDE', + selectedFillOpacity: 0.2, + selectedStrokeColor: 'CECECE', + selectedStrokeOpacity: 1 + }; + #elseif($r.hasCssClass($interaction, "hotspot-green")) + maphilightSettings = { + fillColor: '8E8E8E', + fillOpacity: 0.25, + strokeColor: 'CECECE',//'fd98c9', + strokeOpacity: 0.8, + + selectedFillColor: '86c351', + selectedFillOpacity: 0.5, + selectedStrokeColor: '518b33', + selectedStrokeOpacity: 1.0 + }; + #elseif($r.hasCssClass($interaction, "hotspot-purple")) + maphilightSettings = { + fillColor: '8E8E8E', + fillOpacity: 0.33, + strokeColor: 'CECECE',//'fd98c9', + strokeOpacity: 1.0, + + selectedFillColor: 'eaa8ff', + selectedFillOpacity: 0.5, + selectedStrokeColor: 'ab47cb', + selectedStrokeOpacity: 1.0 + }; + #else + maphilightSettings = { fillColor: 'bbbbbb', - fillOpacity: 0.5, - strokeColor: '666666', - strokeOpacity: 0.8, - strokeWidth: 3, - alwaysOn: true - }); + fillOpacity: 0.5, + strokeColor: '6E6E6E', + strokeOpacity: 1.0, + + selectedFillColor: '0000ff', + selectedFillOpacity: 0.5, + selectedStrokeColor: '0000ff', + selectedStrokeOpacity: 1.0 + }; + #end + + #if($r.hasCssClass($interaction, "hotspot-noshadow")) + maphilightSettings.selectedShadow = false; + #else + maphilightSettings.selectedShadow = true; + #end + + jQuery(function() { + maphilightSettings.strokeWidth = 3; + maphilightSettings.alwaysOn = true; + + + + jQuery('#${qtiContainerId}_img').maphilight(maphilightSettings); jQuery('#${qtiContainerId}').hotspotInteraction({ responseIdentifier: '$responseIdentifier', @@ -40,8 +108,10 @@ maxChoices: $interaction.maxChoices, singleChoice: $singleChoice, responseValue: '$r.toString($responseValue,",")', - opened: $isItemSessionOpen + opened: $isItemSessionOpen, + maphilightSettings: maphilightSettings }); }); + /* ]]> */ </script> </div> diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties index de9b3239486efe23da3af871bbde82e2257439d1..8a29c3b8b3fa6eae9b5cdd24bb350bdb5977fadc 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties @@ -138,6 +138,13 @@ item.session.control.attempts.hint=Diese Einschr\u00E4nkung der L\u00F6sungsvers item.session.control.show.solution=L\u00F6sung anzeigen item.session.control.show.solution.hint=Beim R\u00FCckblick werden auch L\u00F6sungen angezeigt. hour.short=h +hotspot.layout=Darstellung +hotspot.layout.standard=Standard blau +hotspot.layout.inverted=Invertiert +hotspot.layout.light=Hell +hotspot.layout.green=Gr\u00FCn +hotspot.layout.purple=Violett +hotspot.layout.shadow=Shatten wenn ausgewählt minute.short=m MULTIPLE=Multiple choice max.score=Maximal erreichbare Punktzahl diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties index 7818af20a25338a62b96d8cdca861c2424b8188f..eac163f77bc4bb03264578493e15ba3fc920129d 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties @@ -126,6 +126,13 @@ form.testPart.navigationMode.linear=Linear form.testPart.navigationMode.nonlinear=Non linear form.unkown=Unkown form.upload=File upload +hotspot.layout=Layout +hotspot.layout.standard=Standard blue +hotspot.layout.inverted=Inverted +hotspot.layout.light=Hell +hotspot.layout.green=Green +hotspot.layout.purple=Purple +hotspot.layout.shadow=Shadow if selected hour.short=h minute.short=m inherit=Inherit diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotEditorController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotEditorController.java index f3d9a0b277905eb03bb73f023cbbfab057061d4e..262411dc28ea917bb100b6184ddd1925b0c47730 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotEditorController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/HotspotEditorController.java @@ -58,6 +58,8 @@ import org.olat.core.util.ValidationStatus; import org.olat.core.util.WebappHelper; import org.olat.core.util.vfs.LocalFileImpl; import org.olat.core.util.vfs.VFSContainer; +import org.olat.ims.qti21.QTI21Constants; +import org.olat.ims.qti21.QTI21Constants.HotspotLayouts; import org.olat.ims.qti21.model.IdentifierGenerator; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.xml.interactions.HotspotAssessmentItemBuilder; @@ -78,6 +80,7 @@ import uk.ac.ed.ph.jqtiplus.value.Cardinality; */ public class HotspotEditorController extends FormBasicController { + private static final String[] onKeys = new String[] { "on" }; private static final Set<String> mimeTypes = new HashSet<>(); static { mimeTypes.add("image/gif"); @@ -93,6 +96,9 @@ public class HotspotEditorController extends FormBasicController { private FormLayoutContainer hotspotsCont; private FormLink newCircleButton, newRectButton; private MultipleSelectionElement correctHotspotsEl; + private SingleSelection layoutEl; + private MultipleSelectionElement shadowEl; + private final boolean restrictedEdit; private final HotspotAssessmentItemBuilder itemBuilder; @@ -185,6 +191,31 @@ public class HotspotEditorController extends FormBasicController { correctHotspotsEl.setEnabled(!restrictedEdit); correctHotspotsEl.addActionListener(FormEvent.ONCHANGE); rebuildWrappersAndCorrectSelection(); + + HotspotLayouts[] layouts = HotspotLayouts.values(); + String[] layoutKeys = new String[layouts.length]; + String[] layoutValues = new String[layouts.length]; + for(int i=layouts.length; i-->0; ) { + layoutKeys[i] = layouts[i].cssClass(); + layoutValues[i] = translate("hotspot.layout." + layouts[i].name()); + } + layoutEl = uifactory.addDropdownSingleselect("hotspot.layout", "hotspot.layout", formLayout, layoutKeys, layoutValues, null); + boolean found = false; + for(int i=layoutKeys.length; i-->0; ) { + if(itemBuilder.hasHotspotInteractionClass(layoutKeys[i])) { + layoutEl.select(layoutKeys[i], true); + found = true; + } + } + if(!found) { + layoutEl.select(layoutKeys[0], true); + } + + shadowEl = uifactory.addCheckboxesHorizontal("hotspot.layout.shadow", "hotspot.layout.shadow", formLayout, + onKeys, new String[] { "" }); + if(!itemBuilder.hasHotspotInteractionClass(QTI21Constants.CSS_HOTSPOT_DISABLE_SHADOW)) { + shadowEl.select(onKeys[0], true); + } // Submit Button FormLayoutContainer buttonsContainer = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); @@ -424,6 +455,20 @@ public class HotspotEditorController extends FormBasicController { } updateHotspots(ureq); + if(layoutEl.isOneSelected()) { + String selectedLayout = layoutEl.getSelectedKey(); + for(HotspotLayouts layout:HotspotLayouts.values()) { + itemBuilder.removeHotspotInteractionClass(layout.cssClass()); + } + itemBuilder.addHotspotInteractionClass(selectedLayout); + } + + if(shadowEl.isAtLeastSelected(1)) { + itemBuilder.removeHotspotInteractionClass(QTI21Constants.CSS_HOTSPOT_DISABLE_SHADOW); + } else { + itemBuilder.addHotspotInteractionClass(QTI21Constants.CSS_HOTSPOT_DISABLE_SHADOW); + } + fireEvent(ureq, new AssessmentItemEvent(AssessmentItemEvent.ASSESSMENT_ITEM_CHANGED, itemBuilder.getAssessmentItem(), QTI21QuestionType.hotspot)); } diff --git a/src/main/webapp/static/js/jquery/qti/jquery.hotspot.js b/src/main/webapp/static/js/jquery/qti/jquery.hotspot.js index d7737fc0fb93b82e02a32f1424e45ed952492cdb..ac219694537d9f6cc285b5f1fe55bb8d33d24dcb 100644 --- a/src/main/webapp/static/js/jquery/qti/jquery.hotspot.js +++ b/src/main/webapp/static/js/jquery/qti/jquery.hotspot.js @@ -6,7 +6,8 @@ maxChoices: 1, singleChoice: false, responseValue: null, - opened: false + opened: false, + maphilightSettings: {} }, options ); try { @@ -31,7 +32,7 @@ var areaEl = jQuery('#ac_' + settings.responseIdentifier + '_' + areaIds[i]); var data = areaEl.data('maphilight') || {}; data.selectedOn = true; - colorData(data); + colorData(data, settings.maphilightSettings); areaEl.data('maphilight', data).trigger('alwaysOn.maphilight'); var inputElement = jQuery('<input type="hidden"/>') @@ -45,12 +46,12 @@ var containerId = $obj.attr('id'); jQuery('#' + containerId + " map area").each(function(index, el) { jQuery(el).on('click', function() { - clickHotspotArea(this, containerId, settings.responseIdentifier, settings.maxChoices, settings.singleChoice); + clickHotspotArea(this, containerId, settings.responseIdentifier, settings.maxChoices, settings.singleChoice, settings.maphilightSettings); }); }) }; - function clickHotspotArea(spot, containerId, responseIdentifier, maxChoices, singleChoice) { + function clickHotspotArea(spot, containerId, responseIdentifier, maxChoices, singleChoice, maphilightSettings) { var areaEl = jQuery(spot); var data = areaEl.data('maphilight') || {}; if((typeof data.selectedOn === "undefined") || !data.selectedOn) { @@ -59,7 +60,7 @@ var cData = jQuery(el).data('maphilight') || {}; if(cData.selectedOn) { cData.selectedOn = false; - colorData(cData); + colorData(cData, maphilightSettings); jQuery(el).data('maphilight', cData).trigger('alwaysOn.maphilight'); } }); @@ -85,7 +86,7 @@ } else { data.selectedOn = !data.selectedOn; } - colorData(data); + colorData(data, maphilightSettings); areaEl.data('maphilight', data).trigger('alwaysOn.maphilight'); var divContainer = jQuery('#' + containerId); @@ -101,17 +102,17 @@ } }); }; - + /* * Color the data based on the selectedOn flag */ - function colorData(data) { + function colorData(data, maphilightSettings) { if(data.selectedOn) { - data.fillColor = '0000ff'; - data.fillOpacity = 0.5; - data.strokeColor = '0000ff'; - data.strokeOpacity = 1; - data.shadow = true; + data.fillColor = maphilightSettings.selectedFillColor; + data.fillOpacity = maphilightSettings.selectedFillOpacity; + data.strokeColor = maphilightSettings.selectedStrokeColor; + data.strokeOpacity = maphilightSettings.selectedStrokeOpacity; + data.shadow = maphilightSettings.selectedShadow; data.shadowX = 0; data.shadowY = 0; data.shadowRadius = 7; @@ -119,10 +120,10 @@ data.shadowOpacity = 0.8; data.shadowPosition = 'outside'; } else { - data.fillColor = 'bbbbbb'; - data.fillOpacity = 0.5; - data.strokeColor = '666666'; - data.strokeOpacity = 0.8; + data.fillColor = maphilightSettings.fillColor; + data.fillOpacity = maphilightSettings.fillOpacity; + data.strokeColor = maphilightSettings.strokeColor; + data.strokeOpacity = maphilightSettings.strokeOpacity; data.shadow = false; } }