From 37462c206fc59eb4d09863e86909befa7b62e30e Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Thu, 19 May 2016 16:39:08 +0200
Subject: [PATCH] OO-1593: add drag and drop to graphicGapMatchInteraction

---
 .../components/AssessmentObjectComponent.java |   1 -
 .../_content/graphicGapMatchInteraction.html  |   7 +
 .../_content/hotspotInteraction.html          |   4 +-
 .../static/js/jquery/qti/jquery.graphicGap.js | 153 +++++++++++++++++-
 4 files changed, 157 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java
index 2159fd98d80..9239dfd92f9 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java
+++ b/src/main/java/org/olat/ims/qti21/ui/components/AssessmentObjectComponent.java
@@ -169,7 +169,6 @@ public abstract class AssessmentObjectComponent extends AbstractComponent implem
 		
 		jsa.addRequiredStaticJsFile("js/jquery/maphilight/jquery.maphilight.js");
 		jsa.addRequiredStaticJsFile("js/jquery/ui/jquery-ui-1.11.4.custom.qti.min.js");
-		//jsa.addRequiredStaticJsFile("js/openolat/qti21.js");
 		
 		jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.associate.js");
 		jsa.addRequiredStaticJsFile("js/jquery/qti/jquery.graphicAssociate.js");
diff --git a/src/main/java/org/olat/ims/qti21/ui/components/_content/graphicGapMatchInteraction.html b/src/main/java/org/olat/ims/qti21/ui/components/_content/graphicGapMatchInteraction.html
index dbcd067c2e6..49e845ede84 100644
--- a/src/main/java/org/olat/ims/qti21/ui/components/_content/graphicGapMatchInteraction.html
+++ b/src/main/java/org/olat/ims/qti21/ui/components/_content/graphicGapMatchInteraction.html
@@ -31,7 +31,14 @@
 		</div>
 		<script type="text/javascript">
 		jQuery(function() {
+			var map = jQuery('#${containerId}_img').maphilight({
+				fillColor: '888888',
+				strokeColor: '0000ff',
+				strokeWidth: 3
+			});
+
 			jQuery('#${containerId}').graphicGapInteraction({
+				maphilight: map,
 				responseIdentifier: '$responseIdentifier',
 				formDispatchFieldId: '$r.formDispatchFieldId',
 				responseValue: '$r.toString($responseValue,","," ")',
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 891255777fc..589a58351b8 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
@@ -28,9 +28,7 @@
 				strokeColor: '0000ff',
 				strokeWidth: 3
 			});
-		});
-		
-		jQuery(function() {
+
 			jQuery('#${qtiContainerId}').hotspotInteraction({
 				responseIdentifier: '$responseIdentifier',
 				formDispatchFieldId: '$r.formDispatchFieldId',
diff --git a/src/main/webapp/static/js/jquery/qti/jquery.graphicGap.js b/src/main/webapp/static/js/jquery/qti/jquery.graphicGap.js
index 2f2bc9ab502..2b07b01a156 100644
--- a/src/main/webapp/static/js/jquery/qti/jquery.graphicGap.js
+++ b/src/main/webapp/static/js/jquery/qti/jquery.graphicGap.js
@@ -1,6 +1,7 @@
 (function ($) {
     $.fn.graphicGapInteraction = function(options) {
     	var settings = $.extend({
+    		maphilight: null,
     		responseIdentifier: null,
     		formDispatchFieldId: null,
     		responseValue: null,
@@ -59,11 +60,16 @@
     		var gapitem = jQuery(this);
     		
     		if(gapitem.hasClass('oo-choosed')) {
+    			//timestamp
+    			var dropTimestamp = gapitem.data('drop-timestamp');
+    			if(!(typeof dropTimestamp === "undefined") && dropTimestamp != null) {
+    				if((Date.now() - dropTimestamp) < 1500) {
+    					gapitem.data('drop-timestamp', null)
+    					return;
+    				}
+    			}
+
     			gapitem.removeClass('oo-choosed');
-    			//gapitem.css('position','relative');
-    			//gapitem.css('left','auto');
-    			//gapitem.css('top','auto');
-    			
     			var gapitemId = gapitem.attr('id');
     			//remove
     			jQuery('#' + containerId).find("input[type='hidden']").each(function(index, el) {
@@ -72,10 +78,84 @@
     					jQuery(el).remove();
     				}
     			});
+    			gapitem.css({'position':'relative', 'left': '0px', 'top': '0px' });
     		} else {
     			//gapitem.css('border','3px solid grey');
     			gapitem.addClass('oo-selected');
     		}
+    	}).draggable({
+    		containment: "#" + containerId,
+    		scroll: false,
+    		revert: "invalid",
+    		stop: function(event, ui) {
+    			if(!jQuery(this).hasClass('oo-choosed')) {
+        			console.log('stopped');
+    				jQuery(this).css({'position':'relative', 'left': '0px', 'top': '0px' });
+    			}
+    			console.log('stopped');
+    		}
+    	}).mousemove(function(event, ui) {
+    		var itemEl = jQuery(this);
+    		if(itemEl.hasClass("ui-draggable-dragging")) {
+    			var containerEl = jQuery("#" + containerId + "_img");
+    			var containerOffset = containerEl.offset();
+    			var offset = itemEl.offset(); 
+                var x1 = offset.left + (itemEl.width() / 2) - containerOffset.left;
+                var y1 = offset.top + (itemEl.height() / 2) - containerOffset.top;
+                var target = dropTarget(x1, y1, containerId);
+
+                if("invalid" != target) {
+                	jQuery("#" + containerId + " area").each(function(index, el) {
+                		var areaEl = jQuery(el);
+                		var areaId = areaEl.attr('id');
+                		if(target == areaId) {
+                			areaEl.mouseover();
+                		} else {
+                			areaEl.mouseout();
+                		}
+                	});
+                	jQuery('#' + target).mouseover();
+                } else {
+                	jQuery("#" + containerId + " area").each(function(index, el) {
+                		jQuery(el).mouseout();
+                	});
+                }
+    		}
+    	});
+    	
+    	jQuery("#" + containerId).droppable({
+    		drop: function(event, ui) {
+    			var containerEl = jQuery("#" + containerId + "_img");
+    			var containerOffset = containerEl.offset();
+    			
+				var gapitem = jQuery(ui.draggable);
+    			var offset = gapitem.offset();
+                var x1 = offset.left + (gapitem.width() / 2) - containerOffset.left;
+                var y1 = offset.top + (gapitem.height() / 2) - containerOffset.top;
+    			var target = dropTarget(x1, y1, containerId);
+    			if("invalid" != target) {
+    				var areaEl = jQuery('#' + target);
+    				var coords = toCoords(areaEl);
+        			var areaId = areaEl.data('qti-id');
+        			var gapitemId = gapitem.data('qti-id');
+        			
+        			gapitem.css('position','absolute');
+        			gapitem.css('left', coords[0] + 'px');
+        			gapitem.css('top', coords[1] + 'px');
+        		
+        			gapitem.css('border', 'none');
+        			gapitem.removeClass('oo-selected');
+        			gapitem.addClass('oo-choosed');
+        			
+        			//add
+        			var inputElement = jQuery('<input type="hidden"/>')
+        				.attr('name', 'qtiworks_response_' + settings.responseIdentifier)
+        				.attr('value', gapitemId + " " + areaId);
+        			jQuery(this).prepend(inputElement);
+        			jQuery(areaEl).mouseout();
+        			gapitem.data('drop-timestamp', Date.now());
+    			}
+    		}
     	});
     	
     	jQuery("#" + containerId + " area").on('click', function(e, el) {
@@ -104,6 +184,71 @@
     	});
     };
     
+    function dropTarget(dropX, dropY, containerId) {
+    	var target = 'invalid';
+    	jQuery("#" + containerId + " area").each(function(index, el) {
+    		var areaEl = jQuery(el);
+    		var areaId = areaEl.attr("id");
+    		var coords = areaEl.attr('coords');
+    		var shape = areaEl.attr('shape');
+    		
+    		var coordsArr = toCoords(areaEl);
+    		if("circle" == shape) {
+    			if(pncircle(dropX, dropY, coordsArr[0], coordsArr[1], coordsArr[2])) {
+    				target = areaId;
+    			}
+    		} else if("rect" == shape) {
+    			if(pnrect(dropX, dropY, coordsArr[0], coordsArr[1], coordsArr[2], coordsArr[3])) {
+    				target = areaId;
+    			}
+    		} else if("poly" == shape) {
+    			var area = {};
+    		    area.x = [];
+    		    area.y = [];
+    		    var totalPairs = coordsArr.length / 2;
+    		    var coordCounter = 0; //variable to double iterate
+    		    for (ix = 0; ix < totalPairs; ix++) { //fill arrays of x/y coordinates for pnpoly
+    		        area.x[ix] = coordsArr[coordCounter];
+    		        area.y[ix] = coordsArr[coordCounter + 1];
+    		        coordCounter += 2;
+    		    }
+    		    
+    		    for (i = 0; i < area.length; i++) { //iterate through all of our area objects
+    	    	    if (pnpoly(area.x.length, area.x, area.y, dropX, dropY)) {
+    	    	        target = area.id;
+    	    	        break;
+    	    	     }
+    	    	}
+    		}
+    	});
+    	return target;
+    }
+    
+    function pncircle(x, y, centerx, centery, radius) {
+        var c = Math.pow(x - centerx, 2) + Math.pow(y - centery, 2);
+        return Math.pow(radius, 2) >= c;
+    };
+    
+    function pnrect(x, y, leftx, topy, rightx, bottomy) {
+        if ((leftx <= x ) && ( x <= rightx) && (topy <= y) && (y <= bottomy)) {
+            return true;
+        } else {
+            return false;
+        }
+    };
+    
+	//Point in Poly Test http://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html
+    function pnpoly(nvert, vertx, verty, testx, testy) {
+    	var i, j, c = false;
+    	for (i = 0, j = nvert - 1; i < nvert; j = i++) {
+    	    if (((verty[i] > testy) != (verty[j] > testy)) &&
+    	        (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
+    	        c = !c;
+    	    }
+    	}
+    	return c;
+    }
+    
     function toCoords(area) {
     	var coords = area.attr('coords').split(',');
     	for (i=coords.length; i-->0; ) {
-- 
GitLab