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