Mercurial > web-gnusto
view web-zui.js @ 86:e540ceb9b17c
Fixed a bug raised by the color version of Photopia.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Wed, 21 May 2008 20:32:48 -0700 |
parents | 0f6cf96a6aee |
children | e48403e2a3b5 |
line wrap: on
line source
var BACKSPACE_KEYCODE = 8; var RETURN_KEYCODE = 13; function WebZui(logfunc) { this._size = [80, 255]; this._console = null; this._activeWindow = 0; this._inputString = ""; this._currentCallback = null; this._currStyles = ["z-roman"]; if (logfunc) { this._log = logfunc; } else { this._log = function() {}; } var self = this; this.__proto__ = { onConsoleRender: function() { var height = $("#top-window").get(0).clientHeight; $("#content").get(0).style.padding = "" + height + "px 0 0 0"; self._scrollBottomWindow(); }, _scrollBottomWindow: function() { window.scroll(0, document.body.scrollHeight); }, _finalize: function() { if (self._console) { self._console.close(); self._console = null; } $("#content").empty(); self._unbindEventHandlers(); }, _bindEventHandlers: function() { $(window).keypress(self._windowKeypress) .resize(self._windowResize) .keyup(self._windowKeyup) .keydown(self._windowKeydown) .mousewheel(self._windowMousewheel); }, _unbindEventHandlers: function() { $(window).unbind("keypress", self._windowKeypress) .unbind("resize", self._windowResize) .unbind("keyup", self._windowKeyup) .unbind("keydown", self._windowKeydown) .unbind("mousewheel", self._windowMousewheel); }, _windowMousewheel: function(event, delta) { window.scrollBy(0, -delta * 5); }, // We want to make sure that all key events don't bubble up, so // that anything listening in--such as Firefox's "Search for text // when I start typing" feature--doesn't think that we're not // doing anything with the keypresses. If we don't do this, such // listeners may think that they can intervene and capture // keystrokes before they get to us in the future. _isHotKey: function(event) { return (event.altKey || event.ctrlKey || event.metaKey); }, _windowKeyup: function(event) { return self._isHotKey(event); }, _windowKeydown: function(event) { return self._isHotKey(event); }, _windowKeypress: function(event) { if (self._isHotKey(event)) return true; if ($("#current-input").length == 0) { // We're not waiting for a line of input, but we may // be waiting for a character of input. // Note that we have to return a ZSCII keycode here. // // For more information, see: // // http://www.gnelson.demon.co.uk/zspec/sect03.html if (self._currentCallback) { var keyCode = 0; if (event.charCode) { keyCode = event.charCode; } else { // TODO: Deal w/ arrow keys, etc. switch (event.keyCode) { case RETURN_KEYCODE: keyCode = event.keyCode; break; } } if (keyCode != 0) { var callback = self._currentCallback; self._currentCallback = null; self._removeBufferedWindows(); callback(keyCode); } } return false; } var oldInputString = self._inputString; if (event.charCode) { var newChar = String.fromCharCode(event.charCode); var lastChar = self._inputString.slice(-1); if (!(newChar == " " && lastChar == " ")) { self._inputString += newChar; } } else { switch (event.keyCode) { case BACKSPACE_KEYCODE: if (self._inputString) { self._inputString = self._inputString.slice(0, -1); } break; case RETURN_KEYCODE: var finalInputString = self._inputString; var callback = self._currentCallback; self._inputString = ""; self._currentCallback = null; finalInputString = finalInputString.entityify(); $("#current-input").replaceWith( ('<span class="finished-input">' + finalInputString + '</span><br/>') ); self._removeBufferedWindows(); callback(finalInputString); } } if ($("#current-input") && oldInputString != self._inputString) { $("#current-input").html( self._inputString.entityify() + '<span id="cursor">_</span>' ); } return false; }, _windowResize: function() { var contentLeft = $("#content").get(0).offsetLeft + "px"; $(".buffered-window").css({left: contentLeft}); }, _removeBufferedWindows: function() { var windows = $("#buffered-windows > .buffered-window"); windows.fadeOut("slow", function() { windows.remove(); }); // A more conservative alternative to the above is: // $("#buffered-windows").empty(); }, _eraseBottomWindow: function() { $("#content").empty(); }, setVersion: function(version) { self._version = version; }, getSize: function() { return self._size; }, onLineInput: function(callback) { self._currentCallback = callback; $("#content").append( '<span id="current-input"><span id="cursor">_</span></span>' ); }, onCharacterInput: function(callback) { self._currentCallback = callback; }, onSave: function(data) { // TODO: Attempt to use other forms of local storage // (e.g. Google Gears, HTML 5 database storage, etc) if // available; if none are available, we should return false. var saveKey = gStory + '_saveData'; globalStorage[location.hostname][saveKey] = encode_base64(data); return true; }, onRestore: function() { // TODO: Attempt to use other forms of local storage if // available; if none are available, we should return null. var saveData = globalStorage[location.hostname][gStory + '_saveData']; if (saveData) return decode_base64(saveData.value); else return null; }, onQuit: function() { self._finalize(); }, onRestart: function() { self._finalize(); // TODO: It's not high-priority, but according to the Z-Machine // spec documentation for the restart opcode, we need to // preserve the "transcribing to printer" bit and the "use // fixed-pitch font" bit when restarting. window.setTimeout(_webZuiStartup, 0); }, onWimpOut: function(callback) { window.setTimeout(callback, 100); }, onSetStyle: function(textStyle, foreground, background) { switch (textStyle) { case -1: // Don't set the style. break; case 0: this._currStyles = ["z-roman"]; break; case 1: this._currStyles.push("z-reverse-video"); break; case 2: this._currStyles.push("z-bold"); break; case 4: this._currStyles.push("z-italic"); break; case 8: this._currStyles.push("z-fixed-pitch"); break; default: throw new Error("Unknown style: " + textStyle); } }, onSetWindow: function(window) { // From the Z-Spec, section 8.7.2. if (window == 1) self._console.moveTo(0, 0); self._activeWindow = window; }, onEraseWindow: function(window) { if (window == -2) { self._console.clear(); self._eraseBottomWindow(); } else if (window == -1) { // From the Z-Spec, section 8.7.3.3. self.onSplitWindow(0); // TODO: Depending on the Z-Machine version, we want // to move the cursor to the bottom-left or top-left. self._eraseBottomWindow(); } else if (window == 0) { self._eraseBottomWindow(); } else if (window == 1) { self._console.clear(); } }, onSetCursor: function(x, y) { self._console.moveTo(x - 1, y - 1); }, onSetBufferMode: function(flag) { // TODO: How to emulate non-word wrapping in HTML? }, onSplitWindow: function(numlines) { if (numlines == 0) { if (self._console) { self._console.close(); self._console = null; } } else { if (!self._console || self._version == 3) { self._console = new Console(self._size[0], numlines, $("#top-window").get(0), self); } else if (self._console.height != numlines) { // Z-Machine games are peculiar in regards to the way they // sometimes overlay quotations on top of the current text; // we basically want to preserve any text that is already in // the top window "below" the layer of the top window, so // that anything it doesn't write over remains visible, at // least (and this is an arbitrary decision on our part) // until the user has entered some input. var newDiv = document.createElement("div"); var html = $("#top-window").get(0).innerHTML; newDiv.className = "buffered-window"; newDiv.innerHTML = html; newDiv.style.width = self._pixelWidth + "px"; newDiv.style.lineHeight = self._pixelLineHeight + "px"; $("#buffered-windows").append(newDiv); // Pretend the window was just resized, which will position // the new buffered window properly on the x-axis. self._windowResize(); self._console.resize(numlines); } } }, onPrint: function(output) { var styles = self._currStyles.join(" "); self._log("print wind: " + self._activeWindow + " output: " + output.quote() + " style: " + styles); if (self._activeWindow == 0) { var lines = output.split("\n"); for (var i = 0; i < lines.length; i++) { var addNewline = false; if (lines[i]) { var chunk = lines[i].entityify(); chunk = '<span class="' + styles + '">' + chunk + '</span>'; $("#content").append(chunk); if (i < lines.length - 1) addNewline = true; } else addNewline = true; if (addNewline) $("#content").append("<br/>"); } self._scrollBottomWindow(); } else { self._console.write(output, styles); } }, _setFixedPitchSizes: function() { var row = document.createElement("div"); row.className = "buffered-window"; for (var i = 0; i < self._size[0]; i++) row.innerHTML += "O"; // We have to wrap the text in a span to get an accurate line // height value, for some reason... row.innerHTML = "<span>" + row.innerHTML + "</span>"; $("#buffered-windows").append(row); self._pixelWidth = row.clientWidth; self._pixelLineHeight = row.firstChild.offsetHeight; $("#buffered-windows").empty(); } }; self._setFixedPitchSizes(); $("#top-window").get(0).style.width = self._pixelWidth + "px"; $("#top-window").get(0).style.lineHeight = self._pixelLineHeight + "px"; $("#content").get(0).style.width = self._pixelWidth + "px"; self._windowResize(); self._bindEventHandlers(); self._eraseBottomWindow(); } function downloadViaProxy(relPath, callback) { var PROXY_URL = gBaseUrl + "/cgi-bin/xhr_proxy.py"; var url = PROXY_URL + "?file=" + relPath.slice(IF_ARCHIVE_PREFIX.length); // TODO: Ideally the proxy should be communicated with via an HTTP // POST, since we're trying to change data on the server. $.ajax({url: url, success: function(data, textStatus) { if (data.indexOf("SUCCESS") == 0) loadBinaryUrl(relPath, callback, false); else callback("error", "downloadViaProxy() failed: " + data); }, error: function(XMLHttpRequest, textStatus, errorThrown) { callback("error", "downloadViaProxy() failed: " + textStatus); } }); } function loadBinaryUrl(relPath, callback, useProxy) { var url = gBaseUrl + "/" + relPath; var req = new XMLHttpRequest(); req.open('GET',url,true); //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com] req.overrideMimeType('text/plain; charset=x-user-defined'); req.onreadystatechange = function(evt) { if (req.readyState == 4) if (req.status == 200) callback("success", req.responseText); else if (relPath.indexOf(IF_ARCHIVE_PREFIX) == 0 && useProxy) { downloadViaProxy(relPath, callback); } else { callback("error", "loadBinaryUrl() failed, status " + req.status); } }; req.send(null); }; function _zcodeLoaded(status, data) { if (status == "success") { var zcode = new Array(data.length); for (var i = 0; i < data.length; i++) { zcode[i] = data.charCodeAt(i) & 0xff; } _webZuiStartup(zcode); } else { throw new Error("Error occurred when retrieving z-code: " + data); } } function _webZuiStartup(zcode) { var logfunc = undefined; if (window.console) { logfunc = function(msg) { console.log(msg); }; } var engine = new GnustoEngine(); var zui = new WebZui(logfunc); var runner = new EngineRunner(engine, zui, logfunc); engine.loadStory(zcode.slice()); runner.run(); } var gThisUrl = location.protocol + "//" + location.host + location.pathname; var gBaseUrl = gThisUrl.slice(0, gThisUrl.lastIndexOf("/")); var gStory = ""; var IF_ARCHIVE_PREFIX = "if-archive/"; $(document).ready(function() { var qs = new Querystring(); var story = qs.get("story", "stories/troll.z5"); gStory = story; loadBinaryUrl(story, _zcodeLoaded, true); });