mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-10 01:40:17 +01:00
578 lines
17 KiB
JavaScript
578 lines
17 KiB
JavaScript
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
|
*
|
||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
|
* the License. You may obtain a copy of the License at
|
||
|
* http://www.mozilla.org/MPL/
|
||
|
*
|
||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
|
* for the specific language governing rights and limitations under the
|
||
|
* License.
|
||
|
*
|
||
|
* The Original Code is ChatZilla.
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is James Ross.
|
||
|
* Portions created by the Initial Developer are Copyright (C) 2004
|
||
|
* the Initial Developer. All Rights Reserved.
|
||
|
*
|
||
|
* Contributor(s):
|
||
|
* James Ross, <silver@warwickcompsoc.co.uk>
|
||
|
*
|
||
|
* Alternatively, the contents of this file may be used under the terms of
|
||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||
|
* of those above. If you wish to allow use of your version of this file only
|
||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||
|
* use your version of this file under the terms of the MPL, indicate your
|
||
|
* decision by deleting the provisions above and replace them with the notice
|
||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||
|
* the provisions above, a recipient may use your version of this file under
|
||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
var client;
|
||
|
var network;
|
||
|
var channels = new Array();
|
||
|
var createChannelItem;
|
||
|
|
||
|
var channelTreeShare = new Object();
|
||
|
var channelTreeView;
|
||
|
|
||
|
var s = 0;
|
||
|
const STATE_IDLE = ++s; // Doing nothing.
|
||
|
const STATE_LOAD = ++s; // Loading from saved file only.
|
||
|
const STATE_LIST_AND_LOAD = ++s; // Doing /list and loading simultaneously.
|
||
|
const STATE_LIST_THEN_LOAD = ++s; // Doing /list, after which do load.
|
||
|
|
||
|
s = 0;
|
||
|
const SUBSTATE_START = ++s; // Starting an operation.
|
||
|
const SUBSTATE_RUN = ++s; // Running...
|
||
|
const SUBSTATE_END = ++s; // Clean-up/ending operation.
|
||
|
const SUBSTATE_ERROR = ++s; // Error occurred: don't try do to any more.
|
||
|
delete s;
|
||
|
|
||
|
var state = { state: STATE_IDLE, substate: 0 };
|
||
|
const STATE_DELAY = 1000;
|
||
|
|
||
|
const colIDToSortKey = { chanColName: "name",
|
||
|
chanColUsers: "users",
|
||
|
chanColTopic: "topic" };
|
||
|
const sortKeyToColID = { name: "chanColName",
|
||
|
users: "chanColUsers",
|
||
|
topic: "chanColTopic" };
|
||
|
|
||
|
function onLoad()
|
||
|
{
|
||
|
function ondblclick(event) { channelTreeView.onRouteDblClick(event); };
|
||
|
function onkeypress(event) { channelTreeView.onRouteKeyPress(event); };
|
||
|
function onfocus(event) { channelTreeView.onRouteFocus(event); };
|
||
|
function onblur(event) { channelTreeView.onRouteBlur(event); };
|
||
|
|
||
|
function doJoin()
|
||
|
{
|
||
|
if (joinChannel())
|
||
|
window.close();
|
||
|
};
|
||
|
|
||
|
client = window.arguments[0].client;
|
||
|
network = window.arguments[0].network;
|
||
|
network.joinDialog = window;
|
||
|
|
||
|
window.dd = client.mainWindow.dd;
|
||
|
window.toUnicode = client.mainWindow.toUnicode;
|
||
|
window.getMsg = client.mainWindow.getMsg;
|
||
|
window.MSG_CHANNEL_OPENED = client.mainWindow.MSG_CHANNEL_OPENED;
|
||
|
window.MSG_FMT_JSEXCEPTION = client.mainWindow.MSG_FMT_JSEXCEPTION;
|
||
|
window.MT_INFO = client.mainWindow.MT_INFO;
|
||
|
|
||
|
// Import "MSG_CD_*"...
|
||
|
for (var m in client.mainWindow)
|
||
|
{
|
||
|
if (m.substr(0, 7) == "MSG_CD_")
|
||
|
window[m] = client.mainWindow[m];
|
||
|
}
|
||
|
|
||
|
var tree = document.getElementById("channelList");
|
||
|
|
||
|
channelTreeView = new XULTreeView(channelTreeShare);
|
||
|
channelTreeView.onRowCommand = doJoin;
|
||
|
channelTreeView.cycleHeader = changeSort;
|
||
|
tree.treeBoxObject.view = channelTreeView;
|
||
|
|
||
|
// Sort by user count, decending.
|
||
|
changeSort("chanColUsers");
|
||
|
|
||
|
tree.addEventListener("dblclick", ondblclick, false);
|
||
|
tree.addEventListener("keypress", onkeypress, false);
|
||
|
tree.addEventListener("focus", onfocus, false);
|
||
|
tree.addEventListener("blur", onblur, false);
|
||
|
|
||
|
createChannelItem = new ChannelEntry("", "", MSG_CD_CREATE);
|
||
|
createChannelItem.first = true;
|
||
|
channelTreeView.childData.appendChild(createChannelItem);
|
||
|
|
||
|
document.title = getMsg(MSG_CD_TITLE, [network.unicodeName,
|
||
|
network.getURL()]);
|
||
|
|
||
|
setInterval(doCurrentStatus, STATE_DELAY);
|
||
|
setState(STATE_LOAD);
|
||
|
}
|
||
|
|
||
|
function onUnload()
|
||
|
{
|
||
|
delete network.joinDialog;
|
||
|
}
|
||
|
|
||
|
function onKeyPress(event)
|
||
|
{
|
||
|
if (event.keyCode == event.DOM_VK_UP)
|
||
|
{
|
||
|
if (channelTreeView.selectedIndex > 0)
|
||
|
channelTreeView.selectedIndex = channelTreeView.selectedIndex - 1;
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
else if (event.keyCode == event.DOM_VK_DOWN)
|
||
|
{
|
||
|
if (channelTreeView.selectedIndex < channelTreeView.rowCount - 1)
|
||
|
channelTreeView.selectedIndex = channelTreeView.selectedIndex + 1;
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onSelectionChange()
|
||
|
{
|
||
|
var joinBtn = document.getElementById("joinBtn");
|
||
|
joinBtn.disabled = (channelTreeView.selectedIndex == -1);
|
||
|
}
|
||
|
|
||
|
function runFilter()
|
||
|
{
|
||
|
function process()
|
||
|
{
|
||
|
var exactMatch = null;
|
||
|
var channelText = text;
|
||
|
if (!channelText.match(/^[#&+!]/))
|
||
|
channelText = "#" + channelText;
|
||
|
|
||
|
// Should ideally be calling freeze()/thaw() here, but unfortunately they
|
||
|
// mess up the selection, even if they make this so much faster.
|
||
|
for (var i = 0; i < channels.length; i++)
|
||
|
{
|
||
|
var match = (channels[i].name.toLowerCase().indexOf(text) != -1) ||
|
||
|
(searchTopics &&
|
||
|
(channels[i].topic.toLowerCase().indexOf(text) != -1));
|
||
|
|
||
|
if (minUsers && (channels[i].users < minUsers))
|
||
|
match = false;
|
||
|
if (maxUsers && (channels[i].users > maxUsers))
|
||
|
match = false;
|
||
|
|
||
|
if (channels[i].isHidden)
|
||
|
{
|
||
|
if (match)
|
||
|
channels[i].unHide();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!match)
|
||
|
channels[i].hide();
|
||
|
}
|
||
|
|
||
|
if (match && (channels[i].name.toLowerCase() == channelText))
|
||
|
exactMatch = channels[i];
|
||
|
|
||
|
if ((i % 100) == 0)
|
||
|
updateProgress(MSG_CD_FILTERING, 100 * i / channels.length);
|
||
|
}
|
||
|
updateProgress();
|
||
|
|
||
|
createChannelItem.name = channelText;
|
||
|
var tree = document.getElementById("channelList");
|
||
|
tree.treeBoxObject.invalidateRow(0);
|
||
|
|
||
|
if (exactMatch)
|
||
|
{
|
||
|
if (!createChannelItem.isHidden)
|
||
|
createChannelItem.hide();
|
||
|
channelTreeView.selectedIndex = exactMatch.calculateVisualRow();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (createChannelItem.isHidden)
|
||
|
createChannelItem.unHide();
|
||
|
if (channelTreeView.selectedIndex == -1)
|
||
|
{
|
||
|
// The special 'create' row is visible, so prefer the first
|
||
|
// real channel over it.
|
||
|
if (channelTreeView.rowCount >= 2)
|
||
|
channelTreeView.selectedIndex = 1;
|
||
|
else if (channelTreeView.rowCount >= 1)
|
||
|
channelTreeView.selectedIndex = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
var text = document.getElementById("filterText").value.toLowerCase();
|
||
|
var searchTopics = document.getElementById("searchTopics").checked;
|
||
|
var minUsers = document.getElementById("minUsers").value * 1;
|
||
|
var maxUsers = document.getElementById("maxUsers").value * 1;
|
||
|
|
||
|
if (channels.length > 1000)
|
||
|
updateProgress(getMsg(MSG_CD_FILTERING1, channels.length));
|
||
|
else
|
||
|
updateProgress(getMsg(MSG_CD_FILTERING2, channels.length));
|
||
|
|
||
|
setTimeout(process, 100);
|
||
|
}
|
||
|
|
||
|
function joinChannel()
|
||
|
{
|
||
|
var index = channelTreeView.selectedIndex;
|
||
|
if (index == -1)
|
||
|
return false;
|
||
|
|
||
|
var row = channelTreeView.childData.locateChildByVisualRow(index);
|
||
|
network.dispatch("join", { channelName: row.name });
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function focusSearch()
|
||
|
{
|
||
|
document.getElementById("filterText").focus();
|
||
|
}
|
||
|
|
||
|
function refreshList()
|
||
|
{
|
||
|
setState(STATE_LIST_THEN_LOAD);
|
||
|
}
|
||
|
|
||
|
function updateProgress(label, pro)
|
||
|
{
|
||
|
if (label)
|
||
|
{
|
||
|
document.getElementById("loadLabel").value = label;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var msg = getMsg(MSG_CD_SHOWING,
|
||
|
[(channelTreeView.rowCount - (createChannelItem.isHidden ? 0 : 1)),
|
||
|
channels.length]);
|
||
|
document.getElementById("loadLabel").value = msg;
|
||
|
}
|
||
|
|
||
|
var loadBarDeckIndex = ((typeof pro == "undefined") ? 1 : 0);
|
||
|
document.getElementById("loadBarDeck").selectedIndex = loadBarDeckIndex;
|
||
|
|
||
|
if ((typeof pro == "undefined") || (pro == -1))
|
||
|
{
|
||
|
document.getElementById("loadBar").mode = "undetermined";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
document.getElementById("loadBar").mode = "determined";
|
||
|
document.getElementById("loadBar").value = pro;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function changeSort(col)
|
||
|
{
|
||
|
if (typeof col == "object")
|
||
|
col = col.id;
|
||
|
|
||
|
col = colIDToSortKey[col];
|
||
|
// Users default to decending, others accending.
|
||
|
var dir = (col == "users" ? -1 : 1);
|
||
|
|
||
|
if (col == channelTreeShare.sortColumn)
|
||
|
dir = -channelTreeShare.sortDirection;
|
||
|
|
||
|
var colID = sortKeyToColID[channelTreeShare.sortColumn];
|
||
|
var colNode = document.getElementById(colID);
|
||
|
if (colNode)
|
||
|
{
|
||
|
colNode.removeAttribute("sortActive");
|
||
|
colNode.removeAttribute("sortDirection");
|
||
|
}
|
||
|
|
||
|
channelTreeView.childData.setSortColumn(col, dir);
|
||
|
|
||
|
colID = sortKeyToColID[channelTreeShare.sortColumn];
|
||
|
colNode = document.getElementById(colID);
|
||
|
if (colNode)
|
||
|
{
|
||
|
colNode.setAttribute("sortActive", "true");
|
||
|
var sortDir = (dir > 0 ? "ascending" : "descending");
|
||
|
colNode.setAttribute("sortDirection", sortDir);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setState(newState)
|
||
|
{
|
||
|
state.state = newState;
|
||
|
if (newState == STATE_IDLE)
|
||
|
state.substate = 0;
|
||
|
else
|
||
|
state.substate = SUBSTATE_START;
|
||
|
// Force an update when the state changes.
|
||
|
doCurrentStatus();
|
||
|
}
|
||
|
|
||
|
function setSubstate(newSubstate)
|
||
|
{
|
||
|
state.substate = newSubstate;
|
||
|
}
|
||
|
|
||
|
function doCurrentStatus()
|
||
|
{
|
||
|
var st = Number(new Date());
|
||
|
try
|
||
|
{
|
||
|
switch (state.substate)
|
||
|
{
|
||
|
case SUBSTATE_START:
|
||
|
doCurrentStatusStart();
|
||
|
break;
|
||
|
|
||
|
case SUBSTATE_RUN:
|
||
|
doCurrentStatusRun();
|
||
|
break;
|
||
|
|
||
|
case SUBSTATE_END:
|
||
|
doCurrentStatusEnd();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
catch(ex)
|
||
|
{
|
||
|
/* If an error has occured, we display it (updateProgress) and then
|
||
|
* halt our opperations to prevent further damage.
|
||
|
*/
|
||
|
dd("Exception in channels.js:doCurrentStatus: " + formatException(ex));
|
||
|
updateProgress(formatException(ex));
|
||
|
state.substate = SUBSTATE_ERROR;
|
||
|
}
|
||
|
var en = Number(new Date());
|
||
|
state.timeTaken = (en - st);
|
||
|
}
|
||
|
|
||
|
function doCurrentStatusStart()
|
||
|
{
|
||
|
// Only the list has an initial message.
|
||
|
if (state.state == STATE_LIST_THEN_LOAD)
|
||
|
updateProgress(MSG_CD_FETCHING, -1);
|
||
|
|
||
|
// The file is used in all 3 cases.
|
||
|
var file = getListFile();
|
||
|
|
||
|
// Doing the list should disable the refresh button.
|
||
|
if ((state.state == STATE_LIST_AND_LOAD) || (state.state == STATE_LIST_THEN_LOAD))
|
||
|
{
|
||
|
document.getElementById("refreshNow").disabled = true;
|
||
|
network.list("", file.path);
|
||
|
}
|
||
|
|
||
|
// Do file handling setup for both loading states.
|
||
|
if ((state.state == STATE_LOAD) || (state.state == STATE_LIST_AND_LOAD))
|
||
|
{
|
||
|
if (!file.exists())
|
||
|
{
|
||
|
// We tried to do a load, but the file does not exist.
|
||
|
setState(STATE_LIST_AND_LOAD);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Nuke contents.
|
||
|
channelTreeView.selectedIndex = -1;
|
||
|
channelTreeView.freeze();
|
||
|
while (channelTreeView.childData.childData.length > 1)
|
||
|
channelTreeView.childData.removeChildAtIndex(1);
|
||
|
channelTreeView.thaw();
|
||
|
|
||
|
// Nuke more stuff.
|
||
|
channels = new Array();
|
||
|
|
||
|
// And... here we go.
|
||
|
state.loadFile = new LocalFile(file, "<");
|
||
|
state.loadPendingData = "";
|
||
|
state.loadChunk = 10000;
|
||
|
state.loadedSoFar = 0;
|
||
|
// The loading from the file will never complete whilst this is true.
|
||
|
state.loadNeverComplete = (state.state == STATE_LIST_AND_LOAD);
|
||
|
}
|
||
|
|
||
|
setSubstate(SUBSTATE_RUN);
|
||
|
}
|
||
|
|
||
|
function doCurrentStatusRun()
|
||
|
{
|
||
|
if (state.state == STATE_LIST_THEN_LOAD)
|
||
|
{
|
||
|
// Update the progress and end if /list done for "list only" state.
|
||
|
updateProgress(getMsg(MSG_CD_FETCHED, network._list.count), -1);
|
||
|
|
||
|
if (network._list.done)
|
||
|
setSubstate(SUBSTATE_END);
|
||
|
}
|
||
|
|
||
|
/* If we're doing the list+load state, flag is as ok to stop loading the
|
||
|
* file when the network has finished doing the /list.
|
||
|
*/
|
||
|
if ((state.state == STATE_LIST_AND_LOAD) && network._list.done)
|
||
|
state.loadNeverComplete = false;
|
||
|
|
||
|
// Do file handling for loading states.
|
||
|
if ((state.state == STATE_LOAD) || (state.state == STATE_LIST_AND_LOAD))
|
||
|
{
|
||
|
// Adjust chunk size to keep time per-chunk between 750ms and 1000ms.
|
||
|
if ((state.timeTaken > 1.25 * STATE_DELAY) && (state.loadChunk >= 2000))
|
||
|
state.loadChunk -= 1000;
|
||
|
else if ((state.timeTaken > STATE_DELAY) && (state.loadChunk >= 1100))
|
||
|
state.loadChunk -= 100;
|
||
|
if (state.timeTaken < 0.5 * STATE_DELAY)
|
||
|
state.loadChunk += 1000;
|
||
|
else if (state.timeTaken < 0.75 * STATE_DELAY)
|
||
|
state.loadChunk += 100;
|
||
|
state.loadedSoFar += state.loadChunk;
|
||
|
state.loadPendingData += state.loadFile.read(state.loadChunk);
|
||
|
|
||
|
while (state.loadPendingData.indexOf("\n") != -1)
|
||
|
{
|
||
|
var lines = state.loadPendingData.split("\n");
|
||
|
state.loadPendingData = lines.pop();
|
||
|
|
||
|
for (var i = 0; i < lines.length; i++)
|
||
|
{
|
||
|
var ary = lines[i].match(/^([^ ]+) ([^ ]+) (.*)$/);
|
||
|
if (ary)
|
||
|
{
|
||
|
var chan = new ChannelEntry(toUnicode(ary[1], "UTF-8"), ary[2],
|
||
|
toUnicode(ary[3], "UTF-8"));
|
||
|
channels.push(chan);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var dataLeft = state.loadFile.inputStream.available();
|
||
|
var pro = state.loadedSoFar / (state.loadedSoFar + dataLeft);
|
||
|
pro = Math.round(100 * pro);
|
||
|
updateProgress(getMsg(MSG_CD_LOADED, channels.length), pro);
|
||
|
|
||
|
// Done if there is no more data, and we're not *expecting* any more.
|
||
|
if ((dataLeft == 0) && !state.loadNeverComplete)
|
||
|
setSubstate(SUBSTATE_END);
|
||
|
}
|
||
|
}
|
||
|
function doCurrentStatusEnd()
|
||
|
{
|
||
|
// Reset refresh button.
|
||
|
if ((state.state == STATE_LIST_AND_LOAD) || (state.state == STATE_LIST_THEN_LOAD))
|
||
|
document.getElementById("refreshNow").disabled = false;
|
||
|
|
||
|
// Check that /list finished ok if we're just doing a list.
|
||
|
if (state.state == STATE_LIST_THEN_LOAD)
|
||
|
{
|
||
|
if ("error" in network._list)
|
||
|
{
|
||
|
updateProgress(MSG_CD_ERROR_LIST);
|
||
|
// Can't seem to "undefine" the parameters using the function format.
|
||
|
setTimeout("updateProgress()", 10000);
|
||
|
// Bail out if there was an error!
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Replace files.
|
||
|
updateProgress();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Finish file handling work.
|
||
|
if ((state.state == STATE_LOAD) || (state.state == STATE_LIST_AND_LOAD))
|
||
|
{
|
||
|
if (channels.length > 0)
|
||
|
channelTreeView.childData.appendChildren(channels);
|
||
|
state.loadFile.close();
|
||
|
delete state.loadFile;
|
||
|
delete state.loadPendingData;
|
||
|
delete state.loadChunk;
|
||
|
delete state.loadedSoFar;
|
||
|
delete state.loadNeverComplete;
|
||
|
updateProgress();
|
||
|
runFilter();
|
||
|
}
|
||
|
|
||
|
if (state.state == STATE_LIST_THEN_LOAD)
|
||
|
setState(STATE_LOAD);
|
||
|
else
|
||
|
setState(STATE_IDLE);
|
||
|
}
|
||
|
|
||
|
function getListFile(temp)
|
||
|
{
|
||
|
var file = new LocalFile(network.prefs["logFileName"]);
|
||
|
if (temp)
|
||
|
file.localFile.leafName = "list.temp";
|
||
|
else
|
||
|
file.localFile.leafName = "list.txt";
|
||
|
return file.localFile;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Tree ChannelEntry objects //
|
||
|
function ChannelEntry(name, users, topic)
|
||
|
{
|
||
|
this.setColumnPropertyName("chanColName", "name");
|
||
|
this.setColumnPropertyName("chanColUsers", "users");
|
||
|
this.setColumnPropertyName("chanColTopic", "topic");
|
||
|
|
||
|
// Nuke color codes and bold etc.
|
||
|
topic = topic.replace(/[\x1F\x02\x0F\x16]/g, "");
|
||
|
topic = topic.replace(/\x03\d{1,2}(?:,\d{1,2})?/g, "");
|
||
|
|
||
|
this.name = name;
|
||
|
this.users = users;
|
||
|
this.topic = topic;
|
||
|
}
|
||
|
|
||
|
ChannelEntry.prototype = new XULTreeViewRecord(channelTreeShare);
|
||
|
|
||
|
ChannelEntry.prototype.sortCompare =
|
||
|
function chanentry_sortcmp(a, b)
|
||
|
{
|
||
|
var sc = a._share.sortColumn;
|
||
|
var sd = a._share.sortDirection;
|
||
|
|
||
|
// Make sure the special 'first' row is always first.
|
||
|
if ("first" in a)
|
||
|
return -1;
|
||
|
if ("first" in b)
|
||
|
return 1;
|
||
|
|
||
|
if (sc == "users")
|
||
|
{
|
||
|
// Force a numeric comparison.
|
||
|
a = 1 * a[sc];
|
||
|
b = 1 * b[sc];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Case-insensitive, please.
|
||
|
a = a[sc].toLowerCase();
|
||
|
b = b[sc].toLowerCase();
|
||
|
}
|
||
|
|
||
|
if (a < b)
|
||
|
return -1 * sd;
|
||
|
|
||
|
if (a > b)
|
||
|
return 1 * sd;
|
||
|
|
||
|
return 0;
|
||
|
}
|