1/**
2 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 **/
6
7// Checking for "chrome.app.runtime" availability allows this Chrome app code to
8// be tested in a regular web page (like tests/manual.html). Checking for
9// "chrome" and "chrome.app" availability further allows this code to be tested
10// in non-Chrome browsers, which is useful for example to test touch support
11// with a non-Chrome touch device.
12if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
13  var showCalculatorWindow = function () {
14    chrome.app.window.create('calculator.html', {
15      defaultWidth: 243, minWidth: 243, maxWidth: 243,
16      defaultHeight: 380, minHeight: 380, maxHeight: 380,
17      id: 'calculator'
18    }, function(appWindow) {
19      appWindow.contentWindow.onload = function() {
20        new Controller(new Model(9), new View(appWindow.contentWindow));
21      };
22
23      chrome.storage.local.set({windowVisible: true});
24      appWindow.onClosed.addListener(function() {
25        chrome.storage.local.set({windowVisible: false});
26      });
27    });
28  }
29
30  chrome.app.runtime.onLaunched.addListener(showCalculatorWindow);
31  chrome.app.runtime.onRestarted.addListener(function() {
32    chrome.storage.local.get('windowVisible', function(data) {
33      if (data.windowVisible)
34        showCalculatorWindow();
35    });
36  });
37}
38
39function Controller(model, view) {
40  this.inputs = this.defineInputs_();
41  this.model = model;
42  this.view = view;
43  this.view.onButton = function(button) {
44    this.handleInput_(this.inputs.byButton[button]);
45  }.bind(this);
46  this.view.onKey = function(key) {
47    this.handleInput_(this.inputs.byKey[key]);
48  }.bind(this);
49}
50
51/** @private */
52Controller.prototype.defineInputs_ = function() {
53  var inputs = {byButton: {}, byKey: {}};
54  inputs.byButton['zero'] = inputs.byKey['48'] = '0';
55  inputs.byButton['one'] = inputs.byKey['49'] = '1';
56  inputs.byButton['two'] = inputs.byKey['50'] = '2';
57  inputs.byButton['three'] = inputs.byKey['51'] = '3';
58  inputs.byButton['four'] = inputs.byKey['52'] = '4';
59  inputs.byButton['five'] = inputs.byKey['53'] = '5';
60  inputs.byButton['six'] = inputs.byKey['54'] = '6';
61  inputs.byButton['seven'] = inputs.byKey['55'] = '7';
62  inputs.byButton['eight'] = inputs.byKey['56'] = '8';
63  inputs.byButton['nine'] = inputs.byKey['57'] = '9';
64  inputs.byButton['point'] = inputs.byKey['190'] = '.';
65  inputs.byButton['add'] = inputs.byKey['^187'] = '+';
66  inputs.byButton['subtract'] = inputs.byKey['189'] = '-';
67  inputs.byButton['multiply'] = inputs.byKey['^56'] = '*';
68  inputs.byButton['divide'] = inputs.byKey['191'] = '/';
69  inputs.byButton['equals'] = inputs.byKey['187'] = inputs.byKey['13'] = '=';
70  inputs.byButton['negate'] = inputs.byKey['32'] = '+ / -';
71  inputs.byButton['clear'] = inputs.byKey['67'] = 'AC';
72  inputs.byButton['back'] = inputs.byKey['8'] = 'back';
73  return inputs;
74};
75
76/** @private */
77Controller.prototype.handleInput_ = function(input) {
78  var values, accumulator, operator, operand;
79  if (input) {
80    values = this.model.handle(input);
81    accumulator = values.accumulator;
82    operator = values.operator;
83    operand = values.operand;
84    if (input === 'AC') {
85      this.view.clearDisplay({operand: '0'});
86    } else if (input === '=') {
87      this.view.addResults({accumulator: accumulator, operand: accumulator});
88    } else if (input.match(/^[+*/-]$/)) {
89      this.updateValues_({accumulator: accumulator});
90      this.view.addValues({operator: values.operator});
91    } else if (!this.updateValues_({operator: operator, operand: operand})) {
92      this.view.addValues({operator: operator, operand: operand});
93    }
94  }
95};
96
97/** @private */
98Controller.prototype.updateValues_ = function(values) {
99  // Values which are "finalized" (which have an accumulator value) shouldn't
100  // and won't be updated, and this method will return false for them.
101  var before = this.view.getValues();
102  var after = !before.accumulator ? values : {};
103  this.view.setValues({
104    accumulator: this.getUpdatedValue_(before, after, 'accumulator'),
105    operator: this.getUpdatedValue_(before, after, 'operator'),
106    operand: this.getUpdatedValue_(before, after, 'operand', !before.operator)
107  });
108  return !before.accumulator;
109}
110
111/** @private */
112Controller.prototype.getUpdatedValue_ = function(before, after, key, zero) {
113  var value = (typeof after[key] !== 'undefined') ? after[key] : before[key];
114  return zero ? (value || '0') : value;
115}
116