1 /*
  2  * CKEditor - The text editor for Internet - http://ckeditor.com
  3  * Copyright (C) 2003-2008 Frederico Caldeira Knabben
  4  *
  5  * == BEGIN LICENSE ==
  6  *
  7  * Licensed under the terms of any of the following licenses at your
  8  * choice:
  9  *
 10  *  - GNU General Public License Version 2 or later (the "GPL")
 11  *    http://www.gnu.org/licenses/gpl.html
 12  *
 13  *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 14  *    http://www.gnu.org/licenses/lgpl.html
 15  *
 16  *  - Mozilla Public License Version 1.1 or later (the "MPL")
 17  *    http://www.mozilla.org/MPL/MPL-1.1.html
 18  *
 19  * == END LICENSE ==
 20  */
 21
 22 (function()
 23 {
 24 	// #### checkSelectionChange : START
 25
 26 	// The selection change check basically saves the element parent tree of
 27 	// the current node and check it on successive requests. If there is any
 28 	// change on the tree, then the selectionChange event gets fired.
 29 	var checkSelectionPreviousPath;
 30 	var checkSelectionChange = function()
 31 	{
 32 		// In IE, the "selectionchange" event may still get thrown when
 33 		// releasing the WYSIWYG mode, so we need to check it first.
 34 		var sel = this.getSelection();
 35 		if ( !sel )
 36 			return;
 37
 38 		// Get the element at the start of the selection.
 39 		var node = this.getSelection().getStartElement(),
 40 			changed,
 41 			currentPath = [],
 42 			counter = 0;
 43
 44 		// Loops through the parent tree of the main node.
 45 		while( node )
 46 		{
 47 			// Look for changes in the parent node tree.
 48 			if ( !changed && ( !checkSelectionPreviousPath || !node.equals( checkSelectionPreviousPath[ counter++ ] ) ) )
 49 				changed = true;
 50
 51 			currentPath.push( node );
 52 			node = node.getParent();
 53 		}
 54
 55 		checkSelectionPreviousPath = currentPath;
 56
 57 		if ( changed )
 58 			this.fire( 'selectionChange' );
 59 	};
 60
 61 	var checkSelectionChangeTimer;
 62 	var checkSelectionChangeTimeout = function()
 63 	{
 64 		if ( checkSelectionChangeTimer )
 65 			clearTimeout( checkSelectionChangeTimer );
 66
 67 		checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChange, 100, this );
 68 	};
 69
 70 	// #### checkSelectionChange : END
 71
 72 	CKEDITOR.plugins.add( 'selection',
 73 	{
 74 		init : function( editor, pluginPath )
 75 		{
 76 			editor.on( 'contentDom', function()
 77 				{
 78 					if ( CKEDITOR.env.ie )
 79 					{
 80 						// IE is the only to provide the "selectionchange"
 81 						// event.
 82 						editor.document.on( 'selectionchange', checkSelectionChange, editor );
 83 					}
 84 					else
 85 					{
 86 						// In other browsers, we make the selection change
 87 						// check based on other events, like clicks or keys
 88 						// press.
 89
 90 						editor.document.on( 'mouseup', checkSelectionChange, editor );
 91
 92 						// Firing the "OnSelectionChange" event on every key
 93 						// press started to be too slow. So, a timer has been
 94 						// implemented to solve performance issues when typing
 95 						// to quickly.
 96 						editor.document.on( 'keyup', checkSelectionChangeTimeout, editor );
 97 					}
 98 				});
 99 		}
100 	});
101 })();
102
103 /**
104  * Gets the current selection from the editing area when in WYSIWYG mode.
105  * @returns {CKEDITOR.dom.selection} A selection object or null if not on
106  *		WYSIWYG mode or no selection is available.
107  * @example
108  * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>;
109  * alert( selection.getType() );
110  */
111 CKEDITOR.editor.prototype.getSelection = function()
112 {
113 	return this.document ? this.document.getSelection() : null;
114 };
115
116 /**
117  * Gets the current selection from the document.
118  * @returns {CKEDITOR.dom.selection} A selection object.
119  * @example
120  * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>;
121  * alert( selection.getType() );
122  */
123 CKEDITOR.dom.document.prototype.getSelection = function()
124 {
125 	return new CKEDITOR.dom.selection( this );
126 };
127
128 /**
129  * No selection.
130  * @constant
131  * @example
132  * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
133  *     alert( 'Nothing is selected' );
134  */
135 CKEDITOR.SELECTION_NONE		= 1;
136
137 /**
138  * Text or collapsed selection.
139  * @constant
140  * @example
141  * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
142  *     alert( 'Text is selected' );
143  */
144 CKEDITOR.SELECTION_TEXT		= 2;
145
146 /**
147  * Element selection.
148  * @constant
149  * @example
150  * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
151  *     alert( 'An element is selected' );
152  */
153 CKEDITOR.SELECTION_ELEMENT	= 3;
154
155 /**
156  * Manipulates the selection in a DOM document.
157  * @constructor
158  * @example
159  */
160 CKEDITOR.dom.selection = function( document )
161 {
162 	this.document = document;
163 };
164
165 (function()
166 {
167 	var styleObjectElements = { img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1 };
168
169 	CKEDITOR.dom.selection.prototype =
170 	{
171 		/**
172 		 * Gets the native selection object from the browser.
173 		 * @returns {Object} The native selection object.
174 		 * @example
175 		 * var selection = editor.getSelection().<b>getNative()</b>;
176 		 */
177 		getNative : (function()
178 		{
179 			if ( CKEDITOR.env.ie )
180 				return function()
181 					{
182 						return this.document.$.selection;
183 					};
184 			else
185 				return function()
186 					{
187 						return this.document.getWindow().$.getSelection();
188 					};
189 		})(),
190
191 		/**
192 		 * Gets the type of the current selection. The following values are
193 		 * available:
194 		 * <ul>
195 		 *		<li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li>
196 		 *		<li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or
197 		 *			collapsed selection.</li>
198 		 *		<li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element
199 		 *			selection.</li>
200 		 * </ul>
201 		 * @returns {Number} One of the following constant values:
202 		 *		{@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or
203 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
204 		 * @example
205 		 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT )
206 		 *     alert( 'Text is selected' );
207 		 */
208 		getType : (function()
209 		{
210 			if ( CKEDITOR.env.ie )
211 				return function()
212 					{
213 						try
214 						{
215 							var sel = this.getNative(),
216 								ieType = sel.type;
217
218 							if ( ieType == 'Text' )
219 								return CKEDITOR.SELECTION_TEXT;
220
221 							if ( ieType == 'Control' )
222 								return CKEDITOR.SELECTION_ELEMENT;
223
224 							// It is possible that we can still get a text range
225 							// object even when type == 'None' is returned by IE.
226 							// So we'd better check the object returned by
227 							// createRange() rather than by looking at the type.
228 							if ( sel.createRange().parentElement )
229 								return CKEDITOR.SELECTION_TEXT;
230 						}
231 						catch(e) {}
232
233 						return CKEDITOR.SELECTION_NONE;
234 					};
235 			else
236 				return function()
237 					{
238 						var sel = this.getNative();
239 						if ( !sel )
240 							return CKEDITOR.SELECTION_NONE;
241
242 						if ( sel.rangeCount == 1 )
243 						{
244 							// Check if the actual selection is a control (IMG,
245 							// TABLE, HR, etc...).
246
247 							var range = sel.getRangeAt(0),
248 								startContainer = range.startContainer;
249
250 							if ( startContainer == range.endContainer
251 								&& startContainer.nodeType == 1
252 								&& ( range.endOffset - range.startOffset ) == 1
253 								&& styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
254 							{
255 								return CKEDITOR.SELECTION_ELEMENT;
256 							}
257 						}
258
259 						return CKEDITOR.SELECTION_TEXT;
260 					};
261 		})(),
262
263 		/**
264 		 * Gets the DOM element in which the selection starts.
265 		 * @returns {CKEDITOR.dom.element} The element at the beginning of the
266 		 *		selection.
267 		 * @example
268 		 * var element = editor.getSelection().<b>getStartElement()</b>;
269 		 * alert( element.getName() );
270 		 */
271 		getStartElement : function()
272 		{
273 			var node,
274 				sel = this.getNative();
275
276 			switch ( this.getType() )
277 			{
278 				case CKEDITOR.SELECTION_ELEMENT :
279 					return this.getSelectedElement();
280
281 				case CKEDITOR.SELECTION_TEXT :
282
283 					if ( CKEDITOR.env.ie )
284 					{
285 						var range = sel.createRange();
286 						range.collapse( true );
287
288 						node = range.parentElement();
289 					}
290 					else
291 					{
292 						node = sel.anchorNode;
293
294 						if ( node.nodeType != 1 )
295 							node = node.parentNode;
296 					}
297 			}
298
299 			return ( node ? new CKEDITOR.dom.element( node ) : null );
300 		},
301
302 		/**
303 		 * Gets the current selected element.
304 		 * @returns {CKEDITOR.dom.element} The selected element. Null if no
305 		 *		selection is available or the selection type is not
306 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
307 		 * @example
308 		 * var element = editor.getSelection().<b>getSelectedElement()</b>;
309 		 * alert( element.getName() );
310 		 */
311 		getSelectedElement : function()
312 		{
313 			var node;
314
315 			if ( this.getType() == CKEDITOR.SELECTION_ELEMENT )
316 			{
317 				var sel = this.getNative();
318
319 				if ( CKEDITOR.env.ie )
320 				{
321 					try
322 					{
323 						node = sel.createRange().item(0);
324 					}
325 					catch(e) {}
326 				}
327 				else
328 				{
329 					var range = sel.getRangeAt( 0 );
330 					node = range.startContainer.childNodes[ range.startOffset ];
331 				}
332 			}
333
334 			return ( node ? new CKEDITOR.dom.element( node ) : null );
335 		}
336 	};
337 })();