X-Git-Url: https://scm.cri.minesparis.psl.eu/git/ckeditor.git/blobdiff_plain/256592bf803e851aa7fc953e08a6e9e58d970f8c..871bad8291b6dbc29d489d95d185458caab25158:/skins/ckeditor/_source/plugins/dialog/plugin.js diff --git a/skins/ckeditor/_source/plugins/dialog/plugin.js b/skins/ckeditor/_source/plugins/dialog/plugin.js new file mode 100644 index 0000000..d24a80f --- /dev/null +++ b/skins/ckeditor/_source/plugins/dialog/plugin.js @@ -0,0 +1,3289 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The floating dialog plugin. + */ + +/** + * No resize for this dialog. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_NONE = 0; + +/** + * Only allow horizontal resizing for this dialog, disable vertical resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_WIDTH = 1; + +/** + * Only allow vertical resizing for this dialog, disable horizontal resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_HEIGHT = 2; + +/* + * Allow the dialog to be resized in both directions. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_BOTH = 3; + +(function() +{ + var cssLength = CKEDITOR.tools.cssLength; + function isTabVisible( tabId ) + { + return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight; + } + + function getPreviousVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length; + + for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + function getNextVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ); + + for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + + function clearOrRecoverTextInputValue( container, isRecover ) + { + var inputs = container.$.getElementsByTagName( 'input' ); + for ( var i = 0, length = inputs.length; i < length ; i++ ) + { + var item = new CKEDITOR.dom.element( inputs[ i ] ); + + if ( item.getAttribute( 'type' ).toLowerCase() == 'text' ) + { + if ( isRecover ) + { + item.setAttribute( 'value', item.getCustomData( 'fake_value' ) || '' ); + item.removeCustomData( 'fake_value' ); + } + else + { + item.setCustomData( 'fake_value', item.getAttribute( 'value' ) ); + item.setAttribute( 'value', '' ); + } + } + } + } + + // Handle dialog element validation state UI changes. + function handleFieldValidated( isValid, msg ) + { + var input = this.getInputElement(); + if ( input ) + { + isValid ? input.removeAttribute( 'aria-invalid' ) + : input.setAttribute( 'aria-invalid', true ); + } + + if ( !isValid ) + { + if ( this.select ) + this.select(); + else + this.focus(); + } + + msg && alert( msg ); + + this.fire( 'validated', { valid : isValid, msg : msg } ); + } + + function resetField() + { + var input = this.getInputElement(); + input && input.removeAttribute( 'aria-invalid' ); + } + + + /** + * This is the base class for runtime dialog objects. An instance of this + * class represents a single named dialog for a single editor instance. + * @param {Object} editor The editor which created the dialog. + * @param {String} dialogName The dialog's registered name. + * @constructor + * @example + * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' ); + */ + CKEDITOR.dialog = function( editor, dialogName ) + { + // Load the dialog definition. + var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + defaultDefinition = CKEDITOR.tools.clone( defaultDialogDefinition ), + buttonsOrder = editor.config.dialog_buttonsOrder || 'OS', + dir = editor.lang.dir; + + if ( ( buttonsOrder == 'OS' && CKEDITOR.env.mac ) || // The buttons in MacOS Apps are in reverse order (#4750) + ( buttonsOrder == 'rtl' && dir == 'ltr' ) || + ( buttonsOrder == 'ltr' && dir == 'rtl' ) ) + defaultDefinition.buttons.reverse(); + + + // Completes the definition with the default values. + definition = CKEDITOR.tools.extend( definition( editor ), defaultDefinition ); + + // Clone a functionally independent copy for this dialog. + definition = CKEDITOR.tools.clone( definition ); + + // Create a complex definition object, extending it with the API + // functions. + definition = new definitionObject( this, definition ); + + var doc = CKEDITOR.document; + + var themeBuilt = editor.theme.buildDialog( editor ); + + // Initialize some basic parameters. + this._ = + { + editor : editor, + element : themeBuilt.element, + name : dialogName, + contentSize : { width : 0, height : 0 }, + size : { width : 0, height : 0 }, + contents : {}, + buttons : {}, + accessKeyMap : {}, + + // Initialize the tab and page map. + tabs : {}, + tabIdList : [], + currentTabId : null, + currentTabIndex : null, + pageCount : 0, + lastTab : null, + tabBarMode : false, + + // Initialize the tab order array for input widgets. + focusList : [], + currentFocusIndex : 0, + hasFocus : false + }; + + this.parts = themeBuilt.parts; + + CKEDITOR.tools.setTimeout( function() + { + editor.fire( 'ariaWidget', this.parts.contents ); + }, + 0, this ); + + // Set the startup styles for the dialog, avoiding it enlarging the + // page size on the dialog creation. + var startStyles = { + position : CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed', + top : 0, + visibility : 'hidden' + }; + + startStyles[ dir == 'rtl' ? 'right' : 'left' ] = 0; + this.parts.dialog.setStyles( startStyles ); + + + // Call the CKEDITOR.event constructor to initialize this instance. + CKEDITOR.event.call( this ); + + // Fire the "dialogDefinition" event, making it possible to customize + // the dialog definition. + this.definition = definition = CKEDITOR.fire( 'dialogDefinition', + { + name : dialogName, + definition : definition + } + , editor ).definition; + + var tabsToRemove = {}; + // Cache tabs that should be removed. + if ( !( 'removeDialogTabs' in editor._ ) && editor.config.removeDialogTabs ) + { + var removeContents = editor.config.removeDialogTabs.split( ';' ); + + for ( i = 0; i < removeContents.length; i++ ) + { + var parts = removeContents[ i ].split( ':' ); + if ( parts.length == 2 ) + { + var removeDialogName = parts[ 0 ]; + if ( !tabsToRemove[ removeDialogName ] ) + tabsToRemove[ removeDialogName ] = []; + tabsToRemove[ removeDialogName ].push( parts[ 1 ] ); + } + } + editor._.removeDialogTabs = tabsToRemove; + } + + // Remove tabs of this dialog. + if ( editor._.removeDialogTabs && ( tabsToRemove = editor._.removeDialogTabs[ dialogName ] ) ) + { + for ( i = 0; i < tabsToRemove.length; i++ ) + definition.removeContents( tabsToRemove[ i ] ); + } + + // Initialize load, show, hide, ok and cancel events. + if ( definition.onLoad ) + this.on( 'load', definition.onLoad ); + + if ( definition.onShow ) + this.on( 'show', definition.onShow ); + + if ( definition.onHide ) + this.on( 'hide', definition.onHide ); + + if ( definition.onOk ) + { + this.on( 'ok', function( evt ) + { + // Dialog confirm might probably introduce content changes (#5415). + editor.fire( 'saveSnapshot' ); + setTimeout( function () { editor.fire( 'saveSnapshot' ); }, 0 ); + if ( definition.onOk.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + if ( definition.onCancel ) + { + this.on( 'cancel', function( evt ) + { + if ( definition.onCancel.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + var me = this; + + // Iterates over all items inside all content in the dialog, calling a + // function for each of them. + var iterContents = function( func ) + { + var contents = me._.contents, + stop = false; + + for ( var i in contents ) + { + for ( var j in contents[i] ) + { + stop = func.call( this, contents[i][j] ); + if ( stop ) + return; + } + } + }; + + this.on( 'ok', function( evt ) + { + iterContents( function( item ) + { + if ( item.validate ) + { + var retval = item.validate( this ), + invalid = typeof ( retval ) == 'string' || retval === false; + + if ( invalid ) + { + evt.data.hide = false; + evt.stop(); + } + + handleFieldValidated.call( item, !invalid, typeof retval == 'string' ? retval : undefined ); + return invalid; + } + }); + }, this, null, 0 ); + + this.on( 'cancel', function( evt ) + { + iterContents( function( item ) + { + if ( item.isChanged() ) + { + if ( !confirm( editor.lang.common.confirmCancel ) ) + evt.data.hide = false; + return true; + } + }); + }, this, null, 0 ); + + this.parts.close.on( 'click', function( evt ) + { + if ( this.fire( 'cancel', { hide : true } ).hide !== false ) + this.hide(); + evt.data.preventDefault(); + }, this ); + + // Sort focus list according to tab order definitions. + function setupFocus() + { + var focusList = me._.focusList; + focusList.sort( function( a, b ) + { + // Mimics browser tab order logics; + if ( a.tabIndex != b.tabIndex ) + return b.tabIndex - a.tabIndex; + // Sort is not stable in some browsers, + // fall-back the comparator to 'focusIndex'; + else + return a.focusIndex - b.focusIndex; + }); + + var size = focusList.length; + for ( var i = 0; i < size; i++ ) + focusList[ i ].focusIndex = i; + } + + function changeFocus( forward ) + { + var focusList = me._.focusList, + offset = forward ? 1 : -1; + if ( focusList.length < 1 ) + return; + + var current = me._.currentFocusIndex; + + // Trigger the 'blur' event of any input element before anything, + // since certain UI updates may depend on it. + try + { + focusList[ current ].getInputElement().$.blur(); + } + catch( e ){} + + var startIndex = ( current + offset + focusList.length ) % focusList.length, + currentIndex = startIndex; + while ( !focusList[ currentIndex ].isFocusable() ) + { + currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; + if ( currentIndex == startIndex ) + break; + } + focusList[ currentIndex ].focus(); + + // Select whole field content. + if ( focusList[ currentIndex ].type == 'text' ) + focusList[ currentIndex ].select(); + } + + this.changeFocus = changeFocus; + + var processed; + + function focusKeydownHandler( evt ) + { + // If I'm not the top dialog, ignore. + if ( me != CKEDITOR.dialog._.currentTop ) + return; + + var keystroke = evt.data.getKeystroke(), + rtl = editor.lang.dir == 'rtl'; + + processed = 0; + if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) + { + var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); + + // Handling Tab and Shift-Tab. + if ( me._.tabBarMode ) + { + // Change tabs. + var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + } + else + { + // Change the focus of inputs. + changeFocus( !shiftPressed ); + } + + processed = 1; + } + else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 ) + { + // Alt-F10 puts focus into the current tab item in the tab bar. + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) + { + // Arrow keys - used for changing tabs. + nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode ) + { + this.selectPage( this._.currentTabId ); + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + processed = 1; + } + + if ( processed ) + { + evt.stop(); + evt.data.preventDefault(); + } + } + + function focusKeyPressHandler( evt ) + { + processed && evt.data.preventDefault(); + } + + var dialogElement = this._.element; + // Add the dialog keyboard handlers. + this.on( 'show', function() + { + dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 ); + // Some browsers instead, don't cancel key events in the keydown, but in the + // keypress. So we must do a longer trip in those cases. (#4531) + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + dialogElement.on( 'keypress', focusKeyPressHandler, this ); + + } ); + this.on( 'hide', function() + { + dialogElement.removeListener( 'keydown', focusKeydownHandler ); + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + dialogElement.removeListener( 'keypress', focusKeyPressHandler ); + + // Reset fields state when closing dialog. + iterContents( function( item ) { resetField.apply( item ); } ); + } ); + this.on( 'iframeAdded', function( evt ) + { + var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document ); + doc.on( 'keydown', focusKeydownHandler, this, null, 0 ); + } ); + + // Auto-focus logic in dialog. + this.on( 'show', function() + { + // Setup tabIndex on showing the dialog instead of on loading + // to allow dynamic tab order happen in dialog definition. + setupFocus(); + + if ( editor.config.dialog_startupFocusTab + && me._.pageCount > 1 ) + { + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + } + else if ( !this._.hasFocus ) + { + this._.currentFocusIndex = -1; + + // Decide where to put the initial focus. + if ( definition.onFocus ) + { + var initialFocus = definition.onFocus.call( this ); + // Focus the field that the user specified. + initialFocus && initialFocus.focus(); + } + // Focus the first field in layout order. + else + changeFocus( true ); + + /* + * IE BUG: If the initial focus went into a non-text element (e.g. button), + * then IE would still leave the caret inside the editing area. + */ + if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var $selection = editor.document.$.selection, + $range = $selection.createRange(); + + if ( $range ) + { + if ( $range.parentElement && $range.parentElement().ownerDocument == editor.document.$ + || $range.item && $range.item( 0 ).ownerDocument == editor.document.$ ) + { + var $myRange = document.body.createTextRange(); + $myRange.moveToElementText( this.getElement().getFirst().$ ); + $myRange.collapse( true ); + $myRange.select(); + } + } + } + } + }, this, null, 0xffffffff ); + + // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661). + // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken. + if ( CKEDITOR.env.ie6Compat ) + { + this.on( 'load', function( evt ) + { + var outer = this.getElement(), + inner = outer.getFirst(); + inner.remove(); + inner.appendTo( outer ); + }, this ); + } + + initDragAndDrop( this ); + initResizeHandles( this ); + + // Insert the title. + ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title ); + + // Insert the tabs and contents. + for ( var i = 0 ; i < definition.contents.length ; i++ ) + { + var page = definition.contents[i]; + page && this.addPage( page ); + } + + this.parts[ 'tabs' ].on( 'click', function( evt ) + { + var target = evt.data.getTarget(); + // If we aren't inside a tab, bail out. + if ( target.hasClass( 'cke_dialog_tab' ) ) + { + // Get the ID of the tab, without the 'cke_' prefix and the unique number suffix. + var id = target.$.id; + this.selectPage( id.substring( 4, id.lastIndexOf( '_' ) ) ); + + if ( this._.tabBarMode ) + { + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + } + evt.data.preventDefault(); + } + }, this ); + + // Insert buttons. + var buttonsHtml = [], + buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this, + { + type : 'hbox', + className : 'cke_dialog_footer_buttons', + widths : [], + children : definition.buttons + }, buttonsHtml ).getChild(); + this.parts.footer.setHtml( buttonsHtml.join( '' ) ); + + for ( i = 0 ; i < buttons.length ; i++ ) + this._.buttons[ buttons[i].id ] = buttons[i]; + }; + + // Focusable interface. Use it via dialog.addFocusable. + function Focusable( dialog, element, index ) + { + this.element = element; + this.focusIndex = index; + // TODO: support tabIndex for focusables. + this.tabIndex = 0; + this.isFocusable = function() + { + return !element.getAttribute( 'disabled' ) && element.isVisible(); + }; + this.focus = function() + { + dialog._.currentFocusIndex = this.focusIndex; + this.element.focus(); + }; + // Bind events + element.on( 'keydown', function( e ) + { + if ( e.data.getKeystroke() in { 32:1, 13:1 } ) + this.fire( 'click' ); + } ); + element.on( 'focus', function() + { + this.fire( 'mouseover' ); + } ); + element.on( 'blur', function() + { + this.fire( 'mouseout' ); + } ); + } + + CKEDITOR.dialog.prototype = + { + destroy : function() + { + this.hide(); + this._.element.remove(); + }, + + /** + * Resizes the dialog. + * @param {Number} width The width of the dialog in pixels. + * @param {Number} height The height of the dialog in pixels. + * @function + * @example + * dialogObj.resize( 800, 640 ); + */ + resize : (function() + { + return function( width, height ) + { + if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) + return; + + CKEDITOR.dialog.fire( 'resize', + { + dialog : this, + skin : this._.editor.skinName, + width : width, + height : height + }, this._.editor ); + + this.fire( 'resize', + { + skin : this._.editor.skinName, + width : width, + height : height + }, this._.editor ); + + // Update dialog position when dimension get changed in RTL. + if ( this._.editor.lang.dir == 'rtl' && this._.position ) + this._.position.x = CKEDITOR.document.getWindow().getViewPaneSize().width - + this._.contentSize.width - parseInt( this._.element.getFirst().getStyle( 'right' ), 10 ); + + this._.contentSize = { width : width, height : height }; + }; + })(), + + /** + * Gets the current size of the dialog in pixels. + * @returns {Object} An object with "width" and "height" properties. + * @example + * var width = dialogObj.getSize().width; + */ + getSize : function() + { + var element = this._.element.getFirst(); + return { width : element.$.offsetWidth || 0, height : element.$.offsetHeight || 0}; + }, + + /** + * Moves the dialog to an (x, y) coordinate relative to the window. + * @function + * @param {Number} x The target x-coordinate. + * @param {Number} y The target y-coordinate. + * @param {Boolean} save Flag indicate whether the dialog position should be remembered on next open up. + * @example + * dialogObj.move( 10, 40 ); + */ + move : (function() + { + var isFixed; + return function( x, y, save ) + { + // The dialog may be fixed positioned or absolute positioned. Ask the + // browser what is the current situation first. + var element = this._.element.getFirst(), + rtl = this._.editor.lang.dir == 'rtl'; + + if ( isFixed === undefined ) + isFixed = element.getComputedStyle( 'position' ) == 'fixed'; + + if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) + return; + + // Save the current position. + this._.position = { x : x, y : y }; + + // If not fixed positioned, add scroll position to the coordinates. + if ( !isFixed ) + { + var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); + x += scrollPosition.x; + y += scrollPosition.y; + } + + // Translate coordinate for RTL. + if ( rtl ) + { + var dialogSize = this.getSize(), + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(); + x = viewPaneSize.width - dialogSize.width - x; + } + + var styles = { 'top' : ( y > 0 ? y : 0 ) + 'px' }; + styles[ rtl ? 'right' : 'left' ] = ( x > 0 ? x : 0 ) + 'px'; + + element.setStyles( styles ); + + save && ( this._.moved = 1 ); + }; + })(), + + /** + * Gets the dialog's position in the window. + * @returns {Object} An object with "x" and "y" properties. + * @example + * var dialogX = dialogObj.getPosition().x; + */ + getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); }, + + /** + * Shows the dialog box. + * @example + * dialogObj.show(); + */ + show : function() + { + // Insert the dialog's element to the root document. + var element = this._.element; + var definition = this.definition; + if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) + element.appendTo( CKEDITOR.document.getBody() ); + else + element.setStyle( 'display', 'block' ); + + // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + { + var dialogElement = this.parts.dialog; + dialogElement.setStyle( 'position', 'absolute' ); + setTimeout( function() + { + dialogElement.setStyle( 'position', 'fixed' ); + }, 0 ); + } + + + // First, set the dialog to an appropriate size. + this.resize( this._.contentSize && this._.contentSize.width || definition.width || definition.minWidth, + this._.contentSize && this._.contentSize.height || definition.height || definition.minHeight ); + + // Reset all inputs back to their default value. + this.reset(); + + // Select the first tab by default. + this.selectPage( this.definition.contents[0].id ); + + // Set z-index. + if ( CKEDITOR.dialog._.currentZIndex === null ) + CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex; + this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 ); + + // Maintain the dialog ordering and dialog cover. + // Also register key handlers if first dialog. + if ( CKEDITOR.dialog._.currentTop === null ) + { + CKEDITOR.dialog._.currentTop = this; + this._.parentDialog = null; + showCover( this._.editor ); + + element.on( 'keydown', accessKeyDownHandler ); + element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); + + // Prevent some keys from bubbling up. (#4269) + for ( var event in { keyup :1, keydown :1, keypress :1 } ) + element.on( event, preventKeyBubbling ); + } + else + { + this._.parentDialog = CKEDITOR.dialog._.currentTop; + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 ); + CKEDITOR.dialog._.currentTop = this; + } + + // Register the Esc hotkeys. + registerAccessKey( this, this, '\x1b', null, function() + { + this.getButton( 'cancel' ) && this.getButton( 'cancel' ).click(); + } ); + + // Reset the hasFocus state. + this._.hasFocus = false; + + CKEDITOR.tools.setTimeout( function() + { + this.layout(); + this.parts.dialog.setStyle( 'visibility', '' ); + + // Execute onLoad for the first show. + this.fireOnce( 'load', {} ); + CKEDITOR.ui.fire( 'ready', this ); + + this.fire( 'show', {} ); + this._.editor.fire( 'dialogShow', this ); + + // Save the initial values of the dialog. + this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } ); + + }, + 100, this ); + }, + + /** + * Rearrange the dialog to its previous position or the middle of the window. + * @since 3.5 + */ + layout : function() + { + var viewSize = CKEDITOR.document.getWindow().getViewPaneSize(), + dialogSize = this.getSize(); + + this.move( this._.moved ? this._.position.x : ( viewSize.width - dialogSize.width ) / 2, + this._.moved ? this._.position.y : ( viewSize.height - dialogSize.height ) / 2 ); + }, + + /** + * Executes a function for each UI element. + * @param {Function} fn Function to execute for each UI element. + * @returns {CKEDITOR.dialog} The current dialog object. + */ + foreach : function( fn ) + { + for ( var i in this._.contents ) + { + for ( var j in this._.contents[i] ) + fn.call( this, this._.contents[i][j] ); + } + return this; + }, + + /** + * Resets all input values in the dialog. + * @example + * dialogObj.reset(); + * @returns {CKEDITOR.dialog} The current dialog object. + */ + reset : (function() + { + var fn = function( widget ){ if ( widget.reset ) widget.reset( 1 ); }; + return function(){ this.foreach( fn ); return this; }; + })(), + + + /** + * Calls the {@link CKEDITOR.dialog.definition.uiElement#setup} method of each of the UI elements, with the arguments passed through it. + * It is usually being called when the dialog is opened, to put the initial value inside the field. + * @example + * dialogObj.setupContent(); + * @example + * var timestamp = ( new Date() ).valueOf(); + * dialogObj.setupContent( timestamp ); + */ + setupContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + if ( widget.setup ) + widget.setup.apply( widget, args ); + }); + }, + + /** + * Calls the {@link CKEDITOR.dialog.definition.uiElement#commit} method of each of the UI elements, with the arguments passed through it. + * It is usually being called when the user confirms the dialog, to process the values. + * @example + * dialogObj.commitContent(); + * @example + * var timestamp = ( new Date() ).valueOf(); + * dialogObj.commitContent( timestamp ); + */ + commitContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + // Make sure IE triggers "change" event on last focused input before closing the dialog. (#7915) + if ( CKEDITOR.env.ie && this._.currentFocusIndex == widget.focusIndex ) + widget.getInputElement().$.blur(); + + if ( widget.commit ) + widget.commit.apply( widget, args ); + }); + }, + + /** + * Hides the dialog box. + * @example + * dialogObj.hide(); + */ + hide : function() + { + if ( !this.parts.dialog.isVisible() ) + return; + + this.fire( 'hide', {} ); + this._.editor.fire( 'dialogHide', this ); + var element = this._.element; + element.setStyle( 'display', 'none' ); + this.parts.dialog.setStyle( 'visibility', 'hidden' ); + // Unregister all access keys associated with this dialog. + unregisterAccessKey( this ); + + // Close any child(top) dialogs first. + while( CKEDITOR.dialog._.currentTop != this ) + CKEDITOR.dialog._.currentTop.hide(); + + // Maintain dialog ordering and remove cover if needed. + if ( !this._.parentDialog ) + hideCover(); + else + { + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) ); + } + CKEDITOR.dialog._.currentTop = this._.parentDialog; + + // Deduct or clear the z-index. + if ( !this._.parentDialog ) + { + CKEDITOR.dialog._.currentZIndex = null; + + // Remove access key handlers. + element.removeListener( 'keydown', accessKeyDownHandler ); + element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); + + // Remove bubbling-prevention handler. (#4269) + for ( var event in { keyup :1, keydown :1, keypress :1 } ) + element.removeListener( event, preventKeyBubbling ); + + var editor = this._.editor; + editor.focus(); + + if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.unlock( true ); + } + } + else + CKEDITOR.dialog._.currentZIndex -= 10; + + delete this._.parentDialog; + // Reset the initial values of the dialog. + this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } ); + }, + + /** + * Adds a tabbed page into the dialog. + * @param {Object} contents Content definition. + * @example + */ + addPage : function( contents ) + { + var pageHtml = [], + titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '', + elements = contents.elements, + vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this, + { + type : 'vbox', + className : 'cke_dialog_page_contents', + children : contents.elements, + expand : !!contents.expand, + padding : contents.padding, + style : contents.style || 'width: 100%;height:100%' + }, pageHtml ); + + // Create the HTML for the tab and the content block. + var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); + page.setAttribute( 'role', 'tabpanel' ); + + var env = CKEDITOR.env; + var tabId = 'cke_' + contents.id + '_' + CKEDITOR.tools.getNextNumber(), + tab = CKEDITOR.dom.element.createFromHtml( [ + ' 0 ? ' cke_last' : 'cke_first' ), + titleHtml, + ( !!contents.hidden ? ' style="display:none"' : '' ), + ' id="', tabId, '"', + env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"', + ' tabIndex="-1"', + ' hidefocus="true"', + ' role="tab">', + contents.label, + '' + ].join( '' ) ); + + page.setAttribute( 'aria-labelledby', tabId ); + + // Take records for the tabs and elements created. + this._.tabs[ contents.id ] = [ tab, page ]; + this._.tabIdList.push( contents.id ); + !contents.hidden && this._.pageCount++; + this._.lastTab = tab; + this.updateStyle(); + + var contentMap = this._.contents[ contents.id ] = {}, + cursor, + children = vbox.getChild(); + + while ( ( cursor = children.shift() ) ) + { + contentMap[ cursor.id ] = cursor; + if ( typeof( cursor.getChild ) == 'function' ) + children.push.apply( children, cursor.getChild() ); + } + + // Attach the DOM nodes. + + page.setAttribute( 'name', contents.id ); + page.appendTo( this.parts.contents ); + + tab.unselectable(); + this.parts.tabs.append( tab ); + + // Add access key handlers if access key is defined. + if ( contents.accessKey ) + { + registerAccessKey( this, this, 'CTRL+' + contents.accessKey, + tabAccessKeyDown, tabAccessKeyUp ); + this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id; + } + }, + + /** + * Activates a tab page in the dialog by its id. + * @param {String} id The id of the dialog tab to be activated. + * @example + * dialogObj.selectPage( 'tab_1' ); + */ + selectPage : function( id ) + { + if ( this._.currentTabId == id ) + return; + + // Returning true means that the event has been canceled + if ( this.fire( 'selectPage', { page : id, currentPage : this._.currentTabId } ) === true ) + return; + + // Hide the non-selected tabs and pages. + for ( var i in this._.tabs ) + { + var tab = this._.tabs[i][0], + page = this._.tabs[i][1]; + if ( i != id ) + { + tab.removeClass( 'cke_dialog_tab_selected' ); + page.hide(); + } + page.setAttribute( 'aria-hidden', i != id ); + } + + var selected = this._.tabs[ id ]; + selected[ 0 ].addClass( 'cke_dialog_tab_selected' ); + + // [IE] an invisible input[type='text'] will enlarge it's width + // if it's value is long when it shows, so we clear it's value + // before it shows and then recover it (#5649) + if ( CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat ) + { + clearOrRecoverTextInputValue( selected[ 1 ] ); + selected[ 1 ].show(); + setTimeout( function() + { + clearOrRecoverTextInputValue( selected[ 1 ], 1 ); + }, 0 ); + } + else + selected[ 1 ].show(); + + this._.currentTabId = id; + this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id ); + }, + + // Dialog state-specific style updates. + updateStyle : function() + { + // If only a single page shown, a different style is used in the central pane. + this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' ); + }, + + /** + * Hides a page's tab away from the dialog. + * @param {String} id The page's Id. + * @example + * dialog.hidePage( 'tab_3' ); + */ + hidePage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab || this._.pageCount == 1 || !tab.isVisible() ) + return; + // Switch to other tab first when we're hiding the active tab. + else if ( id == this._.currentTabId ) + this.selectPage( getPreviousVisibleTab.call( this ) ); + + tab.hide(); + this._.pageCount--; + this.updateStyle(); + }, + + /** + * Unhides a page's tab. + * @param {String} id The page's Id. + * @example + * dialog.showPage( 'tab_2' ); + */ + showPage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab ) + return; + tab.show(); + this._.pageCount++; + this.updateStyle(); + }, + + /** + * Gets the root DOM element of the dialog. + * @returns {CKEDITOR.dom.element} The <span> element containing this dialog. + * @example + * var dialogElement = dialogObj.getElement().getFirst(); + * dialogElement.setStyle( 'padding', '5px' ); + */ + getElement : function() + { + return this._.element; + }, + + /** + * Gets the name of the dialog. + * @returns {String} The name of this dialog. + * @example + * var dialogName = dialogObj.getName(); + */ + getName : function() + { + return this._.name; + }, + + /** + * Gets a dialog UI element object from a dialog page. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * dialogObj.getContentElement( 'tabId', 'elementId' ).setValue( 'Example' ); + * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element. + */ + getContentElement : function( pageId, elementId ) + { + var page = this._.contents[ pageId ]; + return page && page[ elementId ]; + }, + + /** + * Gets the value of a dialog UI element. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * alert( dialogObj.getValueOf( 'tabId', 'elementId' ) ); + * @returns {Object} The value of the UI element. + */ + getValueOf : function( pageId, elementId ) + { + return this.getContentElement( pageId, elementId ).getValue(); + }, + + /** + * Sets the value of a dialog UI element. + * @param {String} pageId id of the dialog page. + * @param {String} elementId id of the UI element. + * @param {Object} value The new value of the UI element. + * @example + * dialogObj.setValueOf( 'tabId', 'elementId', 'Example' ); + */ + setValueOf : function( pageId, elementId, value ) + { + return this.getContentElement( pageId, elementId ).setValue( value ); + }, + + /** + * Gets the UI element of a button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns {CKEDITOR.ui.dialog.button} The button object. + */ + getButton : function( id ) + { + return this._.buttons[ id ]; + }, + + /** + * Simulates a click to a dialog button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns The return value of the dialog's "click" event. + */ + click : function( id ) + { + return this._.buttons[ id ].click(); + }, + + /** + * Disables a dialog button. + * @param {String} id The id of the button. + * @example + */ + disableButton : function( id ) + { + return this._.buttons[ id ].disable(); + }, + + /** + * Enables a dialog button. + * @param {String} id The id of the button. + * @example + */ + enableButton : function( id ) + { + return this._.buttons[ id ].enable(); + }, + + /** + * Gets the number of pages in the dialog. + * @returns {Number} Page count. + */ + getPageCount : function() + { + return this._.pageCount; + }, + + /** + * Gets the editor instance which opened this dialog. + * @returns {CKEDITOR.editor} Parent editor instances. + */ + getParentEditor : function() + { + return this._.editor; + }, + + /** + * Gets the element that was selected when opening the dialog, if any. + * @returns {CKEDITOR.dom.element} The element that was selected, or null. + */ + getSelectedElement : function() + { + return this.getParentEditor().getSelection().getSelectedElement(); + }, + + /** + * Adds element to dialog's focusable list. + * + * @param {CKEDITOR.dom.element} element + * @param {Number} [index] + */ + addFocusable: function( element, index ) { + if ( typeof index == 'undefined' ) + { + index = this._.focusList.length; + this._.focusList.push( new Focusable( this, element, index ) ); + } + else + { + this._.focusList.splice( index, 0, new Focusable( this, element, index ) ); + for ( var i = index + 1 ; i < this._.focusList.length ; i++ ) + this._.focusList[ i ].focusIndex++; + } + } + }; + + CKEDITOR.tools.extend( CKEDITOR.dialog, + /** + * @lends CKEDITOR.dialog + */ + { + /** + * Registers a dialog. + * @param {String} name The dialog's name. + * @param {Function|String} dialogDefinition + * A function returning the dialog's definition, or the URL to the .js file holding the function. + * The function should accept an argument "editor" which is the current editor instance, and + * return an object conforming to {@link CKEDITOR.dialog.definition}. + * @see CKEDITOR.dialog.definition + * @example + * // Full sample plugin, which does not only register a dialog window but also adds an item to the context menu. + * // To open the dialog window, choose "Open dialog" in the context menu. + * CKEDITOR.plugins.add( 'myplugin', + * { + * init: function( editor ) + * { + * editor.addCommand( 'mydialog',new CKEDITOR.dialogCommand( 'mydialog' ) ); + * + * if ( editor.contextMenu ) + * { + * editor.addMenuGroup( 'mygroup', 10 ); + * editor.addMenuItem( 'My Dialog', + * { + * label : 'Open dialog', + * command : 'mydialog', + * group : 'mygroup' + * }); + * editor.contextMenu.addListener( function( element ) + * { + * return { 'My Dialog' : CKEDITOR.TRISTATE_OFF }; + * }); + * } + * + * CKEDITOR.dialog.add( 'mydialog', function( api ) + * { + * // CKEDITOR.dialog.definition + * var dialogDefinition = + * { + * title : 'Sample dialog', + * minWidth : 390, + * minHeight : 130, + * contents : [ + * { + * id : 'tab1', + * label : 'Label', + * title : 'Title', + * expand : true, + * padding : 0, + * elements : + * [ + * { + * type : 'html', + * html : '<p>This is some sample HTML content.</p>' + * }, + * { + * type : 'textarea', + * id : 'textareaId', + * rows : 4, + * cols : 40 + * } + * ] + * } + * ], + * buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ], + * onOk : function() { + * // "this" is now a CKEDITOR.dialog object. + * // Accessing dialog elements: + * var textareaObj = this.getContentElement( 'tab1', 'textareaId' ); + * alert( "You have entered: " + textareaObj.getValue() ); + * } + * }; + * + * return dialogDefinition; + * } ); + * } + * } ); + * + * CKEDITOR.replace( 'editor1', { extraPlugins : 'myplugin' } ); + */ + add : function( name, dialogDefinition ) + { + // Avoid path registration from multiple instances override definition. + if ( !this._.dialogDefinitions[name] + || typeof dialogDefinition == 'function' ) + this._.dialogDefinitions[name] = dialogDefinition; + }, + + exists : function( name ) + { + return !!this._.dialogDefinitions[ name ]; + }, + + getCurrent : function() + { + return CKEDITOR.dialog._.currentTop; + }, + + /** + * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds. + * @static + * @field + * @example + * @type Function + */ + okButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'ok', + type : 'button', + label : editor.lang.common.ok, + 'class' : 'cke_dialog_ui_button_ok', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'ok', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed. + * @static + * @field + * @example + * @type Function + */ + cancelButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'cancel', + type : 'button', + label : editor.lang.common.cancel, + 'class' : 'cke_dialog_ui_button_cancel', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'cancel', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * Registers a dialog UI element. + * @param {String} typeName The name of the UI element. + * @param {Function} builder The function to build the UI element. + * @example + */ + addUIElement : function( typeName, builder ) + { + this._.uiElementBuilders[ typeName ] = builder; + } + }); + + CKEDITOR.dialog._ = + { + uiElementBuilders : {}, + + dialogDefinitions : {}, + + currentTop : null, + + currentZIndex : null + }; + + // "Inherit" (copy actually) from CKEDITOR.event. + CKEDITOR.event.implementOn( CKEDITOR.dialog ); + CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true ); + + var defaultDialogDefinition = + { + resizable : CKEDITOR.DIALOG_RESIZE_BOTH, + minWidth : 600, + minHeight : 400, + buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] + }; + + // Tool function used to return an item from an array based on its id + // property. + var getById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return item; + if ( recurse && item[ recurse ] ) + { + var retval = getById( item[ recurse ], id, recurse ) ; + if ( retval ) + return retval; + } + } + return null; + }; + + // Tool function used to add an item into an array. + var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound ) + { + if ( nextSiblingId ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == nextSiblingId ) + { + array.splice( i, 0, newItem ); + return newItem; + } + + if ( recurse && item[ recurse ] ) + { + var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true ); + if ( retval ) + return retval; + } + } + + if ( nullIfNotFound ) + return null; + } + + array.push( newItem ); + return newItem; + }; + + // Tool function used to remove an item from an array based on its id. + var removeById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return array.splice( i, 1 ); + if ( recurse && item[ recurse ] ) + { + var retval = removeById( item[ recurse ], id, recurse ); + if ( retval ) + return retval; + } + } + return null; + }; + + /** + * This class is not really part of the API. It is the "definition" property value + * passed to "dialogDefinition" event handlers. + * @constructor + * @name CKEDITOR.dialog.definitionObject + * @extends CKEDITOR.dialog.definition + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * ... + * } ); + */ + var definitionObject = function( dialog, dialogDefinition ) + { + // TODO : Check if needed. + this.dialog = dialog; + + // Transform the contents entries in contentObjects. + var contents = dialogDefinition.contents; + for ( var i = 0, content ; ( content = contents[i] ) ; i++ ) + contents[ i ] = content && new contentObject( dialog, content ); + + CKEDITOR.tools.extend( this, dialogDefinition ); + }; + + definitionObject.prototype = + /** @lends CKEDITOR.dialog.definitionObject.prototype */ + { + /** + * Gets a content definition. + * @param {String} id The id of the content definition. + * @returns {CKEDITOR.dialog.definition.content} The content definition + * matching id. + */ + getContents : function( id ) + { + return getById( this.contents, id ); + }, + + /** + * Gets a button definition. + * @param {String} id The id of the button definition. + * @returns {CKEDITOR.dialog.definition.button} The button definition + * matching id. + */ + getButton : function( id ) + { + return getById( this.buttons, id ); + }, + + /** + * Adds a content definition object under this dialog definition. + * @param {CKEDITOR.dialog.definition.content} contentDefinition The + * content definition. + * @param {String} [nextSiblingId] The id of an existing content + * definition which the new content definition will be inserted + * before. Omit if the new content definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.definition.content} The inserted content + * definition. + */ + addContents : function( contentDefinition, nextSiblingId ) + { + return addById( this.contents, contentDefinition, nextSiblingId ); + }, + + /** + * Adds a button definition object under this dialog definition. + * @param {CKEDITOR.dialog.definition.button} buttonDefinition The + * button definition. + * @param {String} [nextSiblingId] The id of an existing button + * definition which the new button definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.definition.button} The inserted button + * definition. + */ + addButton : function( buttonDefinition, nextSiblingId ) + { + return addById( this.buttons, buttonDefinition, nextSiblingId ); + }, + + /** + * Removes a content definition from this dialog definition. + * @param {String} id The id of the content definition to be removed. + * @returns {CKEDITOR.dialog.definition.content} The removed content + * definition. + */ + removeContents : function( id ) + { + removeById( this.contents, id ); + }, + + /** + * Removes a button definition from the dialog definition. + * @param {String} id The id of the button definition to be removed. + * @returns {CKEDITOR.dialog.definition.button} The removed button + * definition. + */ + removeButton : function( id ) + { + removeById( this.buttons, id ); + } + }; + + /** + * This class is not really part of the API. It is the template of the + * objects representing content pages inside the + * CKEDITOR.dialog.definitionObject. + * @constructor + * @name CKEDITOR.dialog.definition.contentObject + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * content.remove( 'textInput1' ); + * ... + * } ); + */ + function contentObject( dialog, contentDefinition ) + { + this._ = + { + dialog : dialog + }; + + CKEDITOR.tools.extend( this, contentDefinition ); + } + + contentObject.prototype = + /** @lends CKEDITOR.dialog.definition.contentObject.prototype */ + { + /** + * Gets a UI element definition under the content definition. + * @param {String} id The id of the UI element definition. + * @returns {CKEDITOR.dialog.definition.uiElement} + */ + get : function( id ) + { + return getById( this.elements, id, 'children' ); + }, + + /** + * Adds a UI element definition to the content definition. + * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition The + * UI elemnet definition to be added. + * @param {String} nextSiblingId The id of an existing UI element + * definition which the new UI element definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.definition.uiElement} The element + * definition inserted. + */ + add : function( elementDefinition, nextSiblingId ) + { + return addById( this.elements, elementDefinition, nextSiblingId, 'children' ); + }, + + /** + * Removes a UI element definition from the content definition. + * @param {String} id The id of the UI element definition to be + * removed. + * @returns {CKEDITOR.dialog.definition.uiElement} The element + * definition removed. + * @example + */ + remove : function( id ) + { + removeById( this.elements, id, 'children' ); + } + }; + + function initDragAndDrop( dialog ) + { + var lastCoords = null, + abstractDialogCoords = null, + element = dialog.getElement().getFirst(), + editor = dialog.getParentEditor(), + magnetDistance = editor.config.dialog_magnetDistance, + margins = editor.skin.margins || [ 0, 0, 0, 0 ]; + + if ( typeof magnetDistance == 'undefined' ) + magnetDistance = 20; + + function mouseMoveHandler( evt ) + { + var dialogSize = dialog.getSize(), + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), + x = evt.data.$.screenX, + y = evt.data.$.screenY, + dx = x - lastCoords.x, + dy = y - lastCoords.y, + realX, realY; + + lastCoords = { x : x, y : y }; + abstractDialogCoords.x += dx; + abstractDialogCoords.y += dy; + + if ( abstractDialogCoords.x + margins[3] < magnetDistance ) + realX = - margins[3]; + else if ( abstractDialogCoords.x - margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance ) + realX = viewPaneSize.width - dialogSize.width + ( editor.lang.dir == 'rtl' ? 0 : margins[1] ); + else + realX = abstractDialogCoords.x; + + if ( abstractDialogCoords.y + margins[0] < magnetDistance ) + realY = - margins[0]; + else if ( abstractDialogCoords.y - margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance ) + realY = viewPaneSize.height - dialogSize.height + margins[2]; + else + realY = abstractDialogCoords.y; + + dialog.move( realX, realY, 1 ); + + evt.data.preventDefault(); + } + + function mouseUpHandler( evt ) + { + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + } + } + + dialog.parts.title.on( 'mousedown', function( evt ) + { + lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler ); + abstractDialogCoords = dialog.getPosition(); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler ); + coverDoc.on( 'mouseup', mouseUpHandler ); + } + + evt.data.preventDefault(); + }, dialog ); + } + + function initResizeHandles( dialog ) + { + var def = dialog.definition, + resizable = def.resizable; + + if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE ) + return; + + var editor = dialog.getParentEditor(); + var wrapperWidth, wrapperHeight, + viewSize, origin, startSize, + dialogCover; + + var mouseDownFn = CKEDITOR.tools.addFunction( function( $event ) + { + startSize = dialog.getSize(); + + var content = dialog.parts.contents, + iframeDialog = content.$.getElementsByTagName( 'iframe' ).length; + + // Shim to help capturing "mousemove" over iframe. + if ( iframeDialog ) + { + dialogCover = CKEDITOR.dom.element.createFromHtml( '
' ); + content.append( dialogCover ); + } + + // Calculate the offset between content and chrome size. + wrapperHeight = startSize.height - dialog.parts.contents.getSize( 'height', ! ( CKEDITOR.env.gecko || CKEDITOR.env.opera || CKEDITOR.env.ie && CKEDITOR.env.quirks ) ); + wrapperWidth = startSize.width - dialog.parts.contents.getSize( 'width', 1 ); + + origin = { x : $event.screenX, y : $event.screenY }; + + viewSize = CKEDITOR.document.getWindow().getViewPaneSize(); + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler ); + coverDoc.on( 'mouseup', mouseUpHandler ); + } + + $event.preventDefault && $event.preventDefault(); + }); + + // Prepend the grip to the dialog. + dialog.on( 'load', function() + { + var direction = ''; + if ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH ) + direction = ' cke_resizer_horizontal'; + else if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT ) + direction = ' cke_resizer_vertical'; + var resizer = CKEDITOR.dom.element.createFromHtml( '' ); + dialog.parts.footer.append( resizer, 1 ); + }); + editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( mouseDownFn ); } ); + + function mouseMoveHandler( evt ) + { + var rtl = editor.lang.dir == 'rtl', + dx = ( evt.data.$.screenX - origin.x ) * ( rtl ? -1 : 1 ), + dy = evt.data.$.screenY - origin.y, + width = startSize.width, + height = startSize.height, + internalWidth = width + dx * ( dialog._.moved ? 1 : 2 ), + internalHeight = height + dy * ( dialog._.moved ? 1 : 2 ), + element = dialog._.element.getFirst(), + right = rtl && element.getComputedStyle( 'right' ), + position = dialog.getPosition(); + + if ( position.y + internalHeight > viewSize.height ) + internalHeight = viewSize.height - position.y; + + if ( ( rtl ? right : position.x ) + internalWidth > viewSize.width ) + internalWidth = viewSize.width - ( rtl ? right : position.x ); + + // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL. + if ( ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) ) + width = Math.max( def.minWidth || 0, internalWidth - wrapperWidth ); + + if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) + height = Math.max( def.minHeight || 0, internalHeight - wrapperHeight ); + + dialog.resize( width, height ); + + if ( !dialog._.moved ) + dialog.layout(); + + evt.data.preventDefault(); + } + + function mouseUpHandler() + { + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + + if ( dialogCover ) + { + dialogCover.remove(); + dialogCover = null; + } + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + } + } + } + + var resizeCover; + // Caching resuable covers and allowing only one cover + // on screen. + var covers = {}, + currentCover; + + function cancelEvent( ev ) + { + ev.data.preventDefault(1); + } + + function showCover( editor ) + { + var win = CKEDITOR.document.getWindow(); + var config = editor.config, + backgroundColorStyle = config.dialog_backgroundCoverColor || 'white', + backgroundCoverOpacity = config.dialog_backgroundCoverOpacity, + baseFloatZIndex = config.baseFloatZIndex, + coverKey = CKEDITOR.tools.genKey( + backgroundColorStyle, + backgroundCoverOpacity, + baseFloatZIndex ), + coverElement = covers[ coverKey ]; + + if ( !coverElement ) + { + var html = [ + '0 ) + html.push( 'style="', styles.join( '; ' ), '" ' ); + html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], ' |
Note that this event is called only the first time a specific dialog is + * opened. Successive openings will use the cached dialog, and this event will + * not get fired.
+ * @name CKEDITOR#dialogDefinition + * @event + * @param {CKEDITOR.dialog.definition} data The dialog defination that + * is being loaded. + * @param {CKEDITOR.editor} editor The editor instance that will use the + * dialog. + */ + +/** + * Fired when a tab is going to be selected in a dialog + * @name CKEDITOR.dialog#selectPage + * @event + * @param {String} page The id of the page that it's gonna be selected. + * @param {String} currentPage The id of the current page. + */ + +/** + * Fired when the user tries to dismiss a dialog + * @name CKEDITOR.dialog#cancel + * @event + * @param {Boolean} hide Whether the event should proceed or not. + */ + +/** + * Fired when the user tries to confirm a dialog + * @name CKEDITOR.dialog#ok + * @event + * @param {Boolean} hide Whether the event should proceed or not. + */ + +/** + * Fired when a dialog is shown + * @name CKEDITOR.dialog#show + * @event + */ + +/** + * Fired when a dialog is shown + * @name CKEDITOR.editor#dialogShow + * @event + */ + +/** + * Fired when a dialog is hidden + * @name CKEDITOR.dialog#hide + * @event + */ + +/** + * Fired when a dialog is hidden + * @name CKEDITOR.editor#dialogHide + * @event + */ + +/** + * Fired when a dialog is being resized. The event is fired on + * both the 'CKEDITOR.dialog' object and the dialog instance + * since 3.5.3, previously it's available only in the global object. + * @name CKEDITOR.dialog#resize + * @since 3.5 + * @event + * @param {CKEDITOR.dialog} dialog The dialog being resized (if + * it's fired on the dialog itself, this parameter isn't sent). + * @param {String} skin The skin name. + * @param {Number} width The new width. + * @param {Number} height The new height. + */