| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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 // How long to wait to open submenu when mouse hovers. | |
| 6 var SUBMENU_OPEN_DELAY_MS = 200; | |
| 7 // How long to wait to close submenu when mouse left. | |
| 8 var SUBMENU_CLOSE_DELAY_MS = 500; | |
| 9 // Scroll repeat interval. | |
| 10 var SCROLL_INTERVAL_MS = 20; | |
| 11 // Scrolling amount in pixel. | |
| 12 var SCROLL_TICK_PX = 4; | |
| 13 // Regular expression to match/find mnemonic key. | |
| 14 var MNEMONIC_REGEXP = /([^&]*)&(.)(.*)/; | |
| 15 | |
| 16 var localStrings = new LocalStrings(); | |
| 17 | |
| 18 /** | |
| 19 * Sends 'activate' WebUI message. | |
| 20 * @param {number} index The index of menu item to activate in menu model. | |
| 21 * @param {string} mode The activation mode, one of 'close_and_activate', or | |
| 22 * 'activate_no_close'. | |
| 23 * TODO(oshima): change these string to enum numbers once it becomes possible | |
| 24 * to pass number to C++. | |
| 25 */ | |
| 26 function sendActivate(index, mode) { | |
| 27 chrome.send('activate', [String(index), mode]); | |
| 28 } | |
| 29 | |
| 30 /** | |
| 31 * MenuItem class. | |
| 32 */ | |
| 33 var MenuItem = cr.ui.define('div'); | |
| 34 | |
| 35 MenuItem.prototype = { | |
| 36 __proto__ : HTMLDivElement.prototype, | |
| 37 | |
| 38 /** | |
| 39 * Decorates the menu item element. | |
| 40 */ | |
| 41 decorate: function() { | |
| 42 this.className = 'menu-item'; | |
| 43 }, | |
| 44 | |
| 45 /** | |
| 46 * Initialize the MenuItem. | |
| 47 * @param {Menu} menu A {@code Menu} object to which this menu item | |
| 48 * will be added to. | |
| 49 * @param {Object} attrs JSON object that represents this menu items | |
| 50 * properties. This is created from menu model in C code. See | |
| 51 * chromeos/views/native_menu_webui.cc. | |
| 52 * @param {Object} model The model object. | |
| 53 */ | |
| 54 init: function(menu, attrs, model) { | |
| 55 // The left icon's width. 0 if no icon. | |
| 56 var leftIconWidth = model.maxIconWidth; | |
| 57 this.menu_ = menu; | |
| 58 this.attrs = attrs; | |
| 59 var attrs = this.attrs; | |
| 60 if (attrs.type == 'separator') { | |
| 61 this.className = 'separator'; | |
| 62 } else if (attrs.type == 'command' || | |
| 63 attrs.type == 'submenu' || | |
| 64 attrs.type == 'check' || | |
| 65 attrs.type == 'radio') { | |
| 66 this.initMenuItem_(); | |
| 67 this.initPadding_(leftIconWidth); | |
| 68 } else { | |
| 69 // This should not happend. | |
| 70 this.classList.add('disabled'); | |
| 71 this.textContent = 'unknown'; | |
| 72 } | |
| 73 | |
| 74 menu.appendChild(this); | |
| 75 if (!attrs.visible) { | |
| 76 this.classList.add('hidden'); | |
| 77 } | |
| 78 }, | |
| 79 | |
| 80 /** | |
| 81 * Changes the selection state of the menu item. | |
| 82 * @param {boolean} selected True to set the selection, or false | |
| 83 * otherwise. | |
| 84 */ | |
| 85 set selected(selected) { | |
| 86 if (selected) { | |
| 87 this.classList.add('selected'); | |
| 88 this.menu_.selectedItem = this; | |
| 89 } else { | |
| 90 this.classList.remove('selected'); | |
| 91 } | |
| 92 }, | |
| 93 | |
| 94 /** | |
| 95 * Activate the menu item. | |
| 96 */ | |
| 97 activate: function() { | |
| 98 if (this.attrs.type == 'submenu') { | |
| 99 this.menu_.openSubmenu(this); | |
| 100 } else if (this.attrs.type != 'separator' && | |
| 101 this.className.indexOf('selected') >= 0) { | |
| 102 sendActivate(this.menu_.getMenuItemIndexOf(this), | |
| 103 'close_and_activate'); | |
| 104 } | |
| 105 }, | |
| 106 | |
| 107 /** | |
| 108 * Sends open_submenu WebUI message. | |
| 109 */ | |
| 110 sendOpenSubmenuCommand: function() { | |
| 111 chrome.send('open_submenu', | |
| 112 [String(this.menu_.getMenuItemIndexOf(this)), | |
| 113 String(this.getBoundingClientRect().top)]); | |
| 114 }, | |
| 115 | |
| 116 /** | |
| 117 * Internal method to initiailze the MenuItem. | |
| 118 * @private | |
| 119 */ | |
| 120 initMenuItem_: function() { | |
| 121 var attrs = this.attrs; | |
| 122 this.className = 'menu-item ' + attrs.type; | |
| 123 this.menu_.addHandlers(this, this); | |
| 124 var label = document.createElement('div'); | |
| 125 | |
| 126 label.className = 'menu-label'; | |
| 127 this.menu_.addLabelTo(this, attrs.label, label, | |
| 128 true /* enable mnemonic */); | |
| 129 | |
| 130 if (attrs.font) { | |
| 131 label.style.font = attrs.font; | |
| 132 } | |
| 133 this.appendChild(label); | |
| 134 | |
| 135 | |
| 136 if (attrs.accel) { | |
| 137 var accel = document.createElement('div'); | |
| 138 accel.className = 'accelerator'; | |
| 139 accel.textContent = attrs.accel; | |
| 140 accel.style.font = attrs.font; | |
| 141 this.appendChild(accel); | |
| 142 } | |
| 143 | |
| 144 if (attrs.type == 'submenu') { | |
| 145 // This overrides left-icon's position, but it's OK as submenu | |
| 146 // shoudln't have left-icon. | |
| 147 this.classList.add('right-icon'); | |
| 148 this.style.backgroundImage = 'url(' + this.menu_.config_.arrowUrl + ')'; | |
| 149 } | |
| 150 }, | |
| 151 | |
| 152 initPadding_: function(leftIconWidth) { | |
| 153 if (leftIconWidth <= 0) { | |
| 154 this.classList.add('no-icon'); | |
| 155 return; | |
| 156 } | |
| 157 this.classList.add('left-icon'); | |
| 158 | |
| 159 var url; | |
| 160 var attrs = this.attrs; | |
| 161 if (attrs.type == 'radio') { | |
| 162 url = attrs.checked ? | |
| 163 this.menu_.config_.radioOnUrl : | |
| 164 this.menu_.config_.radioOffUrl; | |
| 165 } else if (attrs.icon) { | |
| 166 url = attrs.icon; | |
| 167 } else if (attrs.type == 'check' && attrs.checked) { | |
| 168 url = this.menu_.config_.checkUrl; | |
| 169 } | |
| 170 if (url) { | |
| 171 this.style.backgroundImage = 'url(' + url + ')'; | |
| 172 } | |
| 173 // TODO(oshima): figure out how to update left padding in rule. | |
| 174 // 4 is the padding on left side of icon. | |
| 175 var padding = | |
| 176 4 + leftIconWidth + this.menu_.config_.icon_to_label_padding; | |
| 177 this.style.WebkitPaddingStart = padding + 'px'; | |
| 178 }, | |
| 179 }; | |
| 180 | |
| 181 /** | |
| 182 * Menu class. | |
| 183 */ | |
| 184 var Menu = cr.ui.define('div'); | |
| 185 | |
| 186 Menu.prototype = { | |
| 187 __proto__: HTMLDivElement.prototype, | |
| 188 | |
| 189 /** | |
| 190 * Configuration object. | |
| 191 * @type {Object} | |
| 192 */ | |
| 193 config_ : null, | |
| 194 | |
| 195 /** | |
| 196 * Currently selected menu item. | |
| 197 * @type {MenuItem} | |
| 198 */ | |
| 199 current_ : null, | |
| 200 | |
| 201 /** | |
| 202 * Timers for opening/closing submenu. | |
| 203 * @type {number} | |
| 204 */ | |
| 205 openSubmenuTimer_ : 0, | |
| 206 closeSubmenuTimer_ : 0, | |
| 207 | |
| 208 /** | |
| 209 * Auto scroll timer. | |
| 210 * @type {number} | |
| 211 */ | |
| 212 scrollTimer_ : 0, | |
| 213 | |
| 214 /** | |
| 215 * Pointer to a submenu currently shown, if any. | |
| 216 * @type {MenuItem} | |
| 217 */ | |
| 218 submenuShown_ : null, | |
| 219 | |
| 220 /** | |
| 221 * True if this menu is root. | |
| 222 * @type {boolean} | |
| 223 */ | |
| 224 isRoot_ : false, | |
| 225 | |
| 226 /** | |
| 227 * Scrollable Viewport. | |
| 228 * @type {HTMLElement} | |
| 229 */ | |
| 230 viewpotr_ : null, | |
| 231 | |
| 232 /** | |
| 233 * Total hight of scroll buttons. Used to adjust the height of | |
| 234 * viewport in order to show scroll bottons without scrollbar. | |
| 235 * @type {number} | |
| 236 */ | |
| 237 buttonHeight_ : 0, | |
| 238 | |
| 239 /** | |
| 240 * True to enable scroll button. | |
| 241 * @type {boolean} | |
| 242 */ | |
| 243 scrollEnabled : false, | |
| 244 | |
| 245 /** | |
| 246 * Decorates the menu element. | |
| 247 */ | |
| 248 decorate: function() { | |
| 249 this.id = 'viewport'; | |
| 250 }, | |
| 251 | |
| 252 /** | |
| 253 * Initialize the menu. | |
| 254 * @param {Object} config Configuration parameters in JSON format. | |
| 255 * See chromeos/views/native_menu_webui.cc for details. | |
| 256 */ | |
| 257 init: function(config) { | |
| 258 // List of menu items | |
| 259 this.items_ = []; | |
| 260 // Map from mnemonic character to item to activate | |
| 261 this.mnemonics_ = {}; | |
| 262 | |
| 263 this.config_ = config; | |
| 264 this.addEventListener('mouseout', this.onMouseout_.bind(this)); | |
| 265 | |
| 266 document.addEventListener('keydown', this.onKeydown_.bind(this)); | |
| 267 document.addEventListener('keypress', this.onKeypress_.bind(this)); | |
| 268 document.addEventListener('mousewheel', this.onMouseWheel_.bind(this)); | |
| 269 window.addEventListener('resize', this.onResize_.bind(this)); | |
| 270 | |
| 271 // Setup scroll events. | |
| 272 var up = document.getElementById('scroll-up'); | |
| 273 var down = document.getElementById('scroll-down'); | |
| 274 up.addEventListener('mouseout', this.stopScroll_.bind(this)); | |
| 275 down.addEventListener('mouseout', this.stopScroll_.bind(this)); | |
| 276 var menu = this; | |
| 277 up.addEventListener('mouseover', | |
| 278 function() { | |
| 279 menu.autoScroll_(-SCROLL_TICK_PX); | |
| 280 }); | |
| 281 down.addEventListener('mouseover', | |
| 282 function() { | |
| 283 menu.autoScroll_(SCROLL_TICK_PX); | |
| 284 }); | |
| 285 | |
| 286 this.buttonHeight_ = | |
| 287 up.getBoundingClientRect().height + | |
| 288 down.getBoundingClientRect().height; | |
| 289 }, | |
| 290 | |
| 291 /** | |
| 292 * Adds a label to {@code targetDiv}. A label may contain | |
| 293 * mnemonic key, preceded by '&'. | |
| 294 * @param {MenuItem} item The menu item to be activated by mnemonic | |
| 295 * key. | |
| 296 * @param {string} label The label string to be added to | |
| 297 * {@code targetDiv}. | |
| 298 * @param {HTMLElement} div The div element the label is added to. | |
| 299 * @param {boolean} enableMnemonic True to enable mnemonic, or false | |
| 300 * to not to interprete mnemonic key. The function removes '&' | |
| 301 * from the label in both cases. | |
| 302 */ | |
| 303 addLabelTo: function(item, label, targetDiv, enableMnemonic) { | |
| 304 var mnemonic = MNEMONIC_REGEXP.exec(label); | |
| 305 if (mnemonic && enableMnemonic) { | |
| 306 var c = mnemonic[2].toLowerCase(); | |
| 307 this.mnemonics_[c] = item; | |
| 308 } | |
| 309 if (!mnemonic) { | |
| 310 targetDiv.textContent = label; | |
| 311 } else if (enableMnemonic) { | |
| 312 targetDiv.appendChild(document.createTextNode(mnemonic[1])); | |
| 313 targetDiv.appendChild(document.createElement('span')); | |
| 314 targetDiv.appendChild(document.createTextNode(mnemonic[3])); | |
| 315 targetDiv.childNodes[1].className = 'mnemonic'; | |
| 316 targetDiv.childNodes[1].textContent = mnemonic[2]; | |
| 317 } else { | |
| 318 targetDiv.textContent = mnemonic.splice(1, 3).join(''); | |
| 319 } | |
| 320 }, | |
| 321 | |
| 322 /** | |
| 323 * Returns the index of the {@code item}. | |
| 324 */ | |
| 325 getMenuItemIndexOf: function(item) { | |
| 326 return this.items_.indexOf(item); | |
| 327 }, | |
| 328 | |
| 329 /** | |
| 330 * A template method to create an item object. It can be a subclass | |
| 331 * of MenuItem, or any HTMLElement that implements {@code init}, | |
| 332 * {@code activate} methods as well as {@code selected} attribute. | |
| 333 * @param {Object} attrs The menu item's properties passed from C++. | |
| 334 */ | |
| 335 createMenuItem: function(attrs) { | |
| 336 return new MenuItem(); | |
| 337 }, | |
| 338 | |
| 339 /** | |
| 340 * Update and display the new model. | |
| 341 */ | |
| 342 updateModel: function(model) { | |
| 343 this.isRoot = model.isRoot; | |
| 344 this.current_ = null; | |
| 345 this.items_ = []; | |
| 346 this.mnemonics_ = {}; | |
| 347 this.innerHTML = ''; // remove menu items | |
| 348 | |
| 349 for (var i = 0; i < model.items.length; i++) { | |
| 350 var attrs = model.items[i]; | |
| 351 var item = this.createMenuItem(attrs); | |
| 352 item.init(this, attrs, model); | |
| 353 this.items_.push(item); | |
| 354 } | |
| 355 this.onResize_(); | |
| 356 }, | |
| 357 | |
| 358 /** | |
| 359 * Highlights the currently selected item, or | |
| 360 * select the 1st selectable item if none is selected. | |
| 361 */ | |
| 362 showSelection: function() { | |
| 363 if (this.current_) { | |
| 364 this.current_.selected = true; | |
| 365 } else { | |
| 366 this.findNextEnabled_(1).selected = true; | |
| 367 } | |
| 368 }, | |
| 369 | |
| 370 /** | |
| 371 * Add event handlers for the item. | |
| 372 */ | |
| 373 addHandlers: function(item, target) { | |
| 374 var menu = this; | |
| 375 target.addEventListener('mouseover', function(event) { | |
| 376 menu.onMouseover_(event, item); | |
| 377 }); | |
| 378 if (item.attrs.enabled) { | |
| 379 target.addEventListener('mouseup', function(event) { | |
| 380 menu.onClick_(event, item); | |
| 381 }); | |
| 382 } else { | |
| 383 target.classList.add('disabled'); | |
| 384 } | |
| 385 }, | |
| 386 | |
| 387 /** | |
| 388 * Set the selected item. This controls timers to open/close submenus. | |
| 389 * 1) If the selected menu is submenu, and that submenu is not yet opeend, | |
| 390 * start timer to open. This will not cancel close timer, so | |
| 391 * if there is a submenu opened, it will be closed before new submenu is | |
| 392 * open. | |
| 393 * 2) If the selected menu is submenu, and that submenu is already opened, | |
| 394 * cancel both open/close timer. | |
| 395 * 3) If the selected menu is not submenu, cancel all timers and start | |
| 396 * timer to close submenu. | |
| 397 * This prevents from opening/closing menus while you're actively | |
| 398 * navigating menus. To open submenu, you need to wait a bit, or click | |
| 399 * submenu. | |
| 400 * | |
| 401 * @param {MenuItem} item The selected item. | |
| 402 */ | |
| 403 set selectedItem(item) { | |
| 404 if (this.current_ != item) { | |
| 405 if (this.current_ != null) | |
| 406 this.current_.selected = false; | |
| 407 this.current_ = item; | |
| 408 this.makeSelectedItemVisible_(); | |
| 409 } | |
| 410 | |
| 411 var menu = this; | |
| 412 if (item.attrs.type == 'submenu') { | |
| 413 if (this.submenuShown_ != item) { | |
| 414 this.openSubmenuTimer_ = | |
| 415 setTimeout( | |
| 416 function() { | |
| 417 menu.openSubmenu(item); | |
| 418 }, | |
| 419 SUBMENU_OPEN_DELAY_MS); | |
| 420 } else { | |
| 421 this.cancelSubmenuTimer_(); | |
| 422 } | |
| 423 } else if (this.submenuShown_) { | |
| 424 this.cancelSubmenuTimer_(); | |
| 425 this.closeSubmenuTimer_ = | |
| 426 setTimeout( | |
| 427 function() { | |
| 428 menu.closeSubmenu_(item); | |
| 429 }, | |
| 430 SUBMENU_CLOSE_DELAY_MS); | |
| 431 } | |
| 432 }, | |
| 433 | |
| 434 /** | |
| 435 * Open submenu {@code item}. It does nothing if the submenu is | |
| 436 * already opened. | |
| 437 * @param {MenuItem} item The submenu item to open. | |
| 438 */ | |
| 439 openSubmenu: function(item) { | |
| 440 this.cancelSubmenuTimer_(); | |
| 441 if (this.submenuShown_ != item) { | |
| 442 this.submenuShown_ = item; | |
| 443 item.sendOpenSubmenuCommand(); | |
| 444 } | |
| 445 }, | |
| 446 | |
| 447 /** | |
| 448 * Handle keyboard navigation and activation. | |
| 449 * @private | |
| 450 */ | |
| 451 onKeydown_: function(event) { | |
| 452 switch (event.keyIdentifier) { | |
| 453 case 'Left': | |
| 454 this.moveToParent_(); | |
| 455 break; | |
| 456 case 'Right': | |
| 457 this.moveToSubmenu_(); | |
| 458 break; | |
| 459 case 'Up': | |
| 460 this.classList.add('mnemonic-enabled'); | |
| 461 this.findNextEnabled_(-1).selected = true; | |
| 462 break; | |
| 463 case 'Down': | |
| 464 this.classList.add('mnemonic-enabled'); | |
| 465 this.findNextEnabled_(1).selected = true; | |
| 466 break; | |
| 467 case 'U+0009': // tab | |
| 468 break; | |
| 469 case 'U+001B': // escape | |
| 470 chrome.send('close_all', []); | |
| 471 break; | |
| 472 case 'Enter': | |
| 473 case 'U+0020': // space | |
| 474 if (this.current_) { | |
| 475 this.current_.activate(); | |
| 476 } | |
| 477 break; | |
| 478 } | |
| 479 }, | |
| 480 | |
| 481 /** | |
| 482 * Handle mnemonic keys. | |
| 483 * @private | |
| 484 */ | |
| 485 onKeypress_: function(event) { | |
| 486 // Handles mnemonic. | |
| 487 var c = String.fromCharCode(event.keyCode); | |
| 488 var item = this.mnemonics_[c.toLowerCase()]; | |
| 489 if (item) { | |
| 490 item.selected = true; | |
| 491 item.activate(); | |
| 492 } | |
| 493 }, | |
| 494 | |
| 495 // Mouse Event handlers | |
| 496 onClick_: function(event, item) { | |
| 497 item.activate(); | |
| 498 }, | |
| 499 | |
| 500 onMouseover_: function(event, item) { | |
| 501 this.cancelSubmenuTimer_(); | |
| 502 // Ignore false mouseover event at (0,0) which is | |
| 503 // emitted when opening submenu. | |
| 504 if (item.attrs.enabled && event.clientX != 0 && event.clientY != 0) { | |
| 505 item.selected = true; | |
| 506 } | |
| 507 }, | |
| 508 | |
| 509 onMouseout_: function(event) { | |
| 510 if (this.current_) { | |
| 511 this.current_.selected = false; | |
| 512 } | |
| 513 }, | |
| 514 | |
| 515 onResize_: function() { | |
| 516 var up = document.getElementById('scroll-up'); | |
| 517 var down = document.getElementById('scroll-down'); | |
| 518 // this needs to be < 2 as empty page has height of 1. | |
| 519 if (window.innerHeight < 2) { | |
| 520 // menu window is not visible yet. just hide buttons. | |
| 521 up.classList.add('hidden'); | |
| 522 down.classList.add('hidden'); | |
| 523 return; | |
| 524 } | |
| 525 // Do not use screen width to determin if we need scroll buttons | |
| 526 // as the max renderer hight can be shorter than actual screen size. | |
| 527 // TODO(oshima): Fix this when we implement transparent renderer. | |
| 528 if (this.scrollHeight > window.innerHeight && this.scrollEnabled) { | |
| 529 this.style.height = (window.innerHeight - this.buttonHeight_) + 'px'; | |
| 530 up.classList.remove('hidden'); | |
| 531 down.classList.remove('hidden'); | |
| 532 } else { | |
| 533 this.style.height = ''; | |
| 534 up.classList.add('hidden'); | |
| 535 down.classList.add('hidden'); | |
| 536 } | |
| 537 }, | |
| 538 | |
| 539 onMouseWheel_: function(event) { | |
| 540 var delta = event.wheelDelta / 5; | |
| 541 this.scrollTop -= delta; | |
| 542 }, | |
| 543 | |
| 544 /** | |
| 545 * Closes the submenu. | |
| 546 * a submenu. | |
| 547 * @private | |
| 548 */ | |
| 549 closeSubmenu_: function(item) { | |
| 550 this.submenuShown_ = null; | |
| 551 this.cancelSubmenuTimer_(); | |
| 552 chrome.send('close_submenu', []); | |
| 553 }, | |
| 554 | |
| 555 /** | |
| 556 * Move the selection to parent menu if the current menu is | |
| 557 * a submenu. | |
| 558 * @private | |
| 559 */ | |
| 560 moveToParent_: function() { | |
| 561 if (!this.isRoot) { | |
| 562 if (this.current_) { | |
| 563 this.current_.selected = false; | |
| 564 } | |
| 565 chrome.send('move_to_parent', []); | |
| 566 } | |
| 567 }, | |
| 568 | |
| 569 /** | |
| 570 * Move the selection to submenu if the currently selected | |
| 571 * menu is a submenu. | |
| 572 * @private | |
| 573 */ | |
| 574 moveToSubmenu_: function () { | |
| 575 var current = this.current_; | |
| 576 if (current && current.attrs.type == 'submenu') { | |
| 577 this.openSubmenu(current); | |
| 578 chrome.send('move_to_submenu', []); | |
| 579 } | |
| 580 }, | |
| 581 | |
| 582 /** | |
| 583 * Find a next selectable item. If nothing is selected, the 1st | |
| 584 * selectable item will be chosen. Returns null if nothing is | |
| 585 * selectable. | |
| 586 * @param {number} incr Specifies the direction to search, 1 to | |
| 587 * downwards and -1 for upwards. | |
| 588 * @private | |
| 589 */ | |
| 590 findNextEnabled_: function(incr) { | |
| 591 var len = this.items_.length; | |
| 592 var index; | |
| 593 if (this.current_) { | |
| 594 index = this.getMenuItemIndexOf(this.current_); | |
| 595 } else { | |
| 596 index = incr > 0 ? -1 : len; | |
| 597 } | |
| 598 for (var i = 0; i < len; i++) { | |
| 599 index = (index + incr + len) % len; | |
| 600 var item = this.items_[index]; | |
| 601 if (item.attrs.enabled && item.attrs.type != 'separator' && | |
| 602 !item.classList.contains('hidden')) | |
| 603 return item; | |
| 604 } | |
| 605 return null; | |
| 606 }, | |
| 607 | |
| 608 /** | |
| 609 * Cancels timers to open/close submenus. | |
| 610 * @private | |
| 611 */ | |
| 612 cancelSubmenuTimer_: function() { | |
| 613 clearTimeout(this.openSubmenuTimer_); | |
| 614 this.openSubmenuTimer_ = 0; | |
| 615 clearTimeout(this.closeSubmenuTimer_); | |
| 616 this.closeSubmenuTimer_ = 0; | |
| 617 }, | |
| 618 | |
| 619 /** | |
| 620 * Starts auto scroll. | |
| 621 * @param {number} tick The number of pixels to scroll. | |
| 622 * @private | |
| 623 */ | |
| 624 autoScroll_: function(tick) { | |
| 625 var previous = this.scrollTop; | |
| 626 this.scrollTop += tick; | |
| 627 var menu = this; | |
| 628 this.scrollTimer_ = setTimeout( | |
| 629 function() { | |
| 630 menu.autoScroll_(tick); | |
| 631 }, | |
| 632 SCROLL_INTERVAL_MS); | |
| 633 }, | |
| 634 | |
| 635 /** | |
| 636 * Stops auto scroll. | |
| 637 * @private | |
| 638 */ | |
| 639 stopScroll_: function () { | |
| 640 clearTimeout(this.scrollTimer_); | |
| 641 this.scrollTimer_ = 0; | |
| 642 }, | |
| 643 | |
| 644 /** | |
| 645 * Scrolls the viewport to make the selected item visible. | |
| 646 * @private | |
| 647 */ | |
| 648 makeSelectedItemVisible_: function(){ | |
| 649 this.current_.scrollIntoViewIfNeeded(false); | |
| 650 }, | |
| 651 }; | |
| 652 | |
| 653 /** | |
| 654 * functions to be called from C++. | |
| 655 */ | |
| 656 function init(config) { | |
| 657 document.getElementById('viewport').init(config); | |
| 658 } | |
| 659 | |
| 660 function selectItem() { | |
| 661 document.getElementById('viewport').showSelection(); | |
| 662 } | |
| 663 | |
| 664 function updateModel(model) { | |
| 665 document.getElementById('viewport').updateModel(model); | |
| 666 } | |
| 667 | |
| 668 function modelUpdated() { | |
| 669 chrome.send('model_updated', []); | |
| 670 } | |
| 671 | |
| 672 function enableScroll(enabled) { | |
| 673 document.getElementById('viewport').scrollEnabled = enabled; | |
| 674 } | |
| OLD | NEW |