]> jfr.im git - irc/quakenet/qwebirc.git/blob - js/ui/baseui.js
Merge pull request #323 from retropc/master
[irc/quakenet/qwebirc.git] / js / ui / baseui.js
1 qwebirc.ui.WINDOW_STATUS = 0x01;
2 qwebirc.ui.WINDOW_QUERY = 0x02;
3 qwebirc.ui.WINDOW_CHANNEL = 0x04;
4 qwebirc.ui.WINDOW_CUSTOM = 0x08;
5 qwebirc.ui.WINDOW_CONNECT = 0x10;
6 qwebirc.ui.WINDOW_MESSAGES = 0x20;
7
8 qwebirc.ui.CUSTOM_CLIENT = "custom";
9 qwebirc.ui.DEFAULT_HUE = 210; /* nice blue */
10
11 qwebirc.ui.BaseUI = new Class({
12 Implements: [Events],
13 initialize: function(parentElement, windowClass, uiName, options) {
14 this.options = options;
15
16 this.windows = new QHash();
17 this.clients = new QHash();
18 this.windows.put(qwebirc.ui.CUSTOM_CLIENT, new QHash());
19 this.windowArray = [];
20 this.windowClass = windowClass;
21 this.parentElement = parentElement;
22 this.parentElement.addClass("qwebirc");
23 this.parentElement.addClass("qwebirc-" + uiName);
24 this.firstClient = false;
25 this.commandhistory = new qwebirc.irc.CommandHistory();
26 this.clientId = 0;
27
28 this.windowFocused = true;
29
30 if(Browser.Engine.trident) {
31 var checkFocus = function() {
32 var hasFocus = document.hasFocus();
33 if(hasFocus != this.windowFocused) {
34 this.windowFocused = hasFocus;
35 this.focusChange(hasFocus);
36 }
37 }
38
39 checkFocus.periodical(100, this);
40 } else {
41 var blur = function() { if(this.windowFocused) { this.windowFocused = false; this.focusChange(false); } }.bind(this);
42 var focus = function() { if(!this.windowFocused) { this.windowFocused = true; this.focusChange(true); } }.bind(this);
43
44 /* firefox requires both */
45
46 document.addEvent("blur", blur);
47 window.addEvent("blur", blur);
48 document.addEvent("focus", focus);
49 window.addEvent("focus", focus);
50 }
51
52 qwebirc.util.__log = function(x) {
53 if(QWEBIRC_DEBUG) {
54 if(typeof console != "undefined")
55 console.log(x);
56 this.getActiveWindow().addLine(null, x);
57 }
58 }.bind(this);
59 },
60 newClient: function(client) {
61 client.id = String(this.clientId++);
62 client.hilightController = new qwebirc.ui.HilightController(client);
63 client.addEvent("signedOn", function() {
64 this.poller = new qwebirc.xdomain.Poller(this.oobMessage.bind(this));
65 this.fireEvent("signedOn", client);
66 }.bind(this));
67 this.windows.put(client.id, new QHash());
68 this.clients.put(client.id, client);
69 var w = this.newWindow(client, qwebirc.ui.WINDOW_STATUS, "Status");
70 this.selectWindow(w);
71 if(!this.firstClient) {
72 this.firstClient = true;
73 w.addLine("", "qwebirc v" + qwebirc.VERSION);
74 w.addLine("", "Copyright (C) 2008-2017 Chris Porter and the qwebirc project.");
75 w.addLine("", "http://www.qwebirc.org");
76 w.addLine("", "Licensed under the GNU General Public License, Version 2.");
77 }
78 return w;
79 },
80 getClientId: function(client) {
81 if(client == qwebirc.ui.CUSTOM_CLIENT) {
82 return qwebirc.ui.CUSTOM_CLIENT;
83 } else {
84 return client.id;
85 }
86 },
87 getWindowIdentifier: function(client, type, name) {
88 if(type == qwebirc.ui.WINDOW_MESSAGES)
89 return "-M";
90 if(type == qwebirc.ui.WINDOW_STATUS)
91 return "";
92
93 if(client == qwebirc.ui.CUSTOM_CLIENT) /* HACK */
94 return "_" + name;
95
96 return "_" + client.toIRCLower(name);
97 },
98 newWindow: function(client, type, name) {
99 var w = this.getWindow(client, type, name);
100 if($defined(w))
101 return w;
102
103 var wId = this.getWindowIdentifier(client, type, name);
104 var w = new this.windowClass(this, client, type, name, wId);
105 this.windows.get(this.getClientId(client)).put(wId, w);
106 this.windowArray.push(w);
107
108 return w;
109 },
110 getWindow: function(client, type, name) {
111 var c = this.windows.get(this.getClientId(client));
112 if(!$defined(c))
113 return null;
114
115 return c.get(this.getWindowIdentifier(client, type, name));
116 },
117 getActiveWindow: function() {
118 return this.active;
119 },
120 getActiveIRCWindow: function(client) {
121 if(!this.active || this.active.type == qwebirc.ui.WINDOW_CUSTOM) {
122 return this.windows.get(this.getClientId(client)).get(this.getWindowIdentifier(client, qwebirc.ui.WINDOW_STATUS));
123 } else {
124 return this.active;
125 }
126 },
127 __setActiveWindow: function(window) {
128 this.active = window;
129 },
130 renameWindow: function(window, name) {
131 if(this.getWindow(window.client, window.type, name))
132 return null;
133
134 var clientId = this.getClientId(window.client);
135 var index = this.windowArray.indexOf(window);
136 if(index == -1)
137 return null;
138
139 this.windows.get(clientId).remove(window.identifier);
140
141 var window = this.windowArray[index];
142 window.name = name;
143 window.identifier = this.getWindowIdentifier(window.client, window.type, window.name);
144
145 this.windows.get(clientId).put(window.identifier, this.windowArray[index]);
146
147 if(window.active)
148 this.updateTitle(window.name + " - " + this.options.appTitle);
149
150 window.rename(window.name);
151 return window;
152 },
153 selectWindow: function(window) {
154 if(this.active)
155 this.active.deselect();
156 window.select(); /* calls setActiveWindow */
157 this.updateTitle(window.name + " - " + this.options.appTitle);
158 },
159 updateTitle: function(text) {
160 document.title = text;
161 },
162 nextWindow: function(direction) {
163 if(this.windowArray.length == 0 || !this.active)
164 return;
165
166 if(!direction)
167 direction = 1;
168
169 var index = this.windowArray.indexOf(this.active);
170 if(index == -1)
171 return;
172
173 index = index + direction;
174 if(index < 0) {
175 index = this.windowArray.length - 1;
176 } else if(index >= this.windowArray.length) {
177 index = 0;
178 }
179
180 this.selectWindow(this.windowArray[index]);
181 },
182 prevWindow: function() {
183 this.nextWindow(-1);
184 },
185 __closed: function(window) {
186 if(window.active) {
187 this.active = undefined;
188 if(this.windowArray.length == 1) {
189 this.windowArray = [];
190 } else {
191 var index = this.windowArray.indexOf(window);
192 if(index == -1) {
193 return;
194 } else if(index == 0) {
195 this.selectWindow(this.windowArray[1]);
196 } else {
197 this.selectWindow(this.windowArray[index - 1]);
198 }
199 }
200 }
201
202 this.windowArray = this.windowArray.erase(window);
203 this.windows.get(this.getClientId(window.client)).remove(window.identifier);
204 },
205 /*
206 this shouldn't be called by overriding classes!
207 they should implement their own!
208 some form of user input MUST be received before an
209 IRC connection is made, else users are going to get
210 tricked into getting themselves glined
211 */
212 loginBox: function(callback, initialNickname, initialChannels, autoConnect, autoNick) {
213 this.postInitialize();
214
215 this.addCustomWindow("Connect", qwebirc.ui.ConnectPane, "connectpane", {
216 initialNickname: initialNickname, initialChannels: initialChannels, autoConnect: autoConnect, callback: callback, autoNick: autoNick,
217 uiOptions: this.options
218 }, qwebirc.ui.WINDOW_CONNECT);
219 },
220 focusChange: function(newValue) {
221 var window_ = this.getActiveWindow();
222 if($defined(window_))
223 window_.focusChange(newValue);
224 },
225 oobMessage: function(message) {
226 var c = message.splitMax(" ", 2);
227 if(c.length != 2)
228 return;
229
230 var command = c[0];
231 if(command != "CMD")
232 return;
233
234 var d = c[1].splitMax(" ", 2);
235 if(d.length != 2)
236 return;
237
238 var command = d[0];
239 var args = d[1];
240 if(command == "SAY") {
241 var w = this.getActiveIRCWindow();
242 if($defined(w) && (w.type == qwebirc.ui.WINDOW_CHANNEL || w.type == qwebirc.ui.WINDOW_QUERY)) {
243 w.client.exec("/SAY " + args);
244 return;
245 }
246 }
247 }
248 });
249
250 qwebirc.ui.StandardUI = new Class({
251 Extends: qwebirc.ui.BaseUI,
252 initialize: function(parentElement, windowClass, uiName, options) {
253 this.parent(parentElement, windowClass, uiName, options);
254
255 this.UICommands = this.__build_menu_items(options);
256
257 this.__styleValues = {hue: qwebirc.ui.DEFAULT_HUE, saturation: 0, lightness: 0, textHue: null, textSaturation: null, textLightness: null};
258 if($defined(this.options.hue)) this.__styleValues.hue = this.options.hue;
259 this.tabCompleter = new qwebirc.ui.TabCompleterFactory(this);
260 this.uiOptions = new qwebirc.ui.DefaultOptionsClass(this, options.uiOptionsArg);
261 this.customWindows = new QHash();
262
263 if($defined(this.options.saturation)) this.__styleValues.saturation = this.options.saturation;
264 if($defined(this.options.lightness)) this.__styleValues.lightness = this.options.lightness;
265 if($defined(this.options.tsaturation)) this.__styleValues.textSaturation = this.options.tsaturation;
266 if($defined(this.options.tlightness)) this.__styleValues.textLightness = this.options.tlightness;
267
268 if($defined(this.options.hue)) { /* overridden in url */
269 /* ugh... this will go away when we add proper options for hue/sat/light for text and background */
270 this.uiOptions.setValueByPrefix("STYLE_HUE", this.__styleValues.hue);
271 } else {
272 this.__styleValues.hue = this.uiOptions.STYLE_HUE; /* otherwise copy from serialised store */
273 }
274 this.__styleValues.textHue = $defined(this.options.thue) ? this.options.thue : this.__styleValues.hue;
275
276 document.addEvent("keydown", this.__handleHotkey.bind(this));
277 },
278 __build_menu_items: function(options) {
279 var r = [];
280 var seenAbout = null;
281
282 for(var i=0;i<qwebirc.ui.UI_COMMANDS_P1.length;i++)
283 r.push([true, qwebirc.ui.UI_COMMANDS_P1[i]]);
284 for(var i=0;i<options.customMenuItems.length;i++)
285 r.push([false, options.customMenuItems[i]]);
286 for(var i=0;i<qwebirc.ui.UI_COMMANDS_P2.length;i++)
287 r.push([true, qwebirc.ui.UI_COMMANDS_P2[i]]);
288
289 var r2 = []
290 for(var i=0;i<r.length;i++) {
291 var preset = r[i][0], c = r[i][1];
292
293 if(c[0] == "About qwebirc") { /* HACK */
294 if(!preset) {
295 seenAbout = c;
296 continue;
297 } else if(seenAbout) {
298 c = seenAbout;
299 preset = false;
300 }
301 }
302
303 if(preset) {
304 r2.push([c[0], this[c[1] + "Window"].bind(this)]);
305 } else {
306 r2.push([c[0], (function(c) { return function() {
307 this.addCustomWindow(c[0], qwebirc.ui.URLPane, "urlpane", {url: c[1]});
308 }.bind(this); }).call(this, c)]);
309 }
310 }
311
312 return r2;
313 },
314 __handleHotkey: function(x) {
315 var success = false;
316 if(!x.alt && !x.control && !x.shift && !x.meta) {
317 if((x.key == "backspace" || x.key == "/") && !this.getInputFocused(x)) {
318 success = true;
319 }
320 } else if(!x.alt || x.control || x.meta) {
321 /* do nothing */
322 } else if(x.key == "a" || x.key == "A") {
323 var highestNum = 0;
324 var highestIndex = -1;
325 success = true;
326
327 for(var i=0;i<this.windowArray.length;i++) {
328 var h = this.windowArray[i].hilighted;
329 if(h > highestNum) {
330 highestIndex = i;
331 highestNum = h;
332 }
333 }
334 if(highestIndex > -1)
335 this.selectWindow(this.windowArray[highestIndex]);
336 } else if((x.key >= '0' && x.key <= '9') && !x.shift) {
337 success = true;
338
339 number = x.key - '0';
340 if(number == 0)
341 number = 10
342
343 number = number - 1;
344
345 if(number >= this.windowArray.length)
346 return;
347
348 this.selectWindow(this.windowArray[number]);
349 } else if((x.key == "left" || x.key == "up") && !x.shift) {
350 this.prevWindow();
351 success = true;
352 } else if((x.key == "right" || x.key == "down") && !x.shift) {
353 this.nextWindow();
354 success = true;
355 }
356
357 if(success) {
358 new Event(x).stop();
359 x.preventDefault();
360 }
361 },
362 getInputFocused: function(x) {
363 if($$("input").indexOf(x.target) == -1 && $$("textarea").indexOf(x.target) == -1)
364 return false;
365 return true;
366 },
367 newCustomWindow: function(name, select, type) {
368 if(!type)
369 type = qwebirc.ui.WINDOW_CUSTOM;
370
371 var w = this.newWindow(qwebirc.ui.CUSTOM_CLIENT, type, name);
372 w.addEvent("close", function(w) {
373 this.windows.get(qwebirc.ui.CUSTOM_CLIENT).remove(w.identifier);
374 }.bind(this));
375
376 if(select)
377 this.selectWindow(w);
378
379 return w;
380 },
381 addCustomWindow: function(windowName, class_, cssClass, options, type) {
382 if(!$defined(options))
383 options = {};
384
385 if(this.customWindows.contains(windowName)) {
386 this.selectWindow(this.customWindows.get(windowName));
387 return;
388 }
389
390 var d = this.newCustomWindow(windowName, true, type);
391 this.customWindows.put(windowName, d);
392
393 d.addEvent("close", function() {
394 this.customWindows.remove(windowName);
395 }.bind(this));
396
397 if(cssClass)
398 d.lines.addClass("qwebirc-" + cssClass);
399
400 var ew = new class_(d.lines, options);
401 ew.addEvent("close", function() {
402 d.close();
403 }.bind(this));
404
405 d.setSubWindow(ew);
406 },
407 embeddedWindow: function() {
408 this.addCustomWindow("Add webchat to your site", qwebirc.ui.EmbedWizard, "embeddedwizard", {baseURL: this.options.baseURL, uiOptions: this.uiOptions, optionsCallback: function() {
409 this.optionsWindow();
410 }.bind(this)});
411 },
412 optionsWindow: function() {
413 this.addCustomWindow("Options", qwebirc.ui.OptionsPane, "optionspane", this.uiOptions);
414 },
415 aboutWindow: function() {
416 this.addCustomWindow("About qwebirc", qwebirc.ui.AboutPane, "aboutpane", this.uiOptions);
417 },
418 feedbackWindow: function() {
419 this.addCustomWindow("Feedback", qwebirc.ui.FeedbackPane, "feedbackpane", this.uiOptions);
420 },
421 urlDispatcher: function(name, window) {
422 if(name == "embedded")
423 return ["a", this.embeddedWindow.bind(this)];
424
425 if(name == "options")
426 return ["a", this.optionsWindow.bind(this)];
427
428 /* doesn't really belong here */
429 if(name == "whois") {
430 return ["span", function(nick) {
431 if(this.uiOptions.QUERY_ON_NICK_CLICK) {
432 window.client.exec("/QUERY " + nick);
433 } else {
434 window.client.exec("/WHOIS " + nick);
435 }
436 }.bind(this)];
437 }
438
439 return null;
440 },
441 tabComplete: function(element, backwards) {
442 this.tabCompleter.tabComplete(element, backwards);
443 },
444 resetTabComplete: function() {
445 this.tabCompleter.reset();
446 },
447 setModifiableStylesheet: function(name) {
448 this.__styleSheet = new qwebirc.ui.style.ModifiableStylesheet(qwebirc.global.staticBaseURL + "css/" + (QWEBIRC_DEBUG ? "debug/" : "") + name + qwebirc.FILE_SUFFIX + ".mcss");
449 this.setModifiableStylesheetValues({});
450 },
451 setModifiableStylesheetValues: function(values) {
452 for (var k in values)
453 this.__styleValues[k] = values[k];
454
455 if (!$defined(this.__styleSheet))
456 return;
457
458 var back = {hue: this.__styleValues.hue, lightness: this.__styleValues.lightness, saturation: this.__styleValues.saturation};
459 var front;
460 if (!$defined(this.__styleValues.textHue) && !$defined(this.__styleValues.textLightness) && !$defined(this.__styleValues.textSaturation)) {
461 front = back;
462 } else {
463 front = {hue: Number(this.__styleValues.textHue), lightness: Number(this.__styleValues.textLightness), saturation: Number(this.__styleValues.textSaturation)}
464 }
465 var colours = {
466 back: back,
467 front: front
468 };
469
470 this.__styleSheet.set(function() {
471 var mode = arguments[0];
472 if(mode == "c") {
473 var t = colours[arguments[2]];
474 var x = new Color(arguments[1]);
475 var c = x.setHue(t.hue).setSaturation(x.hsb[1] + t.saturation).setBrightness(x.hsb[2] + t.lightness);
476 if(c == "255,255,255") /* IE confuses white with transparent... */
477 c = "255,255,254";
478
479 return "rgb(" + c + ")";
480 } else if(mode == "o") {
481 return this.uiOptions[arguments[1]] ? arguments[2] : arguments[3];
482 }
483 }.bind(this));
484 }
485 });
486
487 qwebirc.ui.NotificationUI = new Class({
488 Extends: qwebirc.ui.StandardUI,
489 initialize: function(parentElement, windowClass, uiName, options) {
490 this.parent(parentElement, windowClass, uiName, options);
491
492 this.__beeper = new qwebirc.ui.Beeper(this.uiOptions);
493 this.__flasher = new qwebirc.ui.Flasher(this.uiOptions);
494 this.__notifier = new qwebirc.ui.Notifier(this.uiOptions);
495
496 this.cancelFlash = this.__flasher.cancelFlash.bind(this.__flasher);
497 },
498 beep: function() {
499 this.__beeper.beep();
500 },
501 notify: function(title, message, callback) {
502 this.__beeper.beep();
503 this.__flasher.flash();
504 this.__notifier.notify(title, message, callback);
505 },
506 setBeepOnMention: function(value) {
507 if(value)
508 this.__beeper.soundInit();
509 },
510 setNotifications: function(value) {
511 this.__notifier.setEnabled(value);
512 },
513 updateTitle: function(text) {
514 if(this.__flasher.updateTitle(text))
515 this.parent(text);
516 },
517 focusChange: function(value) {
518 this.parent(value);
519 this.__flasher.focusChange(value);
520 this.__notifier.focusChange(value);
521 }
522 });
523
524 qwebirc.ui.QuakeNetUI = new Class({
525 Extends: qwebirc.ui.NotificationUI,
526 urlDispatcher: function(name, window) {
527 if(name == "qwhois") {
528 return ["span", function(auth) {
529 if($defined(this.parentObject.options.accountWhoisCommand))
530 this.client.exec(this.parentObject.options.accountWhoisCommand + auth);
531 }.bind(window)];
532 }
533 return this.parent(name, window);
534 },
535 logout: function() {
536 if(!qwebirc.auth.loggedin())
537 return;
538 if(confirm("Log out?")) {
539 this.clients.each(function(k, v) {
540 v.quit("Logged out");
541 }, this);
542
543 /* HACK */
544 var foo = function() { document.location = qwebirc.global.dynamicBaseURL + "auth?logout=1"; };
545 foo.delay(500);
546 }
547 }
548 });
549
550 qwebirc.ui.RootUI = qwebirc.ui.QuakeNetUI;
551
552 qwebirc.ui.RequestTransformHTML = function(options) {
553 var HREF_ELEMENTS = {
554 "IMG": 1
555 };
556
557 var update = options.update;
558 var onSuccess = options.onSuccess;
559
560 var fixUp = function(node) {
561 if(node.nodeType != 1)
562 return;
563
564 var tagName = node.nodeName.toUpperCase();
565 if(HREF_ELEMENTS[tagName]) {
566 var attr = node.getAttribute("transform_attr");
567 var value = node.getAttribute("transform_value");
568 if($defined(attr) && $defined(value)) {
569 node.removeAttribute("transform_attr");
570 node.removeAttribute("transform_value");
571 node.setAttribute(attr, qwebirc.global.staticBaseURL + value);
572 }
573 }
574
575 for(var i=0;i<node.childNodes.length;i++)
576 fixUp(node.childNodes[i]);
577 };
578
579 delete options["update"];
580 options.onSuccess = function(tree, elements, html, js) {
581 var container = new Element("div");
582 container.set("html", html);
583 fixUp(container);
584 update.empty();
585
586 while(container.childNodes.length > 0) {
587 var x = container.firstChild;
588 container.removeChild(x);
589 update.appendChild(x);
590 }
591 onSuccess();
592 };
593
594 return new Request.HTML(options);
595 };
596