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