]> jfr.im git - irc/quakenet/qwebirc.git/blob - js/ui/frontends/qui.js
Add query renaming (patch from soczol).
[irc/quakenet/qwebirc.git] / js / ui / frontends / qui.js
1 qwebirc.ui.QUI = new Class({
2 Extends: qwebirc.ui.RootUI,
3 initialize: function(parentElement, theme, options) {
4 this.parent(parentElement, qwebirc.ui.QUI.Window, "qui", options);
5 this.theme = theme;
6 this.parentElement = parentElement;
7 this.setModifiableStylesheet("qui");
8 },
9 postInitialize: function() {
10 this.qjsui = new qwebirc.ui.QUI.JSUI("qwebirc-qui", this.parentElement);
11 this.qjsui.addEvent("reflow", function() {
12 var w = this.getActiveWindow();
13 if($defined(w))
14 w.onResize();
15 }.bind(this));
16 this.qjsui.top.addClass("outertabbar");
17
18 this.qjsui.bottom.addClass("input");
19 this.qjsui.right.addClass("nicklist");
20 this.qjsui.topic.addClass("topic");
21 this.qjsui.middle.addClass("lines");
22
23 this.outerTabs = this.qjsui.top;
24
25 this.tabs = new Element("div");
26 this.tabs.addClass("tabbar");
27
28 this.__createDropdownMenu();
29
30 this.outerTabs.appendChild(this.tabs);
31 this.origtopic = this.topic = this.qjsui.topic;
32 this.origlines = this.lines = this.qjsui.middle;
33 this.orignicklist = this.nicklist = this.qjsui.right;
34
35 this.input = this.qjsui.bottom;
36 this.reflow = this.qjsui.reflow.bind(this.qjsui);
37
38 this.tabs.addEvent("mousewheel", function(x) {
39 var event = new Event(x);
40
41 /* up */
42 if(event.wheel > 0) {
43 this.nextWindow();
44 } else if(event.wheel < 0) {
45 /* down */
46 this.prevWindow();
47 }
48 event.stop();
49 }.bind(this));
50
51 this.createInput();
52 this.reflow();
53 this.reflow.delay(100); /* Konqueror fix */
54
55 /* HACK, in Chrome this should work immediately but doesn't */
56 this.__createDropdownHint.delay(100, this);
57 },
58 __createDropdownMenu: function() {
59 var dropdownMenu = new Element("span");
60 dropdownMenu.addClass("dropdownmenu");
61
62 dropdownMenu.hide = function() {
63 dropdownMenu.setStyle("display", "none");
64 dropdownMenu.visible = false;
65 document.removeEvent("mousedown", hideEvent);
66 }.bind(this);
67 var hideEvent = function() { dropdownMenu.hide(); };
68
69 dropdownMenu.hide();
70 this.parentElement.appendChild(dropdownMenu);
71
72 this.UICommands.forEach(function(x) {
73 var text = x[0];
74 var fn = this[x[1] + "Window"].bind(this);
75 var e = new Element("a");
76 e.addEvent("mousedown", function(e) { new Event(e).stop(); });
77 e.addEvent("click", function() {
78 dropdownMenu.hide();
79 fn();
80 });
81 e.set("text", text);
82 dropdownMenu.appendChild(e);
83 }.bind(this));
84
85 var dropdown = new Element("div");
86 dropdown.addClass("dropdown-tab");
87 dropdown.appendChild(new Element("img", {src: qwebirc.global.staticBaseURL + "images/icon.png", title: "menu", alt: "menu"}));
88 dropdown.setStyle("opacity", 1);
89
90 var dropdownEffect = new Fx.Tween(dropdown, {duration: "long", property: "opacity", link: "chain"});
91 dropdownEffect.start(0.25);
92 dropdownEffect.start(1);
93 dropdownEffect.start(0.33);
94 dropdownEffect.start(1);
95
96 this.outerTabs.appendChild(dropdown);
97 dropdownMenu.show = function(x) {
98 new Event(x).stop();
99 this.hideHint();
100
101 if(dropdownMenu.visible) {
102 dropdownMenu.hide();
103 return;
104 }
105 var top = this.outerTabs.getSize().y;
106
107 dropdownMenu.setStyle("left", 0);
108 dropdownMenu.setStyle("top", top-1); /* -1 == top border */
109 dropdownMenu.setStyle("display", "inline-block");
110 dropdownMenu.visible = true;
111
112 document.addEvent("mousedown", hideEvent);
113 }.bind(this);
114 dropdown.addEvent("mousedown", function(e) { new Event(e).stop(); });
115 dropdown.addEvent("click", dropdownMenu.show);
116 },
117 __createDropdownHint: function() {
118 var dropdownhint = new Element("div");
119 dropdownhint.addClass("dropdownhint");
120 dropdownhint.set("text", "Click the icon for the main menu.");
121 dropdownhint.setStyle("top", this.outerTabs.getSize().y + 5);
122
123 this.parentElement.appendChild(dropdownhint);
124 new Fx.Morph(dropdownhint, {duration: "normal", transition: Fx.Transitions.Sine.easeOut}).start({left: [900, 5]});
125
126 var hider = function() {
127 new Fx.Morph(dropdownhint, {duration: "long"}).start({left: [5, -900]});
128 }.delay(4000, this);
129
130 var hider2 = function() {
131 if(dropdownhint.hidden)
132 return;
133 this.parentElement.removeChild(dropdownhint);
134 dropdownhint.hidden = 1;
135 }.bind(this);
136 hider2.delay(4000);
137 this.hideHint = hider2;
138
139 document.addEvent("mousedown", hider2);
140 document.addEvent("keypress", hider2);
141 },
142 createInput: function() {
143 var form = new Element("form");
144 this.input.appendChild(form);
145
146 form.addClass("input");
147
148 var inputbox = new Element("input");
149 form.appendChild(inputbox);
150 this.inputbox = inputbox;
151 this.inputbox.maxLength = 470;
152
153 var sendInput = function() {
154 if(inputbox.value == "")
155 return;
156
157 this.resetTabComplete();
158 this.getActiveWindow().historyExec(inputbox.value);
159 inputbox.value = "";
160 }.bind(this);
161
162 if(!qwebirc.util.deviceHasKeyboard()) {
163 inputbox.addClass("mobile-input");
164 var inputButton = new Element("input", {type: "button"});
165 inputButton.addClass("mobile-button");
166 inputButton.addEvent("click", function() {
167 sendInput();
168 inputbox.focus();
169 });
170 inputButton.value = ">";
171 this.input.appendChild(inputButton);
172 var reflowButton = function() {
173 var containerSize = this.input.getSize();
174 var buttonSize = inputButton.getSize();
175
176 var buttonLeft = containerSize.x - buttonSize.x - 5; /* lovely 5 */
177
178 inputButton.setStyle("left", buttonLeft);
179 inputbox.setStyle("width", buttonLeft - 5);
180 inputButton.setStyle("height", containerSize.y);
181 }.bind(this);
182 this.qjsui.addEvent("reflow", reflowButton);
183 } else {
184 inputbox.addClass("keyboard-input");
185 }
186
187 form.addEvent("submit", function(e) {
188 new Event(e).stop();
189 sendInput();
190 });
191
192 inputbox.addEvent("focus", this.resetTabComplete.bind(this));
193 inputbox.addEvent("mousedown", this.resetTabComplete.bind(this));
194
195 inputbox.addEvent("keydown", function(e) {
196 var resultfn;
197 var cvalue = inputbox.value;
198
199 if(e.key == "up") {
200 resultfn = this.commandhistory.upLine;
201 } else if(e.key == "down") {
202 resultfn = this.commandhistory.downLine;
203 } else if(e.key == "tab") {
204 new Event(e).stop();
205 this.tabComplete(inputbox);
206 return;
207 } else {
208 /* ideally alt and other keys wouldn't break this */
209 this.resetTabComplete();
210 return;
211 }
212
213 this.resetTabComplete();
214 if((cvalue != "") && (this.lastcvalue != cvalue))
215 this.commandhistory.addLine(cvalue, true);
216
217 var result = resultfn.bind(this.commandhistory)();
218
219 new Event(e).stop();
220 if(!result)
221 result = "";
222 this.lastcvalue = result;
223
224 inputbox.value = result;
225 qwebirc.util.setAtEnd(inputbox);
226 }.bind(this));
227 },
228 setLines: function(lines) {
229 this.lines.parentNode.replaceChild(lines, this.lines);
230 this.qjsui.middle = this.lines = lines;
231 },
232 setChannelItems: function(nicklist, topic) {
233 if(!$defined(nicklist)) {
234 nicklist = this.orignicklist;
235 topic = this.origtopic;
236 }
237 this.nicklist.parentNode.replaceChild(nicklist, this.nicklist);
238 this.qjsui.right = this.nicklist = nicklist;
239
240 this.topic.parentNode.replaceChild(topic, this.topic);
241 this.qjsui.topic = this.topic = topic;
242 }
243 });
244
245 qwebirc.ui.QUI.JSUI = new Class({
246 Implements: [Events],
247 initialize: function(class_, parent, sizer) {
248 this.parent = parent;
249 this.sizer = $defined(sizer)?sizer:parent;
250
251 this.class_ = class_;
252 this.create();
253
254 this.reflowevent = null;
255
256 window.addEvent("resize", function() {
257 this.reflow(100);
258 }.bind(this));
259 },
260 applyClasses: function(pos, l) {
261 l.addClass("dynamicpanel");
262 l.addClass(this.class_);
263
264 if(pos == "middle") {
265 l.addClass("leftboundpanel");
266 } else if(pos == "top") {
267 l.addClass("topboundpanel");
268 l.addClass("widepanel");
269 } else if(pos == "topic") {
270 l.addClass("widepanel");
271 } else if(pos == "right") {
272 l.addClass("rightboundpanel");
273 } else if(pos == "bottom") {
274 l.addClass("bottomboundpanel");
275 l.addClass("widepanel");
276 }
277 },
278 create: function() {
279 var XE = function(pos) {
280 var element = new Element("div");
281 this.applyClasses(pos, element);
282
283 this.parent.appendChild(element);
284 return element;
285 }.bind(this);
286
287 this.top = XE("top");
288 this.topic = XE("topic");
289 this.middle = XE("middle");
290 this.right = XE("right");
291 this.bottom = XE("bottom");
292 },
293 reflow: function(delay) {
294 if(!delay)
295 delay = 1;
296
297 if(this.reflowevent)
298 $clear(this.reflowevent);
299 this.__reflow();
300 this.reflowevent = this.__reflow.delay(delay, this);
301 },
302 __reflow: function() {
303 var bottom = this.bottom;
304 var middle = this.middle;
305 var right = this.right;
306 var topic = this.topic;
307 var top = this.top;
308
309 var topicsize = topic.getSize();
310 var topsize = top.getSize();
311 var rightsize = right.getSize();
312 var bottomsize = bottom.getSize();
313 var docsize = this.sizer.getSize();
314
315 var mheight = (docsize.y - topsize.y - bottomsize.y - topicsize.y);
316 var mwidth = (docsize.x - rightsize.x);
317
318 topic.setStyle("top", topsize.y);
319
320 middle.setStyle("top", (topsize.y + topicsize.y));
321 if(mheight > 0) {
322 middle.setStyle("height", mheight);
323 right.setStyle("height", mheight);
324 }
325
326 if(mwidth > 0)
327 middle.setStyle("width", mwidth);
328 right.setStyle("top", (topsize.y + topicsize.y));
329 right.setStyle("left", mwidth);
330
331 bottom.setStyle("top", (docsize.y - bottomsize.y));
332 this.fireEvent("reflow");
333 },
334 showChannel: function(state, nicklistVisible) {
335 var display = "none";
336 if(state)
337 display = "block";
338
339 this.right.setStyle("display", nicklistVisible ? display : "none");
340 this.topic.setStyle("display", display);
341 },
342 showInput: function(state) {
343 this.bottom.isVisible = state;
344 this.bottom.setStyle("display", state?"block":"none");
345 }
346 });
347
348 qwebirc.ui.QUI.Window = new Class({
349 Extends: qwebirc.ui.Window,
350
351 initialize: function(parentObject, client, type, name, identifier) {
352 this.parent(parentObject, client, type, name, identifier);
353
354 this.tab = new Element("a", {"href": "#"});
355 this.tab.addClass("tab");
356 this.tab.addEvent("focus", function() { this.blur() }.bind(this.tab));;
357
358 this.spaceNode = document.createTextNode(" ");
359 parentObject.tabs.appendChild(this.tab);
360 parentObject.tabs.appendChild(this.spaceNode);
361
362 this.tab.appendText(name);
363 this.tab.addEvent("click", function(e) {
364 new Event(e).stop();
365
366 if(this.closed)
367 return;
368
369 parentObject.selectWindow(this);
370 }.bind(this));
371
372 if(type != qwebirc.ui.WINDOW_STATUS && type != qwebirc.ui.WINDOW_CONNECT) {
373 var tabclose = new Element("span");
374 tabclose.set("text", "X");
375 tabclose.addClass("tabclose");
376 var close = function(e) {
377 new Event(e).stop();
378
379 if(this.closed)
380 return;
381
382 if(type == qwebirc.ui.WINDOW_CHANNEL)
383 this.client.exec("/PART " + name);
384
385 this.close();
386
387 //parentObject.inputbox.focus();
388 }.bind(this);
389
390 tabclose.addEvent("click", close);
391 this.tab.addEvent("mouseup", function(e) {
392 var button = 1;
393
394 if(Browser.Engine.trident)
395 button = 4;
396
397 if(e.event.button == button)
398 close(e);
399 }.bind(this));
400
401 this.tab.appendChild(tabclose);
402 }
403
404 this.lines = new Element("div");
405 this.parentObject.qjsui.applyClasses("middle", this.lines);
406 this.lines.addClass("lines");
407 if(type != qwebirc.ui.WINDOW_CUSTOM && type != qwebirc.ui.WINDOW_CONNECT)
408 this.lines.addClass("ircwindow");
409
410 this.lines.addEvent("scroll", function() {
411 this.scrolleddown = this.scrolledDown();
412 this.scrollpos = this.getScrollParent().getScroll();
413 }.bind(this));
414
415 if(type == qwebirc.ui.WINDOW_CHANNEL) {
416 this.topic = new Element("div");
417 this.topic.addClass("topic");
418 this.topic.addClass("tab-invisible");
419 this.topic.set("html", "&nbsp;");
420 this.topic.addEvent("dblclick", this.editTopic.bind(this));
421 this.parentObject.qjsui.applyClasses("topic", this.topic);
422
423 this.prevNick = null;
424 this.nicklist = new Element("div");
425 this.nicklist.addClass("nicklist");
426 this.nicklist.addClass("tab-invisible");
427 this.nicklist.addEvent("click", this.removePrevMenu.bind(this));
428 this.parentObject.qjsui.applyClasses("nicklist", this.nicklist);
429 }
430
431 if(type == qwebirc.ui.WINDOW_CHANNEL)
432 this.updateTopic("");
433
434 this.nicksColoured = this.parentObject.uiOptions.NICK_COLOURS;
435 this.reflow();
436 },
437 rename: function(name) {
438 this.tab.replaceChild(document.createTextNode(name), this.tab.firstChild);
439 },
440 editTopic: function() {
441 if(!this.client.nickOnChanHasPrefix(this.client.nickname, this.name, "@")) {
442 /* var cmodes = this.client.getChannelModes(channel);
443 if(cmodes.indexOf("t")) {*/
444 alert("Sorry, you need to be a channel operator to change the topic!");
445 return;
446 /*}*/
447 }
448 var newTopic = prompt("Change topic of " + this.name + " to:", this.topic.topicText);
449 if(newTopic === null)
450 return;
451
452 this.client.exec("/TOPIC " + newTopic);
453 },
454 reflow: function() {
455 this.parentObject.reflow();
456 },
457 onResize: function() {
458 if(this.scrolleddown) {
459 if(Browser.Engine.trident) {
460 this.scrollToBottom.delay(5, this);
461 } else {
462 this.scrollToBottom();
463 }
464 } else if($defined(this.scrollpos)) {
465 if(Browser.Engine.trident) {
466 this.getScrollParent().scrollTo(this.scrollpos.x, this.scrollpos.y);
467 } else {
468 this.getScrollParent().scrollTo.delay(5, this, [this.scrollpos.x, this.scrollpos.y]);
469 }
470 }
471 },
472 createMenu: function(nick, parent) {
473 var e = new Element("div");
474 parent.appendChild(e);
475 e.addClass("menu");
476
477 var nickArray = [nick];
478 qwebirc.ui.MENU_ITEMS.forEach(function(x) {
479 if(!x.predicate || x.predicate !== true && !x.predicate.apply(this, nickArray))
480 return;
481
482 var e2 = new Element("a");
483 e.appendChild(e2);
484
485 e2.href = "#";
486 e2.set("text", "- " + x.text);
487
488 e2.addEvent("focus", function() { this.blur() }.bind(e2));
489 e2.addEvent("click", function(ev) { new Event(ev.stop()); this.menuClick(x.fn); }.bind(this));
490 }.bind(this));
491 return e;
492 },
493 menuClick: function(fn) {
494 /*
495 this.prevNick.removeChild(this.prevNick.menu);
496 this.prevNick.menu = null;
497 */
498 fn.bind(this)(this.prevNick.realNick);
499 this.removePrevMenu();
500 },
501 moveMenuClass: function() {
502 if(!this.prevNick)
503 return;
504 if(this.nicklist.firstChild == this.prevNick) {
505 this.prevNick.removeClass("selected-middle");
506 } else {
507 this.prevNick.addClass("selected-middle");
508 }
509 },
510 removePrevMenu: function() {
511 if(!this.prevNick)
512 return;
513
514 this.prevNick.removeClass("selected");
515 this.prevNick.removeClass("selected-middle");
516 if(this.prevNick.menu)
517 this.prevNick.removeChild(this.prevNick.menu);
518 this.prevNick = null;
519 },
520 nickListAdd: function(nick, position) {
521 var realNick = this.client.stripPrefix(nick);
522
523 var e = new Element("a");
524 qwebirc.ui.insertAt(position, this.nicklist, e);
525
526 e.href = "#";
527 var span = new Element("span");
528 if(this.parentObject.uiOptions.NICK_COLOURS) {
529 var colour = realNick.toHSBColour(this.client);
530 if($defined(colour))
531 span.setStyle("color", colour.rgbToHex());
532 }
533 span.set("text", nick);
534 e.appendChild(span);
535
536 e.realNick = realNick;
537
538 e.addEvent("click", function(x) {
539 if(this.prevNick == e) {
540 this.removePrevMenu();
541 return;
542 }
543
544 this.removePrevMenu();
545 this.prevNick = e;
546 e.addClass("selected");
547 this.moveMenuClass();
548 e.menu = this.createMenu(e.realNick, e);
549 new Event(x).stop();
550 }.bind(this));
551
552 e.addEvent("focus", function() { this.blur() }.bind(e));
553 this.moveMenuClass();
554 return e;
555 },
556 nickListRemove: function(nick, stored) {
557 this.nicklist.removeChild(stored);
558 this.moveMenuClass();
559 },
560 updateTopic: function(topic) {
561 var t = this.topic;
562
563 while(t.firstChild)
564 t.removeChild(t.firstChild);
565
566 if(topic) {
567 t.topicText = topic;
568 this.parent(topic, t);
569 } else {
570 t.topicText = topic;
571 var e = new Element("div");
572 e.set("text", "(no topic set)");
573 e.addClass("emptytopic");
574 t.appendChild(e);
575 }
576 this.reflow();
577 },
578 select: function() {
579 var inputVisible = this.type != qwebirc.ui.WINDOW_CONNECT && this.type != qwebirc.ui.WINDOW_CUSTOM;
580
581 this.tab.removeClass("tab-unselected");
582 this.tab.addClass("tab-selected");
583
584 this.parentObject.setLines(this.lines);
585 this.parentObject.setChannelItems(this.nicklist, this.topic);
586 this.parentObject.qjsui.showInput(inputVisible);
587 this.parentObject.qjsui.showChannel($defined(this.nicklist), this.parentObject.uiOptions.SHOW_NICKLIST);
588
589 this.reflow();
590
591 this.parent();
592
593 if(inputVisible)
594 this.parentObject.inputbox.focus();
595
596 if(this.type == qwebirc.ui.WINDOW_CHANNEL && this.nicksColoured != this.parentObject.uiOptions.NICK_COLOURS) {
597 this.nicksColoured = this.parentObject.uiOptions.NICK_COLOURS;
598
599 var nodes = this.nicklist.childNodes;
600 if(this.parentObject.uiOptions.NICK_COLOURS) {
601 for(var i=0;i<nodes.length;i++) {
602 var e = nodes[i], span = e.firstChild;
603 var colour = e.realNick.toHSBColour(this.client);
604 if($defined(colour))
605 span.setStyle("color", colour.rgbToHex());
606 };
607 } else {
608 for(var i=0;i<nodes.length;i++) {
609 var span = nodes[i].firstChild;
610 span.setStyle("color", null);
611 };
612 }
613 }
614 },
615 deselect: function() {
616 this.parent();
617
618 this.tab.removeClass("tab-selected");
619 this.tab.addClass("tab-unselected");
620 },
621 close: function() {
622 this.parent();
623
624 this.parentObject.tabs.removeChild(this.tab);
625 this.parentObject.tabs.removeChild(this.spaceNode);
626 this.reflow();
627 },
628 addLine: function(type, line, colourClass) {
629 var e = new Element("div");
630
631 if(colourClass) {
632 e.addClass(colourClass);
633 } else if(this.lastcolour) {
634 e.addClass("linestyle1");
635 } else {
636 e.addClass("linestyle2");
637 }
638 this.lastcolour = !this.lastcolour;
639
640 this.parent(type, line, colourClass, e);
641 },
642 setHilighted: function(state) {
643 var laststate = this.hilighted;
644
645 this.parent(state);
646
647 if(state == laststate)
648 return;
649
650 this.tab.removeClass("tab-hilight-activity");
651 this.tab.removeClass("tab-hilight-us");
652 this.tab.removeClass("tab-hilight-speech");
653
654 switch(this.hilighted) {
655 case qwebirc.ui.HILIGHT_US:
656 this.tab.addClass("tab-hilight-us");
657 break;
658 case qwebirc.ui.HILIGHT_SPEECH:
659 this.tab.addClass("tab-hilight-speech");
660 break;
661 case qwebirc.ui.HILIGHT_ACTIVITY:
662 this.tab.addClass("tab-hilight-activity");
663 break;
664 }
665 }
666 });