Index: resources/bookmark_manager/js/cr/ui/contextmenuhandler.js |
=================================================================== |
--- resources/bookmark_manager/js/cr/ui/contextmenuhandler.js (revision 0) |
+++ resources/bookmark_manager/js/cr/ui/contextmenuhandler.js (revision 0) |
@@ -0,0 +1,211 @@ |
+// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+cr.define('cr.ui', function() { |
+ |
+ /** |
+ * Handles context menus. |
+ * @constructor |
+ */ |
+ function ContextMenuHandler() {} |
+ |
+ ContextMenuHandler.prototype = { |
+ |
+ /** |
+ * The menu that we are currently showing. |
+ * @type {cr.ui.Menu} |
+ */ |
+ menu_: null, |
+ get menu() { |
+ return this.menu_; |
+ }, |
+ |
+ /** |
+ * Shows a menu as a context menu. |
+ * @param {!Event} e The event triggering the show (usally a contextmenu |
+ * event). |
+ * @param {!cr.ui.Menu} menu The menu to show. |
+ */ |
+ showMenu: function(e, menu) { |
+ this.menu_ = menu; |
+ |
+ menu.style.display = 'block'; |
+ // when the menu is shown we steal all keyboard events. |
+ menu.ownerDocument.addEventListener('keydown', this, true); |
+ menu.ownerDocument.addEventListener('mousedown', this, true); |
+ menu.ownerDocument.addEventListener('blur', this, true); |
+ menu.addEventListener('activate', this); |
+ this.positionMenu_(e, menu); |
+ }, |
+ |
+ /** |
+ * Hide the currently shown menu. |
+ */ |
+ hideMenu: function() { |
+ var menu = this.menu; |
+ if (!menu) |
+ return; |
+ |
+ menu.style.display = 'none'; |
+ menu.ownerDocument.removeEventListener('keydown', this, true); |
+ menu.ownerDocument.removeEventListener('mousedown', this, true); |
+ menu.ownerDocument.removeEventListener('blur', this, true); |
+ menu.removeEventListener('activate', this); |
+ menu.selectedIndex = -1; |
+ this.menu_ = null; |
+ |
+ // On windows we might hide the menu in a right mouse button up and if |
+ // that is the case we wait some short period before we allow the menu |
+ // to be shown again. |
+ this.hideTimestamp_ = Date.now(); |
+ }, |
+ |
+ /** |
+ * Positions the menu |
+ * @param {!Event} e The event object triggering the showing. |
+ * @param {!cr.ui.Menu} menu The menu to position. |
+ * @private |
+ */ |
+ positionMenu_: function(e, menu) { |
+ // TODO(arv): Handle scrolled documents when needed. |
+ |
+ var x, y; |
+ // When the user presses the context menu key (on the keyboard) we need |
+ // to detect this. |
+ if (e.screenX == 0 && e.screenY == 0) { |
+ var rect = e.currentTarget.getBoundingClientRect(); |
+ x = rect.left; |
+ y = rect.top; |
+ } else { |
+ x = e.clientX; |
+ y = e.clientY; |
+ } |
+ |
+ var menuRect = menu.getBoundingClientRect(); |
+ var bodyRect = menu.ownerDocument.body.getBoundingClientRect(); |
+ |
+ // Does menu fit below? |
+ if (y + menuRect.height > bodyRect.height) { |
+ // Does menu fit above? |
+ if (y - menuRect.height >= 0) { |
+ y -= menuRect.height; |
+ } else { |
+ // Menu did not fit above nor below. |
+ y = 0; |
+ // We could resize the menu here but lets not worry about that at this |
+ // point. |
+ } |
+ } |
+ |
+ // Does menu fit to the right? |
+ if (x + menuRect.width > bodyRect.width) { |
+ // Does menu fit to the left? |
+ if (x - menuRect.width >= 0) { |
+ x -= menuRect.width; |
+ } else { |
+ // Menu did not fit to the right nor to the left. |
+ x = 0; |
+ // We could resize the menu here but lets not worry about that at this |
+ // point. |
+ } |
+ } |
+ |
+ menu.style.left = x + 'px'; |
+ menu.style.top = y + 'px'; |
+ }, |
+ |
+ /** |
+ * Handles event callbacks. |
+ * @param {!Event} e The event object. |
+ */ |
+ handleEvent: function(e) { |
+ // Context menu is handled even when we have no menu. |
+ if (e.type != 'contextmenu' && !this.menu) |
+ return; |
+ |
+ switch (e.type) { |
+ case 'mousedown': |
+ if (!this.menu.contains(e.target)) |
+ this.hideMenu(); |
+ else |
+ e.preventDefault(); |
+ break; |
+ case 'keydown': |
+ // keyIdentifier does not report 'Esc' correctly |
+ if (e.keyCode == 27 /* Esc */) { |
+ this.hideMenu(); |
+ |
+ // If the menu is visible we let it handle all the keyboard events. |
+ } else if (this.menu) { |
+ this.menu.handleKeyDown(e); |
+ e.preventDefault(); |
+ e.stopPropagation(); |
+ } |
+ break; |
+ |
+ case 'activate': |
+ case 'blur': |
+ this.hideMenu(); |
+ break; |
+ |
+ case 'contextmenu': |
+ if ((!this.menu || !this.menu.contains(e.target)) && |
+ (!this.hideTimestamp_ || Date.now() - this.hideTimestamp_ > 50)) |
+ this.showMenu(e, e.currentTarget.contextMenu); |
+ e.preventDefault(); |
+ // Don't allow elements further up in the DOM to show their menus. |
+ e.stopPropagation(); |
+ break; |
+ } |
+ }, |
+ |
+ /** |
+ * Adds a contextMenu property to an element or element class. |
+ * @param {!Element|!Function} element The element or class to add the |
+ * contextMenu property to. |
+ */ |
+ addContextMenuProperty: function(element) { |
+ if (typeof element == 'function') |
+ element = element.prototype; |
+ |
+ element.__defineGetter__('contextMenu', function() { |
+ return this.contextMenu_; |
+ }); |
+ element.__defineSetter__('contextMenu', function(menu) { |
+ var oldContextMenu = this.contextMenu; |
+ |
+ if (typeof menu == 'string' && menu[0] == '#') { |
+ menu = this.ownerDocument.getElementById(menu.slice(1)); |
+ cr.ui.decorate(menu, Menu); |
+ } |
+ |
+ if (menu === oldContextMenu) |
+ return; |
+ |
+ if (oldContextMenu && !menu) |
+ this.removeEventListener('contextmenu', contextMenuHandler); |
+ if (menu && !oldContextMenu) |
+ this.addEventListener('contextmenu', contextMenuHandler); |
+ |
+ this.contextMenu_ = menu; |
+ |
+ if (menu && menu.id) |
+ this.setAttribute('contextmenu', '#' + menu.id); |
+ |
+ cr.dispatchPropertyChange(this, 'contextMenu', menu, oldContextMenu); |
+ }); |
+ } |
+ }; |
+ |
+ /** |
+ * The singleton context menu handler. |
+ * @type {!ContextMenuHandler} |
+ */ |
+ var contextMenuHandler = new ContextMenuHandler; |
+ |
+ // Export |
+ return { |
+ contextMenuHandler: contextMenuHandler |
+ }; |
+}); |