      1 /*
      2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
      3 For licensing, see LICENSE.html or http://ckeditor.com/license
      4 */
      5
      6 CKEDITOR.plugins.add( 'menu',
      7 {
      8 	beforeInit : function( editor )
      9 	{
     10 		var groups = editor.config.menu_groups.split( ',' ),
     11 			groupsOrder = {};
     12
     13 		for ( var i = 0 ; i < groups.length ; i++ )
     14 			groupsOrder[ groups[ i ] ] = i + 1;
     15
     16 		editor._.menuGroups = groupsOrder;
     17 		editor._.menuItems = {};
     18 	},
     19
     20 	requires : [ 'floatpanel' ]
     21 });
     22
     23 CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
     24 {
     25 	addMenuGroup : function( name, order )
     26 	{
     27 		this._.menuGroups[ name ] = order || 100;
     28 	},
     29
     30 	addMenuItem : function( name, definition )
     31 	{
     32 		if ( this._.menuGroups[ definition.group ] )
     33 			this._.menuItems[ name ] = new CKEDITOR.menuItem( this, name, definition );
     34 	},
     35
     36 	addMenuItems : function( definitions )
     37 	{
     38 		for ( var itemName in definitions )
     39 		{
     40 			this.addMenuItem( itemName, definitions[ itemName ] );
     41 		}
     42 	},
     43
     44 	getMenuItem : function( name )
     45 	{
     46 		return this._.menuItems[ name ];
     47 	}
     48 });
     49
     50 (function()
     51 {
     52 	CKEDITOR.menu = CKEDITOR.tools.createClass(
     53 	{
     54 		$ : function( editor, level )
     55 		{
     56 			this.id = 'cke_' + CKEDITOR.tools.getNextNumber();
     57
     58 			this.editor = editor;
     59 			this.items = [];
     60
     61 			this._.level = level || 1;
     62 		},
     63
     64 		_ :
     65 		{
     66 			showSubMenu : function( index )
     67 			{
     68 				var menu = this._.subMenu,
     69 					item = this.items[ index ],
     70 					subItems = item.getItems && item.getItems();
     71
     72 				// If this item has no subitems, we just hide the submenu, if
     73 				// available, and return back.
     74 				if ( !subItems )
     75 				{
     76 					this._.panel.hideChild();
     77 					return;
     78 				}
     79
     80 				// Create the submenu, if not available, or clean the existing
     81 				// one.
     82 				if ( menu )
     83 					menu.removeAll();
     84 				else
     85 				{
     86 					menu = this._.subMenu = new CKEDITOR.menu( this.editor, this._.level + 1 );
     87 					menu.parent = this;
     88 					menu.onClick = CKEDITOR.tools.bind( this.onClick, this );
     89 				}
     90
     91 				// Add all submenu items to the menu.
     92 				for ( var itemName in subItems )
     93 				{
     94 					menu.add( this.editor.getMenuItem( itemName ) );
     95 				}
     96
     97 				// Get the element representing the current item.
     98 				var element = this._.panel.getBlock( this.id ).element.getDocument().getById( this.id + String( index ) );
     99
    100 				// Show the submenu.
    101 				menu.show( element, 2 );
    102 			}
    103 		},
    104
    105 		proto :
    106 		{
    107 			add : function( item )
    108 			{
    109 				this.items.push( item );
    110 			},
    111
    112 			removeAll : function()
    113 			{
    114 				this.items = [];
    115 			},
    116
    117 			show : function( offsetParent, corner, offsetX, offsetY )
    118 			{
    119 				var items = this.items,
    120 					editor = this.editor,
    121 					panel = this._.panel,
    122 					element = this._.element;
    123
    124 				// Create the floating panel for this menu.
    125 				if ( !panel )
    126 				{
    127 					panel = this._.panel = new CKEDITOR.ui.floatPanel( this.editor, CKEDITOR.document.getBody(),
    128 						{
    129 							css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ],
    130 							level : this._.level - 1,
    131 							className : editor.skinClass + ' cke_contextmenu'
    132 						},
    133 						this._.level);
    134
    135 					panel.onEscape = CKEDITOR.tools.bind( function()
    136 					{
    137 						this.onEscape && this.onEscape();
    138 						this.hide();
    139 					},
    140 					this );
    141
    142 					panel.onHide = CKEDITOR.tools.bind( function()
    143 					{
    144 						this.onHide && this.onHide();
    145 					},
    146 					this );
    147
    148 					// Create an autosize block inside the panel.
    149 					var block = panel.addBlock( this.id );
    150 					block.autoSize = true;
    151
    152 					var keys = block.keys;
    153 					keys[ 40 ]	= 'next';					// ARROW-DOWN
    154 					keys[ 9 ]	= 'next';					// TAB
    155 					keys[ 38 ]	= 'prev';					// ARROW-UP
    156 					keys[ CKEDITOR.SHIFT + 9 ]	= 'prev';	// SHIFT + TAB
    157 					keys[ 32 ]	= 'click';					// SPACE
    158 					keys[ 39 ]	= 'click';					// ARROW-RIGHT
    159
    160 					element = this._.element = block.element;
    161 					element.addClass( editor.skinClass );
    162
    163 					var elementDoc = element.getDocument();
    164 					elementDoc.getBody().setStyle( 'overflow', 'hidden' );
    165 					elementDoc.getElementsByTag( 'html' ).getItem( 0 ).setStyle( 'overflow', 'hidden' );
    166
    167 					this._.itemOverFn = CKEDITOR.tools.addFunction( function( index )
    168 						{
    169 							clearTimeout( this._.showSubTimeout );
    170 							this._.showSubTimeout = CKEDITOR.tools.setTimeout( this._.showSubMenu, editor.config.menu_subMenuDelay, this, [ index ] );
    171 						},
    172 						this);
    173
    174 					this._.itemOutFn = CKEDITOR.tools.addFunction( function( index )
    175 						{
    176 							clearTimeout( this._.showSubTimeout );
    177 						},
    178 						this);
    179
    180 					this._.itemClickFn = CKEDITOR.tools.addFunction( function( index )
    181 						{
    182 							var item = this.items[ index ];
    183
    184 							if ( item.state == CKEDITOR.TRISTATE_DISABLED )
    185 							{
    186 								this.hide();
    187 								return;
    188 							}
    189
    190 							if ( item.getItems )
    191 								this._.showSubMenu( index );
    192 							else
    193 								this.onClick && this.onClick( item );
    194 						},
    195 						this);
    196 				}
    197
    198 				// Put the items in the right order.
    199 				sortItems( items );
    200
    201 				// Build the HTML that composes the menu and its items.
    202 				var output = [ '<div class="cke_menu">' ];
    203
    204 				var length = items.length,
    205 					lastGroup = length && items[ 0 ].group;
    206
    207 				for ( var i = 0 ; i < length ; i++ )
    208 				{
    209 					var item = items[ i ];
    210 					if ( lastGroup != item.group )
    211 					{
    212 						output.push( '<div class="cke_menuseparator"></div>' );
    213 						lastGroup = item.group;
    214 					}
    215
    216 					item.render( this, i, output );
    217 				}
    218
    219 				output.push( '</div>' );
    220
    221 				// Inject the HTML inside the panel.
    222 				element.setHtml( output.join( '' ) );
    223
    224 				// Show the panel.
    225 				if ( this.parent )
    226 					this.parent._.panel.showAsChild( panel, this.id, offsetParent, corner, offsetX, offsetY );
    227 				else
    228 					panel.showBlock( this.id, offsetParent, corner, offsetX, offsetY );
    229 			},
    230
    231 			hide : function()
    232 			{
    233 				this._.panel && this._.panel.hide();
    234 			}
    235 		}
    236 	});
    237
    238 	function sortItems( items )
    239 	{
    240 		items.sort( function( itemA, itemB )
    241 			{
    242 				if ( itemA.group < itemB.group )
    243 					return -1;
    244 				else if ( itemA.group > itemB.group )
    245 					return 1;
    246
    247 				return itemA.order < itemB.order ? -1 :
    248 					itemA.order > itemB.order ? 1 :
    249 					0;
    250 			});
    251 	}
    252 })();
    253
    254 CKEDITOR.menuItem = CKEDITOR.tools.createClass(
    255 {
    256 	$ : function( editor, name, definition )
    257 	{
    258 		CKEDITOR.tools.extend( this, definition,
    259 			// Defaults
    260 			{
    261 				order : 0,
    262 				className : 'cke_button_' + name
    263 			});
    264
    265 		// Transform the group name into its order number.
    266 		this.group = editor._.menuGroups[ this.group ];
    267
    268 		this.editor = editor;
    269 		this.name = name;
    270 	},
    271
    272 	proto :
    273 	{
    274 		render : function( menu, index, output )
    275 		{
    276 			var id = menu.id + String( index ),
    277 				state = ( typeof this.state == 'undefined' ) ? CKEDITOR.TRISTATE_OFF : this.state;
    278
    279 			var classes = ' cke_' + (
    280 				state == CKEDITOR.TRISTATE_ON ? 'on' :
    281 				state == CKEDITOR.TRISTATE_DISABLED ? 'disabled' :
    282 				'off' );
    283
    284 			var htmlLabel = this.label;
    285 			if ( state == CKEDITOR.TRISTATE_DISABLED )
    286 				htmlLabel = this.editor.lang.common.unavailable.replace( '%1', htmlLabel );
    287
    288 			if ( this.className )
    289 				classes += ' ' + this.className;
    290
    291 			output.push(
    292 				'<span class="cke_menuitem">' +
    293 				'<a id="', id, '"' +
    294 					' class="', classes, '" href="javascript:void(\'', ( this.label || '' ).replace( "'", '' ), '\')"' +
    295 					' title="', this.label, '"' +
    296 					' tabindex="-1"' +
    297 					'_cke_focus=1' +
    298 					' hidefocus="true"' );
    299
    300 			// Some browsers don't cancel key events in the keydown but in the
    301 			// keypress.
    302 			// TODO: Check if really needed for Gecko+Mac.
    303 			if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
    304 			{
    305 				output.push(
    306 					' onkeypress="return false;"' );
    307 			}
    308
    309 			// With Firefox, we need to force the button to redraw, otherwise it
    310 			// will remain in the focus state.
    311 			if ( CKEDITOR.env.gecko )
    312 			{
    313 				output.push(
    314 					' onblur="this.style.cssText = this.style.cssText;"' );
    315 			}
    316
    317 			var offset = ( this.iconOffset || 0 ) * -16;
    318 			output.push(
    319 //					' onkeydown="return CKEDITOR.ui.button._.keydown(', index, ', event);"' +
    320 					' onmouseover="CKEDITOR.tools.callFunction(', menu._.itemOverFn, ',', index, ');"' +
    321 					' onmouseout="CKEDITOR.tools.callFunction(', menu._.itemOutFn, ',', index, ');"' +
    322 					' onclick="CKEDITOR.tools.callFunction(', menu._.itemClickFn, ',', index, '); return false;"' +
    323 					'>' +
    324 						'<span class="cke_icon_wrapper"><span class="cke_icon"' +
    325 							( this.icon ? ' style="background-image:url(' + CKEDITOR.getUrl( this.icon ) + ');background-position:0 ' + offset + 'px;"></span>'
    326 							: '' ) +
    327 							'></span></span>' +
    328 						'<span class="cke_label">' );
    329
    330 			if ( this.getItems )
    331 			{
    332 				output.push(
    333 							'<span class="cke_menuarrow"></span>' );
    334 			}
    335
    336 			output.push(
    337 							htmlLabel,
    338 						'</span>' +
    339 				'</a>' +
    340 				'</span>' );
    341 		}
    342 	}
    343 });
    344
    345 CKEDITOR.config.menu_subMenuDelay = 400;
    346 CKEDITOR.config.menu_groups =
    347 	'clipboard,' +
    348 	'form,' +
    349 	'tablecell,tablecellproperties,tablerow,tablecolumn,table,'+
    350 	'anchor,link,image,flash,' +
    351 	'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea';
    352