OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 cr.define('cr.ui', function() { |
| 6 |
| 7 /** |
| 8 * Handles context menus. |
| 9 * @constructor |
| 10 */ |
| 11 function ContextMenuHandler() {} |
| 12 |
| 13 ContextMenuHandler.prototype = { |
| 14 |
| 15 /** |
| 16 * The menu that we are currently showing. |
| 17 * @type {cr.ui.Menu} |
| 18 */ |
| 19 menu_: null, |
| 20 get menu() { |
| 21 return this.menu_; |
| 22 }, |
| 23 |
| 24 /** |
| 25 * Shows a menu as a context menu. |
| 26 * @param {!Event} e The event triggering the show (usally a contextmenu |
| 27 * event). |
| 28 * @param {!cr.ui.Menu} menu The menu to show. |
| 29 */ |
| 30 showMenu: function(e, menu) { |
| 31 this.menu_ = menu; |
| 32 |
| 33 menu.style.display = 'block'; |
| 34 // when the menu is shown we steal all keyboard events. |
| 35 menu.ownerDocument.addEventListener('keydown', this, true); |
| 36 menu.ownerDocument.addEventListener('mousedown', this, true); |
| 37 menu.ownerDocument.addEventListener('blur', this, true); |
| 38 menu.addEventListener('activate', this); |
| 39 this.positionMenu_(e, menu); |
| 40 }, |
| 41 |
| 42 /** |
| 43 * Hide the currently shown menu. |
| 44 */ |
| 45 hideMenu: function() { |
| 46 var menu = this.menu; |
| 47 if (!menu) |
| 48 return; |
| 49 |
| 50 menu.style.display = 'none'; |
| 51 menu.ownerDocument.removeEventListener('keydown', this, true); |
| 52 menu.ownerDocument.removeEventListener('mousedown', this, true); |
| 53 menu.ownerDocument.removeEventListener('blur', this, true); |
| 54 menu.removeEventListener('activate', this); |
| 55 menu.selectedIndex = -1; |
| 56 this.menu_ = null; |
| 57 |
| 58 // On windows we might hide the menu in a right mouse button up and if |
| 59 // that is the case we wait some short period before we allow the menu |
| 60 // to be shown again. |
| 61 this.hideTimestamp_ = Date.now(); |
| 62 }, |
| 63 |
| 64 /** |
| 65 * Positions the menu |
| 66 * @param {!Event} e The event object triggering the showing. |
| 67 * @param {!cr.ui.Menu} menu The menu to position. |
| 68 * @private |
| 69 */ |
| 70 positionMenu_: function(e, menu) { |
| 71 // TODO(arv): Handle scrolled documents when needed. |
| 72 |
| 73 var x, y; |
| 74 // When the user presses the context menu key (on the keyboard) we need |
| 75 // to detect this. |
| 76 if (e.screenX == 0 && e.screenY == 0) { |
| 77 var rect = e.currentTarget.getBoundingClientRect(); |
| 78 x = rect.left; |
| 79 y = rect.top; |
| 80 } else { |
| 81 x = e.clientX; |
| 82 y = e.clientY; |
| 83 } |
| 84 |
| 85 var menuRect = menu.getBoundingClientRect(); |
| 86 var bodyRect = menu.ownerDocument.body.getBoundingClientRect(); |
| 87 |
| 88 // Does menu fit below? |
| 89 if (y + menuRect.height > bodyRect.height) { |
| 90 // Does menu fit above? |
| 91 if (y - menuRect.height >= 0) { |
| 92 y -= menuRect.height; |
| 93 } else { |
| 94 // Menu did not fit above nor below. |
| 95 y = 0; |
| 96 // We could resize the menu here but lets not worry about that at this |
| 97 // point. |
| 98 } |
| 99 } |
| 100 |
| 101 // Does menu fit to the right? |
| 102 if (x + menuRect.width > bodyRect.width) { |
| 103 // Does menu fit to the left? |
| 104 if (x - menuRect.width >= 0) { |
| 105 x -= menuRect.width; |
| 106 } else { |
| 107 // Menu did not fit to the right nor to the left. |
| 108 x = 0; |
| 109 // We could resize the menu here but lets not worry about that at this |
| 110 // point. |
| 111 } |
| 112 } |
| 113 |
| 114 menu.style.left = x + 'px'; |
| 115 menu.style.top = y + 'px'; |
| 116 }, |
| 117 |
| 118 /** |
| 119 * Handles event callbacks. |
| 120 * @param {!Event} e The event object. |
| 121 */ |
| 122 handleEvent: function(e) { |
| 123 // Context menu is handled even when we have no menu. |
| 124 if (e.type != 'contextmenu' && !this.menu) |
| 125 return; |
| 126 |
| 127 switch (e.type) { |
| 128 case 'mousedown': |
| 129 if (!this.menu.contains(e.target)) |
| 130 this.hideMenu(); |
| 131 else |
| 132 e.preventDefault(); |
| 133 break; |
| 134 case 'keydown': |
| 135 // keyIdentifier does not report 'Esc' correctly |
| 136 if (e.keyCode == 27 /* Esc */) { |
| 137 this.hideMenu(); |
| 138 |
| 139 // If the menu is visible we let it handle all the keyboard events. |
| 140 } else if (this.menu) { |
| 141 this.menu.handleKeyDown(e); |
| 142 e.preventDefault(); |
| 143 e.stopPropagation(); |
| 144 } |
| 145 break; |
| 146 |
| 147 case 'activate': |
| 148 case 'blur': |
| 149 this.hideMenu(); |
| 150 break; |
| 151 |
| 152 case 'contextmenu': |
| 153 if ((!this.menu || !this.menu.contains(e.target)) && |
| 154 (!this.hideTimestamp_ || Date.now() - this.hideTimestamp_ > 50)) |
| 155 this.showMenu(e, e.currentTarget.contextMenu); |
| 156 e.preventDefault(); |
| 157 // Don't allow elements further up in the DOM to show their menus. |
| 158 e.stopPropagation(); |
| 159 break; |
| 160 } |
| 161 }, |
| 162 |
| 163 /** |
| 164 * Adds a contextMenu property to an element or element class. |
| 165 * @param {!Element|!Function} element The element or class to add the |
| 166 * contextMenu property to. |
| 167 */ |
| 168 addContextMenuProperty: function(element) { |
| 169 if (typeof element == 'function') |
| 170 element = element.prototype; |
| 171 |
| 172 element.__defineGetter__('contextMenu', function() { |
| 173 return this.contextMenu_; |
| 174 }); |
| 175 element.__defineSetter__('contextMenu', function(menu) { |
| 176 var oldContextMenu = this.contextMenu; |
| 177 |
| 178 if (typeof menu == 'string' && menu[0] == '#') { |
| 179 menu = this.ownerDocument.getElementById(menu.slice(1)); |
| 180 cr.ui.decorate(menu, Menu); |
| 181 } |
| 182 |
| 183 if (menu === oldContextMenu) |
| 184 return; |
| 185 |
| 186 if (oldContextMenu && !menu) |
| 187 this.removeEventListener('contextmenu', contextMenuHandler); |
| 188 if (menu && !oldContextMenu) |
| 189 this.addEventListener('contextmenu', contextMenuHandler); |
| 190 |
| 191 this.contextMenu_ = menu; |
| 192 |
| 193 if (menu && menu.id) |
| 194 this.setAttribute('contextmenu', '#' + menu.id); |
| 195 |
| 196 cr.dispatchPropertyChange(this, 'contextMenu', menu, oldContextMenu); |
| 197 }); |
| 198 } |
| 199 }; |
| 200 |
| 201 /** |
| 202 * The singleton context menu handler. |
| 203 * @type {!ContextMenuHandler} |
| 204 */ |
| 205 var contextMenuHandler = new ContextMenuHandler; |
| 206 |
| 207 // Export |
| 208 return { |
| 209 contextMenuHandler: contextMenuHandler |
| 210 }; |
| 211 }); |
OLD | NEW |