]> jfr.im git - irc/quakenet/qwebirc.git/blame - js/ui/baseui.js
add shift+tab to go back through tab completion
[irc/quakenet/qwebirc.git] / js / ui / baseui.js
CommitLineData
3e66d49c
CP
1qwebirc.ui.WINDOW_STATUS = 0x01;
2qwebirc.ui.WINDOW_QUERY = 0x02;
3qwebirc.ui.WINDOW_CHANNEL = 0x04;
4qwebirc.ui.WINDOW_CUSTOM = 0x08;
5qwebirc.ui.WINDOW_CONNECT = 0x10;
8c3e644b 6qwebirc.ui.WINDOW_MESSAGES = 0x20;
3e66d49c 7
e20e5a6b 8qwebirc.ui.CUSTOM_CLIENT = "custom";
ffb5ea9a 9qwebirc.ui.DEFAULT_HUE = 210; /* nice blue */
9e769c12 10
e20e5a6b 11qwebirc.ui.BaseUI = new Class({
2cad083e 12 Implements: [Events],
a59dc700 13 initialize: function(parentElement, windowClass, uiName, options) {
2cad083e 14 this.options = options;
a59dc700 15
ea29e3d7
CP
16 this.windows = new QHash();
17 this.clients = new QHash();
18 this.windows.put(qwebirc.ui.CUSTOM_CLIENT, new QHash());
9e769c12
CP
19 this.windowArray = [];
20 this.windowClass = windowClass;
21 this.parentElement = parentElement;
22 this.parentElement.addClass("qwebirc");
23 this.parentElement.addClass("qwebirc-" + uiName);
e8db8558 24 this.firstClient = false;
e20e5a6b 25 this.commandhistory = new qwebirc.irc.CommandHistory();
ffbb638d 26 this.clientId = 0;
eb271d86
CP
27
28 this.windowFocused = true;
85449eee
CP
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 }
9e769c12
CP
51 },
52 newClient: function(client) {
ea29e3d7 53 client.id = String(this.clientId++);
96f28062 54 client.hilightController = new qwebirc.ui.HilightController(client);
fc38a626
CP
55 client.addEvent("signedOn", function() {
56 this.fireEvent("signedOn", client);
57 }.bind(this));
ea29e3d7
CP
58 this.windows.put(client.id, new QHash());
59 this.clients.put(client.id, client);
e20e5a6b 60 var w = this.newWindow(client, qwebirc.ui.WINDOW_STATUS, "Status");
9e769c12 61 this.selectWindow(w);
e8db8558
CP
62 if(!this.firstClient) {
63 this.firstClient = true;
e20e5a6b 64 w.addLine("", "qwebirc v" + qwebirc.VERSION);
13828a8d 65 w.addLine("", "Copyright (C) 2008-2014 Chris Porter and the qwebirc project.");
2dfab0e1
CP
66 w.addLine("", "http://www.qwebirc.org");
67 w.addLine("", "Licensed under the GNU General Public License, Version 2.");
e8db8558 68 }
9e769c12
CP
69 return w;
70 },
ffbb638d
CP
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 },
fd3734d4 78 getWindowIdentifier: function(client, type, name) {
f74802c5
CP
79 if(type == qwebirc.ui.WINDOW_MESSAGES)
80 return "-M";
e20e5a6b 81 if(type == qwebirc.ui.WINDOW_STATUS)
f74802c5 82 return "";
fd3734d4
CP
83
84 if(client == qwebirc.ui.CUSTOM_CLIENT) /* HACK */
85 return "_" + name;
86
87 return "_" + client.toIRCLower(name);
f74802c5
CP
88 },
89 newWindow: function(client, type, name) {
90 var w = this.getWindow(client, type, name);
91 if($defined(w))
92 return w;
9e769c12 93
fd3734d4 94 var wId = this.getWindowIdentifier(client, type, name);
ea29e3d7
CP
95 var w = new this.windowClass(this, client, type, name, wId);
96 this.windows.get(this.getClientId(client)).put(wId, w);
9e769c12
CP
97 this.windowArray.push(w);
98
99 return w;
100 },
f74802c5 101 getWindow: function(client, type, name) {
ea29e3d7 102 var c = this.windows.get(this.getClientId(client));
f74802c5
CP
103 if(!$defined(c))
104 return null;
105
69fbfa6d 106 return c.get(this.getWindowIdentifier(client, type, name));
f74802c5 107 },
9e769c12
CP
108 getActiveWindow: function() {
109 return this.active;
110 },
1d42a76f
CP
111 getActiveIRCWindow: function(client) {
112 if(!this.active || this.active.type == qwebirc.ui.WINDOW_CUSTOM) {
ea29e3d7 113 return this.windows.get(this.getClientId(client)).get(this.getWindowIdentifier(client, qwebirc.ui.WINDOW_STATUS));
1d42a76f
CP
114 } else {
115 return this.active;
116 }
117 },
9e769c12
CP
118 __setActiveWindow: function(window) {
119 this.active = window;
120 },
5aa173fb
CP
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
ea29e3d7 130 this.windows.get(clientId).remove(window.identifier);
5aa173fb
CP
131
132 var window = this.windowArray[index];
133 window.name = name;
134 window.identifier = this.getWindowIdentifier(window.client, window.type, window.name);
135
ea29e3d7 136 this.windows.get(clientId).put(window.identifier, this.windowArray[index]);
5aa173fb
CP
137
138 if(window.active)
139 this.updateTitle(window.name + " - " + this.options.appTitle);
140
141 window.rename(window.name);
142 return window;
143 },
9e769c12
CP
144 selectWindow: function(window) {
145 if(this.active)
146 this.active.deselect();
147 window.select(); /* calls setActiveWindow */
326478c2
CP
148 this.updateTitle(window.name + " - " + this.options.appTitle);
149 },
150 updateTitle: function(text) {
151 document.title = text;
9e769c12 152 },
ff4befd8
CP
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 },
9e769c12
CP
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);
6f2e4a37
CP
183 if(index == -1) {
184 return;
185 } else if(index == 0) {
9e769c12
CP
186 this.selectWindow(this.windowArray[1]);
187 } else {
188 this.selectWindow(this.windowArray[index - 1]);
189 }
9e769c12
CP
190 }
191 }
192
404cfb58 193 this.windowArray = this.windowArray.erase(window);
ea29e3d7 194 this.windows.get(this.getClientId(window.client)).remove(window.identifier);
eb9b087b 195 },
eb9b087b
CP
196 /*
197 this shouldn't be called by overriding classes!
66de775f 198 they should implement their own!
eb9b087b
CP
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 */
66de775f 203 loginBox: function(callback, initialNickname, initialChannels, autoConnect, autoNick) {
2cad083e 204 qwebirc.ui.GenericLoginBox(this.parentElement, callback, initialNickname, initialChannels, autoConnect, autoNick, this.options.networkName);
eb271d86
CP
205 },
206 focusChange: function(newValue) {
207 var window_ = this.getActiveWindow();
208 if($defined(window_))
209 window_.focusChange(newValue);
9e769c12
CP
210 }
211});
381fddfd 212
e20e5a6b
CP
213qwebirc.ui.StandardUI = new Class({
214 Extends: qwebirc.ui.BaseUI,
f3d0c9f5 215 UICommands: qwebirc.ui.UI_COMMANDS,
381fddfd
CP
216 initialize: function(parentElement, windowClass, uiName, options) {
217 this.parent(parentElement, windowClass, uiName, options);
3184781b 218
ffb5ea9a
CP
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;
3184781b 221 this.tabCompleter = new qwebirc.ui.TabCompleterFactory(this);
c0f2f367 222 this.uiOptions = new qwebirc.ui.DefaultOptionsClass(this, options.uiOptionsArg);
ea29e3d7 223 this.customWindows = new QHash();
ffb5ea9a 224
6f8a20df
CP
225 if($defined(this.options.saturation)) this.__styleValues.saturation = this.options.saturation;
226 if($defined(this.options.lightness)) this.__styleValues.lightness = this.options.lightness;
69d01d1d
CP
227 if($defined(this.options.tsaturation)) this.__styleValues.textSaturation = this.options.tsaturation;
228 if($defined(this.options.tlightness)) this.__styleValues.textLightness = this.options.tlightness;
ffb5ea9a
CP
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
83d21243 238 document.addEvent("keydown", this.__handleHotkey.bind(this));
20157c51
CP
239 },
240 __handleHotkey: function(x) {
20157c51 241 var success = false;
710fd882 242 if(!x.alt && !x.control && !x.shift && !x.meta) {
83d21243
CP
243 if((x.key == "backspace" || x.key == "/") && !this.getInputFocused(x)) {
244 success = true;
245 }
710fd882
CP
246 } else if(!x.alt || x.control || x.meta) {
247 /* do nothing */
83d21243 248 } else if(x.key == "a" || x.key == "A") {
20157c51
CP
249 var highestNum = 0;
250 var highestIndex = -1;
251 success = true;
83d21243 252
20157c51
CP
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;
381fddfd 258 }
20157c51
CP
259 }
260 if(highestIndex > -1)
261 this.selectWindow(this.windowArray[highestIndex]);
710fd882 262 } else if((x.key >= '0' && x.key <= '9') && !x.shift) {
20157c51
CP
263 success = true;
264
265 number = x.key - '0';
266 if(number == 0)
267 number = 10
424608ac 268
20157c51
CP
269 number = number - 1;
270
271 if(number >= this.windowArray.length)
272 return;
381fddfd 273
20157c51 274 this.selectWindow(this.windowArray[number]);
710fd882 275 } else if((x.key == "left" || x.key == "up") && !x.shift) {
20157c51
CP
276 this.prevWindow();
277 success = true;
710fd882 278 } else if((x.key == "right" || x.key == "down") && !x.shift) {
20157c51
CP
279 this.nextWindow();
280 success = true;
281 }
710fd882 282
83d21243 283 if(success) {
20157c51 284 new Event(x).stop();
83d21243
CP
285 x.preventDefault();
286 }
20157c51
CP
287 },
288 getInputFocused: function(x) {
deebe19a
CP
289 if($$("input").indexOf(x.target) == -1 && $$("textarea").indexOf(x.target) == -1)
290 return false;
291 return true;
841a451d 292 },
8af49135
CP
293 newCustomWindow: function(name, select, type) {
294 if(!type)
e20e5a6b 295 type = qwebirc.ui.WINDOW_CUSTOM;
8af49135 296
e20e5a6b 297 var w = this.newWindow(qwebirc.ui.CUSTOM_CLIENT, type, name);
8af49135 298 w.addEvent("close", function(w) {
ea29e3d7 299 this.windows.get(qwebirc.ui.CUSTOM_CLIENT).remove(w.identifier);
8af49135
CP
300 }.bind(this));
301
302 if(select)
303 this.selectWindow(w);
6c19eb8f 304
8af49135
CP
305 return w;
306 },
ebb21d2e
CP
307 addCustomWindow: function(windowName, class_, cssClass, options) {
308 if(!$defined(options))
309 options = {};
310
ea29e3d7
CP
311 if(this.customWindows.contains(windowName)) {
312 this.selectWindow(this.customWindows.get(windowName));
8af49135 313 return;
841a451d 314 }
8af49135 315
ebb21d2e 316 var d = this.newCustomWindow(windowName, true);
ea29e3d7 317 this.customWindows.put(windowName, d);
ebb21d2e
CP
318
319 d.addEvent("close", function() {
ea29e3d7 320 this.customWindows.remove(windowName);
8af49135
CP
321 }.bind(this));
322
ebb21d2e 323 if(cssClass)
e1a91a8a 324 d.lines.addClass("qwebirc-" + cssClass);
ebb21d2e
CP
325
326 var ew = new class_(d.lines, options);
8af49135 327 ew.addEvent("close", function() {
ebb21d2e 328 d.close();
8af49135 329 }.bind(this));
17f40fd9
CP
330
331 d.setSubWindow(ew);
841a451d 332 },
ebb21d2e 333 embeddedWindow: function() {
bcd2d24f 334 this.addCustomWindow("Add webchat to your site", qwebirc.ui.EmbedWizard, "embeddedwizard", {baseURL: this.options.baseURL, uiOptions: this.uiOptions, optionsCallback: function() {
c0f2f367
CP
335 this.optionsWindow();
336 }.bind(this)});
ebb21d2e
CP
337 },
338 optionsWindow: function() {
339 this.addCustomWindow("Options", qwebirc.ui.OptionsPane, "optionspane", this.uiOptions);
340 },
e1a91a8a
CP
341 aboutWindow: function() {
342 this.addCustomWindow("About", qwebirc.ui.AboutPane, "aboutpane", this.uiOptions);
343 },
b35116e2
CP
344 privacyWindow: function() {
345 this.addCustomWindow("Privacy policy", qwebirc.ui.PrivacyPolicyPane, "privacypolicypane", this.uiOptions);
346 },
391f51ff
CP
347 feedbackWindow: function() {
348 this.addCustomWindow("Feedback", qwebirc.ui.FeedbackPane, "feedbackpane", this.uiOptions);
349 },
355dbcb7
CP
350 helpWindow: function() {
351 this.addCustomWindow("Help!", qwebirc.ui.HelpPane, "helppane", this.uiOptions);
f3d0c9f5 352 },
144ee52f 353 urlDispatcher: function(name, window) {
8af49135 354 if(name == "embedded")
925fc357 355 return ["a", this.embeddedWindow.bind(this)];
ebb21d2e
CP
356
357 if(name == "options")
358 return ["a", this.optionsWindow.bind(this)];
8af49135 359
5f2808af
CP
360 /* doesn't really belong here */
361 if(name == "whois") {
362 return ["span", function(nick) {
cbef082a
CP
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)];
5f2808af
CP
369 }
370
8af49135 371 return null;
3184781b 372 },
710fd882
CP
373 tabComplete: function(element, backwards) {
374 this.tabCompleter.tabComplete(element, backwards);
3184781b
CP
375 },
376 resetTabComplete: function() {
377 this.tabCompleter.reset();
4dd199c3
CP
378 },
379 setModifiableStylesheet: function(name) {
fbe5af77 380 this.__styleSheet = new qwebirc.ui.style.ModifiableStylesheet(qwebirc.global.staticBaseURL + "css/" + name + qwebirc.FILE_SUFFIX + ".mcss");
6f8a20df 381 this.setModifiableStylesheetValues({});
4dd199c3 382 },
6f8a20df 383 setModifiableStylesheetValues: function(values) {
ffb5ea9a 384 for (var k in values)
6f8a20df 385 this.__styleValues[k] = values[k];
ffb5ea9a
CP
386
387 if (!$defined(this.__styleSheet))
4dd199c3 388 return;
656385a2 389
ffb5ea9a
CP
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)) {
656385a2 393 front = back;
ffb5ea9a
CP
394 } else {
395 front = {hue: Number(this.__styleValues.textHue), lightness: Number(this.__styleValues.textLightness), saturation: Number(this.__styleValues.textSaturation)}
396 }
656385a2
CP
397 var colours = {
398 back: back,
399 front: front
400 };
401
6f8a20df
CP
402 this.__styleSheet.set(function() {
403 var mode = arguments[0];
404 if(mode == "c") {
656385a2 405 var t = colours[arguments[2]];
6f8a20df 406 var x = new Color(arguments[1]);
656385a2 407 var c = x.setHue(t.hue).setSaturation(x.hsb[1] + t.saturation).setBrightness(x.hsb[2] + t.lightness);
6f8a20df
CP
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));
8af49135 416 }
381fddfd 417});
6f2e4a37 418
326478c2 419qwebirc.ui.NotificationUI = new Class({
e20e5a6b 420 Extends: qwebirc.ui.StandardUI,
fb71087a
CP
421 initialize: function(parentElement, windowClass, uiName, options) {
422 this.parent(parentElement, windowClass, uiName, options);
423
326478c2
CP
424 this.__beeper = new qwebirc.ui.Beeper(this.uiOptions);
425 this.__flasher = new qwebirc.ui.Flasher(this.uiOptions);
f63006ab
CP
426 this.__notifier = new qwebirc.ui.Notifier(this.uiOptions);
427
326478c2 428 this.cancelFlash = this.__flasher.cancelFlash.bind(this.__flasher);
fb71087a 429 },
f63006ab
CP
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 },
127631e0
CP
438 setBeepOnMention: function(value) {
439 if(value)
326478c2
CP
440 this.__beeper.soundInit();
441 },
f63006ab
CP
442 setNotifications: function(value) {
443 this.__notifier.setEnabled(value);
444 },
326478c2
CP
445 updateTitle: function(text) {
446 if(this.__flasher.updateTitle(text))
447 this.parent(text);
eb271d86
CP
448 },
449 focusChange: function(value) {
450 this.parent(value);
451 this.__flasher.focusChange(value);
f63006ab 452 this.__notifier.focusChange(value);
1211ddcd 453 }
fb71087a
CP
454});
455
5f2808af 456qwebirc.ui.NewLoginUI = new Class({
326478c2 457 Extends: qwebirc.ui.NotificationUI,
5f2808af
CP
458 loginBox: function(callbackfn, initialNickname, initialChannels, autoConnect, autoNick) {
459 this.postInitialize();
460
c837b844 461 /* I'd prefer something shorter and snappier! */
a4a71818 462 var w = this.newCustomWindow("Connect", true, qwebirc.ui.WINDOW_CONNECT);
5f2808af
CP
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
472qwebirc.ui.QuakeNetUI = new Class({
473 Extends: qwebirc.ui.NewLoginUI,
2cd9e32d
CP
474 urlDispatcher: function(name, window) {
475 if(name == "qwhois") {
7cb09779 476 return ["span", function(auth) {
2cd9e32d 477 this.client.exec("/MSG Q whois #" + auth);
925fc357
CP
478 }.bind(window)];
479 }
144ee52f 480 return this.parent(name, window);
ffbb638d
CP
481 },
482 logout: function() {
483 if(!qwebirc.auth.loggedin())
484 return;
485 if(confirm("Log out?")) {
ea29e3d7
CP
486 this.clients.each(function(k, v) {
487 v.quit("Logged out");
488 }, this);
4b9f894d
CP
489
490 /* HACK */
fbe5af77 491 var foo = function() { document.location = qwebirc.global.dynamicBaseURL + "auth?logout=1"; };
4b9f894d 492 foo.delay(500);
ffbb638d 493 }
2cd9e32d
CP
494 }
495});
144ee52f
CP
496
497qwebirc.ui.RootUI = qwebirc.ui.QuakeNetUI;
fbe5af77
CP
498
499qwebirc.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