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