OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 cr.define('options', function() { | 5 cr.define('options', function() { |
6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; | 6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; |
7 /** @const */ var Grid = cr.ui.Grid; | 7 /** @const */ var Grid = cr.ui.Grid; |
8 /** @const */ var GridItem = cr.ui.GridItem; | 8 /** @const */ var GridItem = cr.ui.GridItem; |
9 /** @const */ var GridSelectionController = cr.ui.GridSelectionController; | 9 /** @const */ var GridSelectionController = cr.ui.GridSelectionController; |
10 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; | 10 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; |
11 | 11 |
12 /** | 12 /** |
13 * Dimensions for camera capture. | 13 * Dimensions for camera capture. |
14 * @const | 14 * @const |
15 */ | 15 */ |
16 var CAPTURE_SIZE = { | 16 var CAPTURE_SIZE = {height: 480, width: 480}; |
17 height: 480, | |
18 width: 480 | |
19 }; | |
20 | 17 |
21 /** | 18 /** |
22 * Path for internal URLs. | 19 * Path for internal URLs. |
23 * @const | 20 * @const |
24 */ | 21 */ |
25 var CHROME_THEME_PATH = 'chrome://theme'; | 22 var CHROME_THEME_PATH = 'chrome://theme'; |
26 | 23 |
27 /** | 24 /** |
28 * Creates a new user images grid item. | 25 * Creates a new user images grid item. |
29 * @param {{url: string, title: (string|undefined), | 26 * @param {{url: string, title: (string|undefined), |
(...skipping 26 matching lines...) Expand all Loading... |
56 imageEl.title = this.dataItem.title || ''; | 53 imageEl.title = this.dataItem.title || ''; |
57 imageEl.alt = imageEl.title; | 54 imageEl.alt = imageEl.title; |
58 if (typeof this.dataItem.clickHandler == 'function') | 55 if (typeof this.dataItem.clickHandler == 'function') |
59 imageEl.addEventListener('mousedown', this.dataItem.clickHandler); | 56 imageEl.addEventListener('mousedown', this.dataItem.clickHandler); |
60 // Remove any garbage added by GridItem and ListItem decorators. | 57 // Remove any garbage added by GridItem and ListItem decorators. |
61 this.textContent = ''; | 58 this.textContent = ''; |
62 this.appendChild(imageEl); | 59 this.appendChild(imageEl); |
63 if (typeof this.dataItem.decorateFn == 'function') | 60 if (typeof this.dataItem.decorateFn == 'function') |
64 this.dataItem.decorateFn(this); | 61 this.dataItem.decorateFn(this); |
65 this.setAttribute('role', 'option'); | 62 this.setAttribute('role', 'option'); |
66 this.oncontextmenu = function(e) { e.preventDefault(); }; | 63 this.oncontextmenu = function(e) { |
| 64 e.preventDefault(); |
| 65 }; |
67 } | 66 } |
68 }; | 67 }; |
69 | 68 |
70 /** | 69 /** |
71 * Creates a selection controller that wraps selection on grid ends | 70 * Creates a selection controller that wraps selection on grid ends |
72 * and translates Enter presses into 'activate' events. | 71 * and translates Enter presses into 'activate' events. |
73 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to | 72 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to |
74 * interact with. | 73 * interact with. |
75 * @param {cr.ui.Grid} grid The grid to interact with. | 74 * @param {cr.ui.Grid} grid The grid to interact with. |
76 * @constructor | 75 * @constructor |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
119 | 118 |
120 /** @override */ | 119 /** @override */ |
121 createSelectionController: function(sm) { | 120 createSelectionController: function(sm) { |
122 return new UserImagesGridSelectionController(sm, this); | 121 return new UserImagesGridSelectionController(sm, this); |
123 }, | 122 }, |
124 | 123 |
125 /** @override */ | 124 /** @override */ |
126 decorate: function() { | 125 decorate: function() { |
127 Grid.prototype.decorate.call(this); | 126 Grid.prototype.decorate.call(this); |
128 this.dataModel = new ArrayDataModel([]); | 127 this.dataModel = new ArrayDataModel([]); |
129 this.itemConstructor = /** @type {function(new:cr.ui.ListItem, *)} */( | 128 this.itemConstructor = |
130 UserImagesGridItem); | 129 /** @type {function(new:cr.ui.ListItem, *)} */ (UserImagesGridItem); |
131 this.selectionModel = new ListSingleSelectionModel(); | 130 this.selectionModel = new ListSingleSelectionModel(); |
132 this.inProgramSelection_ = false; | 131 this.inProgramSelection_ = false; |
133 this.addEventListener('dblclick', this.handleDblClick_.bind(this)); | 132 this.addEventListener('dblclick', this.handleDblClick_.bind(this)); |
134 this.addEventListener('change', this.handleChange_.bind(this)); | 133 this.addEventListener('change', this.handleChange_.bind(this)); |
135 this.setAttribute('role', 'listbox'); | 134 this.setAttribute('role', 'listbox'); |
136 this.autoExpands = true; | 135 this.autoExpands = true; |
137 }, | 136 }, |
138 | 137 |
139 /** | 138 /** |
140 * Handles double click on the image grid. | 139 * Handles double click on the image grid. |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
215 /** | 214 /** |
216 * Tries to starts camera stream capture. | 215 * Tries to starts camera stream capture. |
217 * @param {function(): boolean} onAvailable Callback that is called if | 216 * @param {function(): boolean} onAvailable Callback that is called if |
218 * camera is available. If it returns |true|, capture is started | 217 * camera is available. If it returns |true|, capture is started |
219 * immediately. | 218 * immediately. |
220 */ | 219 */ |
221 startCamera: function(onAvailable, onAbsent) { | 220 startCamera: function(onAvailable, onAbsent) { |
222 this.stopCamera(); | 221 this.stopCamera(); |
223 this.cameraStartInProgress_ = true; | 222 this.cameraStartInProgress_ = true; |
224 navigator.webkitGetUserMedia( | 223 navigator.webkitGetUserMedia( |
225 {video: true}, | 224 {video: true}, this.handleCameraAvailable_.bind(this, onAvailable), |
226 this.handleCameraAvailable_.bind(this, onAvailable), | |
227 this.handleCameraAbsent_.bind(this)); | 225 this.handleCameraAbsent_.bind(this)); |
228 }, | 226 }, |
229 | 227 |
230 /** | 228 /** |
231 * Stops camera capture, if it's currently active. | 229 * Stops camera capture, if it's currently active. |
232 */ | 230 */ |
233 stopCamera: function() { | 231 stopCamera: function() { |
234 this.cameraOnline = false; | 232 this.cameraOnline = false; |
235 if (this.cameraVideo_) | 233 if (this.cameraVideo_) |
236 this.cameraVideo_.src = ''; | 234 this.cameraVideo_.src = ''; |
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
422 * @type {HTMLElement} | 420 * @type {HTMLElement} |
423 */ | 421 */ |
424 get previewElement() { | 422 get previewElement() { |
425 // TODO(ivankr): temporary hack for non-HTML5 version. | 423 // TODO(ivankr): temporary hack for non-HTML5 version. |
426 return this.previewElement_ || this; | 424 return this.previewElement_ || this; |
427 }, | 425 }, |
428 set previewElement(value) { | 426 set previewElement(value) { |
429 this.previewElement_ = value; | 427 this.previewElement_ = value; |
430 this.previewImage_ = value.querySelector('img'); | 428 this.previewImage_ = value.querySelector('img'); |
431 this.cameraVideo_ = value.querySelector('video'); | 429 this.cameraVideo_ = value.querySelector('video'); |
432 this.cameraVideo_.addEventListener('canplay', | 430 this.cameraVideo_.addEventListener( |
433 this.handleVideoStarted_.bind(this)); | 431 'canplay', this.handleVideoStarted_.bind(this)); |
434 this.cameraVideo_.addEventListener('timeupdate', | 432 this.cameraVideo_.addEventListener( |
435 this.handleVideoUpdate_.bind(this)); | 433 'timeupdate', this.handleVideoUpdate_.bind(this)); |
436 this.updatePreview_(); | 434 this.updatePreview_(); |
437 // Initialize camera state and check for its presence. | 435 // Initialize camera state and check for its presence. |
438 this.cameraLive = true; | 436 this.cameraLive = true; |
439 this.cameraPresent = false; | 437 this.cameraPresent = false; |
440 }, | 438 }, |
441 | 439 |
442 /** | 440 /** |
443 * Whether the camera live stream and photo should be flipped horizontally. | 441 * Whether the camera live stream and photo should be flipped horizontally. |
444 * If setting this property results in photo update, 'photoupdated' event | 442 * If setting this property results in photo update, 'photoupdated' event |
445 * will be fired with 'dataURL' property containing the photo encoded as | 443 * will be fired with 'dataURL' property containing the photo encoded as |
446 * a data URL | 444 * a data URL |
447 * @type {boolean} | 445 * @type {boolean} |
448 */ | 446 */ |
449 get flipPhoto() { | 447 get flipPhoto() { |
450 return this.flipPhoto_ || false; | 448 return this.flipPhoto_ || false; |
451 }, | 449 }, |
452 set flipPhoto(value) { | 450 set flipPhoto(value) { |
453 if (this.flipPhoto_ == value) | 451 if (this.flipPhoto_ == value) |
454 return; | 452 return; |
455 this.flipPhoto_ = value; | 453 this.flipPhoto_ = value; |
456 this.previewElement.classList.toggle('flip-x', value); | 454 this.previewElement.classList.toggle('flip-x', value); |
457 if (!this.cameraLive) { | 455 if (!this.cameraLive) { |
458 // Flip current still photo. | 456 // Flip current still photo. |
459 var e = new Event('photoupdated'); | 457 var e = new Event('photoupdated'); |
460 e.dataURL = this.flipPhoto ? | 458 e.dataURL = this.flipPhoto ? this.flipFrame_(this.previewImage_) : |
461 this.flipFrame_(this.previewImage_) : this.previewImage_.src; | 459 this.previewImage_.src; |
462 this.dispatchEvent(e); | 460 this.dispatchEvent(e); |
463 } | 461 } |
464 }, | 462 }, |
465 | 463 |
466 /** | 464 /** |
467 * Performs photo capture from the live camera stream. 'phototaken' event | 465 * Performs photo capture from the live camera stream. 'phototaken' event |
468 * will be fired as soon as captured photo is available, with 'dataURL' | 466 * will be fired as soon as captured photo is available, with 'dataURL' |
469 * property containing the photo encoded as a data URL. | 467 * property containing the photo encoded as a data URL. |
470 * @return {boolean} Whether photo capture was successful. | 468 * @return {boolean} Whether photo capture was successful. |
471 */ | 469 */ |
472 takePhoto: function() { | 470 takePhoto: function() { |
473 if (!this.cameraOnline) | 471 if (!this.cameraOnline) |
474 return false; | 472 return false; |
475 var canvas = /** @type {HTMLCanvasElement} */( | 473 var canvas = |
476 document.createElement('canvas')); | 474 /** @type {HTMLCanvasElement} */ (document.createElement('canvas')); |
477 canvas.width = CAPTURE_SIZE.width; | 475 canvas.width = CAPTURE_SIZE.width; |
478 canvas.height = CAPTURE_SIZE.height; | 476 canvas.height = CAPTURE_SIZE.height; |
479 this.captureFrame_( | 477 this.captureFrame_( |
480 this.cameraVideo_, | 478 this.cameraVideo_, |
481 /** @type {CanvasRenderingContext2D} */(canvas.getContext('2d')), | 479 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')), |
482 CAPTURE_SIZE); | 480 CAPTURE_SIZE); |
483 // Preload image before displaying it. | 481 // Preload image before displaying it. |
484 var previewImg = new Image(); | 482 var previewImg = new Image(); |
485 previewImg.addEventListener('load', function(e) { | 483 previewImg.addEventListener('load', function(e) { |
486 this.cameraTitle_ = this.capturedImageTitle_; | 484 this.cameraTitle_ = this.capturedImageTitle_; |
487 this.cameraImage = previewImg.src; | 485 this.cameraImage = previewImg.src; |
488 }.bind(this)); | 486 }.bind(this)); |
489 previewImg.src = canvas.toDataURL('image/png'); | 487 previewImg.src = canvas.toDataURL('image/png'); |
490 var e = new Event('phototaken'); | 488 var e = new Event('phototaken'); |
491 e.dataURL = this.flipPhoto ? this.flipFrame_(canvas) : previewImg.src; | 489 e.dataURL = this.flipPhoto ? this.flipFrame_(canvas) : previewImg.src; |
(...skipping 14 matching lines...) Expand all Loading... |
506 * current drawing origin of a canvas context. | 504 * current drawing origin of a canvas context. |
507 * @param {HTMLVideoElement} video Video element to capture from. | 505 * @param {HTMLVideoElement} video Video element to capture from. |
508 * @param {CanvasRenderingContext2D} ctx Canvas context to draw onto. | 506 * @param {CanvasRenderingContext2D} ctx Canvas context to draw onto. |
509 * @param {{width: number, height: number}} destSize Capture size. | 507 * @param {{width: number, height: number}} destSize Capture size. |
510 * @private | 508 * @private |
511 */ | 509 */ |
512 captureFrame_: function(video, ctx, destSize) { | 510 captureFrame_: function(video, ctx, destSize) { |
513 var width = video.videoWidth; | 511 var width = video.videoWidth; |
514 var height = video.videoHeight; | 512 var height = video.videoHeight; |
515 if (width < destSize.width || height < destSize.height) { | 513 if (width < destSize.width || height < destSize.height) { |
516 console.error('Video capture size too small: ' + | 514 console.error( |
517 width + 'x' + height + '!'); | 515 'Video capture size too small: ' + width + 'x' + height + '!'); |
518 } | 516 } |
519 var src = {}; | 517 var src = {}; |
520 if (width / destSize.width > height / destSize.height) { | 518 if (width / destSize.width > height / destSize.height) { |
521 // Full height, crop left/right. | 519 // Full height, crop left/right. |
522 src.height = height; | 520 src.height = height; |
523 src.width = height * destSize.width / destSize.height; | 521 src.width = height * destSize.width / destSize.height; |
524 } else { | 522 } else { |
525 // Full width, crop top/bottom. | 523 // Full width, crop top/bottom. |
526 src.width = width; | 524 src.width = width; |
527 src.height = width * destSize.height / destSize.width; | 525 src.height = width * destSize.height / destSize.width; |
528 } | 526 } |
529 src.x = (width - src.width) / 2; | 527 src.x = (width - src.width) / 2; |
530 src.y = (height - src.height) / 2; | 528 src.y = (height - src.height) / 2; |
531 ctx.drawImage(video, src.x, src.y, src.width, src.height, | 529 ctx.drawImage( |
532 0, 0, destSize.width, destSize.height); | 530 video, src.x, src.y, src.width, src.height, 0, 0, destSize.width, |
| 531 destSize.height); |
533 }, | 532 }, |
534 | 533 |
535 /** | 534 /** |
536 * Flips frame horizontally. | 535 * Flips frame horizontally. |
537 * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} source | 536 * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} source |
538 * Frame to flip. | 537 * Frame to flip. |
539 * @return {string} Flipped frame as data URL. | 538 * @return {string} Flipped frame as data URL. |
540 */ | 539 */ |
541 flipFrame_: function(source) { | 540 flipFrame_: function(source) { |
542 var canvas = document.createElement('canvas'); | 541 var canvas = document.createElement('canvas'); |
(...skipping 11 matching lines...) Expand all Loading... |
554 * @param {string} url Image URL. | 553 * @param {string} url Image URL. |
555 * @param {string=} opt_title Image tooltip. | 554 * @param {string=} opt_title Image tooltip. |
556 * @param {Function=} opt_clickHandler Image click handler. | 555 * @param {Function=} opt_clickHandler Image click handler. |
557 * @param {number=} opt_position If given, inserts new image into | 556 * @param {number=} opt_position If given, inserts new image into |
558 * that position (0-based) in image list. | 557 * that position (0-based) in image list. |
559 * @param {Function=} opt_decorateFn Function called with the list element | 558 * @param {Function=} opt_decorateFn Function called with the list element |
560 * as argument to do any final decoration. | 559 * as argument to do any final decoration. |
561 * @return {!Object} Image data inserted into the data model. | 560 * @return {!Object} Image data inserted into the data model. |
562 */ | 561 */ |
563 // TODO(ivankr): this function needs some argument list refactoring. | 562 // TODO(ivankr): this function needs some argument list refactoring. |
564 addItem: function(url, opt_title, opt_clickHandler, opt_position, | 563 addItem: function( |
565 opt_decorateFn) { | 564 url, opt_title, opt_clickHandler, opt_position, opt_decorateFn) { |
566 var imageInfo = { | 565 var imageInfo = { |
567 url: url, | 566 url: url, |
568 title: opt_title, | 567 title: opt_title, |
569 clickHandler: opt_clickHandler, | 568 clickHandler: opt_clickHandler, |
570 decorateFn: opt_decorateFn | 569 decorateFn: opt_decorateFn |
571 }; | 570 }; |
572 this.inProgramSelection_ = true; | 571 this.inProgramSelection_ = true; |
573 if (opt_position !== undefined) | 572 if (opt_position !== undefined) |
574 this.dataModel.splice(opt_position, 0, imageInfo); | 573 this.dataModel.splice(opt_position, 0, imageInfo); |
575 else | 574 else |
(...skipping 17 matching lines...) Expand all Loading... |
593 * @param {string} imageUrl New image URL. | 592 * @param {string} imageUrl New image URL. |
594 * @param {string=} opt_title New image tooltip (if undefined, tooltip | 593 * @param {string=} opt_title New image tooltip (if undefined, tooltip |
595 * is left unchanged). | 594 * is left unchanged). |
596 * @return {!Object} Image data of the added or updated image. | 595 * @return {!Object} Image data of the added or updated image. |
597 */ | 596 */ |
598 updateItem: function(imageInfo, imageUrl, opt_title) { | 597 updateItem: function(imageInfo, imageUrl, opt_title) { |
599 var imageIndex = this.indexOf(imageInfo); | 598 var imageIndex = this.indexOf(imageInfo); |
600 var wasSelected = this.selectionModel.selectedIndex == imageIndex; | 599 var wasSelected = this.selectionModel.selectedIndex == imageIndex; |
601 this.removeItem(imageInfo); | 600 this.removeItem(imageInfo); |
602 var newInfo = this.addItem( | 601 var newInfo = this.addItem( |
603 imageUrl, | 602 imageUrl, opt_title === undefined ? imageInfo.title : opt_title, |
604 opt_title === undefined ? imageInfo.title : opt_title, | 603 imageInfo.clickHandler, imageIndex, imageInfo.decorateFn); |
605 imageInfo.clickHandler, | |
606 imageIndex, | |
607 imageInfo.decorateFn); | |
608 // Update image data with the reset of the keys from the old data. | 604 // Update image data with the reset of the keys from the old data. |
609 for (var k in imageInfo) { | 605 for (var k in imageInfo) { |
610 if (!(k in newInfo)) | 606 if (!(k in newInfo)) |
611 newInfo[k] = imageInfo[k]; | 607 newInfo[k] = imageInfo[k]; |
612 } | 608 } |
613 if (wasSelected) | 609 if (wasSelected) |
614 this.selectedItem = newInfo; | 610 this.selectedItem = newInfo; |
615 return newInfo; | 611 return newInfo; |
616 }, | 612 }, |
617 | 613 |
618 /** | 614 /** |
619 * Removes previously added image from the grid. | 615 * Removes previously added image from the grid. |
620 * @param {Object} imageInfo Image data returned from the addItem() call. | 616 * @param {Object} imageInfo Image data returned from the addItem() call. |
621 */ | 617 */ |
622 removeItem: function(imageInfo) { | 618 removeItem: function(imageInfo) { |
623 var index = this.indexOf(imageInfo); | 619 var index = this.indexOf(imageInfo); |
624 if (index != -1) { | 620 if (index != -1) { |
625 var wasSelected = this.selectionModel.selectedIndex == index; | 621 var wasSelected = this.selectionModel.selectedIndex == index; |
626 this.inProgramSelection_ = true; | 622 this.inProgramSelection_ = true; |
627 this.dataModel.splice(index, 1); | 623 this.dataModel.splice(index, 1); |
628 if (wasSelected) { | 624 if (wasSelected) { |
629 // If item removed was selected, select the item next to it. | 625 // If item removed was selected, select the item next to it. |
630 this.selectedItem = this.dataModel.item( | 626 this.selectedItem = |
631 Math.min(this.dataModel.length - 1, index)); | 627 this.dataModel.item(Math.min(this.dataModel.length - 1, index)); |
632 } | 628 } |
633 this.inProgramSelection_ = false; | 629 this.inProgramSelection_ = false; |
634 } | 630 } |
635 }, | 631 }, |
636 | 632 |
637 /** | 633 /** |
638 * Forces re-display, size re-calculation and focuses grid. | 634 * Forces re-display, size re-calculation and focuses grid. |
639 */ | 635 */ |
640 updateAndFocus: function() { | 636 updateAndFocus: function() { |
641 // Recalculate the measured item size. | 637 // Recalculate the measured item size. |
(...skipping 23 matching lines...) Expand all Loading... |
665 /** | 661 /** |
666 * URLs of special button images. | 662 * URLs of special button images. |
667 * @enum {string} | 663 * @enum {string} |
668 */ | 664 */ |
669 UserImagesGrid.ButtonImages = { | 665 UserImagesGrid.ButtonImages = { |
670 TAKE_PHOTO: 'chrome://theme/IDR_BUTTON_USER_IMAGE_TAKE_PHOTO', | 666 TAKE_PHOTO: 'chrome://theme/IDR_BUTTON_USER_IMAGE_TAKE_PHOTO', |
671 CHOOSE_FILE: 'chrome://theme/IDR_BUTTON_USER_IMAGE_CHOOSE_FILE', | 667 CHOOSE_FILE: 'chrome://theme/IDR_BUTTON_USER_IMAGE_CHOOSE_FILE', |
672 PROFILE_PICTURE: 'chrome://theme/IDR_PROFILE_PICTURE_LOADING' | 668 PROFILE_PICTURE: 'chrome://theme/IDR_PROFILE_PICTURE_LOADING' |
673 }; | 669 }; |
674 | 670 |
675 return { | 671 return {UserImagesGrid: UserImagesGrid}; |
676 UserImagesGrid: UserImagesGrid | |
677 }; | |
678 }); | 672 }); |
OLD | NEW |