]> jfr.im git - irc/quakenet/qwebirc.git/blob - js/ui/baseui.js
bump to 1.02
[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-2019 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 getStatusWindow: function(client) {
121 return this.windows.get(this.getClientId(client)).get(this.getWindowIdentifier(client, qwebirc.ui.WINDOW_STATUS));
122 },
123 getActiveIRCWindow: function(client) {
124 if(!this.active || this.active.type == qwebirc.ui.WINDOW_CUSTOM) {
125 return this.getStatusWindow(client);
126 } else {
127 return this.active;
128 }
129 },
130 __setActiveWindow: function(window) {
131 this.active = window;
132 },
133 renameWindow: function(window, name) {
134 if(this.getWindow(window.client, window.type, name))
135 return null;
136
137 var clientId = this.getClientId(window.client);
138 var index = this.windowArray.indexOf(window);
139 if(index == -1)
140 return null;
141
142 this.windows.get(clientId).remove(window.identifier);
143
144 var window = this.windowArray[index];
145 window.name = name;
146 window.identifier = this.getWindowIdentifier(window.client, window.type, window.name);
147
148 this.windows.get(clientId).put(window.identifier, this.windowArray[index]);
149
150 if(window.active)
151 this.updateTitle(window.name + " - " + this.options.appTitle);
152
153 window.rename(window.name);
154 return window;
155 },
156 selectWindow: function(window) {
157 if(this.active)
158 this.active.deselect();
159 window.select(); /* calls setActiveWindow */
160 this.updateTitle(window.name + " - " + this.options.appTitle);
161 },
162 updateTitle: function(text) {
163 document.title = text;
164 },
165 nextWindow: function(direction) {
166 if(this.windowArray.length == 0 || !this.active)
167 return;
168
169 if(!direction)
170 direction = 1;
171
172 var index = this.windowArray.indexOf(this.active);
173 if(index == -1)
174 return;
175
176 index = index + direction;
177 if(index < 0) {
178 index = this.windowArray.length - 1;
179 } else if(index >= this.windowArray.length) {
180 index = 0;
181 }
182
183 this.selectWindow(this.windowArray[index]);
184 },
185 prevWindow: function() {
186 this.nextWindow(-1);
187 },
188 __closed: function(window) {
189 if(window.active) {
190 this.active = undefined;
191 if(this.windowArray.length == 1) {
192 this.windowArray = [];
193 } else {
194 var index = this.windowArray.indexOf(window);
195 if(index == -1) {
196 return;
197 } else if(index == 0) {
198 this.selectWindow(this.windowArray[1]);
199 } else {
200 this.selectWindow(this.windowArray[index - 1]);
201 }
202 }
203 }
204
205 this.windowArray = this.windowArray.erase(window);
206 this.windows.get(this.getClientId(window.client)).remove(window.identifier);
207 },
208 /*
209 this shouldn't be called by overriding classes!
210 they should implement their own!
211 some form of user input MUST be received before an
212 IRC connection is made, else users are going to get
213 tricked into getting themselves glined
214 */
215 loginBox: function(callback, initialNickname, initialChannels, autoConnect, autoNick) {
216 this.postInitialize();
217
218 this.addCustomWindow("Connect", qwebirc.ui.ConnectPane, "connectpane", {
219 initialNickname: initialNickname, initialChannels: initialChannels, autoConnect: autoConnect, callback: callback, autoNick: autoNick,
220 uiOptions: this.options
221 }, qwebirc.ui.WINDOW_CONNECT);
222 },
223 focusChange: function(newValue) {
224 var window_ = this.getActiveWindow();
225 if($defined(window_))
226 window_.focusChange(newValue);
227 },
228 oobMessage: function(message) {
229 var c = message.splitMax(" ", 2);
230 if(c.length != 2)
231 return;
232
233 var command = c[0];
234 if(command != "CMD")
235 return;
236
237 var d = c[1].splitMax(" ", 2);
238 if(d.length != 2)
239 return;
240
241 var command = d[0];
242 var args = d[1];
243 if(command == "SAY") {
244 var w = this.getActiveIRCWindow();
245 if($defined(w) && (w.type == qwebirc.ui.WINDOW_CHANNEL || w.type == qwebirc.ui.WINDOW_QUERY)) {
246 w.client.exec("/SAY " + args);
247 return;
248 }
249 }
250 }
251 });
252
253 qwebirc.ui.StandardUI = new Class({
254 Extends: qwebirc.ui.BaseUI,
255 initialize: function(parentElement, windowClass, uiName, options) {
256 this.parent(parentElement, windowClass, uiName, options);
257
258 this.UICommands = this.__build_menu_items(options);
259
260 this.__styleValues = {hue: qwebirc.ui.DEFAULT_HUE, saturation: 0, lightness: 0, textHue: null, textSaturation: null, textLightness: null};
261 if($defined(this.options.hue)) this.__styleValues.hue = this.options.hue;
262 this.tabCompleter = new qwebirc.ui.TabCompleterFactory(this);
263 this.uiOptions = new qwebirc.ui.DefaultOptionsClass(this, options.uiOptionsArg);
264 this.customWindows = new QHash();
265
266 if($defined(this.options.saturation)) this.__styleValues.saturation = this.options.saturation;
267 if($defined(this.options.lightness)) this.__styleValues.lightness = this.options.lightness;
268 if($defined(this.options.tsaturation)) this.__styleValues.textSaturation = this.options.tsaturation;
269 if($defined(this.options.tlightness)) this.__styleValues.textLightness = this.options.tlightness;
270
271 if($defined(this.options.hue)) { /* overridden in url */
272 /* ugh... this will go away when we add proper options for hue/sat/light for text and background */
273 this.uiOptions.setValueByPrefix("STYLE_HUE", this.__styleValues.hue);
274 } else {
275 this.__styleValues.hue = this.uiOptions.STYLE_HUE; /* otherwise copy from serialised store */
276 }
277 this.__styleValues.textHue = $defined(this.options.thue) ? this.options.thue : this.__styleValues.hue;
278
279 document.addEvent("keydown", this.__handleHotkey.bind(this));
280 },
281 __build_menu_items: function(options) {
282 var r = [];
283 var seenAbout = null;
284
285 for(var i=0;i<qwebirc.ui.UI_COMMANDS_P1.length;i++)
286 r.push([true, qwebirc.ui.UI_COMMANDS_P1[i]]);
287 for(var i=0;i<options.customMenuItems.length;i++)
288 r.push([false, options.customMenuItems[i]]);
289 for(var i=0;i<qwebirc.ui.UI_COMMANDS_P2.length;i++)
290 r.push([true, qwebirc.ui.UI_COMMANDS_P2[i]]);
291
292 var r2 = []
293 for(var i=0;i<r.length;i++) {
294 var preset = r[i][0], c = r[i][1];
295
296 if(c[0] == "About qwebirc") { /* HACK */
297 if(!preset) {
298 seenAbout = c;
299 continue;
300 } else if(seenAbout) {
301 c = seenAbout;
302 preset = false;
303 }
304 }
305
306 if(preset) {
307 r2.push([c[0], this[c[1] + "Window"].bind(this)]);
308 } else {
309 r2.push([c[0], (function(c) { return function() {
310 this.addCustomWindow(c[0], qwebirc.ui.URLPane, "urlpane", {url: c[1]});
311 }.bind(this); }).call(this, c)]);
312 }
313 }
314
315 return r2;
316 },
317 __handleHotkey: function(x) {
318 var success = false;
319 if(!x.alt && !x.control && !x.shift && !x.meta) {
320 if((x.key == "backspace" || x.key == "/") && !this.getInputFocused(x)) {
321 success = true;
322 }
323 } else if(!x.alt || x.control || x.meta) {
324 /* do nothing */
325 } else if(x.key == "a" || x.key == "A") {
326 var highestNum = 0;
327 var highestIndex = -1;
328 success = true;
329
330 for(var i=0;i<this.windowArray.length;i++) {
331 var h = this.windowArray[i].hilighted;
332 if(h > highestNum) {
333 highestIndex = i;
334 highestNum = h;
335 }
336 }
337 if(highestIndex > -1)
338 this.selectWindow(this.windowArray[highestIndex]);
339 } else if((x.key >= '0' && x.key <= '9') && !x.shift) {
340 success = true;
341
342 number = x.key - '0';
343 if(number == 0)
344 number = 10
345
346 number = number - 1;
347
348 if(number >= this.windowArray.length)
349 return;
350
351 this.selectWindow(this.windowArray[number]);
352 } else if((x.key == "left" || x.key == "up") && !x.shift) {
353 this.prevWindow();
354 success = true;
355 } else if((x.key == "right" || x.key == "down") && !x.shift) {
356 this.nextWindow();
357 success = true;
358 }
359
360 if(success) {
361 new Event(x).stop();
362 x.preventDefault();
363 }
364 },
365 getInputFocused: function(x) {
366 if($$("input").indexOf(x.target) == -1 && $$("textarea").indexOf(x.target) == -1)
367 return false;
368 return true;
369 },
370 newCustomWindow: function(name, select, type) {
371 if(!type)
372 type = qwebirc.ui.WINDOW_CUSTOM;
373
374 var w = this.newWindow(qwebirc.ui.CUSTOM_CLIENT, type, name);
375 w.addEvent("close", function(w) {
376 this.windows.get(qwebirc.ui.CUSTOM_CLIENT).remove(w.identifier);
377 }.bind(this));
378
379 if(select)
380 this.selectWindow(w);
381
382 return w;
383 },
384 addCustomWindow: function(windowName, class_, cssClass, options, type) {
385 if(!$defined(options))
386 options = {};
387
388 if(this.customWindows.contains(windowName)) {
389 this.selectWindow(this.customWindows.get(windowName));
390 return;
391 }
392
393 var d = this.newCustomWindow(windowName, true, type);
394 this.customWindows.put(windowName, d);
395
396 d.addEvent("close", function() {
397 this.customWindows.remove(windowName);
398 }.bind(this));
399
400 if(cssClass)
401 d.lines.addClass("qwebirc-" + cssClass);
402
403 var ew = new class_(d.lines, options);
404 ew.addEvent("close", function() {
405 d.close();
406 }.bind(this));
407
408 d.setSubWindow(ew);
409 },
410 embeddedWindow: function() {
411 this.addCustomWindow("Add webchat to your site", qwebirc.ui.EmbedWizard, "embeddedwizard", {baseURL: this.options.baseURL, uiOptions: this.uiOptions, optionsCallback: function() {
412 this.optionsWindow();
413 }.bind(this)});
414 },
415 optionsWindow: function() {
416 this.addCustomWindow("Options", qwebirc.ui.OptionsPane, "optionspane", this.uiOptions);
417 },
418 aboutWindow: function() {
419 this.addCustomWindow("About qwebirc", qwebirc.ui.AboutPane, "aboutpane", 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(notification) {
499 this.__beeper.beep(notification);
500 },
501 notify: function(title, message, callback) {
502 this.__beeper.beep(true);
503 this.__flasher.flash();
504 this.__notifier.notify(title, message, callback);
505 },
506 setNotifications: function(value) {
507 this.__notifier.setEnabled(value);
508 },
509 updateTitle: function(text) {
510 if(this.__flasher.updateTitle(text))
511 this.parent(text);
512 },
513 focusChange: function(value) {
514 this.parent(value);
515 this.__flasher.focusChange(value);
516 this.__notifier.focusChange(value);
517 }
518 });
519
520 qwebirc.ui.QuakeNetUI = new Class({
521 Extends: qwebirc.ui.NotificationUI,
522 urlDispatcher: function(name, window) {
523 if(name == "qwhois") {
524 return ["span", function(auth) {
525 if($defined(this.parentObject.options.accountWhoisCommand))
526 this.client.exec(this.parentObject.options.accountWhoisCommand + auth);
527 }.bind(window)];
528 }
529 return this.parent(name, window);
530 },
531 logout: function() {
532 if(!qwebirc.auth.loggedin(true)) {
533 this.getActiveWindow().errorMessage("Not logged in!");
534 return;
535 }
536 if(confirm("Log out?")) {
537 this.clients.each(function(k, v) {
538 v.quit("Logged out");
539 }, this);
540
541 /* HACK */
542 var foo = function() { document.location = "/auth?logout=1"; };
543 foo.delay(500);
544 }
545 }
546 });
547
548 qwebirc.ui.RootUI = qwebirc.ui.QuakeNetUI;
549
550 qwebirc.ui.RequestTransformHTML = function(options) {
551 var HREF_ELEMENTS = {
552 "IMG": 1
553 };
554
555 var update = options.update;
556 var onSuccess = options.onSuccess;
557
558 var fixUp = function(node) {
559 if(node.nodeType != 1)
560 return;
561
562 var tagName = node.nodeName.toUpperCase();
563 if(HREF_ELEMENTS[tagName]) {
564 var attr = node.getAttribute("transform_attr");
565 var value = node.getAttribute("transform_value");
566 if($defined(attr) && $defined(value)) {
567 node.removeAttribute("transform_attr");
568 node.removeAttribute("transform_value");
569 node.setAttribute(attr, qwebirc.global.staticBaseURL + value);
570 }
571 }
572
573 for(var i=0;i<node.childNodes.length;i++)
574 fixUp(node.childNodes[i]);
575 };
576
577 delete options["update"];
578 options.onSuccess = function(tree, elements, html, js) {
579 var container = new Element("div");
580 container.set("html", html);
581 fixUp(container);
582 update.empty();
583
584 while(container.childNodes.length > 0) {
585 var x = container.firstChild;
586 container.removeChild(x);
587 update.appendChild(x);
588 }
589 onSuccess();
590 };
591
592 return new Request.HTML(options);
593 };
594