Add favicon notifications
[KiwiIRC.git] / client / assets / src / views / application.js
CommitLineData
50ac472f
D
1_kiwi.view.Application = Backbone.View.extend({
2 initialize: function () {
3 var that = this;
4
5 $(window).resize(function() { that.doLayout.apply(that); });
6 this.$el.find('.toolbar').resize(function() { that.doLayout.apply(that); });
7 $('#kiwi .controlbox').resize(function() { that.doLayout.apply(that); });
8
9 // Change the theme when the config is changed
10 _kiwi.global.settings.on('change:theme', this.updateTheme, this);
11 this.updateTheme(getQueryVariable('theme'));
12
13 _kiwi.global.settings.on('change:channel_list_style', this.setTabLayout, this);
14 this.setTabLayout(_kiwi.global.settings.get('channel_list_style'));
15
16 _kiwi.global.settings.on('change:show_timestamps', this.displayTimestamps, this);
17 this.displayTimestamps(_kiwi.global.settings.get('show_timestamps'));
18
19 this.doLayout();
20
21 $(document).keydown(this.setKeyFocus);
22
23 // Confirmation require to leave the page
24 window.onbeforeunload = function () {
25 if (_kiwi.gateway.isConnected()) {
26 return 'This will close all KiwiIRC conversations. Are you sure you want to close this window?';
27 }
28 };
29
2b3eb5b9 30 this.notificationFavicon();
50ac472f
D
31 this.initSound();
32 },
33
34
35
36 updateTheme: function (theme_name) {
37 // If called by the settings callback, get the correct new_value
38 if (theme_name === _kiwi.global.settings) {
39 theme_name = arguments[1];
40 }
41
42 // If we have no theme specified, get it from the settings
43 if (!theme_name) theme_name = _kiwi.global.settings.get('theme');
44
45 // Clear any current theme
46 this.$el.removeClass(function (i, css) {
47 return (css.match(/\btheme_\S+/g) || []).join(' ');
48 });
49
50 // Apply the new theme
51 this.$el.addClass('theme_' + (theme_name || 'relaxed'));
52 },
53
54
55 setTabLayout: function (layout_style) {
56 // If called by the settings callback, get the correct new_value
57 if (layout_style === _kiwi.global.settings) {
58 layout_style = arguments[1];
59 }
60
61 if (layout_style == 'list') {
62 this.$el.addClass('chanlist_treeview');
63 } else {
64 this.$el.removeClass('chanlist_treeview');
65 }
66
67 this.doLayout();
68 },
69
70
71 displayTimestamps: function (show_timestamps) {
72 // If called by the settings callback, get the correct new_value
73 if (show_timestamps === _kiwi.global.settings) {
74 show_timestamps = arguments[1];
75 }
76
77 if (show_timestamps) {
78 this.$el.addClass('timestamps');
79 } else {
80 this.$el.removeClass('timestamps');
81 }
82 },
83
84
85 // Globally shift focus to the command input box on a keypress
86 setKeyFocus: function (ev) {
87 // If we're copying text, don't shift focus
88 if (ev.ctrlKey || ev.altKey || ev.metaKey) {
89 return;
90 }
91
92 // If we're typing into an input box somewhere, ignore
93 if ((ev.target.tagName.toLowerCase() === 'input') || (ev.target.tagName.toLowerCase() === 'textarea') || $(ev.target).attr('contenteditable')) {
94 return;
95 }
96
97 $('#kiwi .controlbox .inp').focus();
98 },
99
100
101 doLayout: function () {
102 var el_kiwi = this.$el;
103 var el_panels = $('#kiwi .panels');
104 var el_memberlists = $('#kiwi .memberlists');
105 var el_toolbar = this.$el.find('.toolbar');
106 var el_controlbox = $('#kiwi .controlbox');
107 var el_resize_handle = $('#kiwi .memberlists_resize_handle');
108
109 var css_heights = {
110 top: el_toolbar.outerHeight(true),
111 bottom: el_controlbox.outerHeight(true)
112 };
113
114
115 // If any elements are not visible, full size the panals instead
116 if (!el_toolbar.is(':visible')) {
117 css_heights.top = 0;
118 }
119
120 if (!el_controlbox.is(':visible')) {
121 css_heights.bottom = 0;
122 }
123
124 // Apply the CSS sizes
125 el_panels.css(css_heights);
126 el_memberlists.css(css_heights);
127 el_resize_handle.css(css_heights);
128
129 // If we have channel tabs on the side, adjust the height
130 if (el_kiwi.hasClass('chanlist_treeview')) {
131 this.$el.find('.tabs', el_kiwi).css(css_heights);
132 }
133
134 // Determine if we have a narrow window (mobile/tablet/or even small desktop window)
135 if (el_kiwi.outerWidth() < 400) {
136 el_kiwi.addClass('narrow');
137 } else {
138 el_kiwi.removeClass('narrow');
139 }
140
141 // Set the panels width depending on the memberlist visibility
142 if (el_memberlists.css('display') != 'none') {
143 // Panels to the side of the memberlist
144 el_panels.css('right', el_memberlists.outerWidth(true));
145 // The resize handle sits overlapping the panels and memberlist
146 el_resize_handle.css('left', el_memberlists.position().left - (el_resize_handle.outerWidth(true) / 2));
147 } else {
148 // Memberlist is hidden so panels to the right edge
149 el_panels.css('right', 0);
150 // And move the handle just out of sight to the right
151 el_resize_handle.css('left', el_panels.outerWidth(true));
152 }
153
154 var input_wrap_width = parseInt($('#kiwi .controlbox .input_tools').outerWidth());
155 el_controlbox.find('.input_wrap').css('right', input_wrap_width + 7);
156 },
157
158
159 alertWindow: function (title) {
160 if (!this.alertWindowTimer) {
161 this.alertWindowTimer = new (function () {
162 var that = this;
163 var tmr;
164 var has_focus = true;
165 var state = 0;
166 var default_title = 'Kiwi IRC';
167 var title = 'Kiwi IRC';
168
169 this.setTitle = function (new_title) {
170 new_title = new_title || default_title;
171 window.document.title = new_title;
172 return new_title;
173 };
174
175 this.start = function (new_title) {
176 // Don't alert if we already have focus
177 if (has_focus) return;
178
179 title = new_title;
180 if (tmr) return;
181 tmr = setInterval(this.update, 1000);
182 };
183
184 this.stop = function () {
185 // Stop the timer and clear the title
186 if (tmr) clearInterval(tmr);
187 tmr = null;
188 this.setTitle();
189
190 // Some browsers don't always update the last title correctly
191 // Wait a few seconds and then reset
192 setTimeout(this.reset, 2000);
193 };
194
195 this.reset = function () {
196 if (tmr) return;
197 that.setTitle();
198 };
199
200
201 this.update = function () {
202 if (state === 0) {
203 that.setTitle(title);
204 state = 1;
205 } else {
206 that.setTitle();
207 state = 0;
208 }
209 };
210
211 $(window).focus(function (event) {
212 has_focus = true;
213 that.stop();
214
215 // Some browsers don't always update the last title correctly
216 // Wait a few seconds and then reset
217 setTimeout(that.reset, 2000);
218 });
219
220 $(window).blur(function (event) {
221 has_focus = false;
222 });
223 })();
224 }
225
226 this.alertWindowTimer.start(title);
227 },
228
229
2b3eb5b9
VC
230 // This will have to be rewiritten to store highlights in kiwi, and not here
231 notificationFavicon: function () {
232 var base_path = this.model.get('base_path');
233
234 this.favicon = new (function () {
235 var that = this,
236 hasFocus = true,
237 highlightCount = 0,
238 hasConvasSupport = !!window.CanvasRenderingContext2D,
239 originalTitle = document.title,
240 originalFaviconLink = $('link[rel~="icon"]')[0].href,
241 font = 'bold 10px Arial',
242 letterSpacing = -1.5;
243
244 var ua = (function () {
245 var agent = navigator.userAgent.toLowerCase();
246 return function (browser) {
247 return agent.indexOf(browser) !== -1;
248 };
249 })();
250
251 var browser = {
252 ie: ua('msie'),
253 chrome: ua('chrome'),
254 webkit: ua('chrome') || ua('safari'),
255 safari: ua('safari') && !ua('chrome'),
256 mozilla: ua('mozilla') && !ua('chrome') && !ua('safari')
257 };
258
259 this.newHighlight = function () {
260 if (!hasFocus) {
261 highlightCount++;
262 that._updateFavicon(highlightCount);
263 }
264 };
265
266 this.resetHighlights = function () {
267 highlightCount = 0;
268 that._refreshFavicon(originalFaviconLink);
269 that._setTitle();
270 }
271
272 this._updateFavicon = function (text) {
273 text = text.toString();
274 if (!hasConvasSupport || browser.ie || browser.safari) {
275 that._setTitle(text);
276 }
277 else {
278 that._drawFavicon(text);
279 }
280 };
281
282 this._drawFavicon = function (text) {
283 var context = that._createCanvas().getContext('2d'),
284 faviconImage = new Image();
285
286 // Allow cross origin resource requests
287 faviconImage.crossOrigin = 'anonymous';
288 // Trigger the load event
289 faviconImage.src = originalFaviconLink;
290
291 // Wait for the favicon image to load
292 faviconImage.onload = function () {
293 // Draw the favicon itself
294 context.drawImage(faviconImage, 0, 0, faviconImage.width, faviconImage.height);
295 // Add highlight bubble
296 that._drawBubble(context, text);
297 //Update
298 that._refreshFavicon(canvas.toDataURL());
299 };
300 };
301
302 this._drawBubble = function (context, text) {
303 var textWidth = 0, textHidth = 0,
304 test = context,
305 canvasWidth = context.canvas.width,
306 canvasHeight = context.canvas.height;
307
308 // A hacky solution for letter-spacing, but works well with small favicon text
309 CanvasRenderingContext2D.prototype.renderText = function (text, x, y, letterSpacing) {
310 if (!text || typeof text !== 'string' || text.length === 0) {
311 return;
312 }
313 if (typeof letterSpacing === 'undefined') {
314 letterSpacing = 0;
315 }
316 // letterSpacing of 0 means normal letter-spacing
317 var characters = String.prototype.split.call(text, ''),
318 index = 0,
319 current,
320 currentPosition = x,
321 align = 1;
322
323 if (this.textAlign === 'right') {
324 characters = characters.reverse();
325 align = -1;
326 } else if (this.textAlign === 'center') {
327 var totalWidth = 0;
328 for (var i = 0; i < characters.length; i++) {
329 totalWidth += (this.measureText(characters[i]).width + letterSpacing);
330 }
331 currentPosition = x - (totalWidth / 2);
332 }
333
334 while (index < text.length) {
335 current = characters[index++];
336 this.fillText(current, currentPosition, y);
337 currentPosition += (align * (this.measureText(current).width + letterSpacing));
338 }
339 }
340
341 // Setup a test canvas to get text width
342 test.font = context.font = 'bold 10px Arial';
343 test.textAlign = 'right';
344 test.renderText(text, 0, 0, letterSpacing);
345
346 // Calculate text width based on letter spacing and padding
347 textWidth = test.measureText(text).width + letterSpacing * (text.length - 1) + 2;
348 textHeight = 8;
349
350 // Set bubble parameters
351 bubbleX = canvasWidth - textWidth;
352 bubbleY = canvasHeight - textHeight;
353
354 // Draw bubble background
355 context.fillStyle = 'red';
356 context.fillRect(bubbleX, bubbleY, textWidth, textHeight);
357
358 // Draw the text
359 context.fillStyle = 'white';
360 context.renderText(text, canvasWidth - 1, canvasHeight - 1, letterSpacing);
361 };
362
363 this._refreshFavicon = function (url) {
364 // Remove existing favicon since Firefox doesn't update fivacons on href change
365 $('link[rel~="icon"]').remove();
366 // Add new favicon
367 $('<link rel="shortcut icon" href="' + url + '">').appendTo($('head'));
368 };
369
370 this._createCanvas = function () {
371 canvas = document.createElement('canvas');
372 canvas.width = 16;
373 canvas.height = 16;
374
375 return canvas;
376 };
377
378 this._setTitle = function (text) {
379 if (text) {
380 document.title = '(' + text + ') ' + originalTitle;
381 }
382 else {
383 document.title = originalTitle;
384 }
385 };
386
387 $(window).on('focus', function () {
388 hasFocus = true;
389 that.resetHighlights();
390 });
391 $(window).on('blur', function () {
392 hasFocus = false;
393 });
394 })();
395 },
396
397
50ac472f
D
398 barsHide: function (instant) {
399 var that = this;
400
401 if (!instant) {
402 this.$el.find('.toolbar').slideUp({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
403 $('#kiwi .controlbox').slideUp({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
404 } else {
405 this.$el.find('.toolbar').slideUp(0);
406 $('#kiwi .controlbox').slideUp(0);
407 this.doLayout();
408 }
409 },
410
411 barsShow: function (instant) {
412 var that = this;
413
414 if (!instant) {
415 this.$el.find('.toolbar').slideDown({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
416 $('#kiwi .controlbox').slideDown({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
417 } else {
418 this.$el.find('.toolbar').slideDown(0);
419 $('#kiwi .controlbox').slideDown(0);
420 this.doLayout();
421 }
422 },
423
424
425 initSound: function () {
426 var that = this,
427 base_path = this.model.get('base_path');
428
429 $script(base_path + '/assets/libs/soundmanager2/soundmanager2-nodebug-jsmin.js', function() {
430 if (typeof soundManager === 'undefined')
431 return;
432
433 soundManager.setup({
434 url: base_path + '/assets/libs/soundmanager2/',
435 flashVersion: 9, // optional: shiny features (default = 8)// optional: ignore Flash where possible, use 100% HTML5 mode
436 preferFlash: true,
437
438 onready: function() {
439 that.sound_object = soundManager.createSound({
440 id: 'highlight',
441 url: base_path + '/assets/sound/highlight.mp3'
442 });
443 }
444 });
445 });
446 },
447
448
449 playSound: function (sound_id) {
450 if (!this.sound_object) return;
451
452 if (_kiwi.global.settings.get('mute_sounds'))
453 return;
454
455 soundManager.play(sound_id);
456 }
457});