1 /*!
  2 * This file is part of Aloha Editor Project http://aloha-editor.org
  3 * Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com
  4 * Contributors http://aloha-editor.org/contribution.php 
  5 * Licensed unter the terms of http://www.aloha-editor.org/license.html
  6 *//*
  7 * Aloha Editor is free software: you can redistribute it and/or modify
  8 * it under the terms of the GNU Affero General Public License as published by
  9 * the Free Software Foundation, either version 3 of the License, or
 10 * (at your option) any later version.*
 11 *
 12 * Aloha Editor is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 * GNU Affero General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU Affero General Public License
 18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20 define(
 21 ['aloha/core', 'aloha/jquery', 'aloha/ext', 'util/class', 'aloha/console', 'vendor/jquery.store'],
 22 function(Aloha, jQuery, Ext, Class, console) {
 23 	"use strict";
 24 	var GENTICS = window.GENTICS;
 25 
 26 	/**
 27 	 * Constructor for a floatingmenu tab
 28 	 * @namespace Aloha.FloatingMenu
 29 	 * @class Tab
 30 	 * @constructor
 31 	 * @param {String} label label of the tab
 32 	 */
 33 	var Tab = Class.extend({
 34 		_constructor: function(label) {
 35 			this.label = label;
 36 			this.groups = [];
 37 			this.groupMap = {};
 38 			this.visible = true;
 39 		},
 40 
 41 		/**
 42 		 * Get the group with given index. If it does not yet exist, create a new one
 43 		 * @method
 44 		 * @param {int} group group index of the group to get
 45  46 		 * @return group object
 47 		 */
 48 		getGroup: function(group) {
 49 			var groupObject = this.groupMap[group];
 50 			if (typeof groupObject === 'undefined') {
 51 				groupObject = new Group();
 52 				this.groupMap[group] = groupObject;
 53 				this.groups.push(groupObject);
 54 				// TODO resort the groups
 55 			}
 56 
 57 			return groupObject;
 58 		},
 59 
 60 		/**
 61 		 * Get the EXT component representing the tab
 62 		 * @return EXT component (EXT.Panel)
 63 		 * @hide
 64 		 */
 65 		getExtComponent: function () {
 66 			var that = this;
 67 
 68 			if (!this.extPanel) {
 69 				this.extPanel = new Ext.Panel({
 70 					'tbar' : [],
 71 					'title' : this.label,
 72 					'style': 'margin-top:0px',
 73 					'bodyStyle': 'display:none',
 74 					'autoScroll': true
 75 				});
 76 			}
 77 
 78 			jQuery.each(this.groups, function(index, group) {
 79 				// let each group generate its ext component and add them to
 80 				// the panel once.
 81 				if (!group.extButtonGroup) {
 82 					that.extPanel.getTopToolbar().add(group.getExtComponent());
 83 				}
 84 			});
 85 
 86 			return this.extPanel;
 87 		},
 88 
 89 		/**
 90 		 * Recalculate the visibility of all groups within the tab
 91 		 * @hide
 92 		 */
 93 		doLayout: function() {
 94 			var that = this;
 95 
 96 			if (Aloha.Log.isDebugEnabled()) {
 97 				Aloha.Log.debug(this, 'doLayout called for tab ' + this.label);
 98 			}
 99 			this.visible = false;
100 
101 			// check all groups in this tab
102 			jQuery.each(this.groups, function(index, group) {
103 				that.visible |= group.doLayout();
104 			});
105 
106 			if (Aloha.Log.isDebugEnabled()) {
107 				Aloha.Log.debug(this, 'tab ' + this.label + (this.visible ? ' is ' : ' is not ') + 'visible now');
108 			}
109 
110 			return this.visible;
111 		}
112 	});
113 
114 	/**
115 	 * Constructor for a floatingmenu group
116 	 * @namespace Aloha.FloatingMenu
117 	 * @class Group
118 	 * @constructor
119 	 */
120 	var Group = Class.extend({
121 		_constructor: function() {
122 			this.buttons = [];
123 			this.fields = [];
124 		},
125 
126 		/**
127 		 * Add a button to this group
128 		 * @param {Button} buttonInfo to add to the group
129 		 */
130 		addButton: function(buttonInfo) {
131 			if (buttonInfo.button instanceof Aloha.ui.AttributeField) {
132 				if (this.fields.length < 2) {
133 					this.fields.push(buttonInfo);
134 				} else {
135 					throw new Error("Too much fields in this group");
136 				}
137 			} else {
138 				// Every plugin API entryPoint (method) should be securised enough
139 				// to avoid Aloha to block at startup even
140 				// if a plugin is badly designed
141 				if (typeof buttonInfo.button !== "undefined"){
142 					this.buttons.push(buttonInfo);
143 				}
144 			}
145 		},
146 
147 		/**
148 		 * Get the EXT component representing the group (Ext.ButtonGroup)
149 		 * @return the Ext.ButtonGroup
150 		 * @hide
151 		 */
152 		getExtComponent: function () {
153 			var that = this, l,
154 				items = [],
155 				buttonCount = 0,
156 				columnCount = 0,
157 				len, idx, half;
158 
159 
160 			if (typeof this.extButtonGroup === 'undefined') {
161 			
162 				if (this.fields.length > 1) {
163 					columnCount = 1;
164 				}
165 
166 				jQuery.each(this.buttons, function(index, button) {
167 					// count the number of buttons (large buttons count as 2)
168 					buttonCount += button.button.size == 'small' ? 1 : 2;
169 				});
170 				columnCount = columnCount + Math.ceil(buttonCount / 2);
171 
172 				len = this.buttons.length;
173 				idx = 0;
174 				half =  Math.ceil(this.buttons.length / 2) - this.buttons.length % 2 ;
175 
176 				if (this.fields.length > 0) {
177 					that.buttons.push(this.fields[0]);
178 					items.push(this.fields[0].button.getExtConfigProperties());
179 				}
180 
181 				while (--len >= half) {
182 					items.push(this.buttons[idx++].button.getExtConfigProperties());
183 				}
184 				++len;
185 				if (this.fields.length > 1) {
186 					that.buttons.push(this.fields[1]);
187 					items.push(this.fields[1].button.getExtConfigProperties());
188 				}
189 				while (--len >=0) {
190 					items.push(this.buttons[idx++].button.getExtConfigProperties());
191 				}
192 
193 				this.extButtonGroup = new Ext.ButtonGroup({
194 					'columns' : columnCount,
195 					'items': items
196 				});
197 
198 	//			jQuery.each(this.fields, function(id, field){
199 	//				that.buttons.push(field);
200 	//			});
201 				// now find the Ext.Buttons and set to the GENTICS buttons
202 				jQuery.each(this.buttons, function(index, buttonInfo) {
203 					buttonInfo.button.extButton = that.extButtonGroup.findById(buttonInfo.button.id);
204 					// the following code is a work arround because ExtJS initializes later.
205 					// The ui wrapper store the information and here we use it... ugly.
206 					// if there are any listeners added before initializing the extButtons
207 					if ( buttonInfo.button.listenerQueue && buttonInfo.button.listenerQueue.length > 0 ) {
208 						while ( true ) {
209 							l = buttonInfo.button.listenerQueue.shift();
210 							if ( !l ) {break;}
211 							buttonInfo.button.extButton.addListener(l.eventName, l.handler, l.scope, l.options);
212 						}
213 					}
214 					if (buttonInfo.button.extButton.setObjectTypeFilter) {
215 						if (buttonInfo.button.objectTypeFilter) {
216 							buttonInfo.button.extButton.noQuery = false;
217 						}
218 						if ( buttonInfo.button.objectTypeFilter == 'all' ) {
219 							buttonInfo.button.objectTypeFilter = null;
220 						}
221 						buttonInfo.button.extButton.setObjectTypeFilter(buttonInfo.button.objectTypeFilter);
222 						if ( buttonInfo.button.displayField) {
223 							buttonInfo.button.extButton.displayField = buttonInfo.button.displayField;
224 						}
225 						if ( buttonInfo.button.tpl ) {
226 							buttonInfo.button.extButton.tpl = buttonInfo.button.tpl;
227 						}
228 					}
229 				});
230 			}
231 
232 233 			return this.extButtonGroup;
234 		},
235 
236 		/**
237 		 * Recalculate the visibility of the buttons and the group
238 		 * @hide
239 		 */
240 		doLayout: function () {
241 			var groupVisible = false,
242 				that = this;
243 			jQuery.each(this.buttons, function(index, button) {
244 				if (typeof button.button !== "undefined") {
245 					var extButton = that.extButtonGroup.findById(button.button.id),
246 					buttonVisible = button.button.isVisible() && button.scopeVisible;
247 					
248 					if (!extButton) {
249 						return;
250 					}
251 				
252 					if (buttonVisible && extButton.hidden) {
253 						extButton.show();
254 					} else if (!buttonVisible && extButton && !extButton.hidden) {
255 						extButton.hide();
256 					}
257 				
258 					groupVisible |= buttonVisible;
259 				}
260 			});
261 			if (groupVisible && this.extButtonGroup.hidden) {
262 				this.extButtonGroup.show();
263 			} else if (!groupVisible && !this.extButtonGroup.hidden) {
264 				this.extButtonGroup.hide();
265 			}
266 		
267 			return groupVisible;
268 
269 		}
270 	});
271 
272 	//=========================================================================
273 	//
274 	// Floating Menu
275 	//
276 	//=========================================================================
277 
278 	var lastFloatingMenuPos = {
279 		top: null,
280 		left: null
281 	};
282 
283 	/**
284 	 * Handler for window scroll event.  Positions the floating menu
285 	 * appropriately.
286 	 *
287 	 * @param {Aloha.FloatingMenu} floatingmenu
288 	 */
289 	function onWindowScroll( floatingmenu ) {
290 		if ( !Aloha.activeEditable ) {
291 			return;
292 		}
293 
294 		var element = floatingmenu.obj;
295 		var editablePos = Aloha.activeEditable.obj.offset();
296 		var isTopAligned = floatingmenu.behaviour === 'topalign';
297 		var isAppended = floatingmenu.behaviour === 'append';
298 		var isManuallyPinned = floatingmenu.pinned
299 							 && ( parseInt( element.css( 'left' ), 10 )
300 								  != ( editablePos.left
301 									   + floatingmenu.horizontalOffset
302 									 ) );
303 
304 		// no calculation when pinned manually or has behaviour 'append'
305 		if ( isTopAligned && isManuallyPinned || isAppended ) {
306 			return;
307 		}
308 
309 		var floatingmenuHeight = element.height();
310 		var scrollTop = jQuery( document ).scrollTop();
311 
312 		// This value is what the top position of the floating menu *would* be
313 		// if we tried to position it above the active editable.
314 		var floatingmenuTop = editablePos.top - floatingmenuHeight
315 		                    + floatingmenu.marginTop
316 		                    - floatingmenu.topalignOffset;
317 
318 		// The floating menu does not fit in the space between the top of the
319 		// viewport and the editable, so position it at the top of the viewport
320 		// and over the editable.
321 		if ( scrollTop > floatingmenuTop ) {
322 			editablePos.top = isTopAligned
323 324 							? scrollTop + floatingmenu.marginTop
325 							: floatingmenu.marginTop;
326 
327 		// There is enough space on top of the editable to fit the entire
328 		// floating menu, so we do so.
329 		} else if ( scrollTop <= floatingmenuTop ) {
330 			editablePos.top -= floatingmenuHeight
331 							 + ( isTopAligned
332 								 ? floatingmenu.marginTop +
333 								   floatingmenu.topalignOffset
334 								 : 0 );
335 		}
336 
337 		floatingmenu.floatTo( editablePos );
338 	}
339 
340 	/**
341 	 * Aloha's Floating Menu
342 	 * @namespace Aloha
343 	 * @class FloatingMenu
344 	 * @singleton
345 	 */
346 	var FloatingMenu = Class.extend({
347 		/**
348 		 * Define the default scopes
349 		 * @property
350 		 * @type Object
351 		 */
352 		scopes: {
353 			'Aloha.empty' : {
354 				'name' : 'Aloha.empty',
355 				'extendedScopes' : [],
356 				'buttons' : []
357 			},
358 			'Aloha.global' : {
359 				'name' : 'Aloha.global',
360 				'extendedScopes' : ['Aloha.empty'],
361 				'buttons' : []
362 			},
363 			'Aloha.continuoustext' : {
364 				'name' : 'Aloha.continuoustext',
365 				'extendedScopes' : ['Aloha.global'],
366 				'buttons' : []
367 			}
368 		},
369 
370 		/**
371 		 * Array of tabs within the floatingmenu
372 		 * @hide
373 		 */
374 		tabs: [],
375 
376 		/**
377 		 * 'Map' of tabs (for easy access)
378 		 * @hide
379 		 */
380 		tabMap: {},
381 
382 		/**
383 		 * Flag to mark whether the floatingmenu is initialized
384 		 * @hide
385 		 */
386 		initialized: false,
387 
388 		/**
389 		 * Array containing all buttons
390 		 * @hide
391 		 */
392 		allButtons: [],
393 
394 		/**
395 		 * top part of the floatingmenu position
396 		 * @hide
397 		 */
398 		top: 100,
399 
400 		/**
401 		 * left part of the floatingmenu position
402 		 * @hide
403 		 */
404 		left: 100,
405 
406 		/**
407 		 * store pinned status - true, if the FloatingMenu is pinned
408 		 * @property
409 		 * @type boolean
410 		 */
411 		pinned: false,
412 
413 		/**
414 		 * just a reference to the jQuery(window) object, which is used quite often
415 		 */
416 		window: jQuery(window),
417 
418 		/**
419 		 * Aloha.settings.floatingmenu.behaviour
420 		 * 
421 		 * Is used to define the floating menu (fm) float behaviour.
422 		 *
423 		 * available: 
424 		 *  'float' (default) the fm will float next to the position where the caret is,
425 		 *  'topalign' the fm is fixed above the contentEditable which is active,
426 		 *  'append' the fm is appended to the defined 'element' element position (top/left)
427 		 */
428 		behaviour: 'float',
429 
430 		/**
431 		 * Aloha.settings.floatingmenu.element
432 		 *
433 		 * Is used to define the element where the floating menu is positioned when
434 		 * Aloha.settings.floatingmenu.behaviour is set to 'append'
435 		 * 
436 		 */
437 		element: 'floatingmenu',
438 
439 		/**
440 		 * topalign offset to be used for topalign behavior
441 		 */
442 		topalignOffset: 0,
443 		
444 		/**
445 		 * topalign offset to be used for topalign behavior
446 		 */
447 		horizontalOffset: 0,
448 		
449 		/**
450 		 * will only be hounoured when behaviour is set to 'topalign'. Adds a margin,
451 		 * so the floating menu is not directly attached to the top of the page
452 		 */
453 		marginTop: 10,
454 		
455 		/**
456 		 * Define whether the floating menu shall be draggable or not via Aloha.settings.floatingmanu.draggable
457 		 * Default is: true 
458 		 */
459 		draggable: true,
460 		
461 		/**
462 		 * Define whether the floating menu shall be pinned or not via Aloha.settings.floatingmanu.pin
463 		 * Default is: false 
464 		 */
465 		pin: false,
466 		
467 		/**
468 		 * A list of all buttons that have been added to the floatingmenu
469 		 * This needs to be tracked, as adding buttons twice will break the fm
470 		 */
471 		buttonsAdded: [],
472 		
473 		/**
474 		 * Will be initialized by checking Aloha.settings.toolbar, which will contain the config for
475 		 * the floating menu. If there is no config, tabs and groups will be generated programmatically
476 		 */
477 		fromConfig: false,
478 		
479 		/**
480 		 * hide a tab
481 		*/
482 		hideTab: false,
483 
484 		/**
485 		 * Initialize the floatingmenu
486 		 * @hide
487 		 */
488 		init: function() {
489 
490 			// check for behaviour setting of the floating menu
491 		    if ( Aloha.settings.floatingmenu ) {
492 		    	if ( typeof Aloha.settings.floatingmenu.draggable ===
493 				         'boolean' ) {
494 		    		this.draggable = Aloha.settings.floatingmenu.draggable;
495 		    	}
496 
497 				if ( typeof Aloha.settings.floatingmenu.behaviour ===
498 				         'string' ) {
499 					this.behaviour = Aloha.settings.floatingmenu.behaviour;
500 				}
501 
502 				if ( typeof Aloha.settings.floatingmenu.topalignOffset !==
503 					    'undefined' ) {
504 					this.topalignOffset = parseInt(
505 						Aloha.settings.floatingmenu.topalignOffset, 10 );
506 				}
507 
508 				if ( typeof Aloha.settings.floatingmenu.horizontalOffset !==
509 				         'undefined' ) {
510 					this.horizontalOffset = parseInt(
511 						Aloha.settings.floatingmenu.horizontalOffset , 10 );
512 				}
513 
514 				if ( typeof Aloha.settings.floatingmenu.marginTop ===
515 				         'number' ) {
516 				    this.marginTop = parseInt(
517 						Aloha.settings.floatingmenu.marginTop , 10 );
518 				}
519 
520 				if ( typeof Aloha.settings.floatingmenu.element ===
521 						'string' ) {
522 					this.element = Aloha.settings.floatingmenu.element;
523 				}
524 				if ( typeof Aloha.settings.floatingmenu.pin ===
525 						'boolean' ) {
526 					this.pin = Aloha.settings.floatingmenu.pin;
527 				}
528 
529 				if ( typeof Aloha.settings.floatingmenu.width !==
530 				         'undefined' ) {
531 					this.width = parseInt( Aloha.settings.floatingmenu.width,
532 						10 );
533 				}
534 		    }
535 
536 			jQuery.storage = new jQuery.store();
537 
538 			this.currentScope = 'Aloha.global';
539 
540 			var that = this;
541 
542 			this.window.unload(function () {
543 				// store fm position if the panel is pinned to be able to restore it next time
544 				if (that.pinned) {
545 					jQuery.storage.set('Aloha.FloatingMenu.pinned', 'true');
546 					jQuery.storage.set('Aloha.FloatingMenu.top', that.top);
547 					jQuery.storage.set('Aloha.FloatingMenu.left', that.left);
548 					if (Aloha.Log.isInfoEnabled()) {
549 						Aloha.Log.info(this, 'stored FloatingMenu pinned position {' + that.left
550 								+ ', ' + that.top + '}');
551 					}
552 				} else {
553 					// delete old localStorages
554 					jQuery.storage.del('Aloha.FloatingMenu.pinned');
555 					jQuery.storage.del('Aloha.FloatingMenu.top');
556 					jQuery.storage.del('Aloha.FloatingMenu.left');
557 				}
558 				if (that.userActivatedTab) {
559 					jQuery.storage.set('Aloha.FloatingMenu.activeTab', that.userActivatedTab);
560 				}
561 			}).resize(function () {
562 				if (that.behaviour === 'float') {
563 					if (that.pinned) {
564 						that.fixPinnedPosition();
565 						that.refreshShadow();
566 						that.extTabPanel.setPosition(that.left, that.top);
567 					} else {
568 						var target = that.calcFloatTarget(Aloha.Selection.getRangeObject());
569 						if (target) {
570 							that.floatTo(target);
571 						}
572 					}
573 				}
574 			});
575 			Aloha.bind('aloha-ready', function() {
576 				that.generateComponent();
577 				that.initialized = true;
578 			});
579 			
580 			if (typeof Aloha.settings.toolbar === 'object') {
581 				this.fromConfig = true;
582 			}
583 		},
584 
585 		/**
586 		 * jQuery reference to the extjs tabpanel
587 		 * @hide
588 		 */
589 		obj: null,
590 
591 		/**
592 		 * jQuery reference to the shadow obj
593 		 * @hide
594 		 */
595 		shadow: null,
596 
597 		/**
598 		 * jQuery reference to the panels body wrap div
599 		 * @hide
600 		 */
601 		panelBody: null,
602 		
603 		/**
604 		 * The panels width
605 		 * @hide
606 		 */
607 		width: 400,
608 
609 		/**
610 		 * initialize tabs and groups according to the current configuration
611 		 */
612 		initTabsAndGroups: function () {
613 			var that = this;
614 			
615 			// if there is no toolbar config tabs and groups have been initialized before
616 			if (!this.fromConfig) {
617 				return;
618 			}
619 			
620 			jQuery.each(Aloha.settings.toolbar.tabs, function (tab, groups) {
621 				// generate or retrieve tab
622 				var tabObject = that.tabMap[tab];
623 				if (typeof tabObject === 'undefined') {
624 					// the tab object does not yet exist, so create a new tab and add it to the list
625 					tabObject = new Tab(tab);
626 627 					that.tabs.push(tabObject);
628 					that.tabMap[tab] = tabObject;
629 				}
630 				
631 				// generate groups for current tab
632 				jQuery.each(groups, function (group, buttons) {
633 					var groupObject = tabObject.getGroup(group),
634 						i;
635 
636 					// now get all the buttons for that group
637 					jQuery.each(buttons, function (j, button) {
638 						if (jQuery.inArray(button, that.buttonsAdded) !== -1) {
639 							// buttons must not be added twice
640 							console.warn('Skipping button {' + button + '}. A button can\'t be added ' + 
641 								'to the floating menu twice. Config key: {Aloha.settings.toolbar.' + 
642 									tab + '.' + group + '}');
643 									return;
644 						}
645 						
646 						// now add the button to the group
647 						for (i = 0; i < that.allButtons.length; i++) {
648 							if (button === that.allButtons[i].button.name) {
649 								groupObject.addButton(that.allButtons[i]);
650 								// remember that we've added the button
651 								that.buttonsAdded.push(that.allButtons[i].button.name);
652 								break;
653 							}
654 						}
655 					});
656 				});
657 			});
658 		},
659 
660 		/**
661 		 * Generate the rendered component for the floatingmenu
662 		 * @hide
663 		 */
664 		generateComponent: function () {
665 			var that = this, pinTab;
666 
667 			// initialize tabs and groups first
668 			this.initTabsAndGroups();
669 
670 			// Initialize and configure the tooltips
671 			Ext.QuickTips.init();
672 			Ext.apply(Ext.QuickTips.getQuickTip(), {
673 				minWidth : 10
674 			});
675 
676 			
677 			
678 			if (this.extTabPanel) {
679 				// TODO dispose of the ext component
680 			} else {
681 				
682 				// Enable or disable the drag functionality
683 				var dragConfiguration = false;
684 
685 				if ( that.draggable ) {
686 					dragConfiguration = {
687 						insertProxy: false,
688 						onDrag : function(e) {
689 							var pel = this.proxy.getEl();
690 							this.x = pel.getLeft(true);
691 							this.y = pel.getTop(true);
692 							this.panel.shadow.hide();
693 						},
694 						endDrag : function(e) {
695 							var top = (that.pinned) ? this.y - jQuery(document).scrollTop() : this.y;
696 					
697 							that.left = this.x;
698 							that.top = top;
699 
700 							// store the last floating menu position when the floating menu was dragged around
701 							lastFloatingMenuPos.left = that.left;
702 							lastFloatingMenuPos.top = that.top;
703 
704 							this.panel.setPosition(this.x, top);
705 							that.refreshShadow();
706 							this.panel.shadow.show();
707 						}
708 					};
709 				}
710 				// generate the tabpanel object
711 				this.extTabPanel = new Ext.TabPanel({
712 					activeTab: 0,
713 					width: that.width, // 336px this fits the multisplit button and 6 small buttons placed in 3 cols
714 					plain: false,
715 					draggable: dragConfiguration,
716 					floating: {shadow: false},
717 					defaults: {
718 						autoScroll: true
719 					},
720 					layoutOnTabChange : true,
721 					shadow: false,
722 					cls: 'aloha-floatingmenu ext-root',
723 					listeners : {
724 						'tabchange' : {
725 							'fn' : function(tabPanel, tab) {
726 								if (tab.title != that.autoActivatedTab) {
727 									if (Aloha.Log.isDebugEnabled()) {
728 										Aloha.Log.debug(that, 'User selected tab ' + tab.title);
729 									}
730 									// remember the last user-selected tab
731 									that.userActivatedTab = tab.title;
732 								} else {
733 									if (Aloha.Log.isDebugEnabled()) {
734 										Aloha.Log.debug(that, 'Tab ' + tab.title + ' was activated automatically');
735 									}
736 								}
737 								that.autoActivatedTab = undefined;
738 						
739 								// ok, this is kind of a hack: when the tab changes, we check all buttons for multisplitbuttons (which have the method setActiveDOMElement).
740 								// if a DOM Element is queued to be set active, we try to do this now.
741 								// the reason for this is that the active DOM element can only be set when the multisplit button is currently visible.
742 								jQuery.each(that.allButtons, function(index, buttonInfo) {
743 									if (typeof buttonInfo.button !== 'undefined'
744 										&& typeof buttonInfo.button.extButton !== 'undefined'
745 										&& buttonInfo.button.extButton !== null
746 										&& typeof buttonInfo.button.extButton.setActiveDOMElement === 'function') {
747 										if (typeof buttonInfo.button.extButton.activeDOMElement !== 'undefined') {
748 											buttonInfo.button.extButton.setActiveDOMElement(buttonInfo.button.extButton.activeDOMElement);
749 										}
750 									}
751 								});
752 						
753 								// adapt the shadow
754 								if (that.extTabPanel.isVisible()) {
755 									that.extTabPanel.shadow.show();
756 									that.refreshShadow();
757 								}
758 							}
759 						}
760 					},
761 					enableTabScroll : true
762 				});
763 		
764 		
765 			}
766 
767 			// add the tabs
768 			jQuery.each(this.tabs, function(index, tab) {
769 				// let each tab generate its ext component and add them to the panel
770 				try {
771 					if (!tab.extPanel) {
772 						// if the tab itself was not generated, we do this and add it to the panel
773 						that.extTabPanel.add(tab.getExtComponent());
774 					} else {
775 						// otherwise, we will make sure that probably missing groups are generated, but don't add the tab to the menu (again)
776 						tab.getExtComponent();
777 					}
778 				} catch(e) {
779 					Aloha.Log.error(that,"Error while inserting tab: " + e);
780 				}
781 			});
782 
783 			// add the dropshadow
784 			if (!this.extTabPanel.shadow) {
785 				this.extTabPanel.shadow = jQuery('<div id="aloha-floatingmenu-shadow" class="aloha-shadow"> </div>').hide();
786 				jQuery('body').append(this.extTabPanel.shadow);
787 			}
788 
789 			// add an empty pin tab item, store reference
790 			pinTab = this.extTabPanel.add({
791 				title : ' '
792 			});
793 
794 			// finally render the panel to the body
795 			this.extTabPanel.render(document.body);
796 
797 			// finish the pin element after the FM has rendered (before there are noe html contents to be manipulated
798 			jQuery(pinTab.tabEl)
799 				.addClass('aloha-floatingmenu-pin')
800 				.html(' ')
801 				.mousedown(function (e) {
802 					that.togglePin();
803 					// Note: this event is deliberately stopped here, although normally,
804 					// we would set the flag GENTICS.Aloha.eventHandled instead.
805 					// But when the event bubbles up, no tab would be selected and
806 					// the floatingmenu would be rather thin.
807 					e.stopPropagation();
808 				});
809 
810 			// a reference to the panels body needed for shadow size & position
811 			this.panelBody = jQuery('div.aloha-floatingmenu div.x-tab-panel-bwrap');
812 
813 			// do the visibility
814 			this.doLayout();
815 
816 			// bind jQuery reference to extjs obj
817 			// this has to be done AFTER the tab panel has been rendered
818 			this.obj = jQuery(this.extTabPanel.getEl().dom);
819 
820 			if (jQuery.storage.get('Aloha.FloatingMenu.pinned') == 'true') {
821 				//this.togglePin();
822 
823 				this.top = parseInt(jQuery.storage.get('Aloha.FloatingMenu.top'),10);
824 				this.left = parseInt(jQuery.storage.get('Aloha.FloatingMenu.left'),10);
825 
826 				// do some positioning fixes
827 				this.fixPinnedPosition();
828 
829 				if (Aloha.Log.isInfoEnabled()) {
830 					Aloha.Log.info(this, 'restored FloatingMenu pinned position {' + this.left + ', ' + this.top + '}');
831 				}
832 
833 				this.refreshShadow();
834 			}
835 
836 			// set the user activated tab stored in a localStorage
837 			if (jQuery.storage.get('Aloha.FloatingMenu.activeTab')) {
838 				this.userActivatedTab = jQuery.storage.get('Aloha.FloatingMenu.activeTab');
839 			}
840 
841 			// for now, position the panel somewhere
842 			this.extTabPanel.setPosition(this.left, this.top);
843 
844 			// mark the event being handled by aloha, because we don't want to recognize
845 			// a click into the floatingmenu to be a click into nowhere (which would
846 			// deactivate the editables)
847 			this.obj.mousedown(function (e) {
848 				e.originalEvent.stopSelectionUpdate = true;
849 				Aloha.eventHandled = true;
850 				//e.stopSelectionUpdate = true;
851 			});
852 
853 			this.obj.mouseup(function (e) {
854 				e.originalEvent.stopSelectionUpdate = true;
855 				Aloha.eventHandled = false;
856 			});
857 
858 			jQuery( window ).scroll(function() {
859 				onWindowScroll( that );
860 			});
861 
862 			// don't display the drag handle bar / pin when floating menu is not draggable
863 			if ( !that.draggable ) {
864 				jQuery('.aloha-floatingmenu').hover( function() {
865 					jQuery(this).css({background: 'none'});
866 					jQuery('.aloha-floatingmenu-pin').hide();
867 				});
868 			}
869 
870 			// adjust float behaviour
871 			if (this.behaviour === 'float') {
872 				// listen to selectionChanged event
873 				Aloha.bind('aloha-selection-changed',function(event, rangeObject) {
874 					if (!that.pinned) {
875 						var pos = that.calcFloatTarget(rangeObject);
876 						if (pos) {
877 							that.floatTo(pos);
878 						}
879 					}
880 				});
881 			} else if (this.behaviour === 'append' ) {
882 				var p = jQuery( "#" + that.element );
883 				var position = p.offset();
884 
885 				if ( !position ) {
886 					Aloha.Log.warn(that, 'Invalid element HTML ID for floating menu: ' + that.element);
887 					return false;
888 				}
889 
890 				// set the position so that it does not float on the first editable activation
891 				this.floatTo( position );
892 				
893 				if ( this.pin ) {
894 					this.togglePin( true );
895 				}
896 
897 				Aloha.bind( 'aloha-editable-activated', function( event, data ) {
898 					if ( that.pinned ) {
899 						return;
900 					}
901 					that.floatTo( position );
902 				});
903 				
904 		    } else if ( this.behaviour === 'topalign' ) {
905 				// topalign will retain the user's pinned status
906 				// TODO maybe the pin should be hidden in that case?
907 				this.togglePin( false );
908 
909 				// Float the menu to the editable that is activated.
910 				Aloha.bind( 'aloha-editable-activated', function( event, data ) {
911 					if ( that.pinned ) {
912 						return;
913 					}
914 
915 					// FIXME: that.obj.height() does not return the correct
916 					//        height of the editable.  We need to fix this, and
917 					//        not hard-code the height as we currently do.
918 					var editable = data.editable.obj;
919 					var floatingmenuHeight = 90;
920 					var editablePos = editable.offset();
921 					var isFloatingmenuAboveViewport = ( (
922 						editablePos.top - floatingmenuHeight )
923 						    < jQuery( document ).scrollTop() );
924 
925 					if ( isFloatingmenuAboveViewport ) {
926 						// Since we don't have space to place the floatingmenu
927 						// above the editable, we want to place it over the
928 						// editable instead.  But if the editable is shorter
929 						// than the floatingmenu, it would be completely
930 						// covered by it, and so, in such cases, we position
931 						// the editable at the bottom of the short editable.
932 						editablePos.top = ( editable.height()
933 						         < floatingmenuHeight )
934 							? editablePos.top + editable.height()
935 							: jQuery( document ).scrollTop();
936 
937 						editablePos.top += that.marginTop;
938 					} else {
939 						editablePos.top -= floatingmenuHeight
940 						                 + that.topalignOffset;
941 					}
942 
943 					editablePos.left += that.horizontalOffset;
944 
945 					var HORIZONTAL_PADDING = 10;
946 					// Calculate by how much the floating menu is pocking
947 					// outside the width of the viewport.  A positive number
948 					// means that it is outside the viewport, negative means
949 					// it is within the viewport.
950 					var overhang = ( ( editablePos.left + that.width
951 						+ HORIZONTAL_PADDING ) - jQuery(window).width() );
952 
953 					if ( overhang > 0 ) {
954 						editablePos.left -= overhang;	
955 					}
956 
957 					that.floatTo( editablePos );
958 				});
959 			}
960 		},
961 
962 		/**
963 		 * Fix the position of the pinned floatingmenu to keep it visible
964 		 */
965 		fixPinnedPosition: function() {
966 			// if not pinned, do not fix the position
967 			if (!this.pinned) {
968 				return;
969 			}
970 
971 			// fix the position of the floatingmenu, to keep it visible
972 			if (this.top < 30) {
973 				// from top position, we want to have 30px margin
974 				this.top = 30;
975 			} else if (this.top > this.window.height() - this.extTabPanel.getHeight()) {
976 				this.top = this.window.height() - this.extTabPanel.getHeight();
977 			}
978 			if (this.left < 0) {
979 				this.left = 0;
980 			} else if (this.left > this.window.width() - this.extTabPanel.getWidth()) {
981 				this.left = this.window.width() - this.extTabPanel.getWidth();
982 			}
983 		},
984 
985 		/**
986 		 * reposition & resize the shadow
987 		 * the shadow must not be repositioned outside this method!
988 		 * position calculation is based on this.top and this.left coordinates
989 		 * @method
990 		 */
991 		refreshShadow: function (resize) {
992 			if (this.panelBody) {
993 				var props = {
994 					'top': this.top + 24, // 24px top offset to reflect tab bar height
995 					'left': this.left
996 				};
997 
998 				if(typeof resize === 'undefined' || !resize) {
999 					props.width = this.panelBody.width() + 'px';
1000 					props.height = this.panelBody.height() + 'px';
1001 				}
1002 
1003 				this.extTabPanel.shadow.css(props);
1004 			}
1005 		},
1006 
1007 		/**
1008 		 * toggles the pinned status of the floating menu
1009 		 * @method
1010 		 * @param {boolean} pinned set to true to activate pin, or set to false to deactivate pin. 
1011 		 *             leave undefined to toggle pin status automatically
1012 		 */
1013 		togglePin: function(pinned) {
1014 			var el = jQuery('.aloha-floatingmenu-pin');
1015 		       
1016 		    if (typeof pinned === 'boolean') {
1017 			this.pinned = !pinned;
1018 		    }
1019 		       
1020 			if (this.pinned) {
1021 				el.removeClass('aloha-floatingmenu-pinned');
1022 				this.top = this.obj.offset().top;
1023 
1024 				this.obj.removeClass('fixed').css({
1025 					'top': this.top
1026 				});
1027 
1028 				this.extTabPanel.shadow.removeClass('fixed');
1029 				this.refreshShadow();
1030 
1031 				this.pinned = false;
1032 			} else {
1033 				el.addClass('aloha-floatingmenu-pinned');
1034 				this.top = this.obj.offset().top - this.window.scrollTop();
1035 
1036 				this.obj.addClass('fixed').css({
1037 					'top': this.top // update position for fixed position
1038 				});
1039 
1040 				// do the same for the shadow
1041 				this.extTabPanel.shadow.addClass('fixed');//props.start
1042 				this.refreshShadow();
1043 
1044 				this.pinned = true;
1045 			}
1046 		},
1047 
1048 		/**
1049 		 * Create a new scopes
1050 		 * @method
1051 		 * @param {String} scope name of the new scope (should be namespaced for uniqueness)
1052 		 * @param {String} extendedScopes Array of scopes this scope extends. Can also be a single String if
1053 		 *            only one scope is extended, or omitted if the scope should extend
1054 		 *            the empty scope
1055 		 */
1056 		createScope: function(scope, extendedScopes) {
1057 			if (typeof extendedScopes === 'undefined') {
1058 				extendedScopes = ['Aloha.empty'];
1059 			} else if (typeof extendedScopes === 'string') {
1060 				extendedScopes = [extendedScopes];
1061 			}
1062 
1063 			// TODO check whether the extended scopes already exist
1064 
1065 			if (this.scopes[scope]) {
1066 				// TODO what if the scope already exists?
1067 			} else {
1068 				// generate the new scope
1069 				this.scopes[scope] = {'name' : scope, 'extendedScopes' : extendedScopes, 'buttons' : []};
1070 			}
1071 		},
1072 
1073 		/**
1074 		 * Adds a button to the floatingmenu
1075 		 * @method
1076 		 * @param {String} scope the scope for the button, should be generated before (either by core or the plugin)
1077 		 * @param {Button} button instance of Aloha.ui.button to add at the floatingmenu
1078 		 * @param {String} tab label of the tab to which the button is added
1079 		 * @param {int} group index of the button group in the tab, lowest index is left
1080 		 */
1081 		addButton: function(scope, button, tab, group) {
1082 			// check whether the scope exists
1083 			var
1084 				scopeObject = this.scopes[scope],
1085 				buttonInfo, tabObject, groupObject;
1086 		
1087 			if (!button.name) {
1088 				console.warn('Added button with iconClass {' + button.iconClass + '} which has no property "name"');
1089 			}
1090 	
1091 			if (typeof scopeObject === 'undefined') {
1092 				Aloha.Log.error("Can't add button to given scope since the scope has not yet been initialized.", scope);
1093 				return false;
1094 			}
1095 
1096 			// generate a buttonInfo object
1097 			buttonInfo = { 'button' : button, 'scopeVisible' : false };
1098 
1099 			// add the button to the list of all buttons
1100 			this.allButtons.push(buttonInfo);
1101 
1102 			// add the button to the scope
1103 			scopeObject.buttons.push(buttonInfo);
1104 
1105 			// if there is no toolbar config tabs and groups will be generated right away
1106 			if (!this.fromConfig) {
1107 				// get the tab object
1108 				tabObject = this.tabMap[tab];
1109 				if (typeof tabObject === 'undefined') {
1110 					// the tab object does not yet exist, so create a new tab and add it to the list
1111 					tabObject = new Tab(tab);
1112 					this.tabs.push(tabObject);
1113 					this.tabMap[tab] = tabObject;
1114 				}
1115 
1116 				// get the group
1117 				groupObject = tabObject.getGroup(group);
1118 
1119 				// now add the button to the group
1120 				groupObject.addButton(buttonInfo);
1121 			}
1122 
1123 			// finally, when the floatingmenu is already initialized, we need to create the ext component now
1124 			if (this.initialized) {
1125 				this.generateComponent();
1126 			}
1127 		},
1128 
1129 		/**
1130 		 * Recalculate the visibility of tabs, groups and buttons (depending on scope and button hiding)
1131 		 * @hide
1132 		 */
1133 		doLayout: function () {
1134 			if (Aloha.Log.isDebugEnabled()) {
1135 				Aloha.Log.debug(this, 'doLayout called for FloatingMenu, scope is ' + this.currentScope);
1136 			}
1137 
1138 			// if there's no floatingmenu don't do anything
1139 			if ( typeof this.extTabPanel === 'undefined' ) {
1140 				return false;
1141 			}
1142 
1143 			var that = this,
1144 				firstVisibleTab = false,
1145 				activeExtTab = this.extTabPanel.getActiveTab(),
1146 				activeTab = false,
1147 				floatingMenuVisible = false,
1148 				showUserActivatedTab = false,
1149 				pos;
1150 			
1151 			// let the tabs layout themselves
1152 			jQuery.each(this.tabs, function(index, tab) {
1153 
1154 				// remember the active tab
1155 				if (tab.extPanel == activeExtTab) {
1156 					activeTab = tab;
1157 				}
1158 
1159 				// remember whether the tab is currently visible
1160 				var tabVisible = tab.visible;
1161 
1162 				// let each tab generate its ext component and add them to the panel
1163 				if (tab.doLayout()) {
1164 					// found a visible tab, so the floatingmenu needs to be visible as well
1165 					floatingMenuVisible = true;
1166 
1167 					// make sure the tabstrip is visible
1168 					if (!tabVisible) {
1169 						if (Aloha.Log.isDebugEnabled()) {
1170 							Aloha.Log.debug(that, 'showing tab strip for tab ' + tab.label);
1171 						}
1172 						that.extTabPanel.unhideTabStripItem(tab.extPanel);
1173 					}
1174 
1175 					// remember the first visible tab
1176 					if (!firstVisibleTab) {
1177 						// this is the first visible tab (in case we need to switch to it)
1178 						firstVisibleTab = tab;
1179 					}
1180 					// check whether this visible tab is the last user activated tab and currently not active
1181 					if (that.userActivatedTab == tab.extPanel.title && tab.extPanel != activeExtTab) {
1182 						showUserActivatedTab = tab;
1183 					}
1184 				} else {
1185 					// make sure the tabstrip is hidden
1186 					if (tabVisible) {
1187 						if (Aloha.Log.isDebugEnabled()) {
1188 							Aloha.Log.debug(that, 'hiding tab strip for tab ' + tab.label);
1189 						}
1190 						that.extTabPanel.hideTabStripItem(tab.extPanel);
1191 					}
1192 				}
1193 
1194 				// hide a tab
1195 				if ( tab.label == that.hideTab ) {
1196 					that.extTabPanel.hideTabStripItem(tab.extPanel);
1197 
1198 					if ( activeExtTab.title == that.hideTab ) {
1199 						showUserActivatedTab = firstVisibleTab;
1200 					}
1201 				}
1202 			});
1203 
1204 			// check whether the last tab which was selected by the user is visible and not the active tab
1205 			if (showUserActivatedTab) {
1206 				if (Aloha.Log.isDebugEnabled()) {
1207 					Aloha.Log.debug(this, 'Setting active tab to ' + showUserActivatedTab.label);
1208 				}
1209 				this.extTabPanel.setActiveTab(showUserActivatedTab.extPanel);
1210 			} else if (typeof activeTab === 'object' && typeof firstVisibleTab === 'object') {
1211 				// now check the currently visible tab, whether it is visible and enabled
1212 				if (!activeTab.visible) {
1213 					if (Aloha.Log.isDebugEnabled()) {
1214 						Aloha.Log.debug(this, 'Setting active tab to ' + firstVisibleTab.label);
1215 					}
1216 					this.autoActivatedTab = firstVisibleTab.extPanel.title;
1217 					this.extTabPanel.setActiveTab(firstVisibleTab.extPanel);
1218 				}
1219 			}
1220 
1221 			// set visibility of floatingmenu
1222 			if (floatingMenuVisible && this.extTabPanel.hidden) {
1223 				// set the remembered position
1224 				this.extTabPanel.show();
1225 				this.refreshShadow();
1226 				this.extTabPanel.shadow.show();
1227 				this.extTabPanel.setPosition(this.left, this.top);
1228 			} else if (!floatingMenuVisible && !this.extTabPanel.hidden) {
1229 				// remember the current position
1230 				pos = this.extTabPanel.getPosition(true);
1231 				// restore previous position if the fm was pinned
1232 				this.left = pos[0] < 0 ? 100 : pos[0];
1233 				this.top = pos[1] < 0 ? 100 : pos[1];
1234 				this.extTabPanel.hide();
1235 				this.extTabPanel.shadow.hide();
1236 			} /*else {
1237 				var target = that.calcFloatTarget(Aloha.Selection.getRangeObject());
1238 				if (target) {
1239 					this.left = target.left;
1240 					this.top = target.top;
1241 					this.extTabPanel.show();
1242 					this.refreshShadow();
1243 					this.extTabPanel.shadow.show();
1244 					this.extTabPanel.setPosition(this.left, this.top);
1245 
1246 					that.floatTo(target);
1247 				}
1248 			}*/
1249 
1250 			// let the Ext object render itself again
1251 			this.extTabPanel.doLayout();
1252 		},
1253 
1254 		/**
1255 		 * Set the current scope
1256 		 * @method
1257 		 * @param {String} scope name of the new current scope
1258 		 */
1259 		setScope: function(scope) {
1260 			// get the scope object
1261 			var scopeObject = this.scopes[scope];
1262 
1263 			if (typeof scopeObject === 'undefined') {
1264 				// TODO log an error
1265 			} else if (this.currentScope != scope) {
1266 				this.currentScope = scope;
1267 
1268 				// first hide all buttons
1269 				jQuery.each(this.allButtons, function(index, buttonInfo) {
1270 					buttonInfo.scopeVisible = false;
1271 				});
1272 
1273 				// now set the buttons in the given scope to be visible
1274 				this.setButtonScopeVisibility(scopeObject);
1275 
1276 				// finally refresh the layout
1277 				this.doLayout();
1278 			}
1279 		},
1280 
1281 		/**
1282 		 * Set the scope visibility of the buttons for the given scope. This method will call itself for the motherscopes of the given scope.
1283 		 * @param scopeObject scope object
1284 		 * @hide
1285 		 */
1286 		setButtonScopeVisibility: function(scopeObject) {
1287 			var that = this;
1288 
1289 			// set all buttons in the given scope to be visible
1290 			jQuery.each(scopeObject.buttons, function(index, buttonInfo) {
1291 				buttonInfo.scopeVisible = true;
1292 			});
1293 
1294 			// now do the recursion for the motherscopes
1295 			jQuery.each(scopeObject.extendedScopes, function(index, scopeName) {
1296 				var motherScopeObject = that.scopes[scopeName];
1297 				if (typeof motherScopeObject === 'object') {
1298 					that.setButtonScopeVisibility(motherScopeObject);
1299 				}
1300 			});
1301 		},
1302 
1303 		/**
1304 		 * returns the next possible float target dom obj
1305 		 * the floating menu should only float to h1-h6, p, div, td and pre elements
1306 		 * if the current object is not valid, it's parentNode will be considered, until
1307 		 * the limit object is hit
1308 		 * @param obj the dom object to start from (commonly this would be the commonAncestorContainer)
1309 		 * @param limitObj the object that limits the range (this would be the editable)
1310 		 * @return dom object which qualifies as a float target
1311 		 * @hide
1312 		 */
1313 		nextFloatTargetObj: function (obj, limitObj) {
1314 			// if we've hit the limit object we don't care for it's type
1315 			if (!obj || obj == limitObj) {
1316 				return obj;
1317 			}
1318 
1319 			// fm will only float to h1-h6, p, div, td
1320 			switch (obj.nodeName.toLowerCase()) {
1321 				case 'h1':
1322 				case 'h2':
1323 				case 'h3':
1324 				case 'h4':
1325 				case 'h5':
1326 				case 'h6':
1327 				case 'p':
1328 				case 'div':
1329 				case 'td':
1330 				case 'pre':
1331 				case 'ul':
1332 				case 'ol':
1333 					return obj;
1334 				default:
1335 					return this.nextFloatTargetObj(obj.parentNode, limitObj);
1336 			}
1337 		},
1338 
1339 		/**
1340 		 * calculates the float target coordinates for a range
1341 		 * @param range the fm should float to
1342 		 * @return object containing left and top coordinates, like { left : 20, top : 43 }
1343 		 * @hide
1344 		 */
1345 		calcFloatTarget: function(range) {
1346 			var
1347 				i, documentWidth, editableLength, left, target,
1348 				targetObj, scrollTop, top;
1349 
1350 			// TODO in IE8 somteimes a broken range is handed to this function - investigate this
1351 			if (!Aloha.activeEditable || typeof range.getCommonAncestorContainer === 'undefined') {
1352 				return false;
1353 			}
1354 
1355 			// check if the designated editable is disabled
1356 			for ( i = 0, editableLength = Aloha.editables.length; i < editableLength; i++) {
1357 				if (Aloha.editables[i].obj.get(0) == range.limitObject &&
1358 						Aloha.editables[i].isDisabled()) {
1359 					return false;
1360 				}
1361 			}
1362 
1363 			target = this.nextFloatTargetObj(range.getCommonAncestorContainer(), range.limitObject);
1364 			if ( ! target ) {
1365 				return false;
1366 			}
1367 
1368 			targetObj = jQuery(target);
1369 			scrollTop = GENTICS.Utils.Position.Scroll.top;
1370 			if (!targetObj || !targetObj.offset()) {
1371 				return false;
1372 			}
1373 			top = targetObj.offset().top - this.obj.height() - 50; // 50px offset above the current obj to have some space above
1374 
1375 			// if the floating menu would be placed higher than the top of the screen...
1376 			if ( top < scrollTop) {
1377 				top += 80 + GENTICS.Utils.Position.ScrollCorrection.top;
1378 				// 80px if editable element is eg h1; 50px was fine for p;
1379 				// todo: maybe just use GENTICS.Utils.Position.ScrollCorrection.top with a better value?
1380 				// check where this is also used ...
1381 			}
1382 
1383 			// if the floating menu would float off the bottom of the screen
1384 			// we don't want it to move, so we'll return false
1385 			if (top > this.window.height() + this.window.scrollTop()) {
1386 				return false;
1387 			}
1388 
1389 			// check if the floating menu does not float off the right side
1390 			left = Aloha.activeEditable.obj.offset().left;
1391 			documentWidth = jQuery(document).width();
1392 			if ( documentWidth - this.width < left ) {
1393 					left = documentWidth - this.width - GENTICS.Utils.Position.ScrollCorrection.left;
1394 			}
1395 			
1396 			return {
1397 				left : left,
1398 				top : top
1399 			};
1400 		},
1401 
1402 		/**
1403 		 * float the fm to the desired position
1404 		 * the floating menu won't float if it is pinned
1405 		 * @method
1406 		 * @param {Object} coordinate object which has a left and top property
1407 		 */
1408 		floatTo: function(position) {
1409 			// no floating if the panel is pinned
1410 			if (this.pinned) {
1411 				return;
1412 			}
1413 
1414 			var floatingmenu = this,
1415 			    fmpos = this.obj.offset(),
1416 				lastLeft,
1417 				lastTop;
1418 			
1419 			if ( lastFloatingMenuPos.left === null ) {
1420 				lastLeft = fmpos.left;
1421 				lastTop = fmpos.top;
1422 			} else {
1423 				lastLeft = lastFloatingMenuPos.left;
1424 				lastTop = lastFloatingMenuPos.top;
1425 			}
1426 
1427 			// Place the floatingmenu to the last place the user had seen it,
1428 			// then animate it into its new position.
1429 			if ( lastLeft != position.left || lastTop != position.top ) {
1430 				this.obj.offset({
1431 					left: lastLeft,
1432 					top: lastTop
1433 				});
1434 
1435 				this.obj.animate( {
1436 					top: position.top,
1437 					left: position.left
1438 				}, {
1439 					queue : false,
1440 					step : function( step, props ) {
1441 						// update position reference
1442 						if ( props.prop === 'top' ) {
1443 							floatingmenu.top = props.now;
1444 						} else if ( props.prop === 'left' ) {
1445 							floatingmenu.left = props.now;
1446 						}
1447 
1448 						floatingmenu.refreshShadow( false );
1449 					},
1450 					complete: function() {
1451 						// When the animation is over, remember the floatingmenu's
1452 						// final resting position.
1453 						lastFloatingMenuPos.left = floatingmenu.left;
1454 						lastFloatingMenuPos.top = floatingmenu.top;
1455 					}
1456 				});
1457 			}
1458 		},
1459 
1460 		/**
1461 		 * Hide the floatingmenu
1462 		 */
1463 		hide: function() {
1464 			if (this.obj) {
1465 				this.obj.hide();
1466 			}
1467 			if (this.shadow) {
1468 				this.shadow.hide();
1469 			}
1470 		},
1471 
1472 		/**
1473 		 * Activate the tab containing the button with given name.
1474 		 * If the button with given name is not found, nothing changes
1475 		 * @param name name of the button
1476 		 */
1477 		activateTabOfButton: function(name) {
1478 			var tabOfButton = null;
1479 
1480 			// find the tab containing the button
1481 			for (var t = 0; t < this.tabs.length && !tabOfButton; t++) {
1482 				var tab = this.tabs[t];
1483 				for (var g = 0; g < tab.groups.length && !tabOfButton; g++) {
1484 					var group = tab.groups[g];
1485 					for (var b = 0; b < group.buttons.length && !tabOfButton; b++) {
1486 						var button = group.buttons[b];
1487 						if (button.button.name == name) {
1488 							tabOfButton = tab;
1489 							break;
1490 						}
1491 					}
1492 				}
1493 			}
1494 
1495 			if (tabOfButton) {
1496 				this.userActivatedTab = tabOfButton.label;
1497 				this.doLayout();
1498 			}
1499 		}
1500 1501 	});
1502 	
1503 	var menu =  new FloatingMenu();
1504 	menu.init();
1505 	
1506 	// set scope to empty if deactivated
1507 	Aloha.bind('aloha-editable-deactivated', function() {
1508 		menu.setScope('Aloha.empty');
1509 	});
1510 	
1511 	// set scope to empty if the user selectes a non contenteditable area
1512 	Aloha.bind('aloha-selection-changed', function() {
1513 		if ( !Aloha.Selection.isSelectionEditable() && !Aloha.Selection.isFloatingMenuVisible() ) {
1514 			menu.setScope('Aloha.empty');
1515 		}
1516 	});
1517 	
1518 	return menu;
1519 });
1520 
1521