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