home *** CD-ROM | disk | FTP | other *** search
/ Chip 2003 January / 01_03.iso / software / ghostzilla_hit / files / ghostzilla-1.0-plus-install.exe / chrome / chatzilla.jar / content / chatzilla / static.js < prev   
Encoding:
Text File  |  2002-05-15  |  85.1 KB  |  2,794 lines

  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2.  *
  3.  * The contents of this file are subject to the Mozilla Public
  4.  * License Version 1.1 (the "License"); you may not use this file
  5.  * except in compliance with the License. You may obtain a copy of
  6.  * the License at http://www.mozilla.org/MPL/
  7.  *
  8.  * Software distributed under the License is distributed on an "AS
  9.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10.  * implied. See the License for the specific language governing
  11.  * rights and limitations under the License.
  12.  *
  13.  * The Original Code is ChatZilla
  14.  *
  15.  * The Initial Developer of the Original Code is New Dimensions Consulting,
  16.  * Inc. Portions created by New Dimensions Consulting, Inc. are
  17.  * Copyright (C) 1999 New Dimenstions Consulting, Inc. All
  18.  * Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Robert Ginda, rginda@ndcico.com, original author
  22.  *  Chiaki Koufugata chiaki@mozilla.gr.jp UI i18n 
  23.  *  Samuel Sieb, samuel@sieb.net, MIRC color code, munger menu, and various
  24.  */
  25.  
  26. if (DEBUG)
  27.     dd = function (m) { dump ("-*- chatzilla: " + m + "\n"); }
  28. else
  29.     dd = function (){};
  30.  
  31. var client = new Object();
  32.  
  33. const MSG_CSP       = getMsg ("commaSpace", " ");
  34. const MSG_NONE      = getMsg ("none");
  35. const MSG_UNKNOWN   = getMsg ("unknown");
  36.  
  37. client.defaultNick = getMsg( "defaultNick" );
  38.  
  39. client.version = "0.8.7";
  40.  
  41. client.TYPE = "IRCClient";
  42. client.COMMAND_CHAR = "/";
  43. client.STEP_TIMEOUT = 500;
  44. client.MAX_MESSAGES = 200;
  45. client.MAX_HISTORY = 50;
  46. /* longest nick to show in display before forcing the message to a block level
  47.  * element */
  48. client.MAX_NICK_DISPLAY = 14;
  49. /* longest word to show in display before abbreviating */
  50. client.MAX_WORD_DISPLAY = 20;
  51. client.PRINT_DIRECTION = 1; /*1 => new messages at bottom, -1 => at top */
  52. client.ADDRESSED_NICK_SEP = ":";
  53.  
  54. client.MAX_MSG_PER_ROW = 3; /* default number of messages to collapse into a
  55.                              * single row, max. */
  56. client.INITIAL_COLSPAN = 5; /* MAX_MSG_PER_ROW cannot grow to greater than 
  57.                              * one half INITIAL_COLSPAN + 1. */
  58. client.COLLAPSE_ROWS = false;
  59. client.NOTIFY_TIMEOUT = 5 * 60 * 1000; /* update notify list every 5 minutes */
  60.  
  61. client.SLOPPY_NETWORKS = true; /* true if msgs from a network can be displayed
  62.                                 * on the current object if it is related to
  63.                                 * the network (ie, /whois results will appear
  64.                                 * on the channel you're viewing, if that channel
  65.                                 * is on the network that the results came from)
  66.                                 */
  67. client.DOUBLETAB_TIME = 500;
  68. client.IMAGEDIR = "chrome://chatzilla/skin/images/";
  69. client.HIDE_CODES = true;      /* true if you'd prefer to show numeric response 
  70.                                 * codes as some default value (ie, "===") */
  71. client.DEFAULT_RESPONSE_CODE = "===";
  72.  
  73.  
  74. /* XXX maybe move this into css */
  75. client.responseCodeMap = new Object();
  76. client.responseCodeMap["HELLO"]  = getMsg("responseCodeMapHello");
  77. client.responseCodeMap["HELP"]  = getMsg("responseCodeMapHelp");
  78. client.responseCodeMap["USAGE"]  = getMsg("responseCodeMapUsage");
  79. client.responseCodeMap["ERROR"]  = getMsg("responseCodeMapError");
  80. client.responseCodeMap["WARNING"]  = getMsg("responseCodeMapWarning");
  81. client.responseCodeMap["INFO"]  = getMsg("responseCodeMapInfo");
  82. client.responseCodeMap["EVAL-IN"]  = getMsg("responseCodeMapEvalIn");
  83. client.responseCodeMap["EVAL-OUT"]  = getMsg("responseCodeMapEvalOut");
  84. client.responseCodeMap["JOIN"]  = "-->|";
  85. client.responseCodeMap["PART"]  = "<--|";
  86. client.responseCodeMap["QUIT"]  = "|<--";
  87. client.responseCodeMap["NICK"]  = "=-=";
  88. client.responseCodeMap["TOPIC"] = "=-=";
  89. client.responseCodeMap["KICK"]  = "=-=";
  90. client.responseCodeMap["MODE"]  = "=-=";
  91. client.responseCodeMap["END_STATUS"] = "---";
  92. client.responseCodeMap["315"]  = "---"; /* end of WHO */
  93. client.responseCodeMap["318"]  = "---"; /* end of WHOIS */
  94. client.responseCodeMap["366"]  = "---"; /* end of NAMES */
  95. client.responseCodeMap["376"]  = "---"; /* end of MOTD */
  96.  
  97. client.name = getMsg("clientname");
  98. client.viewsArray = new Array();
  99. client.activityList = new Object();
  100. client.uiState = new Object(); /* state of ui elements (visible/collapsed) */
  101. client.inputHistory = new Array();
  102. client.lastHistoryReferenced = -1;
  103. client.incompleteLine = "";
  104. client.lastTabUp = new Date();
  105. client.stalkingVictims = new Array();
  106.  
  107. CIRCNetwork.prototype.INITIAL_NICK = client.defaultNick;
  108. CIRCNetwork.prototype.INITIAL_NAME = "chatzilla";
  109. CIRCNetwork.prototype.INITIAL_DESC = getMsg("circnetworkInitialDesc");
  110. CIRCNetwork.prototype.INITIAL_CHANNEL = "";
  111. CIRCNetwork.prototype.MAX_MESSAGES = 100;
  112. CIRCNetwork.prototype.IGNORE_MOTD = false;
  113.  
  114. CIRCServer.prototype.READ_TIMEOUT = 0;
  115. CIRCServer.prototype.VERSION_RPLY = getMsg("circserverVersionRply",
  116.                                            [client.version,
  117.                                             navigator.userAgent]);
  118. CIRCUser.prototype.MAX_MESSAGES = 200;
  119.  
  120. CIRCChannel.prototype.MAX_MESSAGES = 300;
  121.  
  122. CIRCChanUser.prototype.MAX_MESSAGES = 200;
  123.  
  124. window.onresize =
  125. function ()
  126. {
  127.     scrollDown();
  128. }
  129.  
  130. function ucConvertIncomingMessage (e)
  131. {
  132.     e.meat = toUnicode(e.meat);
  133.     return true;
  134. }
  135.  
  136. function toUnicode (msg)
  137. {
  138.     if (!("ucConverter" in client))
  139.         return msg;
  140.     
  141.     /* XXX set charset again to force the encoder to reset, see bug 114923 */
  142.     client.ucConverter.charset = client.CHARSET;
  143.     return client.ucConverter.ConvertToUnicode(msg);
  144. }
  145.  
  146. function fromUnicode (msg)
  147. {
  148.     if (!("ucConverter" in client))
  149.         return msg;
  150.     
  151.     /* XXX set charset again to force the encoder to reset, see bug 114923 */
  152.     client.ucConverter.charset = client.CHARSET;
  153.     return client.ucConverter.ConvertFromUnicode(msg);
  154. }
  155.  
  156. function setCharset (charset)
  157. {
  158.     client.CHARSET = charset;
  159.     
  160.     if (!charset)
  161.     {
  162.         delete client.ucConverter;
  163.         client.eventPump.removeHookByName("uc-hook");
  164.         return true;
  165.     }
  166.     
  167.     var ex;
  168.     
  169.     try
  170.     {
  171.         
  172.         if (!("ucConverter" in client))
  173.         {
  174.             const UC_CTRID = "@mozilla.org/intl/scriptableunicodeconverter";
  175.             const nsIUnicodeConverter = 
  176.                 Components.interfaces.nsIScriptableUnicodeConverter;
  177.             client.ucConverter =
  178.                 Components.classes[UC_CTRID].getService(nsIUnicodeConverter);
  179.         }
  180.         
  181.         client.ucConverter.charset = charset;
  182.         
  183.         if (!client.eventPump.getHook("uc-hook"))
  184.         {
  185.             client.eventPump.addHook ([{type: "parseddata", set: "server"}],
  186.                                       ucConvertIncomingMessage, "uc-hook");
  187.         }
  188.     }
  189.     catch (ex)
  190.     {
  191.         dd ("Caught exception setting charset to " + charset + "\n" + ex);
  192.         delete client.ucConverter;
  193.         client.CHARSET = "";
  194.         client.eventPump.removeHookByName("uc-hook");
  195.         return false;
  196.     }
  197.  
  198.     return true;
  199. }
  200.  
  201. function initStatic()
  202. {
  203.     var obj;
  204.  
  205.     const nsISound = Components.interfaces.nsISound;
  206.     client.sound =
  207.         Components.classes["@mozilla.org/sound;1"].createInstance(nsISound);
  208.  
  209.     if (client.CHARSET)
  210.         setCharset(client.CHARSET);
  211.     
  212.     var ary = navigator.userAgent.match (/;\s*([^;\s]+\s*)\).*\/(\d+)/);
  213.     if (ary)
  214.         client.userAgent = "ChatZilla " + client.version + " [Mozilla " + 
  215.             ary[1] + "/" + ary[2] + "]";
  216.     else
  217.         client.userAgent = "ChatZilla " + client.version + " [" + 
  218.             navigator.userAgent + "]";
  219.  
  220.     obj = document.getElementById("input");
  221.     obj.addEventListener("keypress", onInputKeyPress, false);
  222.     obj = document.getElementById("multiline-input");
  223.     obj.addEventListener("keypress", onMultilineInputKeyPress, false);
  224.     obj = document.getElementById("channel-topicedit");
  225.     obj.addEventListener("keypress", onTopicKeyPress, false);
  226.     obj.active = false;
  227.  
  228.     window.onkeypress = onWindowKeyPress;
  229.  
  230.     setMenuCheck ("menu-dmessages",
  231.                   client.eventPump.getHook ("event-tracer").enabled);
  232.     setMenuCheck ("menu-munger-global", !client.munger.enabled);
  233.     setMenuCheck ("menu-colors", client.enableColors);
  234.  
  235.     setupMungerMenu(client.munger);
  236.  
  237.     client.uiState["tabstrip"] =
  238.         setMenuCheck ("menu-view-tabstrip", isVisible("view-tabs"));
  239.     client.uiState["info"] =
  240.         setMenuCheck ("menu-view-info", isVisible("user-list-box"));
  241.     client.uiState["header"] =
  242.         setMenuCheck ("menu-view-header", isVisible("header-bar-tbox"));
  243.     client.uiState["status"] =
  244.         setMenuCheck ("menu-view-status", isVisible("status-bar"));
  245.  
  246.     client.statusBar = new Object();
  247.     
  248.     client.statusBar["header-url"] =
  249.         document.getElementById ("header-url");
  250.     client.statusBar["server-nick"] =
  251.         document.getElementById ("server-nick");
  252.     client.statusBar["channel-mode"] =
  253.         document.getElementById ("channel-mode");
  254.     client.statusBar["channel-users"] =
  255.         document.getElementById ("channel-users");
  256.     client.statusBar["channel-topic"] =
  257.         document.getElementById ("channel-topic");
  258.     client.statusBar["channel-topicedit"] =
  259.         document.getElementById ("channel-topicedit");
  260.  
  261.     client.statusElement = document.getElementById ("status-text");
  262.     client.defaultStatus = getMsg ("defaultStatus");
  263.     
  264.     onSortCol ("usercol-nick");
  265.  
  266.     client.display (getMsg("welcome"), "HELLO");
  267.     setCurrentObject (client);
  268.  
  269.     client.onInputNetworks();
  270.     client.onInputCommands();
  271.  
  272.     ary = client.INITIAL_VICTIMS.split(/\s*;\s*/);
  273.     for (i in ary)
  274.     {
  275.         if (ary[i])
  276.             client.stalkingVictims.push (ary[i]);
  277.     }
  278.  
  279.     var m = document.getElementById ("menu-settings-autosave");
  280.     m.setAttribute ("checked", String(client.SAVE_SETTINGS));
  281.      
  282.     var wentSomewhere = false;
  283.  
  284.     if ("arguments" in window &&
  285.         0 in window.arguments && typeof window.arguments[0] == "object" &&
  286.         "url" in window.arguments[0])
  287.     {
  288.         url = window.arguments[0].url;
  289.         if (url.search(/^irc:\/?\/?$/i) == -1)
  290.         {
  291.             /* if the url is not irc: irc:/ or irc://, then go to it. */
  292.             gotoIRCURL (url);
  293.             wentSomewhere = true;
  294.         }
  295.     }    
  296.  
  297.     if (!wentSomewhere)
  298.     {
  299.         /* if we had nowhere else to go, connect to any default urls */
  300.         ary = client.INITIAL_URLS.split(/\s*;\s*/).reverse();
  301.         for (var i in ary)
  302.         {
  303.             if (ary[i] && ary[i] != "irc://")
  304.                 gotoIRCURL (ary[i]);
  305.         }
  306.     }
  307.     
  308.     if (client.viewsArray.length > 1 && !isStartupURL("irc://"))
  309.     {
  310.         var tb = getTabForObject (client);
  311.         deleteTab(tb);
  312.     }
  313.         
  314.     setInterval ("onNotifyTimeout()", client.NOTIFY_TIMEOUT);
  315.     
  316. }
  317.  
  318. function setStatus (str)
  319. {
  320.     client.statusElement.setAttribute ("label", str);
  321.     return str;
  322. }
  323.  
  324. client.__defineSetter__ ("status", setStatus);
  325.  
  326. function getStatus ()
  327. {
  328.     return client.statusElement.getAttribute ("label");
  329. }
  330.  
  331. client.__defineGetter__ ("status", getStatus);
  332.  
  333. function setMenuCheck (id, state)
  334. {
  335.     var m = document.getElementById(id);
  336.     m.setAttribute ("checked", String(Boolean(state)));
  337.     return state;
  338. }
  339.  
  340. function isVisible (id)
  341. {
  342.     var e = document.getElementById(id);
  343.  
  344.     if (!e)
  345.     {
  346.         dd ("** Bogus id ``" + id + "'' passed to isVisible() **");
  347.         return false;
  348.     }
  349.     
  350.     return (e.getAttribute ("collapsed") != "true");
  351. }
  352.  
  353. function initHost(obj)
  354. {
  355.     obj.commands = new CCommandManager();
  356.     addCommands (obj.commands);
  357.     
  358.     obj.networks = new Object();
  359.     obj.eventPump = new CEventPump (200);
  360.  
  361.     obj.defaultCompletion = client.COMMAND_CHAR + "help ";
  362.  
  363.     obj.networks["efnet"] =
  364.         new CIRCNetwork ("efnet",
  365.                          [{name: "irc.mcs.net", port: 6667},
  366.                           {name: "irc.prison.net", port: 6667},
  367.                           {name: "irc.freei.net", port: 6667},
  368.                           {name: "irc.magic.ca", port: 6667}],
  369.                          obj.eventPump);
  370.     obj.networks["moznet"] =
  371.         new CIRCNetwork ("moznet", [{name: "irc.mozilla.org", port: 6667}],
  372.                          obj.eventPump);
  373.     obj.networks["hybridnet"] =
  374.         new CIRCNetwork ("hybridnet", [{name: "irc.ssc.net", port: 6667}],
  375.                          obj.eventPump);
  376.     obj.networks["slashnet"] =
  377.         new CIRCNetwork ("slashnet", [{name: "irc.slashnet.org", port:6667}],
  378.                          obj.eventPump);
  379.     obj.networks["dalnet"] =
  380.         new CIRCNetwork ("dalnet", [{name: "irc.dal.net", port:6667}],
  381.                          obj.eventPump);
  382.     obj.networks["undernet"] =
  383.         new CIRCNetwork ("undernet", [{name: "irc.undernet.org", port:6667}],
  384.                          obj.eventPump);
  385.     obj.networks["webbnet"] =
  386.         new CIRCNetwork ("webbnet", [{name: "irc.webbnet.org", port:6667}],
  387.                          obj.eventPump);
  388.     obj.networks["quakenet"] =
  389.         new CIRCNetwork ("quakenet", [{name: "irc.quakenet.org", port:6667}],
  390.                          obj.eventPump);
  391.     obj.networks["opennet"] =
  392.         new CIRCNetwork ("opennet",         
  393.                          [{name: "irc.openprojects.net", port:6667},
  394.                           {name: "eu.opirc.nu", port:6667},
  395.                           {name: "au.opirc.nu", port:6667},
  396.                           {name: "us.opirc.nu", port:6667}],
  397.                          obj.eventPump);
  398.  
  399.     obj.primNet = obj.networks["efnet"];
  400.  
  401.     if (DEBUG)
  402.         /* hook all events EXCEPT server.poll and *.event-end types
  403.          * (the 4th param inverts the match) */
  404.         obj.eventPump.addHook ([{type: "poll", set: /^(server|dcc-chat)$/},
  405.                                {type: "event-end"}], event_tracer,
  406.                                "event-tracer", true /* negate */,
  407.                                false /* disable */);
  408.  
  409.     obj.linkRE = /((\w+):[^<>\[\]()\'\"\s]+|www(\.[^.<>\[\]()\'\"\s]+){2,})/;
  410.  
  411.     obj.munger = new CMunger();
  412.     obj.munger.enabled = true;
  413.     obj.munger.addRule ("link", obj.linkRE, insertLink);
  414.     obj.munger.addRule ("mailto",
  415.        /(?:\s|\W|^)((mailto:)?[^<>\[\]()\'\"\s]+@[^.<>\[\]()\'\"\s]+\.[^<>\[\]()\'\"\s]+)/i,
  416.                         insertMailToLink);
  417.     obj.munger.addRule ("bugzilla-link", /(?:\s|\W|^)(bug\s+#?\d{3,6})/i,
  418.                         insertBugzillaLink);
  419.     obj.munger.addRule ("channel-link",
  420.                 /(?:\s|\W|^)[@+]?(#[^<>\[\](){}\"\s]*[^:,.<>\[\](){}\'\"\s])/i,
  421.                         insertChannelLink);
  422.     
  423.     obj.munger.addRule ("face",
  424.          /((^|\s)[\<\>]?[\;\=\:]\~?[\-\^\v]?[\)\|\(pP\<\>oO0\[\]\/\\](\s|$))/,
  425.          insertSmiley);
  426.     obj.munger.addRule ("ear", /(?:\s|^)(\(\*)(?:\s|$)/, insertEar, false);
  427.     obj.munger.addRule ("rheet", /(?:\s|\W|^)(rhee+t\!*)(?:\s|$)/i, insertRheet);
  428.     obj.munger.addRule ("bold", /(?:\s|^)(\*[^*,.()]*\*)(?:[\s.,]|$)/, 
  429.                         "chatzilla-bold");
  430.     obj.munger.addRule ("italic", /(?:\s|^)(\/[^\/,.()]*\/)(?:[\s.,]|$)/,
  431.                         "chatzilla-italic");
  432.     /* allow () chars inside |code()| blocks */
  433.     obj.munger.addRule ("teletype", /(?:\s|^)(\|[^|,.]*\|)(?:[\s.,]|$)/,
  434.                         "chatzilla-teletype");
  435.     obj.munger.addRule ("underline", /(?:\s|^)(\_[^_,.()]*\_)(?:[\s.,]|$)/,
  436.                         "chatzilla-underline");
  437.     obj.munger.addRule (".mirc-colors", /(\x03((\d{1,2})(,\d{1,2}|)|))/,
  438.                          mircChangeColor);
  439.     obj.munger.addRule (".mirc-bold", /(\x02)/, mircToggleBold);
  440.     obj.munger.addRule (".mirc-underline", /(\x1f)/, mircToggleUnder);
  441.     obj.munger.addRule (".mirc-color-reset", /(\x0f)/, mircResetColor);
  442.     obj.munger.addRule (".mirc-reverse", /(\x16)/, mircReverseColor);
  443.     obj.munger.addRule ("ctrl-char", /([\x01-\x1f])/, showCtrlChar);
  444.     obj.munger.addRule ("word-hyphenator",
  445.                         new RegExp ("(\\S{" + client.MAX_WORD_DISPLAY + ",})"),
  446.                         insertHyphenatedWord);
  447.  
  448.     obj.rdf = new RDFHelper();
  449.     
  450.     obj.rdf.initTree("user-list");
  451.     obj.rdf.setTreeRoot("user-list", obj.rdf.resNullChan);
  452.  
  453.     multilineInputMode(false);
  454. }
  455.  
  456. function insertLink (matchText, containerTag)
  457. {
  458.  
  459.     var href;
  460.     
  461.     if (matchText.match (/^[a-zA-Z-]+:/))
  462.         href = matchText;
  463.     else
  464.         href = "http://" + matchText;
  465.     
  466.     var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
  467.                                            "html:a");
  468.     anchor.setAttribute ("href", href);
  469.     anchor.setAttribute ("class", "chatzilla-link");
  470.     anchor.setAttribute ("target", "_content");
  471.     insertHyphenatedWord (matchText, anchor);
  472.     containerTag.appendChild (anchor);
  473.     
  474. }
  475.  
  476. function insertMailToLink (matchText, containerTag)
  477. {
  478.  
  479.     var href;
  480.     
  481.     if (matchText.indexOf ("mailto:") != 0)
  482.         href = "mailto:" + matchText;
  483.     else
  484.         href = matchText;
  485.     
  486.     var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
  487.                                            "html:a");
  488.     anchor.setAttribute ("href", href);
  489.     anchor.setAttribute ("class", "chatzilla-link");
  490.     //anchor.setAttribute ("target", "_content");
  491.     insertHyphenatedWord (matchText, anchor);
  492.     containerTag.appendChild (anchor);
  493.     
  494. }
  495.  
  496. function insertChannelLink (matchText, containerTag, eventData)
  497. {
  498.     if (!("network" in eventData) || 
  499.         matchText.search
  500.             (/^#(include|error|define|if|ifdef|else|elsif|endif|\d+)$/i) != -1)
  501.  
  502.     {
  503.         containerTag.appendChild (document.createTextNode(matchText));
  504.         return;
  505.     }
  506.     
  507.     var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
  508.                                            "html:a");
  509.     anchor.setAttribute ("href", "irc://" + escape(eventData.network.name) +
  510.                          "/" + escape (matchText));
  511.     anchor.setAttribute ("class", "chatzilla-link");
  512.     //anchor.setAttribute ("target", "_content");
  513.     insertHyphenatedWord (matchText, anchor);
  514.     containerTag.appendChild (anchor);
  515.     
  516. }
  517.  
  518. function insertBugzillaLink (matchText, containerTag)
  519. {
  520.  
  521.     var number = matchText.match (/(\d+)/)[1];
  522.     
  523.     var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
  524.                                            "html:a");
  525.     anchor.setAttribute ("href",
  526.                          "http://bugzilla.mozilla.org/show_bug.cgi?id=" + 
  527.                          number);
  528.     anchor.setAttribute ("class", "chatzilla-link");
  529.     anchor.setAttribute ("target", "_content");
  530.     insertHyphenatedWord (matchText, anchor);
  531.     containerTag.appendChild (anchor);
  532.     
  533. }
  534.  
  535. function insertRheet (matchText, containerTag)
  536. {
  537.  
  538.     var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
  539.                                            "html:a");
  540.     anchor.setAttribute ("href",
  541.                          "ftp://ftp.mozilla.org/pub/mozilla/libraries/bonus-tracks/rheet.wav");
  542.     anchor.setAttribute ("class", "chatzilla-rheet chatzilla-link");
  543.     //anchor.setAttribute ("target", "_content");
  544.     insertHyphenatedWord (matchText, anchor);
  545.     containerTag.appendChild (anchor);    
  546. }
  547.  
  548. function insertEar (matchText, containerTag)
  549. {
  550.     if (client.smileyText)
  551.         containerTag.appendChild (document.createTextNode (matchText));
  552.  
  553.     var img = document.createElementNS ("http://www.w3.org/1999/xhtml",
  554.                                         "html:img");
  555.     img.setAttribute ("src", client.IMAGEDIR + "face-ear.gif");
  556.     img.setAttribute ("title", matchText);
  557.     containerTag.appendChild (img);
  558.     
  559. }
  560.  
  561. function insertSmiley (emoticon, containerTag)
  562. {
  563.     var type = "error";
  564.  
  565.     if (emoticon.search (/\;[-^v]?[\)>\]]/) != -1)
  566.         type = "face-wink";
  567.     else if (emoticon.search (/[=:;][-^v]?[\)>\]]/) != -1)
  568.         type = "face-smile";
  569.     else if (emoticon.search (/[=:;][-^v]?[\/\\]/) != -1)
  570.         type = "face-screw";
  571.     else if (emoticon.search (/[=:;]\~[-^v]?\(/) != -1)
  572.         type = "face-cry";
  573.     else if (emoticon.search (/[=:;][-^v]?[\(<\[]/) != -1)
  574.         type = "face-frown";
  575.     else if (emoticon.search (/\<?[=:;][-^v]?[0oO]/) != -1)
  576.         type = "face-surprise";
  577.     else if (emoticon.search (/[=:;][-^v]?[pP]/) != -1)
  578.         type = "face-tongue";
  579.     else if (emoticon.search (/\>?[=:;][\-\^\v]?[\(\|]/) != -1)
  580.         type = "face-angry";
  581.  
  582.     var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
  583.                                          "html:span");
  584.  
  585.     /* create a span to hold the emoticon text */
  586.     span.setAttribute ("class", "chatzilla-emote-txt");
  587.     span.setAttribute ("type", type);
  588.     span.appendChild (document.createTextNode (emoticon));
  589.     containerTag.appendChild (span);
  590.  
  591.     /* create an empty span after the text.  this span will have an image added
  592.      * after it with a chatzilla-emote:after css rule. using
  593.      * chatzilla-emote-txt:after is not good enough because it does not allow us
  594.      * to turn off the emoticon text, but keep the image.  ie.
  595.      * chatzilla-emote-txt { display: none; } turns off chatzilla-emote-txt:after
  596.      * as well.*/
  597.     span = document.createElementNS ("http://www.w3.org/1999/xhtml",
  598.                                      "html:span");
  599.     span.setAttribute ("class", "chatzilla-emote");
  600.     span.setAttribute ("type", type);
  601.     span.setAttribute ("title", emoticon);
  602.     containerTag.appendChild (span);
  603.     
  604. }
  605.  
  606. function mircChangeColor (colorInfo, containerTag, data)
  607. {
  608.     if (!client.enableColors)
  609.         return;
  610.  
  611.     var ary = colorInfo.match (/.(\d{1,2}|)(,(\d{1,2})|)/);
  612.  
  613.     var fgColor = ary[1];
  614.     if (fgColor > 16)
  615.         fgColor &= 16;
  616.     switch (fgColor.length)
  617.     {
  618.         case 0:
  619.             delete data.currFgColor;
  620.             delete data.currBgColor;
  621.             return;
  622.         case 1:
  623.             data.currFgColor = "0" + fgColor;
  624.             break;
  625.         case 2:
  626.             data.currFgColor = fgColor;
  627.             break;
  628.     }
  629.     if (fgColor == 1)
  630.         delete data.currFgColor;
  631.     if (ary.length >= 4)
  632.     {
  633.         var bgColor = ary[3];
  634.         if (bgColor > 16)
  635.             bgColor &= 16;
  636.         if (bgColor.length == 1)
  637.             data.currBgColor = "0" + bgColor;
  638.         else
  639.             data.currBgColor = bgColor;
  640.         if (bgColor == 0)
  641.             delete data.currBgColor;
  642.     }
  643.     data.hasColorInfo = true;
  644. }
  645.  
  646. function mircToggleBold (colorInfo, containerTag, data)
  647. {
  648.     if (!client.enableColors)
  649.         return;
  650.  
  651.     if ("isBold" in data)
  652.         delete data.isBold;
  653.     else
  654.         data.isBold = true;
  655.     data.hasColorInfo = true;
  656. }
  657.  
  658. function mircToggleUnder (colorInfo, containerTag, data)
  659. {
  660.     if (!client.enableColors)
  661.         return;
  662.  
  663.     if ("isUnderline" in data)
  664.         delete data.isUnderline;
  665.     else
  666.         data.isUnderline = true; 
  667.     data.hasColorInfo = true;
  668. }
  669.  
  670. function mircResetColor (text, containerTag, data)
  671. {
  672.     if (!client.enableColors || !("hasColorInfo" in data))
  673.         return;
  674.  
  675.     delete data.currFgColor;
  676.     delete data.currBgColor;
  677.     delete data.isBold;
  678.     delete data.isUnder;
  679.     delete data.hasColorInfo;
  680. }
  681.  
  682. function mircReverseColor (text, containerTag, data)
  683. {
  684.     if (!client.enableColors)
  685.         return;
  686.  
  687.     var tempColor = ("currFgColor" in data ? data.currFgColor : "01");
  688.  
  689.     if ("currBgColor" in data)
  690.         data.currFgColor = data.currBgColor;
  691.     else
  692.         data.currFgColor = "00";
  693.     data.currBgColor = tempColor;
  694.     data.hasColorInfo = true;
  695. }
  696.  
  697. function showCtrlChar(c, containerTag)
  698. {
  699.     var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
  700.                                          "html:span");
  701.     span.setAttribute ("class", "chatzilla-control-char");
  702.     var ctrlStr = c.charCodeAt(0).toString(16);
  703.     if (ctrlStr.length < 2)
  704.         ctrlStr = "0" + ctrlStr;
  705.     span.appendChild (document.createTextNode ("0x" + ctrlStr));
  706.     containerTag.appendChild (span);
  707. }
  708.  
  709. function insertHyphenatedWord (longWord, containerTag)
  710. {
  711.     var wordParts = splitLongWord (longWord, client.MAX_WORD_DISPLAY);
  712.     for (var i = 0; i < wordParts.length; ++i)
  713.     {
  714.         containerTag.appendChild (document.createTextNode (wordParts[i]));
  715.         if (i != wordParts.length)
  716.         {
  717.             var wbr = document.createElementNS ("http://www.w3.org/1999/xhtml",
  718.                                                 "html:wbr");
  719.             containerTag.appendChild (wbr);
  720.         }
  721.     }
  722. }
  723.  
  724. function msgIsImportant (msg, sourceNick, myNick)
  725. {    
  726.     var sv = "(" + myNick + ")";
  727.     if (client.stalkingVictims.length > 0)
  728.         sv += "|(" + client.stalkingVictims.join(")|(") + ")";
  729.     
  730.     var str = "(^|[\\W\\s])" + sv + "([\\W\\s]|$)";
  731.     var re = new RegExp(str, "i");
  732.     if (msg.search(re) != -1 || sourceNick && sourceNick.search(re) != -1)
  733.     {
  734.         playSounds(client.STALK_BEEP);
  735.         return true;
  736.     }
  737.  
  738.     return false;    
  739. }
  740.  
  741. function isStartupURL(url)
  742. {
  743.     var ary = client.INITIAL_URLS.split(/\s*;\s*/);
  744.     return arrayContains(ary, url);
  745. }
  746.     
  747. function cycleView (amount)
  748. {
  749.     var len = client.viewsArray.length;
  750.     if (len <= 1)
  751.         return;
  752.     
  753.     if (amount > len)
  754.         amount = amount % client.viewsArray.length;
  755.  
  756.     var tb = getTabForObject (client.currentObject);
  757.     if (!tb)
  758.         return;
  759.     
  760.     var vk = Number(tb.getAttribute("viewKey"));
  761.     var destKey = vk + amount;
  762.     if (destKey > len - 1)
  763.     {
  764.         /* wrap past max */
  765.         destKey -= client.viewsArray.length;
  766.     }
  767.     else if (destKey < 0)
  768.     {
  769.         /* wrap past 0 */
  770.         destKey += client.viewsArray.length;
  771.     }
  772.     
  773.     setCurrentObject (client.viewsArray[destKey].source);
  774. }
  775.  
  776. function playSounds (list)
  777. {
  778.     var ary = list.split (" ");
  779.     if (ary.length == 0)
  780.         return;
  781.     
  782.     playSound (ary[0]);
  783.     for (var i = 1; i < ary.length; ++i)
  784.         setTimeout (playSound, 250 * i, ary[i]);
  785. }
  786.  
  787. function playSound (file)
  788. {
  789.     if (!client.sound || !file)
  790.         return;
  791.     
  792.     if (file == "beep")
  793.     {
  794.         client.sound.beep();
  795.     }
  796.     else
  797.     {
  798.         var uri = Components.classes["@mozilla.org/network/standard-url;1"];
  799.         uri = uri.createInstance(Components.interfaces.nsIURI);
  800.         uri.spec = file;
  801.         client.sound.play (uri);
  802.     }
  803. }
  804.  
  805. function fillInTooltip(tipElement, id)
  806. {
  807.   const XLinkNS = "http://www.w3.org/1999/xlink";
  808.  
  809.   var retVal = false;
  810.  
  811.   var titleText = null;
  812.   var XLinkTitleText = null;
  813.   
  814.   while (!titleText && !XLinkTitleText && tipElement) {
  815.     if (tipElement.nodeType == Node.ELEMENT_NODE) {
  816.       titleText = tipElement.getAttribute("title");
  817.       XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
  818.     }
  819.     tipElement = tipElement.parentNode;
  820.   }
  821.  
  822.   var texts = [titleText, XLinkTitleText];
  823.   var tipNode = document.getElementById(id);
  824.  
  825.   for (var i = 0; i < texts.length; ++i) {
  826.     var t = texts[i];
  827.     if (t && t.search(/\S/) >= 0) {
  828.       tipNode.setAttribute("label", t);
  829.       retVal = true;
  830.     }
  831.   }
  832.  
  833.   return retVal;
  834. }
  835.  
  836. /* timer-based mainloop */
  837. function mainStep()
  838. {
  839.     if (!("initialized" in frames[0]))
  840.     {  /* voodoo required for skin switching.  When the user changes a skin,
  841.         * the output document is reloaded. this document *cannot* tell us
  842.         * it has been reloaded, because the type="content" attribute used
  843.         * on the iframe (to allow selection) also keeps the iframe from getting
  844.         * to the chrome above it.  Instead, we poll the document looking for
  845.         * the "initialized" variable. If it's not there, we reset the current
  846.         * object, and set initialized in the document. */
  847.         setClientOutput(frames[0].document);        
  848.         if (client.output)
  849.         {
  850.             var o = client.currentObject;
  851.             client.currentObject = null;
  852.             setCurrentObject (o);
  853.             frames[0].initialized = true;
  854.         }
  855.     }
  856.  
  857.     client.eventPump.stepEvents();
  858.     setTimeout ("mainStep()", client.STEP_TIMEOUT);
  859.     
  860. }
  861.  
  862. function getMsg (msgName)
  863. {
  864.     var restCount = arguments.length - 1;
  865.  
  866.     if (!("bundle" in client))
  867.     {       
  868.         client.bundle = 
  869.             srGetStrBundle("chrome://chatzilla/locale/chatzilla.properties");
  870.     }
  871.     
  872.     try 
  873.     {
  874.         if (restCount == 1 && arguments[1] instanceof Array)
  875.         {
  876.             return client.bundle.formatStringFromName (msgName, arguments[1], 
  877.                                                        arguments[1].length);
  878.         }
  879.         else if (restCount > 0)
  880.         {
  881.             var subPhrases = new Array();
  882.             for (var i = 1; i < arguments.length; ++i)
  883.                 subPhrases.push(arguments[i]);
  884.             return client.bundle.formatStringFromName (msgName, subPhrases,
  885.                                                          subPhrases.length);
  886.         }
  887.  
  888.         return client.bundle.GetStringFromName (msgName);
  889.     }
  890.     catch (ex)
  891.     {
  892.         dd ("caught exception getting value for ``" + msgName + "''\n" + ex +
  893.             "\n" + getStackTrace());
  894.         return msgName;
  895.     }
  896. }
  897.  
  898. function openQueryTab (server, nick)
  899. {    
  900.     var usr = server.addUser(nick.toLowerCase());
  901.     if (!("messages" in usr))
  902.         usr.displayHere (getMsg("cli_imsgMsg3", usr.properNick), "INFO");
  903.     server.sendData ("WHO " + nick + "\n");
  904.     return usr;
  905. }
  906.  
  907. function arraySpeak (ary, single, plural)
  908. {
  909.     var rv = "";
  910.     var and = getMsg ("arraySpeakAnd");
  911.     
  912.     switch (ary.length)
  913.     {
  914.         case 0:
  915.             break;
  916.             
  917.         case 1:
  918.             rv = ary[0];
  919.             if (single)
  920.                 rv += " " + single;            
  921.             break;
  922.  
  923.         case 2:
  924.             rv = ary[0] + " " + and + " " + ary[1];
  925.             if (plural)
  926.                 rv += " " + plural;
  927.             break;
  928.  
  929.         default:
  930.             for (var i = 0; i < ary.length - 1; ++i)
  931.                 rv += ary[i] + ", ";
  932.             rv += and + " " + ary[ary.length - 1];
  933.             if (plural)
  934.                 rv += " " + plural;
  935.             break;
  936.     }
  937.  
  938.     return rv;
  939.     
  940. }
  941.  
  942. function quicklistCallback (element, ndx, ary) 
  943. {   
  944.     /* Check whether the selected attribute == true */
  945.     if (element.getAttribute("selected") == "true")
  946.     {
  947.         /* extract the nick data from the element */
  948.         /* Hmmm, nice walk ... */
  949.         ary.push(element.childNodes[0].childNodes[2].childNodes[0].nodeValue);
  950.     }    
  951. }
  952.  
  953. function getObjectDetails (obj, rv)
  954. {
  955.     if (!rv)
  956.         rv = new Object();
  957.  
  958.     if (!obj || (typeof obj != "object"))
  959.     {
  960.         dd ("** INVALID OBJECT passed to getObjectDetails (" + obj + "). **");
  961.         dd (getStackTrace());
  962.     }
  963.  
  964.     rv.orig = obj;
  965.     rv.parent = ("parent" in obj) ? obj.parent : null;
  966.     
  967.     switch (obj.TYPE)
  968.     {
  969.         case "IRCChannel":
  970.             rv.channel = obj;
  971.             rv.server = rv.channel.parent;
  972.             rv.network = rv.server.parent;
  973.             break;
  974.  
  975.         case "IRCUser":
  976.             rv.user = obj;
  977.             rv.server = rv.user.parent;
  978.             rv.network = rv.server.parent;
  979.             break;
  980.  
  981.         case "IRCChanUser":
  982.             rv.user = obj;
  983.             rv.channel = rv.user.parent;
  984.             rv.server = rv.channel.parent;
  985.             rv.network = rv.server.parent;
  986.             break;
  987.  
  988.         case "IRCNetwork":
  989.             rv.network = obj;
  990.             if ("primServ" in rv.network)
  991.                 rv.server = rv.network.primServ;
  992.             break;
  993.  
  994.         case "IRCClient":
  995.             if ("lastNetwork" in obj)
  996.             {
  997.                 rv.network = obj.lastNetwork;
  998.                 if (rv.network.isConnected())
  999.                     rv.server = rv.network.primServ;
  1000.             }
  1001.             break;
  1002.             
  1003.         default:
  1004.             /* no setup for unknown object */
  1005.             break;
  1006.     }
  1007.  
  1008.     return rv;
  1009.     
  1010. }
  1011.  
  1012. function findDynamicRule (selector)
  1013. {
  1014.     var rules = frames[0].document.styleSheets[1].cssRules;
  1015.     
  1016.     if (selector instanceof RegExp)
  1017.         fun = "search";
  1018.     else
  1019.         fun = "indexOf";
  1020.     
  1021.     for (var i = 0; i < rules.length; ++i)
  1022.     {
  1023.         var rule = rules.item(i);
  1024.         if (rule.selectorText && rule.selectorText[fun](selector) == 0)
  1025.             return {sheet: frames[0].document.styleSheets[1], rule: rule,
  1026.                     index: i};
  1027.     }
  1028.     
  1029.     return null;
  1030. }
  1031.  
  1032. function addDynamicRule (rule)
  1033. {
  1034.     var rules = frames[0].document.styleSheets[1];
  1035.  
  1036.     var pos = rules.cssRules.length;
  1037.     rules.insertRule (rule, pos);
  1038. }
  1039.  
  1040. function setClientOutput(doc) 
  1041. {
  1042.     client.output = frames[0].document.getElementById("output");
  1043.     //XXXcreateHighlightMenu();
  1044. }
  1045.  
  1046. function createHighlightMenu()
  1047. {
  1048.     /* Look for "special" highlighting rules int he motif.  These special rules
  1049.      * are in the format ``.chatzilla-highlight[name="<display-name>"] { ... }''
  1050.      * where <display-name> is a textual description to be placed in the
  1051.      * Highlight submenu of the message area context menu.  The body of
  1052.      * these rules can be applied by the user to different irc messages.  They
  1053.      * are special becaus they do not actually match an element in the content
  1054.      * model.  The style body is copied into a new rule that matches a pettern
  1055.      * determined by the user.
  1056.      */
  1057.     function processStyleRules(rules)
  1058.     {
  1059.         for (var i = 0; i < rules.length; ++i)
  1060.         {
  1061.             var rule = rules.item(i);
  1062.             if (rule instanceof CSSStyleRule)
  1063.             {
  1064.                 var ary = rule.selectorText.
  1065.                     match(/\.chatzilla-highlight\[name=\"?([^\"]+)\"?\]/i);
  1066.                 if (ary)
  1067.                 {
  1068.                     menuitem = document.createElement("menuitem");
  1069.                     menuitem.setAttribute ("class", "highlight-menu-item");
  1070.                     menuitem.setAttribute ("label", ary[1]);
  1071.                     menuitem.setAttribute ("oncommand", "onPopupHighlight('" + 
  1072.                                            rule.style.cssText + "');");
  1073.                     menuitem.setAttribute ("style", rule.style.cssText);
  1074.                     menu.appendChild(menuitem);
  1075.                 }
  1076.             }
  1077.             else if (rule instanceof CSSImportRule)
  1078.             {
  1079.                 processStyleRules(rule.styleSheet.cssRules);
  1080.             }
  1081.         }
  1082.     }
  1083.     
  1084.     var menu = document.getElementById("highlightMenu");
  1085.     while (menu.firstChild)
  1086.         menu.removeChild(menu.firstChild);
  1087.  
  1088.     var menuitem = document.createElement("menuitem");
  1089.     menuitem.setAttribute ("label", getMsg("noStyle"));
  1090.     menuitem.setAttribute ("class", "highlight-menu-item");
  1091.     menuitem.setAttribute ("oncommand", "onPopupHighlight('');");
  1092.     menu.appendChild(menuitem);
  1093.     
  1094.     processStyleRules(frames[0].document.styleSheets[0].cssRules);
  1095. }
  1096.  
  1097. function setupMungerMenu(munger)
  1098. {
  1099.     var menu = document.getElementById("menu-munger");
  1100.     for (var entry in munger.entries)
  1101.     {
  1102.         if (entry[0] != ".")
  1103.         {
  1104.             var menuitem = document.createElement("menuitem");
  1105.             menuitem.setAttribute ("label", munger.entries[entry].description);
  1106.             menuitem.setAttribute ("id", "menu-munger-" + entry);
  1107.             menuitem.setAttribute ("type", "checkbox");
  1108.             if (munger.entries[entry].enabled)
  1109.                 menuitem.setAttribute ("checked", "true");
  1110.             menuitem.setAttribute ("oncommand", "onToggleMungerEntry('" + 
  1111.                                    entry + "');");
  1112.             menu.appendChild(menuitem);
  1113.         }
  1114.     }
  1115. }
  1116.     
  1117. var testURLs =
  1118.     ["irc:", "irc://", "irc:///", "irc:///help", "irc:///help,needkey",
  1119.     "irc://irc.foo.org", "irc://foo:6666",
  1120.     "irc://foo", "irc://irc.foo.org/", "irc://foo:6666/", "irc://foo/",
  1121.     "irc://irc.foo.org/,needpass", "irc://foo/,isserver",
  1122.     "irc://moznet/,isserver", "irc://moznet/",
  1123.     "irc://foo/chatzilla", "irc://foo/chatzilla/",
  1124.     "irc://irc.foo.org/?msg=hello%20there",
  1125.     "irc://irc.foo.org/?msg=hello%20there&ignorethis",
  1126.     "irc://irc.foo.org/%23mozilla,needkey?msg=hello%20there&ignorethis",
  1127.     "invalids",
  1128.     "irc://irc.foo.org/,isnick"];
  1129.  
  1130. function doURLTest()
  1131. {
  1132.     for (var u in testURLs)
  1133.     {
  1134.         dd ("testing url \"" + testURLs[u] + "\"");
  1135.         var o = parseIRCURL(testURLs[u]);
  1136.         if (!o)
  1137.             dd ("PARSE FAILED!");
  1138.         else
  1139.             dd (dumpObjectTree(o));
  1140.         dd ("---");
  1141.     }
  1142. }
  1143.  
  1144. function parseIRCURL (url)
  1145. {
  1146.     var specifiedHost = "";
  1147.     
  1148.     var rv = new Object();
  1149.     rv.spec = url;
  1150.     rv.host = null;
  1151.     rv.target = "";
  1152.     rv.port = 6667;
  1153.     rv.msg = "";
  1154.     rv.needpass = false;
  1155.     rv.needkey = false;
  1156.     rv.isnick = false;
  1157.     rv.isserver = false;
  1158.     
  1159.     if (url.search(/^(irc:\/?\/?)$/i) != -1)
  1160.         return rv;
  1161.  
  1162.     rv.host = client.DEFAULT_NETWORK;
  1163.     
  1164.     /* split url into <host>/<everything-else> pieces */
  1165.     var ary = url.match (/^irc:\/\/([^\/\s]+)?(\/.*)?\s*$/i);
  1166.     if (!ary)
  1167.     {
  1168.         dd ("parseIRCURL: initial split failed");
  1169.         return null;
  1170.     }
  1171.     var host = ary[1];
  1172.     var rest = (2 in ary) ? ary[2] : "";
  1173.  
  1174.     /* split <host> into server (or network) / port */
  1175.     ary = host.match (/^([^\s\:]+)?(\:\d+)?$/);
  1176.     if (!ary)
  1177.     {
  1178.         dd ("parseIRCURL: host/port split failed");
  1179.         return null;
  1180.     }
  1181.     
  1182.     if (2 in ary)
  1183.     {
  1184.         if (!(1 in ary))
  1185.         {
  1186.             dd ("parseIRCURL: port with no host");
  1187.             return null;
  1188.         }
  1189.         specifiedHost = rv.host = ary[1].toLowerCase();
  1190.         rv.isserver = true;
  1191.         rv.port = parseInt(ary[2].substr(1));
  1192.     }
  1193.     else if (1 in ary)
  1194.     {
  1195.         specifiedHost = rv.host = ary[1].toLowerCase();
  1196.         if (specifiedHost.indexOf(".") != -1)
  1197.             rv.isserver = true;
  1198.     }
  1199.  
  1200.     if (rest)
  1201.     {
  1202.         ary = rest.match (/^\/([^\,\?\s\/]*)?\/?(,[^\?]*)?(\?.*)?$/);
  1203.         if (!ary)
  1204.         {
  1205.             dd ("parseIRCURL: rest split failed ``" + rest + "''");
  1206.             return null;
  1207.         }
  1208.         
  1209.         rv.target = (1 in ary) ? 
  1210.             unescape(ary[1]).toLowerCase().replace("\n", "\\n"): "";
  1211.         var params = (2 in ary) ? ary[2].toLowerCase() : "";
  1212.         var query = (3 in ary) ? ary[3] : "";
  1213.  
  1214.         if (params)
  1215.         {
  1216.             rv.isnick =
  1217.                 (params.search (/,\s*isnick\s*,|,\s*isnick\s*$/) != -1);
  1218.             if (rv.isnick && !rv.target)
  1219.             {
  1220.                 dd ("parseIRCURL: isnick w/o target");
  1221.                     /* isnick w/o a target is bogus */
  1222.                 return null;
  1223.             }
  1224.         
  1225.             rv.isserver =
  1226.                 (params.search (/,\s*isserver\s*,|,\s*isserver\s*$/) != -1);
  1227.             if (rv.isserver && !specifiedHost)
  1228.             {
  1229.                 dd ("parseIRCURL: isserver w/o host");
  1230.                     /* isserver w/o a host is bogus */
  1231.                 return null;
  1232.             }
  1233.                 
  1234.             rv.needpass =
  1235.                 (params.search (/,\s*needpass\s*,|,\s*needpass\s*$/) != -1);
  1236.  
  1237.             rv.needkey =
  1238.                 (params.search (/,\s*needkey\s*,|,\s*needkey\s*$/) != -1);
  1239.  
  1240.         }
  1241.  
  1242.         if (query)
  1243.         {
  1244.             ary = query.match
  1245.                 (/^\?msg=([^\&]*)$|^\?msg=([^\&]*)\&|\&msg=([^\&]*)\&|\&msg=([^\&]*)$/);
  1246.             if (ary)
  1247.                 for (var i = 1; i < ary.length; i++)
  1248.                     if (i in ary)
  1249.                     {
  1250.                         rv.msg = unescape(ary[i]).replace ("\n", "\\n");
  1251.                         break;
  1252.                     }
  1253.             
  1254.         }
  1255.     }
  1256.  
  1257.     return rv;
  1258.     
  1259. }
  1260.  
  1261. function gotoIRCURL (url)
  1262. {
  1263.     if (typeof url == "string")
  1264.         url = parseIRCURL(url);
  1265.     
  1266.     if (!url)
  1267.     {
  1268.         window.alert (getMsg("badIRCURL",url));
  1269.         return;
  1270.     }
  1271.  
  1272.     if (!url.host)
  1273.     {
  1274.         /* focus the *client* view for irc:, irc:/, and irc:// (the only irc
  1275.          * urls that don't have a host.  (irc:/// implies a connect to the
  1276.          * default network.)
  1277.          */
  1278.         onSimulateCommand("/client");
  1279.         return;
  1280.     }
  1281.  
  1282.     var net;
  1283.     var pass = "";
  1284.     
  1285.     if (url.needpass)
  1286.         pass = window.prompt (getMsg("gotoIRCURLMsg2",url.spec));
  1287.     
  1288.     if (url.isserver)
  1289.     {
  1290.         var alreadyThere = false;
  1291.         for (var n in client.networks)
  1292.         {
  1293.             if ((client.networks[n].isConnected()) &&
  1294.                 (client.networks[n].primServ.connection.host == url.host) &&
  1295.                 (client.networks[n].primServ.connection.port == url.port))
  1296.             {
  1297.                 /* already connected to this server/port */
  1298.                 net = client.networks[n];
  1299.                 alreadyThere = true;
  1300.                 break;
  1301.             }
  1302.         }
  1303.  
  1304.         if (!alreadyThere)
  1305.         {
  1306.             /*
  1307.             dd ("gotoIRCURL: not already connected to " +
  1308.                 "server " + url.host + " trying to connect...");
  1309.             */
  1310.             client.onInputServer ({inputData: url.host + " " + url.port +
  1311.                                                   " " + pass});
  1312.             net = client.networks[url.host];
  1313.             if (!("pendingURLs" in net))
  1314.                 net.pendingURLs = new Array();
  1315.             net.pendingURLs.push (url);            
  1316.             return;
  1317.         }
  1318.     }
  1319.     else
  1320.     /* parsed as a network name */
  1321.     {
  1322.         net = client.networks[url.host];
  1323.         if (!net.isConnected())
  1324.         {
  1325.             /*
  1326.             dd ("gotoIRCURL: not already connected to " +
  1327.                 "network " + url.host + " trying to connect...");
  1328.             */
  1329.             client.connectToNetwork (url.host, pass);
  1330.             if (!("pendingURLs" in net))
  1331.                 net.pendingURLs = new Array();
  1332.             net.pendingURLs.push (url);            
  1333.             return;
  1334.         }
  1335.     }
  1336.     
  1337.     /* already connected, do whatever comes next in the url */
  1338.     //dd ("gotoIRCURL: connected, time to finish parsing ``" + url + "''");
  1339.     if (url.target)
  1340.     {
  1341.         var targetObject;
  1342.         var ev;
  1343.         if (url.isnick)
  1344.         {
  1345.             /* url points to a person. */
  1346.             var nick = url.target;
  1347.             var ary = url.target.split("!");
  1348.             if (ary)
  1349.             {
  1350.                 nick = ary[0];
  1351.             }    
  1352.  
  1353.             ev =  {inputData: nick, network: net, server: net.primServ};
  1354.             client.onInputQuery(ev);
  1355.             targetObject = ev.user;
  1356.         }
  1357.         else
  1358.         {
  1359.             /* url points to a channel */
  1360.             var key = "";
  1361.             if (url.needkey)
  1362.                 key = window.prompt (getMsg("gotoIRCURLMsg3", url.spec));
  1363.             ev = {inputData: url.target + " " + key,
  1364.                   network: net, server: net.primServ};
  1365.             client.onInputJoin (ev);
  1366.             targetObject = ev.channel;
  1367.         }
  1368.  
  1369.         if (url.msg)
  1370.         {
  1371.             var msg;
  1372.             if (url.msg.indexOf("\01ACTION") == 0)
  1373.             {
  1374.                 msg = filterOutput (url.msg, "ACTION", "ME!");
  1375.                 targetObject.display (msg, "ACTION", "ME!",
  1376.                                       client.currentObject);
  1377.             }
  1378.             else
  1379.             {
  1380.                 msg = filterOutput (url.msg, "PRIVMSG", "ME!");
  1381.                 targetObject.display (msg, "PRIVMSG", "ME!",
  1382.                                       client.currentObject);
  1383.             }
  1384.             targetObject.say (fromUnicode(msg));
  1385.             setCurrentObject (targetObject);
  1386.         }
  1387.     }
  1388.     else
  1389.     {
  1390.         if (!net.messages)
  1391.             net.displayHere (getMsg("gotoIRCURLMsg4",net.name),
  1392.                              "INFO");
  1393.         setCurrentObject (net);
  1394.     }
  1395.  
  1396. }
  1397.  
  1398. function setTopicText (text)
  1399. {
  1400.     var topic = client.statusBar["channel-topic"];
  1401.     var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
  1402.                                          "html:span");
  1403.     
  1404.     span.appendChild(stringToMsg(text, client.currentObject));
  1405.     topic.removeChild(topic.firstChild);
  1406.     topic.appendChild(span);
  1407. }
  1408.     
  1409. function updateNetwork(obj)
  1410. {
  1411.     var o = getObjectDetails (client.currentObject);
  1412.  
  1413.     var lag = MSG_UNKNOWN;
  1414.     var nick = "";
  1415.     if ("server" in o)
  1416.     {
  1417.         if (o.server.me)
  1418.             nick = o.server.me.properNick;
  1419.         lag = (o.server.lag != -1) ? o.server.lag : MSG_UNKNOWN;
  1420.     }
  1421.     client.statusBar["header-url"].setAttribute("value", 
  1422.                                                  client.currentObject.getURL());
  1423.     client.statusBar["header-url"].setAttribute("href", 
  1424.                                                  client.currentObject.getURL());
  1425.     client.statusBar["header-url"].setAttribute("name", 
  1426.                                                  client.currentObject.name);
  1427.     client.statusBar["server-nick"].setAttribute("value", nick);
  1428. }
  1429.  
  1430. function updateChannel (obj)
  1431. {
  1432.     if (obj && obj != client.currentObject)
  1433.         return;
  1434.     
  1435.     var o = getObjectDetails (client.currentObject);
  1436.  
  1437.     var mode = MSG_NONE, users = MSG_NONE, topic = MSG_UNKNOWN;
  1438.  
  1439.     if ("channel" in o)
  1440.     {
  1441.         mode = o.channel.mode.getModeStr();
  1442.         if (!mode)
  1443.             mode = MSG_NONE;
  1444.         users = getMsg("userCountDetails",
  1445.                        [o.channel.getUsersLength(), o.channel.opCount,
  1446.                         o.channel.voiceCount]);
  1447.         topic = o.channel.topic ? o.channel.topic : MSG_NONE;
  1448.     }
  1449.     
  1450.     client.statusBar["channel-mode"].setAttribute("value", mode);
  1451.     client.statusBar["channel-users"].setAttribute("value", users);
  1452.     var regex = new RegExp ("(\\S{" + client.MAX_WORD_DISPLAY + ",})", "g");
  1453.     var ary = topic.match(regex);
  1454.     if (ary && ary.length)
  1455.     {
  1456.         for (var i = 0; i < ary.length; ++i)
  1457.         {
  1458.             var hyphenated = hyphenateWord(ary[i], client.MAX_WORD_DISPLAY);
  1459.             topic = topic.replace(ary[i], hyphenated);
  1460.         }
  1461.     }        
  1462.  
  1463.     client.statusBar["channel-topic"].firstChild.data = topic;
  1464.  
  1465. }
  1466.  
  1467. function updateTitle (obj)
  1468. {
  1469.     if (!("currentObject" in client) || (obj && obj != client.currentObject))
  1470.         return;
  1471.  
  1472.     var tstring;
  1473.     var o = getObjectDetails (client.currentObject);
  1474.     var net = "network" in o ? o.network.name : "";
  1475.     var nick = "";
  1476.     
  1477.     switch (client.currentObject.TYPE)
  1478.     {
  1479.         case "IRCNetwork":
  1480.             var serv = "", port = "";
  1481.             if ("server" in o)
  1482.             {
  1483.                 serv = o.server.connection.host;
  1484.                 port = o.server.connection.port;
  1485.                 if (o.server.me)
  1486.                     nick = o.server.me.properNick;
  1487.                 tstring = getMsg("updateTitleNetwork", [nick, net, serv, port]);
  1488.             }
  1489.             else
  1490.             {
  1491.                 nick = client.currentObject.INITIAL_NICK;
  1492.                 tstring = getMsg("updateTitleNetwork2", [nick, net]);
  1493.             }
  1494.             break;
  1495.             
  1496.         case "IRCChannel":
  1497.             var chan = "", mode = "", topic = "";
  1498.             nick = "me" in o.parent ? o.parent.me.properNick : 
  1499.                 getMsg ("updateTitleNoNick");
  1500.             chan = o.channel.name;
  1501.             mode = o.channel.mode.getModeStr();
  1502.             if (!mode)
  1503.                 mode = getMsg("updateTitleNoMode");
  1504.             topic = o.channel.topic ? o.channel.topic : 
  1505.                                       getMsg("updateTitleNoTopic");
  1506.  
  1507.             tstring = getMsg("updateTitleChannel", [nick, chan, mode, topic]);
  1508.             break;
  1509.  
  1510.         case "IRCUser":
  1511.             nick = client.currentObject.properNick;
  1512.             var source = "";
  1513.             if (client.currentObject.name)
  1514.             {
  1515.                 source = client.currentObject.name + "@" +
  1516.                     client.currentObject.host;
  1517.             }
  1518.             tstring = getMsg("updateTitleUser", [nick, source]);
  1519.             //client.statusBar["channel-topic"].setAttribute("value", tstring);
  1520.             client.statusBar["channel-topic"].firstChild.data = tstring;
  1521.             break;
  1522.  
  1523.         default:
  1524.             tstring = getMsg("updateTitleUnknown");
  1525.             break;
  1526.     }
  1527.  
  1528.     if (!client.uiState["tabstrip"])
  1529.     {
  1530.         var actl = new Array();
  1531.         for (var i in client.activityList)
  1532.             actl.push ((client.activityList[i] == "!") ?
  1533.                        (Number(i) + 1) + "!" : (Number(i) + 1));
  1534.         if (actl.length > 0)
  1535.             tstring = getMsg("updateTitleWithActivity",
  1536.                               [tstring, actl.join (MSG_CSP)]);
  1537.     }
  1538.  
  1539.     document.title = tstring;
  1540.  
  1541. }
  1542.  
  1543. function multilineInputMode (state)
  1544. {
  1545.     var multiInput = document.getElementById("multiline-input");
  1546.     var singleInput = document.getElementById("input");
  1547.     var splitter = document.getElementById("input-splitter");
  1548.     var iw = document.getElementById("input-widgets");
  1549.     var h;
  1550.  
  1551.     client._mlMode = state;
  1552.     
  1553.     if (state)  /* turn on multiline input mode */
  1554.     {
  1555.         
  1556.         h = iw.getAttribute ("lastHeight");
  1557.         if (h)
  1558.             iw.setAttribute ("height", h); /* restore the slider position */
  1559.  
  1560.         singleInput.setAttribute ("collapsed", "true");
  1561.         splitter.setAttribute ("collapsed", "false");
  1562.         multiInput.setAttribute ("collapsed", "false");
  1563.         client.input = multiInput;
  1564.     }
  1565.     else  /* turn off multiline input mode */
  1566.     {
  1567.         h = iw.getAttribute ("height");
  1568.         iw.setAttribute ("lastHeight", h); /* save the slider position */
  1569.         iw.removeAttribute ("height");     /* let the slider drop */
  1570.         
  1571.         splitter.setAttribute ("collapsed", "true");
  1572.         multiInput.setAttribute ("collapsed", "true");
  1573.         singleInput.setAttribute ("collapsed", "false");
  1574.         client.input = singleInput;
  1575.     }
  1576.  
  1577.     client.input.focus();
  1578. }
  1579.  
  1580. function focusInput ()
  1581. {
  1582.     client.input.focus();
  1583. }
  1584.  
  1585. function newInlineText (data, className, tagName)
  1586. {
  1587.     if (typeof tagName == "undefined")
  1588.         tagName = "html:span";
  1589.     
  1590.     var a = document.createElementNS ("http://www.w3.org/1999/xhtml",
  1591.                                       tagName);
  1592.     if (className)
  1593.         a.setAttribute ("class", className);
  1594.  
  1595.     switch (typeof data)
  1596.     {
  1597.         case "string":
  1598.             a.appendChild (document.createTextNode (data));
  1599.             break;
  1600.             
  1601.         case "object":
  1602.             for (var p in data)
  1603.                 if (p != "data")
  1604.                     a.setAttribute (p, data[p]);
  1605.                 else
  1606.                     a.appendChild (document.createTextNode (data[p]));
  1607.             break;
  1608.  
  1609.         case "undefined":
  1610.             break;
  1611.  
  1612.         default:
  1613.             dd ("** INVALID TYPE ('" + typeof data + "') passed to " +
  1614.                 "newInlineText.");
  1615.             break;
  1616.             
  1617.     }
  1618.  
  1619.     return a;
  1620.     
  1621. }
  1622.  
  1623. function stringToMsg (message, obj)
  1624. {
  1625.     var ary = message.split ("\n");
  1626.     var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
  1627.                                          "html:span");
  1628.     var data = getObjectDetails(obj);
  1629.     
  1630.     if (ary.length == 1)
  1631.         client.munger.munge(ary[0], span, data);
  1632.     else
  1633.     {
  1634.         for (var l = 0; l < ary.length - 1; ++l)
  1635.         {
  1636.             client.munger.munge(ary[l], span, data);
  1637.             span.appendChild 
  1638.                 (document.createElementNS ("http://www.w3.org/1999/xhtml",
  1639.                                            "html:br"));
  1640.         }
  1641.         client.munger.munge(ary[l], span, data);
  1642.     }
  1643.  
  1644.     return span;
  1645. }
  1646.  
  1647. function setCurrentObject (obj)
  1648. {
  1649.     if (!obj.messages)
  1650.     {
  1651.         dd ("** INVALID OBJECT passed to setCurrentObject **");
  1652.         return;
  1653.     }
  1654.  
  1655.     if (("currentObject" in client && client.currentObject == obj) ||
  1656.         !("output" in client) || !client.output)
  1657.         return;
  1658.         
  1659.     var tb, userList;
  1660.  
  1661.     if ("currentObject" in client && client.currentObject)
  1662.     {
  1663.         tb = getTabForObject(client.currentObject);
  1664.     }
  1665.     if (tb)
  1666.     {
  1667.         tb.selected = false;
  1668.         tb.setAttribute ("state", "normal");
  1669.     }
  1670.     
  1671.     if (client.output.firstChild)
  1672.         client.output.removeChild (client.output.firstChild);
  1673.     client.output.appendChild (obj.messages);
  1674.  
  1675.     /* Unselect currently selected users. */
  1676.     userList = document.getElementById("user-list");
  1677.     if (isVisible("user-list-box"))
  1678.     {
  1679.         /* Remove currently selected items before this tree gets rerooted,
  1680.          * because it seems to remember the selections for eternity if not. */
  1681.         if (userList.treeBoxObject.selection)
  1682.             userList.treeBoxObject.selection.clearSelection ();
  1683.  
  1684.         if (obj.TYPE == "IRCChannel")
  1685.             client.rdf.setTreeRoot ("user-list", obj.getGraphResource());
  1686.         else
  1687.             client.rdf.setTreeRoot ("user-list", client.rdf.resNullChan);
  1688.     }
  1689.     
  1690.     client.currentObject = obj;
  1691.     tb = getTabForObject(obj);
  1692.     if (tb)
  1693.     {
  1694.         tb.selected = true;
  1695.         tb.setAttribute ("state", "current");
  1696.     }
  1697.     
  1698.     var vk = Number(tb.getAttribute("viewKey"));
  1699.     delete client.activityList[vk];
  1700.  
  1701.     updateNetwork();
  1702.     updateChannel();
  1703.     updateTitle ();
  1704.  
  1705.     if (client.PRINT_DIRECTION == 1)
  1706.     {
  1707.         scrollDown();
  1708.         setTimeout ("scrollDown()", 500);
  1709.         setTimeout ("scrollDown()", 1000);
  1710.         setTimeout ("scrollDown()", 2000);
  1711.     }
  1712.     
  1713.     onTopicEditEnd();
  1714.  
  1715.     if (client.currentObject.TYPE == "IRCChannel")
  1716.         client.statusBar["channel-topic"].setAttribute ("editable", "true");
  1717.     else
  1718.         client.statusBar["channel-topic"].removeAttribute ("editable");
  1719.  
  1720.     var status = document.getElementById("offline-status");
  1721.     if (client.currentObject == client)
  1722.     {
  1723.         status.setAttribute ("hidden", "true");
  1724.     }
  1725.     else
  1726.     {
  1727.         status.removeAttribute ("hidden");
  1728.         var details = getObjectDetails(client.currentObject);
  1729.         if ("network" in details && details.network.isConnected())
  1730.             status.removeAttribute ("offline");    
  1731.         else
  1732.             status.setAttribute ("offline", "true");
  1733.     }
  1734. }
  1735.  
  1736. function scrollDown ()
  1737. {
  1738.     window.frames[0].scrollTo(0, window.frames[0].document.height);
  1739. }
  1740.  
  1741. /* valid values for |what| are "superfluous", "activity", and "attention".
  1742.  * final value for state is dependant on priority of the current state, and the
  1743.  * new state. the priority is: normal < superfluous < activity < attention.
  1744.  */
  1745. function setTabState (source, what)
  1746. {
  1747.     if (typeof source != "object")
  1748.         source = client.viewsArray[source].source;
  1749.     
  1750.     var tb = getTabForObject (source, true);
  1751.     var vk = Number(tb.getAttribute("viewKey"));
  1752.     
  1753.     if ("currentObject" in client && client.currentObject != source)
  1754.     {
  1755.         var state = tb.getAttribute ("state");
  1756.         if (state == what)
  1757.         {
  1758.             /* if the tab state has an equal priority to what we are setting
  1759.              * then blink it */
  1760.             tb.setAttribute ("state", "normal");
  1761.             setTimeout (setTabState, 200, vk, what);
  1762.         }
  1763.         else
  1764.         {
  1765.             if (state == "normal" || state == "superfluous" ||
  1766.                (state == "activity" && what == "attention"))
  1767.             {
  1768.                 /* if the tab state has a lower priority than what we are
  1769.                  * setting, change it to the new state */
  1770.                 tb.setAttribute ("state", what);
  1771.                 /* we only change the activity list if priority has increased */
  1772.                 if (what == "attention")
  1773.                    client.activityList[vk] = "!";
  1774.                 else if (what == "activity")
  1775.                     client.activityList[vk] = "+";
  1776.                 else
  1777.                 {
  1778.                    /* this is functionally equivalent to "+" for now */
  1779.                    client.activityList[vk] = "-";
  1780.                 }
  1781.                 updateTitle();
  1782.             }
  1783.             else
  1784.             {
  1785.                 /* the current state of the tab has a higher priority than the
  1786.                  * new state.
  1787.                  * blink the new lower state quickly, then back to the old */
  1788.                 tb.setAttribute ("state", what);
  1789.                 setTimeout (setTabState, 200, vk, state);
  1790.             }
  1791.         }
  1792.     }
  1793. }
  1794.  
  1795. function notifyAttention (source)
  1796. {
  1797.     if (typeof source != "object")
  1798.         source = client.viewsArray[source].source;
  1799.     
  1800.     if (client.currentObject != source)
  1801.     {
  1802.         var tb = getTabForObject (source, true);
  1803.         var vk = Number(tb.getAttribute("viewKey"));
  1804.  
  1805.         tb.setAttribute ("state", "attention");
  1806.         client.activityList[vk] = "!";
  1807.         updateTitle();
  1808.     }
  1809.  
  1810.     if (client.FLASH_WINDOW)
  1811.         window.getAttention();
  1812.     
  1813. }
  1814.  
  1815. /* gets the toolbutton associated with an object
  1816.  * if |create| is present, and true, create if not found */
  1817. function getTabForObject (source, create)
  1818. {
  1819.     var name;
  1820.  
  1821.     if (!source)
  1822.     {
  1823.         dd ("** UNDEFINED passed to getTabForObject **");
  1824.         dd (getStackTrace());
  1825.         return null;
  1826.     }
  1827.     
  1828.     create = (typeof create != "undefined") ? Boolean(create) : false;
  1829.  
  1830.     switch (source.TYPE)
  1831.     {
  1832.         case "IRCChanUser":
  1833.         case "IRCUser":
  1834.             name = source.nick;
  1835.             break;
  1836.             
  1837.         case "IRCNetwork":
  1838.         case "IRCChannel":
  1839.         case "IRCClient":
  1840.             name = source.name;
  1841.             break;
  1842.  
  1843.         default:
  1844.             dd ("** INVALID OBJECT passed to getTabForObject **");
  1845.             return null;
  1846.     }
  1847.  
  1848.     var tb, id = "tb[" + name + "]";
  1849.     var matches = 1;
  1850.  
  1851.     for (var i in client.viewsArray)
  1852.     {
  1853.         if (client.viewsArray[i].source == source)
  1854.         {
  1855.             tb = client.viewsArray[i].tb;
  1856.             break;
  1857.         }
  1858.         else
  1859.             if (client.viewsArray[i].tb.getAttribute("id") == id)
  1860.                 id = "tb[" + name + "<" + (++matches) + ">]";
  1861.     }
  1862.  
  1863.     if (!tb && create) /* not found, create one */
  1864.     {
  1865.         var views = document.getElementById ("views-tbar-inner");
  1866.         tb = document.createElement ("tab");
  1867.         tb.setAttribute ("ondraggesture",
  1868.                          "nsDragAndDrop.startDrag(event, tabDNDObserver);");
  1869.         tb.setAttribute ("href", source.getURL());
  1870.         tb.setAttribute ("name", source.name);
  1871.         tb.setAttribute ("onclick", "onTabClick(" + id.quote() + ");");
  1872.         tb.setAttribute ("crop", "right");
  1873.         
  1874.         tb.setAttribute ("class", "tab-bottom view-button");
  1875.         tb.setAttribute ("id", id);
  1876.         tb.setAttribute ("state", "normal");
  1877.  
  1878.         client.viewsArray.push ({source: source, tb: tb});
  1879.         tb.setAttribute ("viewKey", client.viewsArray.length - 1);
  1880.         if (matches > 1)
  1881.             tb.setAttribute("label", name + "<" + matches + ">");
  1882.         else
  1883.             tb.setAttribute("label", name);
  1884.  
  1885.         views.appendChild (tb);        
  1886.     }
  1887.  
  1888.     return tb;
  1889.     
  1890. }
  1891.  
  1892. function retrieveURLFromData (aData, flavour)
  1893. {
  1894.     switch (flavour) 
  1895.     {
  1896.         case "text/unicode":
  1897.             if (aData.search(client.linkRE) != -1)
  1898.                 return aData;
  1899.             else
  1900.                 return null;
  1901.  
  1902.         case "text/x-moz-url":
  1903.             var data = aData.toString();
  1904.             var separator = data.indexOf("\n");
  1905.             if (separator != -1)
  1906.                 data = data.substr(0, separator);
  1907.             return data;
  1908.  
  1909.         case "application/x-moz-file":
  1910.             return aData.URL;
  1911.     }
  1912.  
  1913.     return null;                                                   
  1914. }
  1915.  
  1916.  
  1917. var contentDropObserver = new Object();
  1918.  
  1919. contentDropObserver.onDragOver =
  1920. function tabdnd_dover (aEvent, aFlavour, aDragSession)
  1921. {
  1922.     if (aEvent.getPreventDefault())
  1923.         return;
  1924.  
  1925.     if (aDragSession.sourceDocument == aEvent.view.document)
  1926.     {
  1927.         aDragSession.canDrop = false;
  1928.         return;
  1929.     }
  1930. }
  1931.  
  1932. contentDropObserver.onDrop =
  1933. function tabdnd_drop (aEvent, aXferData, aDragSession)
  1934. {
  1935.     var url = retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);
  1936.     if (!url)
  1937.         return;
  1938.     
  1939.     if (url.search(/\.css$/i) != -1  && confirm (getMsg("tabdnd_drop", url)))
  1940.     {
  1941.         onSimulateCommand("/css " + url);
  1942.     }
  1943.     else if (url.search(/^irc:\/\//i) != -1)
  1944.     {
  1945.         gotoIRCURL (url);
  1946.     }
  1947. }
  1948.  
  1949. contentDropObserver.getSupportedFlavours =
  1950. function tabdnd_gsf ()
  1951. {
  1952.     var flavourSet = new FlavourSet();
  1953.     flavourSet.appendFlavour("text/x-moz-url");
  1954.     flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
  1955.     flavourSet.appendFlavour("text/unicode");
  1956.     return flavourSet;
  1957. }
  1958.  
  1959. var tabDNDObserver = new Object();
  1960.  
  1961. tabDNDObserver.onDragStart =
  1962. function tabdnd_dstart (aEvent, aXferData, aDragAction)
  1963. {
  1964.     var tb = aEvent.currentTarget;
  1965.     var href = tb.getAttribute("href");
  1966.     var name = tb.getAttribute("name");
  1967.     
  1968.     aXferData.data = new TransferData();
  1969.     /* x-moz-url has the format "<url>\n<name>", goodie */
  1970.     aXferData.data.addDataForFlavour("text/x-moz-url", href + "\n" + name);
  1971.     aXferData.data.addDataForFlavour("text/unicode", href);
  1972.     aXferData.data.addDataForFlavour("text/html", "<a href='" + href + "'>" +
  1973.                                      name + "</a>");
  1974. }
  1975.  
  1976. function deleteTab (tb)
  1977. {
  1978.     var i, key = Number(tb.getAttribute("viewKey"));
  1979.     
  1980.     if (!isNaN(key))
  1981.     {
  1982.         if ("isPermanent" in client.viewsArray[key].source &&
  1983.             client.viewsArray[key].source.isPermanent)
  1984.         {
  1985.             window.alert (getMsg("deleteTabMsg"));
  1986.             return -1;
  1987.         }
  1988.         else
  1989.         {
  1990.             /* re-index higher toolbuttons */
  1991.             for (i = key + 1; i < client.viewsArray.length; i++)
  1992.             {
  1993.                 client.viewsArray[i].tb.setAttribute ("viewKey", i - 1);
  1994.             }
  1995.             arrayRemoveAt(client.viewsArray, key);
  1996.             var tbinner = document.getElementById("views-tbar-inner");
  1997.             tbinner.removeChild(tb);
  1998.         }
  1999.     }
  2000.     else
  2001.         dd  ("*** INVALID OBJECT passed to deleteTab (" + tb + ") " +
  2002.              "no viewKey attribute. (" + key + ")");
  2003.  
  2004.     return key;
  2005.  
  2006. }
  2007.  
  2008. function filterOutput (msg, msgtype)
  2009. {
  2010.     if ("outputFilters" in client)
  2011.     {
  2012.         for (var f in client.outputFilters)
  2013.         {
  2014.             if (client.outputFilters[f].enabled)
  2015.                 msg = client.outputFilters[f].func(msg, msgtype);
  2016.         }
  2017.     }
  2018.  
  2019.     return msg;
  2020. }
  2021.  
  2022. client.connectToNetwork =
  2023. function cli_connet (netname, pass)
  2024. {
  2025.     if (!(netname in client.networks))
  2026.     {
  2027.         display (getMsg("cli_attachNoNet", netname), "ERROR");
  2028.         return false;
  2029.     }
  2030.     
  2031.     var netobj = client.networks[netname];
  2032.  
  2033.     if (!("messages" in netobj))
  2034.         netobj.displayHere (getMsg("cli_attachOpened", netname), "INFO");
  2035.     setCurrentObject(netobj);
  2036.  
  2037.     if (netobj.isConnected())
  2038.     {
  2039.         netobj.display (getMsg("cli_attachAlreadyThere", netname), "ERROR");
  2040.         return true;
  2041.     }
  2042.     
  2043.     if (CIRCNetwork.prototype.INITIAL_NICK == client.defaultNick)
  2044.         CIRCNetwork.prototype.INITIAL_NICK =
  2045.             prompt (getMsg("cli_attachGetNick"), client.defaultNick);
  2046.     
  2047.     if (!("connecting" in netobj))
  2048.         netobj.display (getMsg("cli_attachWorking",netobj.name), "INFO");
  2049.     netobj.connect(pass);
  2050.     return true;
  2051. }
  2052.  
  2053.  
  2054. client.getURL =
  2055. function cli_geturl ()
  2056. {
  2057.     return "irc://";
  2058. }
  2059.  
  2060. client.load =
  2061. function cli_load(url, obj)
  2062. {
  2063.     if (!client._loader)
  2064.     {
  2065.         const LOADER_CTRID = "@mozilla.org/moz/jssubscript-loader;1";
  2066.         const mozIJSSubScriptLoader = 
  2067.             Components.interfaces.mozIJSSubScriptLoader;
  2068.  
  2069.         var cls;
  2070.         if ((cls = Components.classes[LOADER_CTRID]))
  2071.             client._loader = cls.createInstance (mozIJSSubScriptLoader);
  2072.     }
  2073.     
  2074.     try {
  2075.         client.currentObject.display (getMsg("cli_loadLoading", url));
  2076.         client._loader.loadSubScript (url, obj);
  2077.     }
  2078.     catch (ex)
  2079.     {
  2080.         var msg = getMsg("cli_loadError", ex.lineNumber, ex.fileName, ex);
  2081.         client.currentObject.display (msg, "ERROR");        
  2082.     }
  2083. }
  2084.  
  2085.     
  2086. client.sayToCurrentTarget =
  2087. function cli_say(msg)
  2088. {
  2089.     switch (client.currentObject.TYPE)
  2090.     {
  2091.         case "IRCChannel":
  2092.         case "IRCUser":
  2093.         case "IRCChanUser":
  2094.             msg = filterOutput (msg, "PRIVMSG");
  2095.             client.currentObject.display (msg, "PRIVMSG", "ME!",
  2096.                                           client.currentObject);
  2097.             client.currentObject.say (fromUnicode(msg));
  2098.             break;
  2099.  
  2100.         case "IRCClient":
  2101.             client.onInputEval ({inputData: msg});
  2102.             break;
  2103.  
  2104.         default:
  2105.             if (msg != "")
  2106.                 client.currentObject.display 
  2107.                     (getMsg("cli_sayMsg", client.currentObject.TYPE), "ERROR");
  2108.             break;
  2109.     }
  2110.  
  2111. }
  2112.  
  2113. CIRCNetwork.prototype.display =
  2114. function n_display (message, msgtype, sourceObj, destObj)
  2115. {
  2116.     var o = getObjectDetails(client.currentObject);
  2117.  
  2118.     if (client.SLOPPY_NETWORKS && client.currentObject != client &&
  2119.         client.currentObject != this && o.network == this &&
  2120.         o.server.connection.isConnected)
  2121.         client.currentObject.display (message, msgtype, sourceObj, destObj);
  2122.     else
  2123.         this.displayHere (message, msgtype, sourceObj, destObj);
  2124. }
  2125.  
  2126. CIRCUser.prototype.display =
  2127. function u_display(message, msgtype, sourceObj, destObj)
  2128. {
  2129.     if ("messages" in this)
  2130.         this.displayHere (message, msgtype, sourceObj, destObj);
  2131.     else
  2132.     {
  2133.         var o = getObjectDetails(client.currentObject);
  2134.         if (o.server.connection.isConnected &&
  2135.             o.network == this.parent.parent &&
  2136.             client.currentObject.TYPE != "IRCUser")
  2137.             client.currentObject.display (message, msgtype, sourceObj, destObj);
  2138.         else
  2139.             this.parent.parent.displayHere (message, msgtype, sourceObj,
  2140.                                             destObj);
  2141.     }
  2142. }
  2143.  
  2144. function display (message, msgtype, sourceObj, destObj)
  2145. {
  2146.     client.currentObject.display (message, msgtype, sourceObj, destObj);
  2147. }
  2148.  
  2149. client.display =
  2150. client.displayHere =
  2151. CIRCNetwork.prototype.displayHere =
  2152. CIRCChannel.prototype.display =
  2153. CIRCChannel.prototype.displayHere =
  2154. CIRCUser.prototype.displayHere =
  2155. function __display(message, msgtype, sourceObj, destObj)
  2156. {            
  2157.     var canMergeData = false;
  2158.     var canCollapseRow = false;
  2159.  
  2160.     function setAttribs (obj, c, attrs)
  2161.     {
  2162.         if (attrs)
  2163.         {
  2164.             for (var a in attrs)
  2165.                 obj.setAttribute (a, attrs[a]);
  2166.         }
  2167.         obj.setAttribute ("class", c);
  2168.         obj.setAttribute ("msg-type", msgtype);
  2169.         obj.setAttribute ("msg-dest", toAttr);
  2170.         obj.setAttribute ("dest-type", toType);
  2171.         obj.setAttribute ("view-type", viewType); 
  2172.         if (fromAttr)
  2173.             if (fromUser)
  2174.                 obj.setAttribute ("msg-user", fromAttr);
  2175.             else
  2176.                 obj.setAttribute ("msg-source", fromAttr);
  2177.    }
  2178.  
  2179.     var blockLevel = false; /* true if this row should be rendered at block
  2180.                              * level, (like, if it has a really long nickname
  2181.                              * that might disturb the rest of the layout)     */
  2182.     var o = getObjectDetails (this);          /* get the skinny on |this|     */
  2183.     var me;
  2184.     if ("server" in o && "me" in o.server)
  2185.     {
  2186.         me = o.server.me;    /* get the object representing the user           */
  2187.     }
  2188.     if (sourceObj == "ME!") sourceObj = me;   /* if the caller to passes "ME!"*/
  2189.     if (destObj == "ME!") destObj = me;       /* substitute the actual object */
  2190.  
  2191.     var fromType = (sourceObj && sourceObj.TYPE) ? sourceObj.TYPE : "unk";
  2192.     var fromUser = (fromType.search(/IRC.*User/) != -1);    
  2193.     var fromAttr;
  2194.     if      (fromUser && sourceObj == me)  fromAttr = me.nick + " ME!";
  2195.     else if (fromUser)                     fromAttr = sourceObj.nick;
  2196.     else if (typeof sourceObj == "object") fromAttr = sourceObj.name;
  2197.         
  2198.     var toType = (destObj) ? destObj.TYPE : "unk";
  2199.     var toAttr;
  2200.     
  2201.     if (destObj && destObj == me)
  2202.         toAttr = me.nick + " ME!";
  2203.     else if (toType == "IRCUser")
  2204.         toAttr = destObj.nick;
  2205.     else if (typeof destObj == "object")
  2206.         toAttr = destObj.name;
  2207.  
  2208.     /* isImportant means to style the messages as important, and flash the
  2209.      * window, getAttention means just flash the window. */
  2210.     var isImportant = false, getAttention = false, isSuperfluous = false;
  2211.     var viewType = this.TYPE;
  2212.     var code;
  2213.     var msgRow = document.createElementNS("http://www.w3.org/1999/xhtml",
  2214.                                           "html:tr");
  2215.     setAttribs(msgRow, "msg");
  2216.  
  2217.     //dd ("fromType is " + fromType + ", fromAttr is " + fromAttr);
  2218.     var d = new Date();
  2219.     var mins = d.getMinutes();
  2220.     if (mins < 10)
  2221.         mins = "0" + mins;
  2222.     var statusString;
  2223.     
  2224.     if (fromUser)
  2225.     {
  2226.         statusString =
  2227.             getMsg("cli_statusString", [d.getMonth() + 1, d.getDate(),
  2228.                                         d.getHours(), mins,
  2229.                                         sourceObj.nick + "!" + 
  2230.                                         sourceObj.name + "@" + sourceObj.host]);
  2231.     }
  2232.     else
  2233.     {
  2234.         statusString =
  2235.             getMsg("cli_statusString", [d.getMonth() + 1, d.getDate(),
  2236.                                         d.getHours(), mins,
  2237.                                         sourceObj ? sourceObj.name : this.name]);
  2238.     }
  2239.     
  2240.     if (fromType.search(/IRC.*User/) != -1 &&
  2241.         msgtype.search(/PRIVMSG|ACTION|NOTICE/) != -1)
  2242.     {
  2243.         /* do nick things here */
  2244.         var nick;
  2245.         var nickURL;
  2246.         
  2247.         if (sourceObj != me)
  2248.         {
  2249.             nick = sourceObj.properNick;
  2250.             if ("getURL" in sourceObj)
  2251.                 nickURL = sourceObj.getURL();
  2252.             if (toType == "IRCUser") /* msg from user to me */
  2253.             {
  2254.                 getAttention = true;
  2255.                 this.defaultCompletion = "/msg " + nick + " ";
  2256.             }
  2257.             else /* msg from user to channel */
  2258.             {
  2259.                 if (typeof (message == "string") && me)
  2260.                 {
  2261.                     isImportant = msgIsImportant (message, nick, me.nick);
  2262.                     if (isImportant)
  2263.                         this.defaultCompletion = nick +
  2264.                             client.ADDRESSED_NICK_SEP + " ";
  2265.                 }
  2266.             }
  2267.         }
  2268.         else if (toType == "IRCUser") /* msg from me to user */
  2269.         {
  2270.             if (this.TYPE == "IRCUser")
  2271.                 nick    = sourceObj.properNick;
  2272.             else
  2273.                 nick    = destObj.properNick;
  2274.         }
  2275.         else /* msg from me to channel */
  2276.         {
  2277.             nick = sourceObj.properNick;
  2278.         }
  2279.         
  2280.         if (!("mark" in this))
  2281.             this.mark = "odd";
  2282.         
  2283.         if (!("lastNickDisplayed" in this) ||
  2284.             this.lastNickDisplayed != nick)
  2285.         {
  2286.             this.lastNickDisplayed = nick;
  2287.             this.mark = (this.mark == "even") ? "odd" : "even";
  2288.         }
  2289.  
  2290.         var msgSource = document.createElementNS("http://www.w3.org/1999/xhtml",
  2291.                                                  "html:td");
  2292.         setAttribs (msgSource, "msg-user", {statusText: statusString});
  2293.         if (isImportant)
  2294.             msgSource.setAttribute ("important", "true");
  2295.         if (nick.length > client.MAX_NICK_DISPLAY)
  2296.             blockLevel = true;
  2297.         if (nickURL)
  2298.         {
  2299.             var nick_anchor =
  2300.                 document.createElementNS("http://www.w3.org/1999/xhtml",
  2301.                                          "html:a");
  2302.             nick_anchor.setAttribute ("class", "chatzilla-link");
  2303.             nick_anchor.setAttribute ("href", nickURL);
  2304.             nick_anchor.appendChild (newInlineText (nick));
  2305.             msgSource.appendChild (nick_anchor);
  2306.         }
  2307.         else
  2308.         {
  2309.             msgSource.appendChild (newInlineText (nick));
  2310.         }
  2311.         msgRow.appendChild (msgSource);
  2312.         canMergeData = client.COLLAPSE_MSGS;
  2313.         canCollapseRow = client.COLLAPSE_ROWS;
  2314.  
  2315.     }
  2316.     else
  2317.     {
  2318.         isSuperfluous = true;
  2319.         if (!client.debugMode && msgtype in client.responseCodeMap)
  2320.         {
  2321.             code = client.responseCodeMap[msgtype];
  2322.         }
  2323.         else
  2324.         {
  2325.             if (!client.debugMode && client.HIDE_CODES)
  2326.                 code = client.DEFAULT_RESPONSE_CODE;
  2327.             else
  2328.                 code = "[" + msgtype + "]";
  2329.         }
  2330.         
  2331.         /* Display the message code */
  2332.         var msgType = document.createElementNS("http://www.w3.org/1999/xhtml",
  2333.                                                "html:td");
  2334.         setAttribs (msgType, "msg-type", {statusText: statusString});
  2335.  
  2336.         msgType.appendChild (newInlineText (code));
  2337.         msgRow.appendChild (msgType);
  2338.     }
  2339.              
  2340.     if (message)
  2341.     {
  2342.         var msgData = document.createElementNS("http://www.w3.org/1999/xhtml",
  2343.                                                "html:td");
  2344.         setAttribs (msgData, "msg-data", {statusText: statusString, 
  2345.                                           colspan: client.INITIAL_COLSPAN});
  2346.         if (isImportant)
  2347.             msgData.setAttribute ("important", "true");
  2348.  
  2349.         if ("mark" in this)
  2350.             msgData.setAttribute ("mark", this.mark);
  2351.         
  2352.         if (typeof message == "string")
  2353.         {
  2354.             msgData.appendChild (stringToMsg (message, this));
  2355.         }
  2356.         else
  2357.             msgData.appendChild (message);
  2358.  
  2359.         msgRow.appendChild (msgData);
  2360.     }
  2361.  
  2362.     if (isImportant)
  2363.         msgRow.setAttribute ("important", "true");
  2364.  
  2365.     if (blockLevel)
  2366.     {
  2367.         /* putting a div here crashes mozilla, so fake it with nested tables
  2368.          * for now */
  2369.         var tr = document.createElementNS ("http://www.w3.org/1999/xhtml",
  2370.                                            "html:tr");
  2371.         tr.setAttribute ("class", "msg-nested-tr");
  2372.         var td = document.createElementNS ("http://www.w3.org/1999/xhtml",
  2373.                                            "html:td");        
  2374.         td.setAttribute ("class", "msg-nested-td");
  2375.         td.setAttribute ("colspan", "2");
  2376.         
  2377.         tr.appendChild(td);
  2378.         var table = document.createElementNS ("http://www.w3.org/1999/xhtml",
  2379.                                               "html:table");
  2380.         table.setAttribute ("class", "msg-nested-table");
  2381.         table.setAttribute ("cellpadding", "0");
  2382.         
  2383.         td.appendChild (table);
  2384.         var tbody =  document.createElementNS ("http://www.w3.org/1999/xhtml",
  2385.                                                "html:tbody");
  2386.         
  2387.         tbody.appendChild (msgRow);
  2388.         table.appendChild (tbody);
  2389.         msgRow = tr;
  2390.         canMergeData = false;
  2391.         canCollapseRow = false;
  2392.     }
  2393.  
  2394.     addHistory (this, msgRow, canMergeData, canCollapseRow);
  2395.     if (isImportant || getAttention)
  2396.     {
  2397.         setTabState(this, "attention");
  2398.         if (client.FLASH_WINDOW)
  2399.             window.getAttention();
  2400.     }
  2401.     else
  2402.     {
  2403.         if (isSuperfluous)
  2404.             setTabState(this, "superfluous");
  2405.         else
  2406.             setTabState(this, "activity");
  2407.     }
  2408.  
  2409.     if (isImportant && client.COPY_MESSAGES)
  2410.     {
  2411.         if ("network" in o && o.network != this)
  2412.             o.network.displayHere("{" + this.name + "} " + message, msgtype,
  2413.                                   sourceObj, destObj);
  2414.     }
  2415. }
  2416.  
  2417. function addHistory (source, obj, mergeData, collapseRow)
  2418. {
  2419.     var tbody;
  2420.     
  2421.     if (!("messages" in source) || source.messages == null)
  2422.     {
  2423.         source.messages =
  2424.             document.createElementNS ("http://www.w3.org/1999/xhtml",
  2425.                                       "html:table");
  2426.  
  2427.         source.messages.setAttribute ("class", "msg-table");
  2428.         source.messages.setAttribute ("view-type", source.TYPE);
  2429.         tbody = document.createElementNS ("http://www.w3.org/1999/xhtml",
  2430.                                           "html:tbody");
  2431.         source.messages.appendChild (tbody);
  2432.     }
  2433.     else
  2434.     {
  2435.         tbody = source.messages.firstChild;
  2436.     }
  2437.  
  2438.     var needScroll = false;
  2439.     var w = window.frames[0];
  2440.     
  2441.     if (client.PRINT_DIRECTION == 1)
  2442.     {
  2443.         if (mergeData || collapseRow)
  2444.         {
  2445.             var thisUserCol = obj.firstChild;
  2446.             var thisMessageCol = thisUserCol.nextSibling;
  2447.             var ci = findPreviousColumnInfo(source.messages);
  2448.             var nickColumns = ci.nickColumns;
  2449.             var rowExtents = ci.extents;
  2450.             var nickColumnCount = nickColumns.length;
  2451.             var sameNick = (nickColumnCount > 0 &&
  2452.                             nickColumns[nickColumnCount - 1].
  2453.                             getAttribute("msg-user") ==
  2454.                             thisUserCol.getAttribute("msg-user"));
  2455.             var lastRowSpan = (nickColumnCount > 0) ?
  2456.                 Number(nickColumns[0].getAttribute("rowspan")) : 0;
  2457.             if (sameNick && mergeData)
  2458.             {
  2459.                 if (obj.getAttribute("important"))
  2460.                 {
  2461.                     nickColumns[nickColumnCount - 1].setAttribute ("important",
  2462.                                                                    true);
  2463.                 }
  2464.                 /* message is from the same person as last time,
  2465.                  * strip the nick first... */
  2466.                 obj.removeChild(obj.firstChild);
  2467.                 /* Adjust height of previous cells, maybe. */
  2468.                 for (i = 0; i < rowExtents.length - 1; ++i)
  2469.                 {
  2470.                     var myLastData = 
  2471.                         rowExtents[i].childNodes[nickColumnCount - 1];
  2472.                     var myLastRowSpan = (myLastData) ?
  2473.                         myLastData.getAttribute("rowspan") : 0;
  2474.                     if (myLastData && myLastRowSpan > 1)
  2475.                     {
  2476.                         myLastData.removeAttribute("rowspan");
  2477.                     }
  2478.                 }
  2479.                 /* then add one to the colspan for the previous user columns */
  2480.                 if (!lastRowSpan)
  2481.                     lastRowSpan = 1;
  2482.                 for (var i = 0; i < nickColumns.length; ++i)
  2483.                     nickColumns[i].setAttribute ("rowspan", lastRowSpan + 1);
  2484.             }
  2485.             else if (!sameNick && collapseRow && nickColumnCount > 0 &&
  2486.                      nickColumnCount < client.MAX_MSG_PER_ROW)
  2487.             {
  2488.                 /* message is from a different person, but is elegible to
  2489.                  * be contained by the previous row. */
  2490.                 var tr = nickColumns[0].parentNode;
  2491.                 for (i = 0; i < rowExtents.length; ++i)
  2492.                     rowExtents[i].lastChild.removeAttribute("colspan");
  2493.                 obj.firstChild.setAttribute ("rowspan", lastRowSpan);
  2494.                 tr.appendChild (obj.firstChild);
  2495.                 var lastColSpan =
  2496.                     Number(rowExtents[0].lastChild.getAttribute("colspan"));
  2497.                 obj.lastChild.setAttribute ("colspan", lastColSpan - 2);
  2498.                 obj.lastChild.setAttribute ("rowspan", lastRowSpan);
  2499.                 tr.appendChild (obj.lastChild);
  2500.                 obj = null;
  2501.             }   
  2502.         }
  2503.         
  2504.         if ((w.document.height - (w.innerHeight + w.pageYOffset)) <
  2505.             (w.innerHeight / 3))
  2506.             needScroll = true;
  2507.         if (obj)
  2508.             tbody.appendChild (obj);
  2509.     }
  2510.     else
  2511.         tbody.insertBefore (obj, source.messages.firstChild);
  2512.     
  2513.     if (source.MAX_MESSAGES)
  2514.     {
  2515.         if (typeof source.messageCount != "number")
  2516.             source.messageCount = 1;
  2517.         else
  2518.             source.messageCount++;
  2519.  
  2520.         if (source.messageCount > source.MAX_MESSAGES)
  2521.             if (client.PRINT_DIRECTION == 1)
  2522.             {
  2523.                 tbody.removeChild (tbody.firstChild);
  2524.                 --source.messageCount;
  2525.                 while (tbody.firstChild &&
  2526.                        tbody.firstChild.firstChild.getAttribute("class") ==
  2527.                        "msg-data")
  2528.                 {
  2529.                     --source.messageCount;
  2530.                     tbody.removeChild (tbody.firstChild);
  2531.                 }
  2532.             }
  2533.             else
  2534.             {
  2535.                 tbody.removeChild (tbody.lastChild);
  2536.                 --source.messageCount;
  2537.                 while (tbody.lastChild &&
  2538.                        tbody.lastChild.firstChild.getAttribute("class") ==
  2539.                        "msg-data")
  2540.                 {
  2541.                     --source.messageCount;
  2542.                     tbody.removeChild (tbody.lastChild);
  2543.                 }
  2544.             }
  2545.     }
  2546.  
  2547.     if ("currentObject" in client && client.currentObject == source &&
  2548.         needScroll)
  2549.     {
  2550.         scrollDown();
  2551.         setTimeout ("scrollDown()", 500);
  2552.         setTimeout ("scrollDown()", 1000);
  2553.         setTimeout ("scrollDown()", 2000);
  2554.     }                    
  2555.     
  2556. }
  2557.  
  2558. function findPreviousColumnInfo (table)
  2559. {
  2560.     var extents = new Array();
  2561.     var tr = table.firstChild.lastChild;
  2562.     var className = tr ? tr.firstChild.getAttribute("class") : "";
  2563.     while (tr && className.search(/msg-user|msg-type|msg-nested-td/) == -1)
  2564.     {
  2565.         extents.push(tr);
  2566.         tr = tr.previousSibling;
  2567.         if (tr)
  2568.             className = tr.firstChild.getAttribute("class");
  2569.     }
  2570.     
  2571.     if (!tr || className != "msg-user")
  2572.         return {extents: [], nickColumns: []};
  2573.     
  2574.     extents.push(tr);
  2575.     var nickCol = tr.firstChild;
  2576.     var nickCols = new Array();
  2577.     while (nickCol)
  2578.     {
  2579.         if (nickCol.getAttribute("class") == "msg-user")
  2580.             nickCols.push (nickCol);
  2581.         nickCol = nickCol.nextSibling.nextSibling;
  2582.     }
  2583.  
  2584.     return {extents: extents, nickColumns: nickCols};
  2585. }    
  2586.  
  2587. client.getConnectionCount =
  2588. function cli_gccount ()
  2589. {
  2590.     var count = 0;
  2591.     
  2592.     for (var n in client.networks)
  2593.         if (client.networks[n].isConnected())
  2594.             ++count;
  2595.  
  2596.     return count;
  2597. }   
  2598.  
  2599. client.quit =
  2600. function cli_quit (reason)
  2601. {    
  2602.     for (var n in client.networks)
  2603.         if ("primServ" in client.networks[n])
  2604.             client.networks[n].quit (reason);   
  2605. }
  2606.  
  2607. /* gets a tab-complete match for the line of text specified by |line|.  wordStart
  2608.  * is the position within |line| that starts the word being matched, wordEnd
  2609.  * marks the end position.  |cursorPos| marks the position of the caret in the
  2610.  * textbox.
  2611.  */
  2612. client.performTabMatch =
  2613. function gettabmatch_usr (line, wordStart, wordEnd, word, cursorPos)
  2614. {
  2615.     if (wordStart != 0 || line[0] != client.COMMAND_CHAR)
  2616.         return null;
  2617.  
  2618.     var matches = client.commands.listNames(word.substr(1));
  2619.     if (matches.length == 1 && wordEnd == line.length)
  2620.     {
  2621.         matches[0] = client.COMMAND_CHAR + matches[0] + " ";
  2622.     }
  2623.     else
  2624.     {
  2625.         for (var i in matches)
  2626.             matches[i] = client.COMMAND_CHAR + matches[i];
  2627.     }
  2628.  
  2629.     return matches;
  2630. }
  2631.  
  2632. CIRCChannel.prototype.performTabMatch =
  2633. CIRCNetwork.prototype.performTabMatch =
  2634. CIRCUser.prototype.performTabMatch    =
  2635. function gettabmatch_usr (line, wordStart, wordEnd, word, cursorpos)
  2636. {
  2637.     if (wordStart == 0 && line[0] == client.COMMAND_CHAR)
  2638.         return client.performTabMatch (line, wordStart, wordEnd, word,
  2639.                                        cursorpos);
  2640.     
  2641.     if (!("users" in this))
  2642.         return [];
  2643.     
  2644.     var users = this.users;
  2645.     var nicks = new Array();
  2646.         
  2647.     for (var n in users)
  2648.         nicks.push (users[n].nick);
  2649.         
  2650.     var matches = matchEntry (word, nicks);
  2651.     if (matches.length == 1)
  2652.     {
  2653.         matches[0] = this.users[matches[0]].properNick;
  2654.         if (wordStart == 0)
  2655.             matches[0] += client.ADDRESSED_NICK_SEP;
  2656.  
  2657.         if (wordEnd == line.length)
  2658.         {
  2659.             /* add a space if the word is at the end of the line. */
  2660.             matches[0] += " ";
  2661.         }
  2662.     }
  2663.  
  2664.     return matches;
  2665. }
  2666.  
  2667. /**
  2668.  * Retrieves the selected nicks from the user-list
  2669.  * tree object. This grabs the tree element's
  2670.  * selected items, extracts the appropriate text
  2671.  * for the nick, promotes each nick to a CIRCChanUser
  2672.  * instance and returns an array of these objects.
  2673.  */
  2674. CIRCChannel.prototype.getSelectedUsers =
  2675. function my_getselectedusers () 
  2676. {
  2677.  
  2678.     /* Grab a reference to the tree element with ID = user-list . See chatzilla.xul */
  2679.     var tree = document.getElementById("user-list");
  2680.     var cell; /* reference to each selected cell of the tree object */
  2681.     var rv_ary = new Array; /* return value arrray for CIRCChanUser objects */
  2682.  
  2683.     var rangeCount = tree.view.selection.getRangeCount();
  2684.     for (var i = 0; i < rangeCount; ++i)
  2685.     {
  2686.         var start = {}, end = {};
  2687.         tree.view.selection.getRangeAt(i, start, end);
  2688.         for (var k = start.value; k <= end.value; ++k)
  2689.         {
  2690.             var item = tree.contentView.getItemAtIndex(k);
  2691.  
  2692.             /* First, set the reference to the XUL element. */
  2693.             cell = item.firstChild.childNodes[2];
  2694.             
  2695.             /* Now, create an instance of CIRCChaneUser by passing the text
  2696.           *  of the cell to the getUser function of this CIRCChannel instance.
  2697.           */
  2698.             rv_ary[i] = this.getUser( cell.getAttribute("label") );
  2699.         }
  2700.     }
  2701.  
  2702.     /* 
  2703.      *  USAGE NOTE: If the return value is non-null, the caller
  2704.      *  can assume the array is valid, and NOT 
  2705.      *  need to check the length, and vice versa.
  2706.      */
  2707.  
  2708.     return rv_ary.length > 0 ? rv_ary : null;
  2709.  
  2710. }
  2711.  
  2712. CIRCChannel.prototype.getGraphResource =
  2713. function my_graphres ()
  2714. {
  2715.     if (!("rdfRes" in this))
  2716.     {
  2717.         this.rdfRes = 
  2718.             client.rdf.GetResource(RES_PFX + "CHANNEL:" +
  2719.                                    this.parent.parent.name +
  2720.                                    ":" + this.name);
  2721.             //dd ("created channel resource " + this.rdfRes.Value);
  2722.  
  2723.     }
  2724.     
  2725.     return this.rdfRes;
  2726. }
  2727.  
  2728. CIRCUser.prototype.getGraphResource =
  2729. function usr_graphres()
  2730. {
  2731.     if (this.TYPE != "IRCChanUser")
  2732.         dd ("** WARNING: cuser.getGraphResource called on wrong object **");
  2733.     
  2734.     var rdf = client.rdf;
  2735.     
  2736.     if (!("rdfRes" in this))
  2737.     {
  2738.         if (!("nextResID" in CIRCUser))
  2739.             CIRCUser.nextResID = 0;
  2740.         
  2741.         this.rdfRes = rdf.GetResource (RES_PFX + "CUSER:" + 
  2742.                                        this.parent.parent.parent.name + ":" +
  2743.                                        this.parent.name + ":" +
  2744.                                        CIRCUser.nextResID++);
  2745.  
  2746.             //dd ("created cuser resource " + this.rdfRes.Value);
  2747.         
  2748.         rdf.Assert (this.rdfRes, rdf.resNick, rdf.GetLiteral(this.properNick));
  2749.         if (this.name)
  2750.             rdf.Assert (this.rdfRes, rdf.resUser, rdf.GetLiteral(this.name));
  2751.         else
  2752.             rdf.Assert (this.rdfRes, rdf.resUser, rdf.litUnk);
  2753.         if (this.host)
  2754.             rdf.Assert (this.rdfRes, rdf.resHost, rdf.GetLiteral(this.host));
  2755.         else
  2756.             rdf.Assert (this.rdfRes, rdf.resHost, rdf.litUnk);
  2757.  
  2758.         rdf.Assert (this.rdfRes, rdf.resOp, 
  2759.                     this.isOp ? rdf.litTrue : rdf.litFalse);
  2760.         rdf.Assert (this.rdfRes, rdf.resVoice,
  2761.                     this.isVoice ? rdf.litTrue : rdf.litFalse);
  2762.     }
  2763.  
  2764.     return this.rdfRes;
  2765.  
  2766. }
  2767.  
  2768. CIRCUser.prototype.updateGraphResource =
  2769. function usr_updres()
  2770. {
  2771.     if (this.TYPE != "IRCChanUser")
  2772.         dd ("** WARNING: cuser.updateGraphResource called on wrong object **");
  2773.  
  2774.     if (!("rdfRes" in this))
  2775.         this.getGraphResource();
  2776.     
  2777.     var rdf = client.rdf;
  2778.     
  2779.     rdf.Change (this.rdfRes, rdf.resNick, rdf.GetLiteral(this.properNick));
  2780.     if (this.name)
  2781.         rdf.Change (this.rdfRes, rdf.resUser, rdf.GetLiteral(this.name));
  2782.     else
  2783.         rdf.Change (this.rdfRes, rdf.resUser, rdf.litUnk);
  2784.     if (this.host)
  2785.         rdf.Change (this.rdfRes, rdf.resHost, rdf.GetLiteral(this.host));
  2786.     else
  2787.         rdf.Change (this.rdfRes, rdf.resHost, rdf.litUnk);
  2788.     
  2789.     rdf.Change (this.rdfRes, rdf.resOp, 
  2790.                 this.isOp ? rdf.litTrue : rdf.litFalse);
  2791.     rdf.Change (this.rdfRes, rdf.resVoice,
  2792.                 this.isVoice ? rdf.litTrue : rdf.litFalse);
  2793. }
  2794.