1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 CKEDITOR.dialog.add( 'specialchar', function( editor )
  7 {
  8 	/**
  9 	 * Simulate "this" of a dialog for non-dialog events.
 10 	 * @type {CKEDITOR.dialog}
 11 	 */
 12 	var dialog,
 13 		lang = editor.lang.specialChar;
 14 
 15 	var onChoice = function( evt )
 16 	{
 17 		var target, value;
 18 		if ( evt.data )
 19 			target = evt.data.getTarget();
 20 		else
 21 			target = new CKEDITOR.dom.element( evt );
 22 
 23 		if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) )
 24 		{
 25 			target.removeClass( "cke_light_background" );
 26 			dialog.hide();
 27 
 28 			// We must use "insertText" here to keep text styled.
 29 			var span = editor.document.createElement( 'span' );
 30 			span.setHtml( value );
 31 			editor.insertText( span.getText() );
 32 		}
 33 	};
 34 
 35 	var onClick = CKEDITOR.tools.addFunction( onChoice );
 36 
 37 	var focusedNode;
 38 
 39 	var onFocus = function( evt, target )
 40 	{
 41 		var value;
 42 		target = target || evt.data.getTarget();
 43 
 44 		if ( target.getName() == 'span' )
 45 			target = target.getParent();
 46 
 47 		if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) )
 48 		{
 49 			// Trigger blur manually if there is focused node.
 50 			if ( focusedNode )
 51 				onBlur( null, focusedNode );
 52 
 53 			var htmlPreview = dialog.getContentElement( 'info', 'htmlPreview' ).getElement();
 54 
 55 			dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( value );
 56 			htmlPreview.setHtml( CKEDITOR.tools.htmlEncode( value ) );
 57 			target.getParent().addClass( "cke_light_background" );
 58 
 59 			// Memorize focused node.
 60 			focusedNode = target;
 61 		}
 62 	};
 63 
 64 	var onBlur = function( evt, target )
 65 	{
 66 		target = target || evt.data.getTarget();
 67 
 68 		if ( target.getName() == 'span' )
 69 			target = target.getParent();
 70 
 71 		if ( target.getName() == 'a' )
 72 		{
 73 			dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( ' ' );
 74 			dialog.getContentElement( 'info', 'htmlPreview' ).getElement().setHtml( ' ' );
 75 			target.getParent().removeClass( "cke_light_background" );
 76 
 77 			focusedNode = undefined;
 78 		}
 79 	};
 80 
 81 	var onKeydown = CKEDITOR.tools.addFunction( function( ev )
 82 	{
 83 		ev = new CKEDITOR.dom.event( ev );
 84 
 85 		// Get an Anchor element.
 86 		var element = ev.getTarget();
 87 		var relative, nodeToMove;
 88 		var keystroke = ev.getKeystroke(),
 89 			rtl = editor.lang.dir == 'rtl';
 90 
 91 		switch ( keystroke )
 92 		{
 93 			// UP-ARROW
 94 			case 38 :
 95 				// relative is TR
 96 				if ( ( relative = element.getParent().getParent().getPrevious() ) )
 97 				{
 98 					nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] );
 99 					nodeToMove.focus();
100 					onBlur( null, element );
101 					onFocus( null, nodeToMove );
102 				}
103 				ev.preventDefault();
104 				break;
105 			// DOWN-ARROW
106 			case 40 :
107 				// relative is TR
108 				if ( ( relative = element.getParent().getParent().getNext() ) )
109 				{
110 					nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] );
111 					if ( nodeToMove && nodeToMove.type == 1 )
112 					{
113 						nodeToMove.focus();
114 						onBlur( null, element );
115 						onFocus( null, nodeToMove );
116 					}
117 				}
118 				ev.preventDefault();
119 				break;
120 			// SPACE
121 			// ENTER is already handled as onClick
122 			case 32 :
123 				onChoice( { data: ev } );
124 				ev.preventDefault();
125 				break;
126 
127 			// RIGHT-ARROW
128 			case rtl ? 37 : 39 :
129 				// relative is TD
130 				if ( ( relative = element.getParent().getNext() ) )
131 				{
132 					nodeToMove = relative.getChild( 0 );
133 					if ( nodeToMove.type == 1 )
134 					{
135 						nodeToMove.focus();
136 						onBlur( null, element );
137 						onFocus( null, nodeToMove );
138 						ev.preventDefault( true );
139 					}
140 					else
141 						onBlur( null, element );
142 				}
143 				// relative is TR
144 				else if ( ( relative = element.getParent().getParent().getNext() ) )
145 				{
146 					nodeToMove = relative.getChild( [ 0, 0 ] );
147 					if ( nodeToMove && nodeToMove.type == 1 )
148 					{
149 						nodeToMove.focus();
150 						onBlur( null, element );
151 						onFocus( null, nodeToMove );
152 						ev.preventDefault( true );
153 					}
154 					else
155 						onBlur( null, element );
156 				}
157 				break;
158 
159 			// LEFT-ARROW
160 			case rtl ? 39 : 37 :
161 				// relative is TD
162 				if ( ( relative = element.getParent().getPrevious() ) )
163 				{
164 					nodeToMove = relative.getChild( 0 );
165 					nodeToMove.focus();
166 					onBlur( null, element );
167 					onFocus( null, nodeToMove );
168 					ev.preventDefault( true );
169 				}
170 				// relative is TR
171 				else if ( ( relative = element.getParent().getParent().getPrevious() ) )
172 				{
173 					nodeToMove = relative.getLast().getChild( 0 );
174 					nodeToMove.focus();
175 					onBlur( null, element );
176 					onFocus( null, nodeToMove );
177 					ev.preventDefault( true );
178 				}
179 				else
180 					onBlur( null, element );
181 				break;
182 			default :
183 				// Do not stop not handled events.
184 				return;
185 		}
186 	});
187 
188 	return {
189 		title : lang.title,
190 		minWidth : 430,
191 		minHeight : 280,
192 		buttons : [ CKEDITOR.dialog.cancelButton ],
193 		charColumns : 17,
194 		onLoad :  function()
195 		{
196 			var columns = this.definition.charColumns,
197 				extraChars = editor.config.extraSpecialChars,
198 				chars = editor.config.specialChars;
199 
200 			var charsTableLabel =  CKEDITOR.tools.getNextId() + '_specialchar_table_label';
201 			var html = [ '<table role="listbox" aria-labelledby="' + charsTableLabel + '"' +
202 						 			' style="width: 320px; height: 100%; border-collapse: separate;"' +
203 						 			' align="center" cellspacing="2" cellpadding="2" border="0">' ];
204 
205 			var i = 0,
206 				size = chars.length,
207 				character,
208 				charDesc;
209 
210 			while ( i < size )
211 			{
212 				html.push( '<tr role="presentation">' ) ;
213 
214 				for ( var j = 0 ; j < columns ; j++, i++ )
215 				{
216 					if ( ( character = chars[ i ] ) )
217 					{
218 						charDesc = '';
219 
220 						if ( character instanceof Array )
221 						{
222 							charDesc = character[ 1 ];
223 							character = character[ 0 ];
224 						}
225 						else
226 						{
227 							var _tmpName = character.replace( '&', '' ).replace( ';', '' ).replace( '#', '' );
228 
229 							// Use character in case description unavailable.
230 							charDesc = lang[ _tmpName ] || character;
231 						}
232 
233 						var charLabelId =  'cke_specialchar_label_' + i + '_' + CKEDITOR.tools.getNextNumber();
234 
235 						html.push(
236 							'<td class="cke_dark_background" style="cursor: default" role="presentation">' +
237 							'<a href="javascript: void(0);" role="option"' +
238 							' aria-posinset="' + ( i +1 ) + '"',
239 							' aria-setsize="' + size + '"',
240 							' aria-labelledby="' + charLabelId + '"',
241 							' style="cursor: inherit; display: block; height: 1.25em; margin-top: 0.25em; text-align: center;" title="', CKEDITOR.tools.htmlEncode( charDesc ), '"' +
242 							' onkeydown="CKEDITOR.tools.callFunction( ' + onKeydown + ', event, this )"' +
243 							' onclick="CKEDITOR.tools.callFunction(' + onClick + ', this); return false;"' +
244 							' tabindex="-1">' +
245 							'<span style="margin: 0 auto;cursor: inherit">' +
246 							character +
247 							'</span>' +
248 							'<span class="cke_voice_label" id="' + charLabelId + '">' +
249 							charDesc +
250 							'</span></a>');
251 					}
252 					else
253 						html.push( '<td class="cke_dark_background"> ' );
254 
255 					html.push( '</td>' );
256 				}
257 				html.push( '</tr>' );
258 			}
259 
260 			html.push( '</tbody></table>', '<span id="' + charsTableLabel + '" class="cke_voice_label">' + lang.options +'</span>' );
261 
262 			this.getContentElement( 'info', 'charContainer' ).getElement().setHtml( html.join( '' ) );
263 		},
264 		contents : [
265 			{
266 				id : 'info',
267 				label : editor.lang.common.generalTab,
268 				title : editor.lang.common.generalTab,
269 				padding : 0,
270 				align : 'top',
271 				elements : [
272 					{
273 						type : 'hbox',
274 						align : 'top',
275 						widths : [ '320px', '90px' ],
276 						children :
277 						[
278 							{
279 								type : 'html',
280 								id : 'charContainer',
281 								html : '',
282 								onMouseover : onFocus,
283 								onMouseout : onBlur,
284 								focus : function()
285 								{
286 									var firstChar = this.getElement().getElementsByTag( 'a' ).getItem( 0 );
287 									setTimeout( function()
288 									{
289 										firstChar.focus();
290 										onFocus( null, firstChar );
291 									}, 0 );
292 								},
293 								onShow : function()
294 								{
295 									var firstChar = this.getElement().getChild( [ 0, 0, 0, 0, 0 ] );
296 									setTimeout( function()
297 										{
298 											firstChar.focus();
299 											onFocus( null, firstChar );
300 										}, 0 );
301 								},
302 								onLoad : function( event )
303 								{
304 									dialog = event.sender;
305 								}
306 							},
307 							{
308 								type : 'hbox',
309 								align : 'top',
310 								widths : [ '100%' ],
311 								children :
312 								[
313 									{
314 										type : 'vbox',
315 										align : 'top',
316 										children :
317 										[
318 											{
319 												type : 'html',
320 												html : '<div></div>'
321 											},
322 											{
323 												type : 'html',
324 												id : 'charPreview',
325 												className : 'cke_dark_background',
326 												style : 'border:1px solid #eeeeee;font-size:28px;height:40px;width:70px;padding-top:9px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
327 												html : '<div> </div>'
328 											},
329 											{
330 												type : 'html',
331 												id : 'htmlPreview',
332 												className : 'cke_dark_background',
333 												style : 'border:1px solid #eeeeee;font-size:14px;height:20px;width:70px;padding-top:2px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
334 												html : '<div> </div>'
335 											}
336 										]
337 									}
338 								]
339 							}
340 						]
341 					}
342 				]
343 			}
344 		]
345 	};
346 } );
347