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