src/pyams_skin/resources/js/ext/tinymce/dev/classes/dom/ControlSelection.js
changeset 69 a361355b55c7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/tinymce/dev/classes/dom/ControlSelection.js	Wed Jun 17 10:00:10 2015 +0200
@@ -0,0 +1,593 @@
+/**
+ * ControlSelection.js
+ *
+ * Copyright, Moxiecode Systems AB
+ * Released under LGPL License.
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * This class handles control selection of elements. Controls are elements
+ * that can be resized and needs to be selected as a whole. It adds custom resize handles
+ * to all browser engines that support properly disabling the built in resize logic.
+ *
+ * @class tinymce.dom.ControlSelection
+ */
+define("tinymce/dom/ControlSelection", [
+	"tinymce/util/VK",
+	"tinymce/util/Tools",
+	"tinymce/Env"
+], function(VK, Tools, Env) {
+	return function(selection, editor) {
+		var dom = editor.dom, each = Tools.each;
+		var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
+		var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
+		var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
+		var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;
+
+		// Details about each resize handle how to scale etc
+		resizeHandles = {
+			// Name: x multiplier, y multiplier, delta size x, delta size y
+			n: [0.5, 0, 0, -1],
+			e: [1, 0.5, 1, 0],
+			s: [0.5, 1, 0, 1],
+			w: [0, 0.5, -1, 0],
+			nw: [0, 0, -1, -1],
+			ne: [1, 0, 1, -1],
+			se: [1, 1, 1, 1],
+			sw: [0, 1, -1, 1]
+		};
+
+		// Add CSS for resize handles, cloned element and selected
+		var rootClass = '.mce-content-body';
+		editor.contentStyles.push(
+			rootClass + ' div.mce-resizehandle {' +
+				'position: absolute;' +
+				'border: 1px solid black;' +
+				'background: #FFF;' +
+				'width: 5px;' +
+				'height: 5px;' +
+				'z-index: 10000' +
+			'}' +
+			rootClass + ' .mce-resizehandle:hover {' +
+				'background: #000' +
+			'}' +
+			rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
+				'outline: 1px solid black;' +
+				'resize: none' + // Have been talks about implementing this in browsers
+			'}' +
+			rootClass + ' .mce-clonedresizable {' +
+				'position: absolute;' +
+				(Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
+				'opacity: .5;' +
+				'filter: alpha(opacity=50);' +
+				'z-index: 10000' +
+			'}' +
+			rootClass + ' .mce-resize-helper {' +
+				'background: #555;' +
+				'background: rgba(0,0,0,0.75);' +
+				'border-radius: 3px;' +
+				'border: 1px;' +
+				'color: white;' +
+				'display: none;' +
+				'font-family: sans-serif;' +
+				'font-size: 12px;' +
+				'white-space: nowrap;' +
+				'line-height: 14px;' +
+				'margin: 5px 10px;' +
+				'padding: 5px;' +
+				'position: absolute;' +
+				'z-index: 10001' +
+			'}'
+		);
+
+		function isResizable(elm) {
+			var selector = editor.settings.object_resizing;
+
+			if (selector === false || Env.iOS) {
+				return false;
+			}
+
+			if (typeof selector != 'string') {
+				selector = 'table,img,div';
+			}
+
+			if (elm.getAttribute('data-mce-resize') === 'false') {
+				return false;
+			}
+
+			return editor.dom.is(elm, selector);
+		}
+
+		function resizeGhostElement(e) {
+			var deltaX, deltaY, proportional;
+			var resizeHelperX, resizeHelperY;
+
+			// Calc new width/height
+			deltaX = e.screenX - startX;
+			deltaY = e.screenY - startY;
+
+			// Calc new size
+			width = deltaX * selectedHandle[2] + startW;
+			height = deltaY * selectedHandle[3] + startH;
+
+			// Never scale down lower than 5 pixels
+			width = width < 5 ? 5 : width;
+			height = height < 5 ? 5 : height;
+
+			if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
+				proportional = !VK.modifierPressed(e);
+			} else {
+				proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
+			}
+
+			// Constrain proportions
+			if (proportional) {
+				if (abs(deltaX) > abs(deltaY)) {
+					height = round(width * ratio);
+					width = round(height / ratio);
+				} else {
+					width = round(height / ratio);
+					height = round(width * ratio);
+				}
+			}
+
+			// Update ghost size
+			dom.setStyles(selectedElmGhost, {
+				width: width,
+				height: height
+			});
+
+			// Update resize helper position
+			resizeHelperX = selectedHandle.startPos.x + deltaX;
+			resizeHelperY = selectedHandle.startPos.y + deltaY;
+			resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
+			resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
+
+			dom.setStyles(resizeHelper, {
+				left: resizeHelperX,
+				top: resizeHelperY,
+				display: 'block'
+			});
+
+			resizeHelper.innerHTML = width + ' &times; ' + height;
+
+			// Update ghost X position if needed
+			if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
+				dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
+			}
+
+			// Update ghost Y position if needed
+			if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
+				dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
+			}
+
+			// Calculate how must overflow we got
+			deltaX = rootElement.scrollWidth - startScrollWidth;
+			deltaY = rootElement.scrollHeight - startScrollHeight;
+
+			// Re-position the resize helper based on the overflow
+			if (deltaX + deltaY !== 0) {
+				dom.setStyles(resizeHelper, {
+					left: resizeHelperX - deltaX,
+					top: resizeHelperY - deltaY
+				});
+			}
+
+			if (!resizeStarted) {
+				editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
+				resizeStarted = true;
+			}
+		}
+
+		function endGhostResize() {
+			resizeStarted = false;
+
+			function setSizeProp(name, value) {
+				if (value) {
+					// Resize by using style or attribute
+					if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
+						dom.setStyle(selectedElm, name, value);
+					} else {
+						dom.setAttrib(selectedElm, name, value);
+					}
+				}
+			}
+
+			// Set width/height properties
+			setSizeProp('width', width);
+			setSizeProp('height', height);
+
+			dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
+			dom.unbind(editableDoc, 'mouseup', endGhostResize);
+
+			if (rootDocument != editableDoc) {
+				dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
+				dom.unbind(rootDocument, 'mouseup', endGhostResize);
+			}
+
+			// Remove ghost/helper and update resize handle positions
+			dom.remove(selectedElmGhost);
+			dom.remove(resizeHelper);
+
+			if (!isIE || selectedElm.nodeName == "TABLE") {
+				showResizeRect(selectedElm);
+			}
+
+			editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
+			dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
+			editor.nodeChanged();
+		}
+
+		function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
+			var position, targetWidth, targetHeight, e, rect;
+
+			unbindResizeHandleEvents();
+
+			// Get position and size of target
+			position = dom.getPos(targetElm, rootElement);
+			selectedElmX = position.x;
+			selectedElmY = position.y;
+			rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
+			targetWidth = rect.width || (rect.right - rect.left);
+			targetHeight = rect.height || (rect.bottom - rect.top);
+
+			// Reset width/height if user selects a new image/table
+			if (selectedElm != targetElm) {
+				detachResizeStartListener();
+				selectedElm = targetElm;
+				width = height = 0;
+			}
+
+			// Makes it possible to disable resizing
+			e = editor.fire('ObjectSelected', {target: targetElm});
+
+			if (isResizable(targetElm) && !e.isDefaultPrevented()) {
+				each(resizeHandles, function(handle, name) {
+					var handleElm, handlerContainerElm;
+
+					function startDrag(e) {
+						startX = e.screenX;
+						startY = e.screenY;
+						startW = selectedElm.clientWidth;
+						startH = selectedElm.clientHeight;
+						ratio = startH / startW;
+						selectedHandle = handle;
+
+						handle.startPos = {
+							x: targetWidth * handle[0] + selectedElmX,
+							y: targetHeight * handle[1] + selectedElmY
+						};
+
+						startScrollWidth = rootElement.scrollWidth;
+						startScrollHeight = rootElement.scrollHeight;
+
+						selectedElmGhost = selectedElm.cloneNode(true);
+						dom.addClass(selectedElmGhost, 'mce-clonedresizable');
+						dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
+						selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
+						selectedElmGhost.unSelectabe = true;
+						dom.setStyles(selectedElmGhost, {
+							left: selectedElmX,
+							top: selectedElmY,
+							margin: 0
+						});
+
+						selectedElmGhost.removeAttribute('data-mce-selected');
+						rootElement.appendChild(selectedElmGhost);
+
+						dom.bind(editableDoc, 'mousemove', resizeGhostElement);
+						dom.bind(editableDoc, 'mouseup', endGhostResize);
+
+						if (rootDocument != editableDoc) {
+							dom.bind(rootDocument, 'mousemove', resizeGhostElement);
+							dom.bind(rootDocument, 'mouseup', endGhostResize);
+						}
+
+						resizeHelper = dom.add(rootElement, 'div', {
+							'class': 'mce-resize-helper',
+							'data-mce-bogus': 'all'
+						}, startW + ' &times; ' + startH);
+					}
+
+					if (mouseDownHandleName) {
+						// Drag started by IE native resizestart
+						if (name == mouseDownHandleName) {
+							startDrag(mouseDownEvent);
+						}
+
+						return;
+					}
+
+					// Get existing or render resize handle
+					handleElm = dom.get('mceResizeHandle' + name);
+					if (!handleElm) {
+						handlerContainerElm = rootElement;
+
+						handleElm = dom.add(handlerContainerElm, 'div', {
+							id: 'mceResizeHandle' + name,
+							'data-mce-bogus': 'all',
+							'class': 'mce-resizehandle',
+							unselectable: true,
+							style: 'cursor:' + name + '-resize; margin:0; padding:0'
+						});
+
+						// Hides IE move layer cursor
+						// If we set it on Chrome we get this wounderful bug: #6725
+						if (Env.ie) {
+							handleElm.contentEditable = false;
+						}
+					} else {
+						dom.show(handleElm);
+					}
+
+					if (!handle.elm) {
+						dom.bind(handleElm, 'mousedown', function(e) {
+							e.stopImmediatePropagation();
+							e.preventDefault();
+							startDrag(e);
+						});
+
+						handle.elm = handleElm;
+					}
+
+					// Position element
+					dom.setStyles(handleElm, {
+						left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
+						top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
+					});
+				});
+			} else {
+				hideResizeRect();
+			}
+
+			selectedElm.setAttribute('data-mce-selected', '1');
+		}
+
+		function hideResizeRect() {
+			var name, handleElm;
+
+			unbindResizeHandleEvents();
+
+			if (selectedElm) {
+				selectedElm.removeAttribute('data-mce-selected');
+			}
+
+			for (name in resizeHandles) {
+				handleElm = dom.get('mceResizeHandle' + name);
+				if (handleElm) {
+					dom.unbind(handleElm);
+					dom.remove(handleElm);
+				}
+			}
+		}
+
+		function updateResizeRect(e) {
+			var startElm, controlElm;
+
+			function isChildOrEqual(node, parent) {
+				if (node) {
+					do {
+						if (node === parent) {
+							return true;
+						}
+					} while ((node = node.parentNode));
+				}
+			}
+
+			// Ignore all events while resizing
+			if (resizeStarted) {
+				return;
+			}
+
+			// Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
+			each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
+				img.removeAttribute('data-mce-selected');
+			});
+
+			controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
+			controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
+
+			if (isChildOrEqual(controlElm, rootElement)) {
+				disableGeckoResize();
+				startElm = selection.getStart(true);
+
+				if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
+					if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) {
+						showResizeRect(controlElm);
+						return;
+					}
+				}
+			}
+
+			hideResizeRect();
+		}
+
+		function attachEvent(elm, name, func) {
+			if (elm && elm.attachEvent) {
+				elm.attachEvent('on' + name, func);
+			}
+		}
+
+		function detachEvent(elm, name, func) {
+			if (elm && elm.detachEvent) {
+				elm.detachEvent('on' + name, func);
+			}
+		}
+
+		function resizeNativeStart(e) {
+			var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
+
+			pos = target.getBoundingClientRect();
+			relativeX = lastMouseDownEvent.clientX - pos.left;
+			relativeY = lastMouseDownEvent.clientY - pos.top;
+
+			// Figure out what corner we are draging on
+			for (name in resizeHandles) {
+				corner = resizeHandles[name];
+
+				cornerX = target.offsetWidth * corner[0];
+				cornerY = target.offsetHeight * corner[1];
+
+				if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
+					selectedHandle = corner;
+					break;
+				}
+			}
+
+			// Remove native selection and let the magic begin
+			resizeStarted = true;
+			editor.fire('ObjectResizeStart', {
+				target: selectedElm,
+				width: selectedElm.clientWidth,
+				height: selectedElm.clientHeight
+			});
+			editor.getDoc().selection.empty();
+			showResizeRect(target, name, lastMouseDownEvent);
+		}
+
+		function nativeControlSelect(e) {
+			var target = e.srcElement;
+
+			if (target != selectedElm) {
+				editor.fire('ObjectSelected', {target: target});
+				detachResizeStartListener();
+
+				if (target.id.indexOf('mceResizeHandle') === 0) {
+					e.returnValue = false;
+					return;
+				}
+
+				if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
+					hideResizeRect();
+					selectedElm = target;
+					attachEvent(target, 'resizestart', resizeNativeStart);
+				}
+			}
+		}
+
+		function detachResizeStartListener() {
+			detachEvent(selectedElm, 'resizestart', resizeNativeStart);
+		}
+
+		function unbindResizeHandleEvents() {
+			for (var name in resizeHandles) {
+				var handle = resizeHandles[name];
+
+				if (handle.elm) {
+					dom.unbind(handle.elm);
+					delete handle.elm;
+				}
+			}
+		}
+
+		function disableGeckoResize() {
+			try {
+				// Disable object resizing on Gecko
+				editor.getDoc().execCommand('enableObjectResizing', false, false);
+			} catch (ex) {
+				// Ignore
+			}
+		}
+
+		function controlSelect(elm) {
+			var ctrlRng;
+
+			if (!isIE) {
+				return;
+			}
+
+			ctrlRng = editableDoc.body.createControlRange();
+
+			try {
+				ctrlRng.addElement(elm);
+				ctrlRng.select();
+				return true;
+			} catch (ex) {
+				// Ignore since the element can't be control selected for example a P tag
+			}
+		}
+
+		editor.on('init', function() {
+			if (isIE) {
+				// Hide the resize rect on resize and reselect the image
+				editor.on('ObjectResized', function(e) {
+					if (e.target.nodeName != 'TABLE') {
+						hideResizeRect();
+						controlSelect(e.target);
+					}
+				});
+
+				attachEvent(rootElement, 'controlselect', nativeControlSelect);
+
+				editor.on('mousedown', function(e) {
+					lastMouseDownEvent = e;
+				});
+			} else {
+				disableGeckoResize();
+
+				if (Env.ie >= 11) {
+					// TODO: Drag/drop doesn't work
+					editor.on('mouseup', function(e) {
+						var nodeName = e.target.nodeName;
+
+						if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) {
+							editor.selection.select(e.target, nodeName == 'TABLE');
+							editor.nodeChanged();
+						}
+					});
+
+					editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
+						if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
+							e.preventDefault();
+
+							// This moves the selection from being a control selection to a text like selection like in WebKit #6753
+							// TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
+							if (e.target.tagName == 'IMG') {
+								window.setTimeout(function() {
+									editor.selection.select(e.target);
+								}, 0);
+							}
+						}
+					});
+				}
+			}
+
+			editor.on('nodechange ResizeEditor', updateResizeRect);
+
+			// Update resize rect while typing in a table
+			editor.on('keydown keyup', function(e) {
+				if (selectedElm && selectedElm.nodeName == "TABLE") {
+					updateResizeRect(e);
+				}
+			});
+
+			editor.on('hide', hideResizeRect);
+
+			// Hide rect on focusout since it would float on top of windows otherwise
+			//editor.on('focusout', hideResizeRect);
+		});
+
+		editor.on('remove', unbindResizeHandleEvents);
+
+		function destroy() {
+			selectedElm = selectedElmGhost = null;
+
+			if (isIE) {
+				detachResizeStartListener();
+				detachEvent(rootElement, 'controlselect', nativeControlSelect);
+			}
+		}
+
+		return {
+			isResizable: isResizable,
+			showResizeRect: showResizeRect,
+			hideResizeRect: hideResizeRect,
+			updateResizeRect: updateResizeRect,
+			controlSelect: controlSelect,
+			destroy: destroy
+		};
+	};
+});