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 (function()
  7 {
  8 	var imageDialog = function( editor, dialogType )
  9 	{
 10 		// Load image preview.
 11 		var IMAGE = 1,
 12 			LINK = 2,
 13 			PREVIEW = 4,
 14 			CLEANUP = 8,
 15 			regexGetSize = /^\s*(\d+)((px)|\%)?\s*$/i,
 16 			regexGetSizeOrEmpty = /(^\s*(\d+)((px)|\%)?\s*$)|^$/i,
 17 			pxLengthRegex = /^\d+px$/;
 18 
 19 		var onSizeChange = function()
 20 		{
 21 			var value = this.getValue(),	// This = input element.
 22 				dialog = this.getDialog(),
 23 				aMatch  =  value.match( regexGetSize );	// Check value
 24 			if ( aMatch )
 25 			{
 26 				if ( aMatch[2] == '%' )			// % is allowed - > unlock ratio.
 27 					switchLockRatio( dialog, false );	// Unlock.
 28 				value = aMatch[1];
 29 			}
 30 
 31 			// Only if ratio is locked
 32 			if ( dialog.lockRatio )
 33 			{
 34 				var oImageOriginal = dialog.originalElement;
 35 				if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
 36 				{
 37 					if ( this.id == 'txtHeight' )
 38 					{
 39 						if ( value && value != '0' )
 40 							value = Math.round( oImageOriginal.$.width * ( value  / oImageOriginal.$.height ) );
 41 						if ( !isNaN( value ) )
 42 							dialog.setValueOf( 'info', 'txtWidth', value );
 43 					}
 44 					else		//this.id = txtWidth.
 45 					{
 46 						if ( value && value != '0' )
 47 							value = Math.round( oImageOriginal.$.height * ( value  / oImageOriginal.$.width ) );
 48 						if ( !isNaN( value ) )
 49 							dialog.setValueOf( 'info', 'txtHeight', value );
 50 					}
 51 				}
 52 			}
 53 			updatePreview( dialog );
 54 		};
 55 
 56 		var updatePreview = function( dialog )
 57 		{
 58 			//Don't load before onShow.
 59 			if ( !dialog.originalElement || !dialog.preview )
 60 				return 1;
 61 
 62 			// Read attributes and update imagePreview;
 63 			dialog.commitContent( PREVIEW, dialog.preview );
 64 			return 0;
 65 		};
 66 
 67 		// Custom commit dialog logic, where we're intended to give inline style
 68 		// field (txtdlgGenStyle) higher priority to avoid overwriting styles contribute
 69 		// by other fields.
 70 		function commitContent()
 71 		{
 72 			var args = arguments;
 73 			var inlineStyleField = this.getContentElement( 'advanced', 'txtdlgGenStyle' );
 74 			inlineStyleField && inlineStyleField.commit.apply( inlineStyleField, args );
 75 
 76 			this.foreach( function( widget )
 77 			{
 78 				if ( widget.commit &&  widget.id != 'txtdlgGenStyle' )
 79 					widget.commit.apply( widget, args );
 80 			});
 81 		}
 82 
 83 		// Avoid recursions.
 84 		var incommit;
 85 
 86 		// Synchronous field values to other impacted fields is required, e.g. border
 87 		// size change should alter inline-style text as well.
 88 		function commitInternally( targetFields )
 89 		{
 90 			if ( incommit )
 91 				return;
 92 
 93 			incommit = 1;
 94 
 95 			var dialog = this.getDialog(),
 96 				element = dialog.imageElement;
 97 			if ( element )
 98 			{
 99 				// Commit this field and broadcast to target fields.
100 				this.commit( IMAGE, element );
101 
102 				targetFields = [].concat( targetFields );
103 				var length = targetFields.length,
104 					field;
105 				for ( var i = 0; i < length; i++ )
106 				{
107 					field = dialog.getContentElement.apply( dialog, targetFields[ i ].split( ':' ) );
108 					// May cause recursion.
109 					field && field.setup( IMAGE, element );
110 				}
111 			}
112 
113 			incommit = 0;
114 		}
115 
116 		var switchLockRatio = function( dialog, value )
117 		{
118 			if ( !dialog.getContentElement( 'info', 'ratioLock' ) )
119 				return null;
120 
121 			var oImageOriginal = dialog.originalElement;
122 
123 			// Dialog may already closed. (#5505)
124 			if( !oImageOriginal )
125 				return null;
126 
127 			// Check image ratio and original image ratio, but respecting user's preference.
128 			if ( value == 'check' )
129 			{
130 				if ( !dialog.userlockRatio && oImageOriginal.getCustomData( 'isReady' ) == 'true'  )
131 				{
132 					var width = dialog.getValueOf( 'info', 'txtWidth' ),
133 						height = dialog.getValueOf( 'info', 'txtHeight' ),
134 						originalRatio = oImageOriginal.$.width * 1000 / oImageOriginal.$.height,
135 						thisRatio = width * 1000 / height;
136 					dialog.lockRatio  = false;		// Default: unlock ratio
137 
138 					if ( !width && !height )
139 						dialog.lockRatio = true;
140 					else if ( !isNaN( originalRatio ) && !isNaN( thisRatio ) )
141 					{
142 						if ( Math.round( originalRatio ) == Math.round( thisRatio ) )
143 							dialog.lockRatio = true;
144 					}
145 				}
146 			}
147 			else if ( value != undefined )
148 				dialog.lockRatio = value;
149 			else
150 			{
151 				dialog.userlockRatio = 1;
152 				dialog.lockRatio = !dialog.lockRatio;
153 			}
154 
155 			var ratioButton = CKEDITOR.document.getById( btnLockSizesId );
156 			if ( dialog.lockRatio )
157 				ratioButton.removeClass( 'cke_btn_unlocked' );
158 			else
159 				ratioButton.addClass( 'cke_btn_unlocked' );
160 
161 			ratioButton.setAttribute( 'aria-checked', dialog.lockRatio );
162 
163 			// Ratio button hc presentation - WHITE SQUARE / BLACK SQUARE
164 			if ( CKEDITOR.env.hc )
165 			{
166 				var icon = ratioButton.getChild( 0 );
167 				icon.setHtml(  dialog.lockRatio ? CKEDITOR.env.ie ? '\u25A0': '\u25A3' : CKEDITOR.env.ie ? '\u25A1' : '\u25A2' );
168 			}
169 
170 			return dialog.lockRatio;
171 		};
172 
173 		var resetSize = function( dialog )
174 		{
175 			var oImageOriginal = dialog.originalElement;
176 			if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
177 			{
178 				var widthField = dialog.getContentElement( 'info', 'txtWidth' ),
179 					heightField = dialog.getContentElement( 'info', 'txtHeight' );
180 				widthField && widthField.setValue( oImageOriginal.$.width );
181 				heightField && heightField.setValue( oImageOriginal.$.height );
182 			}
183 			updatePreview( dialog );
184 		};
185 
186 		var setupDimension = function( type, element )
187 		{
188 			if ( type != IMAGE )
189 				return;
190 
191 			function checkDimension( size, defaultValue )
192 			{
193 				var aMatch  =  size.match( regexGetSize );
194 				if ( aMatch )
195 				{
196 					if ( aMatch[2] == '%' )				// % is allowed.
197 					{
198 						aMatch[1] += '%';
199 						switchLockRatio( dialog, false );	// Unlock ratio
200 					}
201 					return aMatch[1];
202 				}
203 				return defaultValue;
204 			}
205 
206 			var dialog = this.getDialog(),
207 				value = '',
208 				dimension = this.id == 'txtWidth' ? 'width' : 'height',
209 				size = element.getAttribute( dimension );
210 
211 			if ( size )
212 				value = checkDimension( size, value );
213 			value = checkDimension( element.getStyle( dimension ), value );
214 
215 			this.setValue( value );
216 		};
217 
218 		var previewPreloader;
219 
220 		var onImgLoadEvent = function()
221 		{
222 			// Image is ready.
223 			var original = this.originalElement;
224 			original.setCustomData( 'isReady', 'true' );
225 			original.removeListener( 'load', onImgLoadEvent );
226 			original.removeListener( 'error', onImgLoadErrorEvent );
227 			original.removeListener( 'abort', onImgLoadErrorEvent );
228 
229 			// Hide loader
230 			CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' );
231 
232 			// New image -> new domensions
233 			if ( !this.dontResetSize )
234 				resetSize( this );
235 
236 			if ( this.firstLoad )
237 				CKEDITOR.tools.setTimeout( function(){ switchLockRatio( this, 'check' ); }, 0, this );
238 
239 			this.firstLoad = false;
240 			this.dontResetSize = false;
241 		};
242 
243 		var onImgLoadErrorEvent = function()
244 		{
245 			// Error. Image is not loaded.
246 			var original = this.originalElement;
247 			original.removeListener( 'load', onImgLoadEvent );
248 			original.removeListener( 'error', onImgLoadErrorEvent );
249 			original.removeListener( 'abort', onImgLoadErrorEvent );
250 
251 			// Set Error image.
252 			var noimage = CKEDITOR.getUrl( editor.skinPath + 'images/noimage.png' );
253 
254 			if ( this.preview )
255 				this.preview.setAttribute( 'src', noimage );
256 
257 			// Hide loader
258 			CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' );
259 			switchLockRatio( this, false );	// Unlock.
260 		};
261 
262 		var numbering = function( id )
263 			{
264 				return CKEDITOR.tools.getNextId() + '_' + id;
265 			},
266 			btnLockSizesId = numbering( 'btnLockSizes' ),
267 			btnResetSizeId = numbering( 'btnResetSize' ),
268 			imagePreviewLoaderId = numbering( 'ImagePreviewLoader' ),
269 			previewLinkId = numbering( 'previewLink' ),
270 			previewImageId = numbering( 'previewImage' );
271 
272 		return {
273 			title : editor.lang.image[ dialogType == 'image' ? 'title' : 'titleButton' ],
274 			minWidth : 420,
275 			minHeight : 360,
276 			onShow : function()
277 			{
278 				this.imageElement = false;
279 				this.linkElement = false;
280 
281 				// Default: create a new element.
282 				this.imageEditMode = false;
283 				this.linkEditMode = false;
284 
285 				this.lockRatio = true;
286 				this.userlockRatio = 0;
287 				this.dontResetSize = false;
288 				this.firstLoad = true;
289 				this.addLink = false;
290 
291 				var editor = this.getParentEditor(),
292 					sel = editor.getSelection(),
293 					element = sel && sel.getSelectedElement(),
294 					link = element && element.getAscendant( 'a' );
295 
296 				//Hide loader.
297 				CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' );
298 				// Create the preview before setup the dialog contents.
299 				previewPreloader = new CKEDITOR.dom.element( 'img', editor.document );
300 				this.preview = CKEDITOR.document.getById( previewImageId );
301 
302 				// Copy of the image
303 				this.originalElement = editor.document.createElement( 'img' );
304 				this.originalElement.setAttribute( 'alt', '' );
305 				this.originalElement.setCustomData( 'isReady', 'false' );
306 
307 				if ( link )
308 				{
309 					this.linkElement = link;
310 					this.linkEditMode = true;
311 
312 					// Look for Image element.
313 					var linkChildren = link.getChildren();
314 					if ( linkChildren.count() == 1 )			// 1 child.
315 					{
316 						var childTagName = linkChildren.getItem( 0 ).getName();
317 						if ( childTagName == 'img' || childTagName == 'input' )
318 						{
319 							this.imageElement = linkChildren.getItem( 0 );
320 							if ( this.imageElement.getName() == 'img' )
321 								this.imageEditMode = 'img';
322 							else if ( this.imageElement.getName() == 'input' )
323 								this.imageEditMode = 'input';
324 						}
325 					}
326 					// Fill out all fields.
327 					if ( dialogType == 'image' )
328 						this.setupContent( LINK, link );
329 				}
330 
331 				if ( element && element.getName() == 'img' && !element.data( 'cke-realelement' )
332 					|| element && element.getName() == 'input' && element.getAttribute( 'type' ) == 'image' )
333 				{
334 					this.imageEditMode = element.getName();
335 					this.imageElement = element;
336 				}
337 
338 				if ( this.imageEditMode )
339 				{
340 					// Use the original element as a buffer from  since we don't want
341 					// temporary changes to be committed, e.g. if the dialog is canceled.
342 					this.cleanImageElement = this.imageElement;
343 					this.imageElement = this.cleanImageElement.clone( true, true );
344 
345 					// Fill out all fields.
346 					this.setupContent( IMAGE, this.imageElement );
347 				}
348 				else
349 					this.imageElement =  editor.document.createElement( 'img' );
350 
351 				// Refresh LockRatio button
352 				switchLockRatio ( this, true );
353 
354 				// Dont show preview if no URL given.
355 				if ( !CKEDITOR.tools.trim( this.getValueOf( 'info', 'txtUrl' ) ) )
356 				{
357 					this.preview.removeAttribute( 'src' );
358 					this.preview.setStyle( 'display', 'none' );
359 				}
360 			},
361 			onOk : function()
362 			{
363 				// Edit existing Image.
364 				if ( this.imageEditMode )
365 				{
366 					var imgTagName = this.imageEditMode;
367 
368 					// Image dialog and Input element.
369 					if ( dialogType == 'image' && imgTagName == 'input' && confirm( editor.lang.image.button2Img ) )
370 					{
371 						// Replace INPUT-> IMG
372 						imgTagName = 'img';
373 						this.imageElement = editor.document.createElement( 'img' );
374 						this.imageElement.setAttribute( 'alt', '' );
375 						editor.insertElement( this.imageElement );
376 					}
377 					// ImageButton dialog and Image element.
378 					else if ( dialogType != 'image' && imgTagName == 'img' && confirm( editor.lang.image.img2Button ))
379 					{
380 						// Replace IMG -> INPUT
381 						imgTagName = 'input';
382 						this.imageElement = editor.document.createElement( 'input' );
383 						this.imageElement.setAttributes(
384 							{
385 								type : 'image',
386 								alt : ''
387 							}
388 						);
389 						editor.insertElement( this.imageElement );
390 					}
391 					else
392 					{
393 						// Restore the original element before all commits.
394 						this.imageElement = this.cleanImageElement;
395 						delete this.cleanImageElement;
396 					}
397 				}
398 				else	// Create a new image.
399 				{
400 					// Image dialog -> create IMG element.
401 					if ( dialogType == 'image' )
402 						this.imageElement = editor.document.createElement( 'img' );
403 					else
404 					{
405 						this.imageElement = editor.document.createElement( 'input' );
406 						this.imageElement.setAttribute ( 'type' ,'image' );
407 					}
408 					this.imageElement.setAttribute( 'alt', '' );
409 				}
410 
411 				// Create a new link.
412 				if ( !this.linkEditMode )
413 					this.linkElement = editor.document.createElement( 'a' );
414 
415 				// Set attributes.
416 				this.commitContent( IMAGE, this.imageElement );
417 				this.commitContent( LINK, this.linkElement );
418 
419 				// Remove empty style attribute.
420 				if ( !this.imageElement.getAttribute( 'style' ) )
421 					this.imageElement.removeAttribute( 'style' );
422 
423 				// Insert a new Image.
424 				if ( !this.imageEditMode )
425 				{
426 					if ( this.addLink )
427 					{
428 						//Insert a new Link.
429 						if ( !this.linkEditMode )
430 						{
431 							editor.insertElement( this.linkElement );
432 							this.linkElement.append( this.imageElement, false );
433 						}
434 						else	 //Link already exists, image not.
435 							editor.insertElement( this.imageElement );
436 					}
437 					else
438 						editor.insertElement( this.imageElement );
439 				}
440 				else		// Image already exists.
441 				{
442 					//Add a new link element.
443 					if ( !this.linkEditMode && this.addLink )
444 					{
445 						editor.insertElement( this.linkElement );
446 						this.imageElement.appendTo( this.linkElement );
447 					}
448 					//Remove Link, Image exists.
449 					else if ( this.linkEditMode && !this.addLink )
450 					{
451 						editor.getSelection().selectElement( this.linkElement );
452 						editor.insertElement( this.imageElement );
453 					}
454 				}
455 			},
456 			onLoad : function()
457 			{
458 				if ( dialogType != 'image' )
459 					this.hidePage( 'Link' );		//Hide Link tab.
460 				var doc = this._.element.getDocument();
461 
462 				if ( this.getContentElement( 'info', 'ratioLock' ) )
463 				{
464 					this.addFocusable( doc.getById( btnResetSizeId ), 5 );
465 					this.addFocusable( doc.getById( btnLockSizesId ), 5 );
466 				}
467 
468 				this.commitContent = commitContent;
469 			},
470 			onHide : function()
471 			{
472 				if ( this.preview )
473 					this.commitContent( CLEANUP, this.preview );
474 
475 				if ( this.originalElement )
476 				{
477 					this.originalElement.removeListener( 'load', onImgLoadEvent );
478 					this.originalElement.removeListener( 'error', onImgLoadErrorEvent );
479 					this.originalElement.removeListener( 'abort', onImgLoadErrorEvent );
480 					this.originalElement.remove();
481 					this.originalElement = false;		// Dialog is closed.
482 				}
483 
484 				delete this.imageElement;
485 			},
486 			contents : [
487 				{
488 					id : 'info',
489 					label : editor.lang.image.infoTab,
490 					accessKey : 'I',
491 					elements :
492 					[
493 						{
494 							type : 'vbox',
495 							padding : 0,
496 							children :
497 							[
498 								{
499 									type : 'hbox',
500 									widths : [ '280px', '110px' ],
501 									align : 'right',
502 									children :
503 									[
504 										{
505 											id : 'txtUrl',
506 											type : 'text',
507 											label : editor.lang.common.url,
508 											required: true,
509 											onChange : function()
510 											{
511 												var dialog = this.getDialog(),
512 													newUrl = this.getValue();
513 
514 												//Update original image
515 												if ( newUrl.length > 0 )	//Prevent from load before onShow
516 												{
517 													dialog = this.getDialog();
518 													var original = dialog.originalElement;
519 
520 													dialog.preview.removeStyle( 'display' );
521 
522 													original.setCustomData( 'isReady', 'false' );
523 													// Show loader
524 													var loader = CKEDITOR.document.getById( imagePreviewLoaderId );
525 													if ( loader )
526 														loader.setStyle( 'display', '' );
527 
528 													original.on( 'load', onImgLoadEvent, dialog );
529 													original.on( 'error', onImgLoadErrorEvent, dialog );
530 													original.on( 'abort', onImgLoadErrorEvent, dialog );
531 													original.setAttribute( 'src', newUrl );
532 
533 													// Query the preloader to figure out the url impacted by based href.
534 													previewPreloader.setAttribute( 'src', newUrl );
535 													dialog.preview.setAttribute( 'src', previewPreloader.$.src );
536 													updatePreview( dialog );
537 												}
538 												// Dont show preview if no URL given.
539 												else if ( dialog.preview )
540 												{
541 													dialog.preview.removeAttribute( 'src' );
542 													dialog.preview.setStyle( 'display', 'none' );
543 												}
544 											},
545 											setup : function( type, element )
546 											{
547 												if ( type == IMAGE )
548 												{
549 													var url = element.data( 'cke-saved-src' ) || element.getAttribute( 'src' );
550 													var field = this;
551 
552 													this.getDialog().dontResetSize = true;
553 
554 													field.setValue( url );		// And call this.onChange()
555 													// Manually set the initial value.(#4191)
556 													field.setInitValue();
557 												}
558 											},
559 											commit : function( type, element )
560 											{
561 												if ( type == IMAGE && ( this.getValue() || this.isChanged() ) )
562 												{
563 													element.data( 'cke-saved-src', this.getValue() );
564 													element.setAttribute( 'src', this.getValue() );
565 												}
566 												else if ( type == CLEANUP )
567 												{
568 													element.setAttribute( 'src', '' );	// If removeAttribute doesn't work.
569 													element.removeAttribute( 'src' );
570 												}
571 											},
572 											validate : CKEDITOR.dialog.validate.notEmpty( editor.lang.image.urlMissing )
573 										},
574 										{
575 											type : 'button',
576 											id : 'browse',
577 											// v-align with the 'txtUrl' field.
578 											// TODO: We need something better than a fixed size here.
579 											style : 'display:inline-block;margin-top:10px;',
580 											align : 'center',
581 											label : editor.lang.common.browseServer,
582 											hidden : true,
583 											filebrowser : 'info:txtUrl'
584 										}
585 									]
586 								}
587 							]
588 						},
589 						{
590 							id : 'txtAlt',
591 							type : 'text',
592 							label : editor.lang.image.alt,
593 							accessKey : 'T',
594 							'default' : '',
595 							onChange : function()
596 							{
597 								updatePreview( this.getDialog() );
598 							},
599 							setup : function( type, element )
600 							{
601 								if ( type == IMAGE )
602 									this.setValue( element.getAttribute( 'alt' ) );
603 							},
604 							commit : function( type, element )
605 							{
606 								if ( type == IMAGE )
607 								{
608 									if ( this.getValue() || this.isChanged() )
609 										element.setAttribute( 'alt', this.getValue() );
610 								}
611 								else if ( type == PREVIEW )
612 								{
613 									element.setAttribute( 'alt', this.getValue() );
614 								}
615 								else if ( type == CLEANUP )
616 								{
617 									element.removeAttribute( 'alt' );
618 								}
619 							}
620 						},
621 						{
622 							type : 'hbox',
623 							children :
624 							[
625 								{
626 									id : 'basic',
627 									type : 'vbox',
628 									children :
629 									[
630 										{
631 											type : 'hbox',
632 											widths : [ '50%', '50%' ],
633 											children :
634 											[
635 												{
636 													type : 'vbox',
637 													padding : 1,
638 													children :
639 													[
640 														{
641 															type : 'text',
642 															width: '40px',
643 															id : 'txtWidth',
644 															label : editor.lang.common.width,
645 															onKeyUp : onSizeChange,
646 															onChange : function()
647 															{
648 																commitInternally.call( this, 'advanced:txtdlgGenStyle' );
649 															},
650 															validate : function()
651 															{
652 																var aMatch  =  this.getValue().match( regexGetSizeOrEmpty ),
653 																	isValid = !!( aMatch && parseInt( aMatch[1], 10 ) !== 0 );
654 																if ( !isValid )
655 																	alert( editor.lang.common.invalidWidth );
656 																return isValid;
657 															},
658 															setup : setupDimension,
659 															commit : function( type, element, internalCommit )
660 															{
661 																var value = this.getValue();
662 																if ( type == IMAGE )
663 																{
664 																	if ( value )
665 																		element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
666 																	else
667 																		element.removeStyle( 'width' );
668 
669 																	!internalCommit && element.removeAttribute( 'width' );
670 																}
671 																else if ( type == PREVIEW )
672 																{
673 																	var aMatch = value.match( regexGetSize );
674 																	if ( !aMatch )
675 																	{
676 																		var oImageOriginal = this.getDialog().originalElement;
677 																		if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
678 																			element.setStyle( 'width',  oImageOriginal.$.width + 'px');
679 																	}
680 																	else
681 																		element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
682 																}
683 																else if ( type == CLEANUP )
684 																{
685 																	element.removeAttribute( 'width' );
686 																	element.removeStyle( 'width' );
687 																}
688 															}
689 														},
690 														{
691 															type : 'text',
692 															id : 'txtHeight',
693 															width: '40px',
694 															label : editor.lang.common.height,
695 															onKeyUp : onSizeChange,
696 															onChange : function()
697 															{
698 																commitInternally.call( this, 'advanced:txtdlgGenStyle' );
699 															},
700 															validate : function()
701 															{
702 																var aMatch = this.getValue().match( regexGetSizeOrEmpty ),
703 																	isValid = !!( aMatch && parseInt( aMatch[1], 10 ) !== 0 );
704 																if ( !isValid )
705 																	alert( editor.lang.common.invalidHeight );
706 																return isValid;
707 															},
708 															setup : setupDimension,
709 															commit : function( type, element, internalCommit )
710 															{
711 																var value = this.getValue();
712 																if ( type == IMAGE )
713 																{
714 																	if ( value )
715 																		element.setStyle( 'height', CKEDITOR.tools.cssLength( value ) );
716 																	else
717 																		element.removeStyle( 'height' );
718 
719 																	!internalCommit && element.removeAttribute( 'height' );
720 																}
721 																else if ( type == PREVIEW )
722 																{
723 																	var aMatch = value.match( regexGetSize );
724 																	if ( !aMatch )
725 																	{
726 																		var oImageOriginal = this.getDialog().originalElement;
727 																		if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
728 																			element.setStyle( 'height', oImageOriginal.$.height + 'px' );
729 																	}
730 																	else
731 																		element.setStyle( 'height',  CKEDITOR.tools.cssLength( value ) );
732 																}
733 																else if ( type == CLEANUP )
734 																{
735 																	element.removeAttribute( 'height' );
736 																	element.removeStyle( 'height' );
737 																}
738 															}
739 														}
740 													]
741 												},
742 												{
743 													id : 'ratioLock',
744 													type : 'html',
745 													style : 'margin-top:30px;width:40px;height:40px;',
746 													onLoad : function()
747 													{
748 														// Activate Reset button
749 														var	resetButton = CKEDITOR.document.getById( btnResetSizeId ),
750 															ratioButton = CKEDITOR.document.getById( btnLockSizesId );
751 														if ( resetButton )
752 														{
753 															resetButton.on( 'click', function( evt )
754 																{
755 																	resetSize( this );
756 																	evt.data && evt.data.preventDefault();
757 																}, this.getDialog() );
758 															resetButton.on( 'mouseover', function()
759 																{
760 																	this.addClass( 'cke_btn_over' );
761 																}, resetButton );
762 															resetButton.on( 'mouseout', function()
763 																{
764 																	this.removeClass( 'cke_btn_over' );
765 																}, resetButton );
766 														}
767 														// Activate (Un)LockRatio button
768 														if ( ratioButton )
769 														{
770 															ratioButton.on( 'click', function(evt)
771 																{
772 																	var locked = switchLockRatio( this ),
773 																		oImageOriginal = this.originalElement,
774 																		width = this.getValueOf( 'info', 'txtWidth' );
775 
776 																	if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' && width )
777 																	{
778 																		var height = oImageOriginal.$.height / oImageOriginal.$.width * width;
779 																		if ( !isNaN( height ) )
780 																		{
781 																			this.setValueOf( 'info', 'txtHeight', Math.round( height ) );
782 																			updatePreview( this );
783 																		}
784 																	}
785 																	evt.data && evt.data.preventDefault();
786 																}, this.getDialog() );
787 															ratioButton.on( 'mouseover', function()
788 																{
789 																	this.addClass( 'cke_btn_over' );
790 																}, ratioButton );
791 															ratioButton.on( 'mouseout', function()
792 																{
793 																	this.removeClass( 'cke_btn_over' );
794 																}, ratioButton );
795 														}
796 													},
797 													html : '<div>'+
798 														'<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.lockRatio +
799 														'" class="cke_btn_locked" id="' + btnLockSizesId + '" role="checkbox"><span class="cke_icon"></span><span class="cke_label">' + editor.lang.image.lockRatio + '</span></a>' +
800 														'<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.resetSize +
801 														'" class="cke_btn_reset" id="' + btnResetSizeId + '" role="button"><span class="cke_label">' + editor.lang.image.resetSize + '</span></a>'+
802 														'</div>'
803 												}
804 											]
805 										},
806 										{
807 											type : 'vbox',
808 											padding : 1,
809 											children :
810 											[
811 												{
812 													type : 'text',
813 													id : 'txtBorder',
814 													width: '60px',
815 													label : editor.lang.image.border,
816 													'default' : '',
817 													onKeyUp : function()
818 													{
819 														updatePreview( this.getDialog() );
820 													},
821 													onChange : function()
822 													{
823 														commitInternally.call( this, 'advanced:txtdlgGenStyle' );
824 													},
825 													validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateBorder ),
826 													setup : function( type, element )
827 													{
828 														if ( type == IMAGE )
829 														{
830 															var value,
831 																borderStyle = element.getStyle( 'border-width' );
832 															borderStyle = borderStyle && borderStyle.match( /^(\d+px)(?: \1 \1 \1)?$/ );
833 															value = borderStyle && parseInt( borderStyle[ 1 ], 10 );
834 															isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'border' ) );
835 															this.setValue( value );
836 														}
837 													},
838 													commit : function( type, element, internalCommit )
839 													{
840 														var value = parseInt( this.getValue(), 10 );
841 														if ( type == IMAGE || type == PREVIEW )
842 														{
843 															if ( !isNaN( value ) )
844 															{
845 																element.setStyle( 'border-width', CKEDITOR.tools.cssLength( value ) );
846 																element.setStyle( 'border-style', 'solid' );
847 															}
848 															else if ( !value && this.isChanged() )
849 																element.removeStyle( 'border' );
850 
851 															if ( !internalCommit && type == IMAGE )
852 																element.removeAttribute( 'border' );
853 														}
854 														else if ( type == CLEANUP )
855 														{
856 															element.removeAttribute( 'border' );
857 															element.removeStyle( 'border-width' );
858 															element.removeStyle( 'border-style' );
859 															element.removeStyle( 'border-color' );
860 														}
861 													}
862 												},
863 												{
864 													type : 'text',
865 													id : 'txtHSpace',
866 													width: '60px',
867 													label : editor.lang.image.hSpace,
868 													'default' : '',
869 													onKeyUp : function()
870 													{
871 														updatePreview( this.getDialog() );
872 													},
873 													onChange : function()
874 													{
875 														commitInternally.call( this, 'advanced:txtdlgGenStyle' );
876 													},
877 													validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateHSpace ),
878 													setup : function( type, element )
879 													{
880 														if ( type == IMAGE )
881 														{
882 															var value,
883 																marginLeftPx,
884 																marginRightPx,
885 																marginLeftStyle = element.getStyle( 'margin-left' ),
886 																marginRightStyle = element.getStyle( 'margin-right' );
887 
888 															marginLeftStyle = marginLeftStyle && marginLeftStyle.match( pxLengthRegex );
889 															marginRightStyle = marginRightStyle && marginRightStyle.match( pxLengthRegex );
890 															marginLeftPx = parseInt( marginLeftStyle, 10 );
891 															marginRightPx = parseInt( marginRightStyle, 10 );
892 
893 															value = ( marginLeftPx == marginRightPx ) && marginLeftPx;
894 															isNaN( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'hspace' ) );
895 
896 															this.setValue( value );
897 														}
898 													},
899 													commit : function( type, element, internalCommit )
900 													{
901 														var value = parseInt( this.getValue(), 10 );
902 														if ( type == IMAGE || type == PREVIEW )
903 														{
904 															if ( !isNaN( value ) )
905 															{
906 																element.setStyle( 'margin-left', CKEDITOR.tools.cssLength( value ) );
907 																element.setStyle( 'margin-right', CKEDITOR.tools.cssLength( value ) );
908 															}
909 															else if ( !value && this.isChanged( ) )
910 															{
911 																element.removeStyle( 'margin-left' );
912 																element.removeStyle( 'margin-right' );
913 															}
914 
915 															if ( !internalCommit && type == IMAGE )
916 																element.removeAttribute( 'hspace' );
917 														}
918 														else if ( type == CLEANUP )
919 														{
920 															element.removeAttribute( 'hspace' );
921 															element.removeStyle( 'margin-left' );
922 															element.removeStyle( 'margin-right' );
923 														}
924 													}
925 												},
926 												{
927 													type : 'text',
928 													id : 'txtVSpace',
929 													width : '60px',
930 													label : editor.lang.image.vSpace,
931 													'default' : '',
932 													onKeyUp : function()
933 													{
934 														updatePreview( this.getDialog() );
935 													},
936 													onChange : function()
937 													{
938 														commitInternally.call( this, 'advanced:txtdlgGenStyle' );
939 													},
940 													validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateVSpace ),
941 													setup : function( type, element )
942 													{
943 														if ( type == IMAGE )
944 														{
945 															var value,
946 																marginTopPx,
947 																marginBottomPx,
948 																marginTopStyle = element.getStyle( 'margin-top' ),
949 																marginBottomStyle = element.getStyle( 'margin-bottom' );
950 
951 															marginTopStyle = marginTopStyle && marginTopStyle.match( pxLengthRegex );
952 															marginBottomStyle = marginBottomStyle && marginBottomStyle.match( pxLengthRegex );
953 															marginTopPx = parseInt( marginTopStyle, 10 );
954 															marginBottomPx = parseInt( marginBottomStyle, 10 );
955 
956 															value = ( marginTopPx == marginBottomPx ) && marginTopPx;
957 															isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'vspace' ) );
958 															this.setValue( value );
959 														}
960 													},
961 													commit : function( type, element, internalCommit )
962 													{
963 														var value = parseInt( this.getValue(), 10 );
964 														if ( type == IMAGE || type == PREVIEW )
965 														{
966 															if ( !isNaN( value ) )
967 															{
968 																element.setStyle( 'margin-top', CKEDITOR.tools.cssLength( value ) );
969 																element.setStyle( 'margin-bottom', CKEDITOR.tools.cssLength( value ) );
970 															}
971 															else if ( !value && this.isChanged( ) )
972 															{
973 																element.removeStyle( 'margin-top' );
974 																element.removeStyle( 'margin-bottom' );
975 															}
976 
977 															if ( !internalCommit && type == IMAGE )
978 																element.removeAttribute( 'vspace' );
979 														}
980 														else if ( type == CLEANUP )
981 														{
982 															element.removeAttribute( 'vspace' );
983 															element.removeStyle( 'margin-top' );
984 															element.removeStyle( 'margin-bottom' );
985 														}
986 													}
987 												},
988 												{
989 													id : 'cmbAlign',
990 													type : 'select',
991 													widths : [ '35%','65%' ],
992 													style : 'width:90px',
993 													label : editor.lang.common.align,
994 													'default' : '',
995 													items :
996 													[
997 														[ editor.lang.common.notSet , ''],
998 														[ editor.lang.common.alignLeft , 'left'],
999 														[ editor.lang.common.alignRight , 'right']
1000 														// Backward compatible with v2 on setup when specified as attribute value,
1001 														// while these values are no more available as select options.
1002 														//	[ editor.lang.image.alignAbsBottom , 'absBottom'],
1003 														//	[ editor.lang.image.alignAbsMiddle , 'absMiddle'],
1004 														//  [ editor.lang.image.alignBaseline , 'baseline'],
1005 														//  [ editor.lang.image.alignTextTop , 'text-top'],
1006 														//  [ editor.lang.image.alignBottom , 'bottom'],
1007 														//  [ editor.lang.image.alignMiddle , 'middle'],
1008 														//  [ editor.lang.image.alignTop , 'top']
1009 													],
1010 													onChange : function()
1011 													{
1012 														updatePreview( this.getDialog() );
1013 														commitInternally.call( this, 'advanced:txtdlgGenStyle' );
1014 													},
1015 													setup : function( type, element )
1016 													{
1017 														if ( type == IMAGE )
1018 														{
1019 															var value = element.getStyle( 'float' );
1020 															switch( value )
1021 															{
1022 																// Ignore those unrelated values.
1023 																case 'inherit':
1024 																case 'none':
1025 																	value = '';
1026 															}
1027 
1028 															!value && ( value = ( element.getAttribute( 'align' ) || '' ).toLowerCase() );
1029 															this.setValue( value );
1030 														}
1031 													},
1032 													commit : function( type, element, internalCommit )
1033 													{
1034 														var value = this.getValue();
1035 														if ( type == IMAGE || type == PREVIEW )
1036 														{
1037 															if ( value )
1038 																element.setStyle( 'float', value );
1039 															else
1040 																element.removeStyle( 'float' );
1041 
1042 															if ( !internalCommit && type == IMAGE )
1043 															{
1044 																value = ( element.getAttribute( 'align' ) || '' ).toLowerCase();
1045 																switch( value )
1046 																{
1047 																	// we should remove it only if it matches "left" or "right",
1048 																	// otherwise leave it intact.
1049 																	case 'left':
1050 																	case 'right':
1051 																		element.removeAttribute( 'align' );
1052 																}
1053 															}
1054 														}
1055 														else if ( type == CLEANUP )
1056 															element.removeStyle( 'float' );
1057 
1058 													}
1059 												}
1060 											]
1061 										}
1062 									]
1063 								},
1064 								{
1065 									type : 'vbox',
1066 									height : '250px',
1067 									children :
1068 									[
1069 										{
1070 											type : 'html',
1071 											id : 'htmlPreview',
1072 											style : 'width:95%;',
1073 											html : '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.common.preview ) +'<br>'+
1074 											'<div id="' + imagePreviewLoaderId + '" class="ImagePreviewLoader" style="display:none"><div class="loading"> </div></div>'+
1075 											'<div class="ImagePreviewBox"><table><tr><td>'+
1076 											'<a href="javascript:void(0)" target="_blank" onclick="return false;" id="' + previewLinkId + '">'+
1077 											'<img id="' + previewImageId + '" alt="" /></a>' +
1078 											( editor.config.image_previewText ||
1079 											'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. '+
1080 											'Maecenas feugiat consequat diam. Maecenas metus. Vivamus diam purus, cursus a, commodo non, facilisis vitae, '+
1081 											'nulla. Aenean dictum lacinia tortor. Nunc iaculis, nibh non iaculis aliquam, orci felis euismod neque, sed ornare massa mauris sed velit. Nulla pretium mi et risus. Fusce mi pede, tempor id, cursus ac, ullamcorper nec, enim. Sed tortor. Curabitur molestie. Duis velit augue, condimentum at, ultrices a, luctus ut, orci. Donec pellentesque egestas eros. Integer cursus, augue in cursus faucibus, eros pede bibendum sem, in tempus tellus justo quis ligula. Etiam eget tortor. Vestibulum rutrum, est ut placerat elementum, lectus nisl aliquam velit, tempor aliquam eros nunc nonummy metus. In eros metus, gravida a, gravida sed, lobortis id, turpis. Ut ultrices, ipsum at venenatis fringilla, sem nulla lacinia tellus, eget aliquet turpis mauris non enim. Nam turpis. Suspendisse lacinia. Curabitur ac tortor ut ipsum egestas elementum. Nunc imperdiet gravida mauris.' ) +
1082 											'</td></tr></table></div></div>'
1083 										}
1084 									]
1085 								}
1086 							]
1087 						}
1088 					]
1089 				},
1090 				{
1091 					id : 'Link',
1092 					label : editor.lang.link.title,
1093 					padding : 0,
1094 					elements :
1095 					[
1096 						{
1097 							id : 'txtUrl',
1098 							type : 'text',
1099 							label : editor.lang.common.url,
1100 							style : 'width: 100%',
1101 							'default' : '',
1102 							setup : function( type, element )
1103 							{
1104 								if ( type == LINK )
1105 								{
1106 									var href = element.data( 'cke-saved-href' );
1107 									if ( !href )
1108 										href = element.getAttribute( 'href' );
1109 									this.setValue( href );
1110 								}
1111 							},
1112 							commit : function( type, element )
1113 							{
1114 								if ( type == LINK )
1115 								{
1116 									if ( this.getValue() || this.isChanged() )
1117 									{
1118 										var url = decodeURI( this.getValue() );
1119 										element.data( 'cke-saved-href', url );
1120 										element.setAttribute( 'href', url );
1121 
1122 										if ( this.getValue() || !editor.config.image_removeLinkByEmptyURL )
1123 											this.getDialog().addLink = true;
1124 									}
1125 								}
1126 							}
1127 						},
1128 						{
1129 							type : 'button',
1130 							id : 'browse',
1131 							filebrowser :
1132 							{
1133 								action : 'Browse',
1134 								target: 'Link:txtUrl',
1135 								url: editor.config.filebrowserImageBrowseLinkUrl
1136 							},
1137 							style : 'float:right',
1138 							hidden : true,
1139 							label : editor.lang.common.browseServer
1140 						},
1141 						{
1142 							id : 'cmbTarget',
1143 							type : 'select',
1144 							label : editor.lang.common.target,
1145 							'default' : '',
1146 							items :
1147 							[
1148 								[ editor.lang.common.notSet , ''],
1149 								[ editor.lang.common.targetNew , '_blank'],
1150 								[ editor.lang.common.targetTop , '_top'],
1151 								[ editor.lang.common.targetSelf , '_self'],
1152 								[ editor.lang.common.targetParent , '_parent']
1153 							],
1154 							setup : function( type, element )
1155 							{
1156 								if ( type == LINK )
1157 									this.setValue( element.getAttribute( 'target' ) || '' );
1158 							},
1159 							commit : function( type, element )
1160 							{
1161 								if ( type == LINK )
1162 								{
1163 									if ( this.getValue() || this.isChanged() )
1164 										element.setAttribute( 'target', this.getValue() );
1165 								}
1166 							}
1167 						}
1168 					]
1169 				},
1170 				{
1171 					id : 'Upload',
1172 					hidden : true,
1173 					filebrowser : 'uploadButton',
1174 					label : editor.lang.image.upload,
1175 					elements :
1176 					[
1177 						{
1178 							type : 'file',
1179 							id : 'upload',
1180 							label : editor.lang.image.btnUpload,
1181 							style: 'height:40px',
1182 							size : 38
1183 						},
1184 						{
1185 							type : 'fileButton',
1186 							id : 'uploadButton',
1187 							filebrowser : 'info:txtUrl',
1188 							label : editor.lang.image.btnUpload,
1189 							'for' : [ 'Upload', 'upload' ]
1190 						}
1191 					]
1192 				},
1193 				{
1194 					id : 'advanced',
1195 					label : editor.lang.common.advancedTab,
1196 					elements :
1197 					[
1198 						{
1199 							type : 'hbox',
1200 							widths : [ '50%', '25%', '25%' ],
1201 							children :
1202 							[
1203 								{
1204 									type : 'text',
1205 									id : 'linkId',
1206 									label : editor.lang.common.id,
1207 									setup : function( type, element )
1208 									{
1209 										if ( type == IMAGE )
1210 											this.setValue( element.getAttribute( 'id' ) );
1211 									},
1212 									commit : function( type, element )
1213 									{
1214 										if ( type == IMAGE )
1215 										{
1216 											if ( this.getValue() || this.isChanged() )
1217 												element.setAttribute( 'id', this.getValue() );
1218 										}
1219 									}
1220 								},
1221 								{
1222 									id : 'cmbLangDir',
1223 									type : 'select',
1224 									style : 'width : 100px;',
1225 									label : editor.lang.common.langDir,
1226 									'default' : '',
1227 									items :
1228 									[
1229 										[ editor.lang.common.notSet, '' ],
1230 										[ editor.lang.common.langDirLtr, 'ltr' ],
1231 										[ editor.lang.common.langDirRtl, 'rtl' ]
1232 									],
1233 									setup : function( type, element )
1234 									{
1235 										if ( type == IMAGE )
1236 											this.setValue( element.getAttribute( 'dir' ) );
1237 									},
1238 									commit : function( type, element )
1239 									{
1240 										if ( type == IMAGE )
1241 										{
1242 											if ( this.getValue() || this.isChanged() )
1243 												element.setAttribute( 'dir', this.getValue() );
1244 										}
1245 									}
1246 								},
1247 								{
1248 									type : 'text',
1249 									id : 'txtLangCode',
1250 									label : editor.lang.common.langCode,
1251 									'default' : '',
1252 									setup : function( type, element )
1253 									{
1254 										if ( type == IMAGE )
1255 											this.setValue( element.getAttribute( 'lang' ) );
1256 									},
1257 									commit : function( type, element )
1258 									{
1259 										if ( type == IMAGE )
1260 										{
1261 											if ( this.getValue() || this.isChanged() )
1262 												element.setAttribute( 'lang', this.getValue() );
1263 										}
1264 									}
1265 								}
1266 							]
1267 						},
1268 						{
1269 							type : 'text',
1270 							id : 'txtGenLongDescr',
1271 							label : editor.lang.common.longDescr,
1272 							setup : function( type, element )
1273 							{
1274 								if ( type == IMAGE )
1275 									this.setValue( element.getAttribute( 'longDesc' ) );
1276 							},
1277 							commit : function( type, element )
1278 							{
1279 								if ( type == IMAGE )
1280 								{
1281 									if ( this.getValue() || this.isChanged() )
1282 										element.setAttribute( 'longDesc', this.getValue() );
1283 								}
1284 							}
1285 						},
1286 						{
1287 							type : 'hbox',
1288 							widths : [ '50%', '50%' ],
1289 							children :
1290 							[
1291 								{
1292 									type : 'text',
1293 									id : 'txtGenClass',
1294 									label : editor.lang.common.cssClass,
1295 									'default' : '',
1296 									setup : function( type, element )
1297 									{
1298 										if ( type == IMAGE )
1299 											this.setValue( element.getAttribute( 'class' ) );
1300 									},
1301 									commit : function( type, element )
1302 									{
1303 										if ( type == IMAGE )
1304 										{
1305 											if ( this.getValue() || this.isChanged() )
1306 												element.setAttribute( 'class', this.getValue() );
1307 										}
1308 									}
1309 								},
1310 								{
1311 									type : 'text',
1312 									id : 'txtGenTitle',
1313 									label : editor.lang.common.advisoryTitle,
1314 									'default' : '',
1315 									onChange : function()
1316 									{
1317 										updatePreview( this.getDialog() );
1318 									},
1319 									setup : function( type, element )
1320 									{
1321 										if ( type == IMAGE )
1322 											this.setValue( element.getAttribute( 'title' ) );
1323 									},
1324 									commit : function( type, element )
1325 									{
1326 										if ( type == IMAGE )
1327 										{
1328 											if ( this.getValue() || this.isChanged() )
1329 												element.setAttribute( 'title', this.getValue() );
1330 										}
1331 										else if ( type == PREVIEW )
1332 										{
1333 											element.setAttribute( 'title', this.getValue() );
1334 										}
1335 										else if ( type == CLEANUP )
1336 										{
1337 											element.removeAttribute( 'title' );
1338 										}
1339 									}
1340 								}
1341 							]
1342 						},
1343 						{
1344 							type : 'text',
1345 							id : 'txtdlgGenStyle',
1346 							label : editor.lang.common.cssStyle,
1347 							validate : CKEDITOR.dialog.validate.inlineStyle( editor.lang.common.invalidInlineStyle ),
1348 							'default' : '',
1349 							setup : function( type, element )
1350 							{
1351 								if ( type == IMAGE )
1352 								{
1353 									var genStyle = element.getAttribute( 'style' );
1354 									if ( !genStyle && element.$.style.cssText )
1355 										genStyle = element.$.style.cssText;
1356 									this.setValue( genStyle );
1357 
1358 									var height = element.$.style.height,
1359 										width = element.$.style.width,
1360 										aMatchH  = ( height ? height : '' ).match( regexGetSize ),
1361 										aMatchW  = ( width ? width : '').match( regexGetSize );
1362 
1363 									this.attributesInStyle =
1364 									{
1365 										height : !!aMatchH,
1366 										width : !!aMatchW
1367 									};
1368 								}
1369 							},
1370 							onChange : function ()
1371 							{
1372 								commitInternally.call( this,
1373 									[ 'info:cmbFloat', 'info:cmbAlign',
1374 									  'info:txtVSpace', 'info:txtHSpace',
1375 									  'info:txtBorder',
1376 									  'info:txtWidth', 'info:txtHeight' ] );
1377 								updatePreview( this );
1378 							},
1379 							commit : function( type, element )
1380 							{
1381 								if ( type == IMAGE && ( this.getValue() || this.isChanged() ) )
1382 								{
1383 									element.setAttribute( 'style', this.getValue() );
1384 								}
1385 							}
1386 						}
1387 					]
1388 				}
1389 			]
1390 		};
1391 	};
1392 
1393 	CKEDITOR.dialog.add( 'image', function( editor )
1394 		{
1395 			return imageDialog( editor, 'image' );
1396 		});
1397 
1398 	CKEDITOR.dialog.add( 'imagebutton', function( editor )
1399 		{
1400 			return imageDialog( editor, 'imagebutton' );
1401 		});
1402 })();
1403