]> jfr.im git - irc/quakenet/qwebirc.git/blob - js/ui/baseui.js
another silly QHash bug
[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.get(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, textHue: this.uiOptions.STYLE_HUE, textSaturation: 0, textLightness: 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 if($defined(this.options.thue)) this.__styleValues.textHue = this.options.thue;
227 if($defined(this.options.tsaturation)) this.__styleValues.textSaturation = this.options.tsaturation;
228 if($defined(this.options.tlightness)) this.__styleValues.textLightness = this.options.tlightness;
229
230 document.addEvent("keydown", this.__handleHotkey.bind(this));
231 },
232 __handleHotkey: function(x) {
233 var success = false;
234 if(!x.alt || x.control) {
235 if((x.key == "backspace" || x.key == "/") && !this.getInputFocused(x)) {
236 success = true;
237 }
238 } else if(x.key == "a" || x.key == "A") {
239 var highestNum = 0;
240 var highestIndex = -1;
241 success = true;
242
243 for(var i=0;i<this.windowArray.length;i++) {
244 var h = this.windowArray[i].hilighted;
245 if(h > highestNum) {
246 highestIndex = i;
247 highestNum = h;
248 }
249 }
250 if(highestIndex > -1)
251 this.selectWindow(this.windowArray[highestIndex]);
252 } else if(x.key >= '0' && x.key <= '9') {
253 success = true;
254
255 number = x.key - '0';
256 if(number == 0)
257 number = 10
258
259 number = number - 1;
260
261 if(number >= this.windowArray.length)
262 return;
263
264 this.selectWindow(this.windowArray[number]);
265 } else if(x.key == "left") {
266 this.prevWindow();
267 success = true;
268 } else if(x.key == "right") {
269 this.nextWindow();
270 success = true;
271 }
272 if(success) {
273 new Event(x).stop();
274 x.preventDefault();
275 }
276 },
277 getInputFocused: function(x) {
278 if($$("input").indexOf(x.target) == -1 && $$("textarea").indexOf(x.target) == -1)
279 return false;
280 return true;
281 },
282 newCustomWindow: function(name, select, type) {
283 if(!type)
284 type = qwebirc.ui.WINDOW_CUSTOM;
285
286 var w = this.newWindow(qwebirc.ui.CUSTOM_CLIENT, type, name);
287 w.addEvent("close", function(w) {
288 this.windows.get(qwebirc.ui.CUSTOM_CLIENT).remove(w.identifier);
289 }.bind(this));
290
291 if(select)
292 this.selectWindow(w);
293
294 return w;
295 },
296 addCustomWindow: function(windowName, class_, cssClass, options) {
297 if(!$defined(options))
298 options = {};
299
300 if(this.customWindows.contains(windowName)) {
301 this.selectWindow(this.customWindows.get(windowName));
302 return;
303 }
304
305 var d = this.newCustomWindow(windowName, true);
306 this.customWindows.put(windowName, d);
307
308 d.addEvent("close", function() {
309 this.customWindows.remove(windowName);
310 }.bind(this));
311
312 if(cssClass)
313 d.lines.addClass("qwebirc-" + cssClass);
314
315 var ew = new class_(d.lines, options);
316 ew.addEvent("close", function() {
317 d.close();
318 }.bind(this));
319
320 d.setSubWindow(ew);
321 },
322 embeddedWindow: function() {
323 this.addCustomWindow("Add webchat to your site", qwebirc.ui.EmbedWizard, "embeddedwizard", {baseURL: this.options.baseURL, uiOptions: this.uiOptions, optionsCallback: function() {
324 this.optionsWindow();
325 }.bind(this)});
326 },
327 optionsWindow: function() {
328 this.addCustomWindow("Options", qwebirc.ui.OptionsPane, "optionspane", this.uiOptions);
329 },
330 aboutWindow: function() {
331 this.addCustomWindow("About", qwebirc.ui.AboutPane, "aboutpane", this.uiOptions);
332 },
333 privacyWindow: function() {
334 this.addCustomWindow("Privacy policy", qwebirc.ui.PrivacyPolicyPane, "privacypolicypane", this.uiOptions);
335 },
336 feedbackWindow: function() {
337 this.addCustomWindow("Feedback", qwebirc.ui.FeedbackPane, "feedbackpane", this.uiOptions);
338 },
339 helpWindow: function() {
340 this.addCustomWindow("Help!", qwebirc.ui.HelpPane, "helppane", this.uiOptions);
341 },
342 urlDispatcher: function(name, window) {
343 if(name == "embedded")
344 return ["a", this.embeddedWindow.bind(this)];
345
346 if(name == "options")
347 return ["a", this.optionsWindow.bind(this)];
348
349 /* doesn't really belong here */
350 if(name == "whois") {
351 return ["span", function(nick) {
352 if(this.uiOptions.QUERY_ON_NICK_CLICK) {
353 window.client.exec("/QUERY " + nick);
354 } else {
355 window.client.exec("/WHOIS " + nick);
356 }
357 }.bind(this)];
358 }
359
360 return null;
361 },
362 tabComplete: function(element) {
363 this.tabCompleter.tabComplete(element);
364 },
365 resetTabComplete: function() {
366 this.tabCompleter.reset();
367 },
368 setModifiableStylesheet: function(name) {
369 this.__styleSheet = new qwebirc.ui.style.ModifiableStylesheet(qwebirc.global.staticBaseURL + "css/" + name + qwebirc.FILE_SUFFIX + ".mcss");
370 this.setModifiableStylesheetValues({});
371 },
372 setModifiableStylesheetValues: function(values) {
373 for(var k in values)
374 this.__styleValues[k] = values[k];
375
376 if(!$defined(this.__styleSheet))
377 return;
378
379 var back = {hue: this.__styleValues.hue, lightness: this.__styleValues.lightness, saturation: this.__styleValues.saturation};
380 var front = {hue: this.__styleValues.textHue, lightness: this.__styleValues.textLightness, saturation: this.__styleValues.textSaturation};
381
382 if(!this.__styleValues.textHue && !this.__styleValues.textLightness && !this.__styleValues.textSaturation)
383 front = back;
384
385 var colours = {
386 back: back,
387 front: front
388 };
389
390 this.__styleSheet.set(function() {
391 var mode = arguments[0];
392 if(mode == "c") {
393 var t = colours[arguments[2]];
394 var x = new Color(arguments[1]);
395 var c = x.setHue(t.hue).setSaturation(x.hsb[1] + t.saturation).setBrightness(x.hsb[2] + t.lightness);
396 if(c == "255,255,255") /* IE confuses white with transparent... */
397 c = "255,255,254";
398
399 return "rgb(" + c + ")";
400 } else if(mode == "o") {
401 return this.uiOptions[arguments[1]] ? arguments[2] : arguments[3];
402 }
403 }.bind(this));
404 }
405 });
406
407 qwebirc.ui.NotificationUI = new Class({
408 Extends: qwebirc.ui.StandardUI,
409 initialize: function(parentElement, windowClass, uiName, options) {
410 this.parent(parentElement, windowClass, uiName, options);
411
412 this.__beeper = new qwebirc.ui.Beeper(this.uiOptions);
413 this.__flasher = new qwebirc.ui.Flasher(this.uiOptions);
414 this.__notifier = new qwebirc.ui.Notifier(this.uiOptions);
415
416 this.cancelFlash = this.__flasher.cancelFlash.bind(this.__flasher);
417 },
418 beep: function() {
419 this.__beeper.beep();
420 },
421 notify: function(title, message, callback) {
422 this.__beeper.beep();
423 this.__flasher.flash();
424 this.__notifier.notify(title, message, callback);
425 },
426 setBeepOnMention: function(value) {
427 if(value)
428 this.__beeper.soundInit();
429 },
430 setNotifications: function(value) {
431 this.__notifier.setEnabled(value);
432 },
433 updateTitle: function(text) {
434 if(this.__flasher.updateTitle(text))
435 this.parent(text);
436 },
437 focusChange: function(value) {
438 this.parent(value);
439 this.__flasher.focusChange(value);
440 this.__notifier.focusChange(value);
441 }
442 });
443
444 qwebirc.ui.NewLoginUI = new Class({
445 Extends: qwebirc.ui.NotificationUI,
446 loginBox: function(callbackfn, initialNickname, initialChannels, autoConnect, autoNick) {
447 this.postInitialize();
448
449 /* I'd prefer something shorter and snappier! */
450 var w = this.newCustomWindow("Connect", true, qwebirc.ui.WINDOW_CONNECT);
451 var callback = function(args) {
452 w.close();
453 callbackfn(args);
454 };
455
456 qwebirc.ui.GenericLoginBox(w.lines, callback, initialNickname, initialChannels, autoConnect, autoNick, this.options.networkName);
457 }
458 });
459
460 qwebirc.ui.QuakeNetUI = new Class({
461 Extends: qwebirc.ui.NewLoginUI,
462 urlDispatcher: function(name, window) {
463 if(name == "qwhois") {
464 return ["span", function(auth) {
465 this.client.exec("/MSG Q whois #" + auth);
466 }.bind(window)];
467 }
468 return this.parent(name, window);
469 },
470 logout: function() {
471 if(!qwebirc.auth.loggedin())
472 return;
473 if(confirm("Log out?")) {
474 this.clients.each(function(k, v) {
475 v.quit("Logged out");
476 }, this);
477
478 /* HACK */
479 var foo = function() { document.location = qwebirc.global.dynamicBaseURL + "auth?logout=1"; };
480 foo.delay(500);
481 }
482 }
483 });
484
485 qwebirc.ui.RootUI = qwebirc.ui.QuakeNetUI;
486
487 qwebirc.ui.RequestTransformHTML = function(options) {
488 var HREF_ELEMENTS = {
489 "IMG": 1
490 };
491
492 var update = options.update;
493 var onSuccess = options.onSuccess;
494
495 var fixUp = function(node) {
496 if(node.nodeType != 1)
497 return;
498
499 var tagName = node.nodeName.toUpperCase();
500 if(HREF_ELEMENTS[tagName]) {
501 var attr = node.getAttribute("transform_attr");
502 var value = node.getAttribute("transform_value");
503 if($defined(attr) && $defined(value)) {
504 node.removeAttribute("transform_attr");
505 node.removeAttribute("transform_value");
506 node.setAttribute(attr, qwebirc.global.staticBaseURL + value);
507 }
508 }
509
510 for(var i=0;i<node.childNodes.length;i++)
511 fixUp(node.childNodes[i]);
512 };
513
514 delete options["update"];
515 options.onSuccess = function(tree, elements, html, js) {
516 var container = new Element("div");
517 container.set("html", html);
518 fixUp(container);
519 update.empty();
520
521 while(container.childNodes.length > 0) {
522 var x = container.firstChild;
523 container.removeChild(x);
524 update.appendChild(x);
525 }
526 onSuccess();
527 };
528
529 return new Request.HTML(options);
530 };
531