tinymce-editor/copy_this/modules/hdi/hdi-tinymce/tinymce/classes/dom/ControlSelection.js
2013-07-25 23:50:43 +02:00

457 lines
12 KiB
JavaScript
Executable File

/**
* 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, resizeHandles, selectedHandle;
var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie;
// 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' +
'}'
);
function isResizable(elm) {
if (editor.settings.object_resizing === false) {
return false;
}
if (!/TABLE|IMG|DIV/.test(elm.nodeName)) {
return false;
}
if (elm.getAttribute('data-mce-resize') === 'false') {
return false;
}
return true;
}
function resizeGhostElement(e) {
var deltaX, deltaY;
// 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;
// Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
width = Math.round(height / ratio);
height = Math.round(width * ratio);
}
// Update ghost size
dom.setStyles(selectedElmGhost, {
width: width,
height: 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));
}
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 and update resize handle positions
dom.remove(selectedElmGhost);
if (!isIE || selectedElm.nodeName == "TABLE") {
showResizeRect(selectedElm);
}
editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
editor.nodeChanged();
}
function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
var position, targetWidth, targetHeight, e, rect;
// Get position and size of target
position = dom.getPos(targetElm, editor.getBody());
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) {
resizeStarted = true;
startX = e.screenX;
startY = e.screenY;
startW = selectedElm.clientWidth;
startH = selectedElm.clientHeight;
ratio = startH / startW;
selectedHandle = handle;
selectedElmGhost = selectedElm.cloneNode(true);
dom.addClass(selectedElmGhost, 'mce-clonedresizable');
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');
editor.getBody().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);
}
}
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 = editor.getBody();
handleElm = dom.add(handlerContainerElm, 'div', {
id: 'mceResizeHandle' + name,
'data-mce-bogus': true,
'class': 'mce-resizehandle',
contentEditable: false, // Hides IE move layer cursor
unSelectabe: true,
style: 'cursor:' + name + '-resize; margin:0; padding:0'
});
dom.bind(handleElm, 'mousedown', function(e) {
e.preventDefault();
startDrag(e);
});
} else {
dom.show(handleElm);
}
/*
var halfHandleW = handleElm.offsetWidth / 2;
var halfHandleH = handleElm.offsetHeight / 2;
// Position element
dom.setStyles(handleElm, {
left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)),
top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH))
});
*/
// 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;
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 controlElm;
function isChildOrEqual(node, parent) {
do {
if (node === parent) {
return true;
}
} while ((node = node.parentNode));
}
// 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.getParent(controlElm, isIE ? 'table' : 'table,img,hr');
if (controlElm) {
disableGeckoResize();
if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) {
if (!isIE || (controlElm != selection.getStart() && selection.getStart().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 = e.clientX - pos.left;
relativeY = e.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 (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) {
selectedHandle = corner;
break;
}
}
// Remove native selection and let the magic begin
resizeStarted = true;
editor.getDoc().selection.empty();
showResizeRect(target, name, e);
}
function nativeControlSelect(e) {
var target = e.srcElement;
if (target != selectedElm) {
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 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(editor.getBody(), 'controlselect', nativeControlSelect);
} else {
disableGeckoResize();
}
editor.on('nodechange mousedown ResizeEditor', updateResizeRect);
// Update resize rect while typing in a table
editor.on('keydown keyup', function(e) {
if (selectedElm && selectedElm.nodeName == "TABLE") {
updateResizeRect(e);
}
});
// Hide rect on focusout since it would float on top of windows otherwise
//editor.on('focusout', hideResizeRect);
});
function destroy() {
selectedElm = selectedElmGhost = null;
if (isIE) {
detachResizeStartListener();
detachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
}
}
return {
controlSelect: controlSelect,
destroy: destroy
};
};
});