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
6// Network status constants.
7const StatusConnected = 'connected';
8const StatusDisconnected = 'disconnected';
9const StatusConnecting = 'connecting';
10const StatusError = 'error';
11
12const NetworkOther = 'other';
13
14// Setup css canvas 'spinner-circle'
15(function() {
16  var lineWidth = 3;
17  var r = 8;
18  var ctx = document.getCSSCanvasContext('2d', 'spinner-circle', 2 * r, 2 * r);
19
20  ctx.lineWidth = lineWidth;
21  ctx.lineCap = 'round';
22  ctx.lineJoin = 'round';
23
24  ctx.strokeStyle = '#4e73c7';
25  ctx.beginPath();
26  ctx.moveTo(lineWidth / 2, r - lineWidth / 2);
27  ctx.arc(r, r, r - lineWidth / 2, Math.PI, Math.PI * 3 / 2);
28  ctx.stroke();
29})();
30
31/**
32 * Sends "connect" using the 'action' WebUI message.
33 */
34function sendConnect(index, passphrase, identity, auto_connect) {
35  chrome.send('action',
36      ['connect',
37       String(index),
38       passphrase,
39       identity,
40       auto_connect ? '1' : '0']);
41}
42
43var networkMenuItemProto = (function() {
44    var networkMenuItem = cr.doc.createElement('div');
45    networkMenuItem.innerHTML = '<div class="network-menu-item">' +
46          '<div class="network-label-icon">' +
47            '<div class="network-label"></div>' +
48            '<div class="network-icon hidden"></div>' +
49          '</div>' +
50          '<div class="network-status hidden"></div>' +
51          '<div class="hidden"></div>' +
52        '</div>';
53    return networkMenuItem;
54  })();
55
56var NetworkMenuItem = cr.ui.define(function() {
57    return networkMenuItemProto.cloneNode(true);
58  });
59
60NetworkMenuItem.prototype = {
61  __proto__: MenuItem.prototype,
62
63  ssidEdit: null,
64  passwordEdit: null,
65  autoConnectCheckbox: null,
66
67  /**
68   * The label element.
69   * @private
70   */
71  get label_() {
72    return this.firstElementChild.firstElementChild.firstElementChild;
73  },
74
75  /**
76   * The icon element.
77   * @private
78   */
79  get icon_() {
80    return this.label_.nextElementSibling;
81  },
82
83  /**
84   * The status area element.
85   * @private
86   */
87  get status_() {
88    return this.firstElementChild.firstElementChild.nextElementSibling;
89  },
90
91  /**
92   * The action area container element.
93   * @private
94   */
95  get action_() {
96    return this.status_.nextElementSibling;
97  },
98
99  /**
100   * Set status message.
101   * @param {string} message The message to display in status area.
102   * @private
103   */
104  setStatus_: function(message) {
105    if (message) {
106      this.status_.textContent = message;
107      this.status_.classList.remove('hidden');
108    } else {
109      this.status_.classList.add('hidden');
110    }
111  },
112
113  /**
114   * Set status icon.
115   * @param {string} icon Source url for the icon image.
116   * @private
117   */
118  setIcon_: function(icon) {
119    if (icon) {
120      this.icon_.style.backgroundImage = 'url(' + icon + ')';
121      this.icon_.classList.remove('hidden');
122    } else {
123      this.icon_.classList.add('hidden');
124    }
125  },
126
127  /**
128   * Handle reconnect.
129   * @private
130   */
131  handleConnect_ : function(e) {
132    var index = this.menu_.getMenuItemIndexOf(this);
133    if (this.ssidEdit && this.passwordEdit) {
134      if (this.ssidEdit.value) {
135        sendConnect(index,
136            this.passwordEdit.value,
137            this.ssidEdit.value,
138            this.autoConnectCheckbox.checked);
139      }
140    } else if (this.passwordEdit) {
141      if (this.passwordEdit.value) {
142        sendConnect(index,
143            this.passwordEdit.value, '', this.autoConnectCheckbox.checked);
144      }
145    } else {
146      if (this.attrs.remembered) {
147        sendConnect(index, this.attrs.passphrase, '', this.attrs.auto_connect);
148      } else {
149        sendConnect(index, '', '', this.autoConnectCheckbox.checked);
150      }
151    }
152  },
153
154  /**
155   * Handle keydown event in ssid edit.
156   * @private
157   */
158  handleSsidEditKeydown_: function(e) {
159    if (e.target == this.ssidEdit &&
160        e.keyIdentifier == 'Enter') {
161      this.passwordEdit.focus();
162    }
163  },
164
165  /**
166   * Handle keydown event in password edit.
167   * @private
168   */
169  handlePassEditKeydown_: function(e) {
170    if (e.target == this.passwordEdit &&
171        e.keyIdentifier == 'Enter') {
172      this.handleConnect_();
173    }
174  },
175
176  /**
177   * Returns whether action area is visible.
178   * @private
179   */
180  isActionVisible_: function() {
181    return !this.action_.classList.contains('hidden');
182  },
183
184  /**
185   * Show/hide action area.
186   * @private
187   */
188  showAction_: function(show) {
189    var visible = this.isActionVisible_();
190    if (show && !visible) {
191      this.action_.classList.remove('hidden');
192    } else if (!show && visible) {
193      this.action_.classList.add('hidden');
194    }
195  },
196
197  /**
198   * Add network name edit to action area.
199   * @private
200   */
201  addSsidEdit_: function() {
202    this.ssidEdit = this.ownerDocument.createElement('input');
203    this.ssidEdit.type = 'text';
204    this.ssidEdit.placeholder = localStrings.getString('ssid_prompt');
205    this.ssidEdit.pattern = '^\\S+$';
206    this.ssidEdit.addEventListener('keydown',
207        this.handleSsidEditKeydown_.bind(this));
208
209    var box = this.ownerDocument.createElement('div');
210    box.appendChild(this.ssidEdit);
211    this.action_.appendChild(box);
212  },
213
214  /**
215   * Add password edit to action area.
216   * @private
217   */
218  addPasswordEdit_: function() {
219    this.passwordEdit = this.ownerDocument.createElement('input');
220    this.passwordEdit.type = 'password';
221    this.passwordEdit.placeholder = localStrings.getString('pass_prompt');
222    this.passwordEdit.pattern = '^\\S+$';
223    this.passwordEdit.addEventListener('keydown',
224        this.handlePassEditKeydown_.bind(this));
225
226    var box = this.ownerDocument.createElement('div');
227    box.appendChild(this.passwordEdit);
228    this.action_.appendChild(box);
229  },
230
231  /**
232   * Add auto-connect this network check box to action area.
233   * @private
234   */
235  addAutoConnectCheckbox_: function() {
236    this.autoConnectCheckbox = this.ownerDocument.createElement('input');
237    this.autoConnectCheckbox.type = 'checkbox';
238    this.autoConnectCheckbox.checked = this.attrs.auto_connect;
239
240    var autoConnectSpan = this.ownerDocument.createElement('span');
241    autoConnectSpan.textContent =
242        localStrings.getString('auto_connect_this_network');
243
244    var autoConnectLabel = this.ownerDocument.createElement('label');
245    autoConnectLabel.appendChild(this.autoConnectCheckbox);
246    autoConnectLabel.appendChild(autoConnectSpan);
247
248    this.action_.appendChild(autoConnectLabel);
249  },
250
251  /**
252   * Internal method to initiailze the MenuItem.
253   * @private
254   */
255  initMenuItem_: function() {
256    // *TODO: eliminate code duplication with menu.js
257    // MenuItem.prototype.initMenuItem_();
258    var attrs = this.attrs;
259    this.classList.add(attrs.type);
260    this.menu_.addHandlers(this, this);
261
262    //////// NetworkMenuItem specific code:
263    // TODO: Handle specific types of network, connecting icon.
264    this.label_.textContent = attrs.label;
265
266    if (attrs.network_type == NetworkOther) {
267      this.addSsidEdit_();
268      this.addPasswordEdit_();
269      this.addAutoConnectCheckbox_();
270    } else if (attrs.status && attrs.status != 'unknown') {
271      if (attrs.status == StatusConnected) {
272        this.setStatus_(attrs.ip_address);
273      } else if (attrs.status == StatusConnecting) {
274        this.setStatus_(attrs.message);
275
276        this.icon_.classList.add('spinner');
277        this.icon_.classList.remove('hidden');
278      } else if (attrs.status == StatusError) {
279        this.setStatus_(attrs.message);
280        this.setIcon_('chrome://theme/IDR_WARNING');
281
282        var button = this.ownerDocument.createElement('button');
283        button.textContent = localStrings.getString('reconnect');
284        button.addEventListener('click', this.handleConnect_.bind(this));
285        var box = this.ownerDocument.createElement('div');
286        box.appendChild(button);
287        this.action_.appendChild(box);
288
289        this.showAction_(true);
290      }
291
292      if (attrs.need_passphrase) {
293        this.addPasswordEdit_();
294      }
295
296      this.addAutoConnectCheckbox_();
297    }
298    //////// End NetworkMenuItem specifi code
299
300    if (attrs.font) {
301      this.label_.style.font = attrs.font;
302
303      var base_font = attrs.font.replace(/bold/, '').replace(/italic/, '');
304      this.status_.style.font = base_font;
305      this.action_.style.font = base_font;
306    }
307  },
308
309  /** @inheritDoc */
310  activate: function() {
311    // Close action area and connect if it is visible.
312    if (this.isActionVisible_()) {
313      this.showAction_(false);
314      this.handleConnect_();
315      return;
316    }
317
318    // Show action area for encrypted network and 'other' network.
319    if ((this.attrs.network_type == NetworkOther ||
320         this.attrs.status == StatusDisconnected) &&
321        this.attrs.need_passphrase &&
322        !this.isActionVisible_()) {
323      this.showAction_(true);
324      return;
325    }
326
327    MenuItem.prototype.activate.call(this);
328  }
329};
330
331
332var NetworkMenu = cr.ui.define('div');
333
334NetworkMenu.prototype = {
335  __proto__: Menu.prototype,
336
337  /** @inheritDoc */
338  createMenuItem: function(attrs) {
339    if (attrs.type == 'command') {
340      return new NetworkMenuItem();
341    } else {
342      return new MenuItem();
343    }
344  },
345
346  /** @inheritDoc */
347  onClick_: function(event, item) {
348    // If item is a NetworkMenuItem, it must have at least one of the following.
349    if (item.autoConnectCheckbox || item.ssidEdit || item.passwordEdit) {
350      // Ignore clicks other than on the NetworkMenuItem itself.
351      if (event.target == item.autoConnectCheckbox ||
352          event.target == item.autoConnectCheckbox.nextElementSibling ||
353          event.target == item.ssidEdit ||
354          event.target == item.passwordEdit) {
355        return;
356      }
357    }
358
359    Menu.prototype.onClick_.call(this, event, item);
360  },
361};
362