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 checkSelectionChangeTimeoutPending;
 63 	var checkSelectionChangeTimeout = function()
 64 	{
 65 		// Firing the "OnSelectionChange" event on every key press started to
 66 		// be too slow. This function guarantees that there will be at least
 67 		// 200ms delay between selection checks.
 68
 69 		checkSelectionChangeTimeoutPending = true;
 70
 71 		if ( checkSelectionChangeTimer )
 72 			return;
 73
 74 		checkSelectionChangeTimeoutExec.call( this );
 75
 76 		checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this );
 77 	};
 78
 79 	var checkSelectionChangeTimeoutExec = function()
 80 	{
 81 		checkSelectionChangeTimer = null;
 82
 83 		if ( checkSelectionChangeTimeoutPending )
 84 		{
 85 			// Call this with a timeout so the browser properly moves the
 86 			// selection after the mouseup. It happened that the selection was
 87 			// being moved after the mouseup when clicking inside selected text
 88 			// with Firefox.
 89 			CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this );
 90
 91 			checkSelectionChangeTimeoutPending = false;
 92 		}
 93 	};
 94
 95 	// #### checkSelectionChange : END
 96
 97 	CKEDITOR.plugins.add( 'selection',
 98 	{
 99 		init : function( editor, pluginPath )
100 		{
101 			editor.on( 'contentDom', function()
102 				{
103 					if ( CKEDITOR.env.ie )
104 					{
105 						// IE is the only to provide the "selectionchange"
106 						// event.
107 						editor.document.on( 'selectionchange', checkSelectionChangeTimeout, editor );
108 					}
109 					else
110 					{
111 						// In other browsers, we make the selection change
112 						// check based on other events, like clicks or keys
113 						// press.
114
115 						editor.document.on( 'mouseup', checkSelectionChangeTimeout, editor );
116 						editor.document.on( 'keyup', checkSelectionChangeTimeout, editor );
117 					}
118 				});
119 		}
120 	});
121 })();
122
123 /**
124  * Gets the current selection from the editing area when in WYSIWYG mode.
125  * @returns {CKEDITOR.dom.selection} A selection object or null if not on
126  *		WYSIWYG mode or no selection is available.
127  * @example
128  * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>;
129  * alert( selection.getType() );
130  */
131 CKEDITOR.editor.prototype.getSelection = function()
132 {
133 	return this.document ? this.document.getSelection() : null;
134 };
135
136 /**
137  * Gets the current selection from the document.
138  * @returns {CKEDITOR.dom.selection} A selection object.
139  * @example
140  * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>;
141  * alert( selection.getType() );
142  */
143 CKEDITOR.dom.document.prototype.getSelection = function()
144 {
145 	return new CKEDITOR.dom.selection( this );
146 };
147
148 /**
149  * No selection.
150  * @constant
151  * @example
152  * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
153  *     alert( 'Nothing is selected' );
154  */
155 CKEDITOR.SELECTION_NONE		= 1;
156
157 /**
158  * Text or collapsed selection.
159  * @constant
160  * @example
161  * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
162  *     alert( 'Text is selected' );
163  */
164 CKEDITOR.SELECTION_TEXT		= 2;
165
166 /**
167  * Element selection.
168  * @constant
169  * @example
170  * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
171  *     alert( 'An element is selected' );
172  */
173 CKEDITOR.SELECTION_ELEMENT	= 3;
174
175 /**
176  * Manipulates the selection in a DOM document.
177  * @constructor
178  * @example
179  */
180 CKEDITOR.dom.selection = function( document )
181 {
182 	this.document = document;
183 };
184
185 (function()
186 {
187 	var styleObjectElements = { img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1 };
188
189 	CKEDITOR.dom.selection.prototype =
190 	{
191 		/**
192 		 * Gets the native selection object from the browser.
193 		 * @returns {Object} The native selection object.
194 		 * @example
195 		 * var selection = editor.getSelection().<b>getNative()</b>;
196 		 */
197 		getNative :
198 			CKEDITOR.env.ie ?
199 				function()
200 				{
201 					return this.document.$.selection;
202 				}
203 			:
204 				function()
205 				{
206 					return this.document.getWindow().$.getSelection();
207 				},
208
209 		/**
210 		 * Gets the type of the current selection. The following values are
211 		 * available:
212 		 * <ul>
213 		 *		<li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li>
214 		 *		<li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or
215 		 *			collapsed selection.</li>
216 		 *		<li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element
217 		 *			selection.</li>
218 		 * </ul>
219 		 * @returns {Number} One of the following constant values:
220 		 *		{@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or
221 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
222 		 * @example
223 		 * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT )
224 		 *     alert( 'Text is selected' );
225 		 */
226 		getType :
227 			CKEDITOR.env.ie ?
228 				function()
229 				{
230 					try
231 					{
232 						var sel = this.getNative(),
233 							ieType = sel.type;
234
235 						if ( ieType == 'Text' )
236 							return CKEDITOR.SELECTION_TEXT;
237
238 						if ( ieType == 'Control' )
239 							return CKEDITOR.SELECTION_ELEMENT;
240
241 						// It is possible that we can still get a text range
242 						// object even when type == 'None' is returned by IE.
243 						// So we'd better check the object returned by
244 						// createRange() rather than by looking at the type.
245 						if ( sel.createRange().parentElement )
246 							return CKEDITOR.SELECTION_TEXT;
247 					}
248 					catch(e) {}
249
250 					return CKEDITOR.SELECTION_NONE;
251 				}
252 			:
253 				function()
254 				{
255 					var sel = this.getNative();
256 					if ( !sel )
257 						return CKEDITOR.SELECTION_NONE;
258
259 					if ( sel.rangeCount == 1 )
260 					{
261 						// Check if the actual selection is a control (IMG,
262 						// TABLE, HR, etc...).
263
264 						var range = sel.getRangeAt(0),
265 							startContainer = range.startContainer;
266
267 						if ( startContainer == range.endContainer
268 							&& startContainer.nodeType == 1
269 							&& ( range.endOffset - range.startOffset ) == 1
270 							&& styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
271 						{
272 							return CKEDITOR.SELECTION_ELEMENT;
273 						}
274 					}
275
276 					return CKEDITOR.SELECTION_TEXT;
277 				},
278
279 		/**
280 		 * Gets the DOM element in which the selection starts.
281 		 * @returns {CKEDITOR.dom.element} The element at the beginning of the
282 		 *		selection.
283 		 * @example
284 		 * var element = editor.getSelection().<b>getStartElement()</b>;
285 		 * alert( element.getName() );
286 		 */
287 		getStartElement : function()
288 		{
289 			var node,
290 				sel = this.getNative();
291
292 			switch ( this.getType() )
293 			{
294 				case CKEDITOR.SELECTION_ELEMENT :
295 					return this.getSelectedElement();
296
297 				case CKEDITOR.SELECTION_TEXT :
298
299 					if ( CKEDITOR.env.ie )
300 					{
301 						var range = sel.createRange();
302 						range.collapse( true );
303
304 						node = range.parentElement();
305 					}
306 					else
307 					{
308 						node = sel.anchorNode;
309
310 						if ( node.nodeType != 1 )
311 							node = node.parentNode;
312 					}
313 			}
314
315 			return ( node ? new CKEDITOR.dom.element( node ) : null );
316 		},
317
318 		/**
319 		 * Gets the current selected element.
320 		 * @returns {CKEDITOR.dom.element} The selected element. Null if no
321 		 *		selection is available or the selection type is not
322 		 *		{@link CKEDITOR.SELECTION_ELEMENT}.
323 		 * @example
324 		 * var element = editor.getSelection().<b>getSelectedElement()</b>;
325 		 * alert( element.getName() );
326 		 */
327 		getSelectedElement : function()
328 		{
329 			var node;
330
331 			if ( this.getType() == CKEDITOR.SELECTION_ELEMENT )
332 			{
333 				var sel = this.getNative();
334
335 				if ( CKEDITOR.env.ie )
336 				{
337 					try
338 					{
339 						node = sel.createRange().item(0);
340 					}
341 					catch(e) {}
342 				}
343 				else
344 				{
345 					var range = sel.getRangeAt( 0 );
346 					node = range.startContainer.childNodes[ range.startOffset ];
347 				}
348 			}
349
350 			return ( node ? new CKEDITOR.dom.element( node ) : null );
351 		},
352
353 		selectElement :
354 			CKEDITOR.env.ie ?
355 				function( element )
356 				{
357 					this.getNative().empty() ;
358
359 					var range ;
360 					try
361 					{
362 						// Try to select the node as a control.
363 						range = this.document.$.body.createControlRange() ;
364 						range.addElement( element.$ ) ;
365 					}
366 					catch(e)
367 					{
368 						// If failed, select it as a text range.
369 						range = this.document.$.body.createTextRange() ;
370 						range.moveToElementText( element.$ ) ;
371 					}
372
373 					range.select() ;
374 				}
375 			:
376 				function( element )
377 				{
378 					// Create the range for the element.
379 					var range = this.document.$.createRange() ;
380 					range.selectNode( element.$ ) ;
381
382 					// Select the range.
383 					var sel = this.getNative() ;
384 					sel.removeAllRanges() ;
385 					sel.addRange( range ) ;
386 				}
387 	};
388 })();
389