]> jfr.im git - irc/quakenet/qwebirc.git/blob - js/ui/baseui.js
make hue overridding more logical
[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 newClient: function(client) {
53 client.id = String(this.clientId++);
54 client.hilightController = new qwebirc.ui.HilightController(client);
55 client.addEvent("signedOn", function() {
56 this.fireEvent("signedOn", client);
57 }.bind(this));
58 this.windows.put(client.id, new QHash());
59 this.clients.put(client.id, client);
60 var w = this.newWindow(client, qwebirc.ui.WINDOW_STATUS, "Status");
61 this.selectWindow(w);
62 if(!this.firstClient) {
63 this.firstClient = true;
64 w.addLine("", "qwebirc v" + qwebirc.VERSION);
65 w.addLine("", "Copyright (C) 2008-2014 Chris Porter and the qwebirc project.");
66 w.addLine("", "http://www.qwebirc.org");
67 w.addLine("", "Licensed under the GNU General Public License, Version 2.");
68 }
69 return w;
70 },
71 getClientId: function(client) {
72 if(client == qwebirc.ui.CUSTOM_CLIENT) {
73 return qwebirc.ui.CUSTOM_CLIENT;
74 } else {
75 return client.id;
76 }
77 },
78 getWindowIdentifier: function(client, type, name) {
79 if(type == qwebirc.ui.WINDOW_MESSAGES)
80 return "-M";
81 if(type == qwebirc.ui.WINDOW_STATUS)
82 return "";
83
84 if(client == qwebirc.ui.CUSTOM_CLIENT) /* HACK */
85 return "_" + name;
86
87 return "_" + client.toIRCLower(name);
88 },
89 newWindow: function(client, type, name) {
90 var w = this.getWindow(client, type, name);
91 if($defined(w))
92 return w;
93
94 var wId = this.getWindowIdentifier(client, type, name);
95 var w = new this.windowClass(this, client, type, name, wId);
96 this.windows.get(this.getClientId(client)).put(wId, w);
97 this.windowArray.push(w);
98
99 return w;
100 },
101 getWindow: function(client, type, name) {
102 var c = this.windows.get(this.getClientId(client));
103 if(!$defined(c))
104 return null;
105
106 return c.get(this.getWindowIdentifier(client, type, name));
107 },
108 getActiveWindow: function() {
109 return this.active;
110 },
111 getActiveIRCWindow: function(client) {
112 if(!this.active || this.active.type == qwebirc.ui.WINDOW_CUSTOM) {
113 return this.windows.get(this.getClientId(client)).get(this.getWindowIdentifier(client, qwebirc.ui.WINDOW_STATUS));
114 } else {
115 return this.active;
116 }
117 },
118 __setActiveWindow: function(window) {
119 this.active = window;
120 },
121 renameWindow: function(window, name) {
122 if(this.getWindow(window.client, window.type, name))
123 return null;
124
125 var clientId = this.getClientId(window.client);
126 var index = this.windowArray.indexOf(window);
127 if(index == -1)
128 return null;
129
130 this.windows.get(clientId).remove(window.identifier);
131
132 var window = this.windowArray[index];
133 window.name = name;
134 window.identifier = this.getWindowIdentifier(window.client, window.type, window.name);
135
136 this.windows.get(clientId).put(window.identifier, this.windowArray[index]);
137
138 if(window.active)
139 this.updateTitle(window.name + " - " + this.options.appTitle);
140
141 window.rename(window.name);
142 return window;
143 },
144 selectWindow: function(window) {
145 if(this.active)
146 this.active.deselect();
147 window.select(); /* calls setActiveWindow */
148 this.updateTitle(window.name + " - " + this.options.appTitle);
149 },
150 updateTitle: function(text) {
151 document.title = text;
152 },
153 nextWindow: function(direction) {
154 if(this.windowArray.length == 0 || !this.active)
155 return;
156
157 if(!direction)
158 direction = 1;
159
160 var index = this.windowArray.indexOf(this.active);
161 if(index == -1)
162 return;
163
164 index = index + direction;
165 if(index < 0) {
166 index = this.windowArray.length - 1;
167 } else if(index >= this.windowArray.length) {
168 index = 0;
169 }
170
171 this.selectWindow(this.windowArray[index]);
172 },
173 prevWindow: function() {
174 this.nextWindow(-1);
175 },
176 __closed: function(window) {
177 if(window.active) {
178 this.active = undefined;
179 if(this.windowArray.length == 1) {
180 this.windowArray = [];
181 } else {
182 var index = this.windowArray.indexOf(window);
183 if(index == -1) {
184 return;
185 } else if(index == 0) {
186 this.selectWindow(this.windowArray[1]);
187 } else {
188 this.selectWindow(this.windowArray[index - 1]);
189 }
190 }
191 }
192
193 this.windowArray = this.windowArray.erase(window);
194 this.windows.get(this.getClientId(window.client)).remove(window.identifier);
195 },
196 /*
197 this shouldn't be called by overriding classes!
198 they should implement their own!
199 some form of user input MUST be received before an
200 IRC connection is made, else users are going to get
201 tricked into getting themselves glined
202 */
203 loginBox: function(callback, initialNickname, initialChannels, autoConnect, autoNick) {
204 qwebirc.ui.GenericLoginBox(this.parentElement, callback, initialNickname, initialChannels, autoConnect, autoNick, this.options.networkName);
205 },
206 focusChange: function(newValue) {
207 var window_ = this.getActiveWindow();
208 if($defined(window_))
209 window_.focusChange(newValue);
210 }
211 });
212
213 qwebirc.ui.StandardUI = new Class({
214 Extends: qwebirc.ui.BaseUI,
215 UICommands: qwebirc.ui.UI_COMMANDS,
216 initialize: function(parentElement, windowClass, uiName, options) {
217 this.parent(parentElement, windowClass, uiName, options);
218
219 this.__styleValues = {hue: qwebirc.ui.DEFAULT_HUE, saturation: 0, lightness: 0, textHue: null, textSaturation: null, textLightness: null};
220 if($defined(this.options.hue)) this.__styleValues.hue = this.options.hue;
221 this.tabCompleter = new qwebirc.ui.TabCompleterFactory(this);
222 this.uiOptions = new qwebirc.ui.DefaultOptionsClass(this, options.uiOptionsArg);
223 this.customWindows = new QHash();
224
225 if($defined(this.options.saturation)) this.__styleValues.saturation = this.options.saturation;
226 if($defined(this.options.lightness)) this.__styleValues.lightness = this.options.lightness;
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 if($defined(this.options.hue)) { /* overridden in url */
231 /* ugh... this will go away when we add proper options for hue/sat/light for text and background */
232 this.uiOptions.setValueByPrefix("STYLE_HUE", this.__styleValues.hue);
233 } else {
234 this.__styleValues.hue = this.uiOptions.STYLE_HUE; /* otherwise copy from serialised store */
235 }
236 this.__styleValues.textHue = $defined(this.options.thue) ? this.options.thue : this.__styleValues.hue;
237
238 document.addEvent("keydown", this.__handleHotkey.bind(this));
239 },
240 __handleHotkey: function(x) {
241 var success = false;
242 if(!x.alt || x.control) {
243 if((x.key == "backspace" || x.key == "/") && !this.getInputFocused(x)) {
244 success = true;
245 }
246 } else if(x.key == "a" || x.key == "A") {
247 var highestNum = 0;
248 var highestIndex = -1;
249 success = true;
250
251 for(var i=0;i<this.windowArray.length;i++) {
252 var h = this.windowArray[i].hilighted;
253 if(h > highestNum) {
254 highestIndex = i;
255 highestNum = h;
256 }
257 }
258 if(highestIndex > -1)
259 this.selectWindow(this.windowArray[highestIndex]);
260 } else if(x.key >= '0' && x.key <= '9') {
261 success = true;
262
263 number = x.key - '0';
264 if(number == 0)
265 number = 10
266
267 number = number - 1;
268
269 if(number >= this.windowArray.length)
270 return;
271
272 this.selectWindow(this.windowArray[number]);
273 } else if(x.key == "left") {
274 this.prevWindow();
275 success = true;
276 } else if(x.key == "right") {
277 this.nextWindow();
278 success = true;
279 }
280 if(success) {
281 new Event(x).stop();
282 x.preventDefault();
283 }
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;
389 if (!$defined(this.__styleValues.textHue) && !$defined(this.__styleValues.textLightness) && !$defined(this.__styleValues.textSaturation)) {
390 front = back;
391 } else {
392 front = {hue: Number(this.__styleValues.textHue), lightness: Number(this.__styleValues.textLightness), saturation: Number(this.__styleValues.textSaturation)}
393 }
394 var colours = {
395 back: back,
396 front: front
397 };
398
399 this.__styleSheet.set(function() {
400 var mode = arguments[0];
401 if(mode == "c") {
402 var t = colours[arguments[2]];
403 var x = new Color(arguments[1]);
404 var c = x.setHue(t.hue).setSaturation(x.hsb[1] + t.saturation).setBrightness(x.hsb[2] + t.lightness);
405 if(c == "255,255,255") /* IE confuses white with transparent... */
406 c = "255,255,254";
407
408 return "rgb(" + c + ")";
409 } else if(mode == "o") {
410 return this.uiOptions[arguments[1]] ? arguments[2] : arguments[3];
411 }
412 }.bind(this));
413 }
414 });
415
416 qwebirc.ui.NotificationUI = new Class({
417 Extends: qwebirc.ui.StandardUI,
418 initialize: function(parentElement, windowClass, uiName, options) {
419 this.parent(parentElement, windowClass, uiName, options);
420
421 this.__beeper = new qwebirc.ui.Beeper(this.uiOptions);
422 this.__flasher = new qwebirc.ui.Flasher(this.uiOptions);
423 this.__notifier = new qwebirc.ui.Notifier(this.uiOptions);
424
425 this.cancelFlash = this.__flasher.cancelFlash.bind(this.__flasher);
426 },
427 beep: function() {
428 this.__beeper.beep();
429 },
430 notify: function(title, message, callback) {
431 this.__beeper.beep();
432 this.__flasher.flash();
433 this.__notifier.notify(title, message, callback);
434 },
435 setBeepOnMention: function(value) {
436 if(value)
437 this.__beeper.soundInit();
438 },
439 setNotifications: function(value) {
440 this.__notifier.setEnabled(value);
441 },
442 updateTitle: function(text) {
443 if(this.__flasher.updateTitle(text))
444 this.parent(text);
445 },
446 focusChange: function(value) {
447 this.parent(value);
448 this.__flasher.focusChange(value);
449 this.__notifier.focusChange(value);
450 }
451 });
452
453 qwebirc.ui.NewLoginUI = new Class({
454 Extends: qwebirc.ui.NotificationUI,
455 loginBox: function(callbackfn, initialNickname, initialChannels, autoConnect, autoNick) {
456 this.postInitialize();
457
458 /* I'd prefer something shorter and snappier! */
459 var w = this.newCustomWindow("Connect", true, qwebirc.ui.WINDOW_CONNECT);
460 var callback = function(args) {
461 w.close();
462 callbackfn(args);
463 };
464
465 qwebirc.ui.GenericLoginBox(w.lines, callback, initialNickname, initialChannels, autoConnect, autoNick, this.options.networkName);
466 }
467 });
468
469 qwebirc.ui.QuakeNetUI = new Class({
470 Extends: qwebirc.ui.NewLoginUI,
471 urlDispatcher: function(name, window) {
472 if(name == "qwhois") {
473 return ["span", function(auth) {
474 this.client.exec("/MSG Q whois #" + auth);
475 }.bind(window)];
476 }
477 return this.parent(name, window);
478 },
479 logout: function() {
480 if(!qwebirc.auth.loggedin())
481 return;
482 if(confirm("Log out?")) {
483 this.clients.each(function(k, v) {
484 v.quit("Logged out");
485 }, this);
486
487 /* HACK */
488 var foo = function() { document.location = qwebirc.global.dynamicBaseURL + "auth?logout=1"; };
489 foo.delay(500);
490 }
491 }
492 });
493
494 qwebirc.ui.RootUI = qwebirc.ui.QuakeNetUI;
495
496 qwebirc.ui.RequestTransformHTML = function(options) {
497 var HREF_ELEMENTS = {
498 "IMG": 1
499 };
500
501 var update = options.update;
502 var onSuccess = options.onSuccess;
503
504 var fixUp = function(node) {
505 if(node.nodeType != 1)
506 return;
507
508 var tagName = node.nodeName.toUpperCase();
509 if(HREF_ELEMENTS[tagName]) {
510 var attr = node.getAttribute("transform_attr");
511 var value = node.getAttribute("transform_value");
512 if($defined(attr) && $defined(value)) {
513 node.removeAttribute("transform_attr");
514 node.removeAttribute("transform_value");
515 node.setAttribute(attr, qwebirc.global.staticBaseURL + value);
516 }
517 }
518
519 for(var i=0;i<node.childNodes.length;i++)
520 fixUp(node.childNodes[i]);
521 };
522
523 delete options["update"];
524 options.onSuccess = function(tree, elements, html, js) {
525 var container = new Element("div");
526 container.set("html", html);
527 fixUp(container);
528 update.empty();
529
530 while(container.childNodes.length > 0) {
531 var x = container.firstChild;
532 container.removeChild(x);
533 update.appendChild(x);
534 }
535 onSuccess();
536 };
537
538 return new Request.HTML(options);
539 };
540