From 8b0d19debad7dffa081848b430f88a3676149a53 Mon Sep 17 00:00:00 2001 From: "paul.kimpel@digm.com" Date: Mon, 29 Sep 2014 15:28:56 +0000 Subject: [PATCH] Release emulator version 1.00: 1. Implement new system and disk subsystem configuration mechanism. 2. Implement initial Mark-XIII Cold Start card deck for use with new configuration interfaces. 3. Deprecate use of B5500ColdLoader.html script (replaced by new configuration mechanism and Cold Start deck), but correct and enhance IndexedDB database detection, creation, and deletion in it. 4. Implement "Application Cache" support to allow emulator to run off-line in a browser. 5. Implement web-font support and update all UIs to use DejaVu Sans and DejaVu Sans Mono from downloaded .woff or .ttf font files. 6. Rework some code in Processor OPDC, DESC, and indexDescriptor routines, attempting to resolve Flag Bit errors (issue #23). This appears to result in some improvement, but we still see them occasionally under load. 7. Line Printer: - Implement new line printer driver with more realistic UI and operator controls. - Implement Algol Glyphs option to render special Algol characters in Unicode. - Implement support for optional "greenbar" shading on the "paper". 8. SPO: - Redesign SPO driver to accept input from a text element instead of capturing keystrokes directly from the window or "paper" + + +
- Stacker 2 - + + +
diff --git a/webUI/B5500CardPunch.js b/webUI/B5500CardPunch.js index f049848..c0889df 100644 --- a/webUI/B5500CardPunch.js +++ b/webUI/B5500CardPunch.js @@ -16,9 +16,8 @@ "use strict"; /**************************************/ -function B5500CardPunch(mnemonic, unitIndex, designate, statusChange, signal) { +function B5500CardPunch(mnemonic, unitIndex, designate, statusChange, signal, options) { /* Constructor for the CardPunch object */ - var that = this; this.mnemonic = mnemonic; // Unit mnemonic this.unitIndex = unitIndex; // Ready-mask bit number @@ -28,6 +27,7 @@ function B5500CardPunch(mnemonic, unitIndex, designate, statusChange, signal) { this.timer = 0; // setCallback() token this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit) + this.useAlgolGlyphs = options.algolGlyphs; // format Unicode for special Algol chars this.clear(); @@ -42,14 +42,14 @@ function B5500CardPunch(mnemonic, unitIndex, designate, statusChange, signal) { this.stacker2 = null; this.endOfStacker2 = null; this.window = window.open("../webUI/B5500CardPunch.html", mnemonic, - "scrollbars=no,resizable,width=560,height=204,left=0,top=220"); - this.window.addEventListener("load", function windowLoad() { - that.punchOnload(); - }, false); + "location=no,scrollbars=no,resizable,width=560,height=204,left=0,top=220"); + this.window.addEventListener("load", + B5500CentralControl.bindMethod(this, B5500CardPunch.prototype.punchOnload), false); } B5500CardPunch.prototype.cardsPerMinute = 300; // Punch speed B5500CardPunch.prototype.maxScrollLines = 850; // Maximum punch stacker scrollback (stacker capacity) +B5500CardPunch.prototype.rtrimRex = /\s+$/g; // regular expression for right-trimming card text /**************************************/ B5500CardPunch.prototype.$$ = function $$(e) { @@ -67,59 +67,30 @@ B5500CardPunch.prototype.clear = function clear() { this.finish = null; // external function to call for I/O completion this.runoutArmed = false; // EOF button: armed state - this.stopCount = 0; // stopCount for clearing the input buffer this.stacker1Count = 0; // cards in stacker #1 this.stacker2Count = 0; // cards in stacker #2 }; -/**************************************/ -B5500CardPunch.prototype.hasClass = function hasClass(e, name) { - /* returns true if element "e" has class "name" in its class list */ - var classes = e.className; - - if (!e) { - return false; - } else if (classes == name) { - return true; - } else { - return (classes.search("\\b" + name + "\\b") >= 0); - } -}; - -/**************************************/ -B5500CardPunch.prototype.addClass = function addClass(e, name) { - /* Adds a class "name" to the element "e"s class list */ - - if (!this.hasClass(e, name)) { - e.className += (" " + name); - } -}; - -/**************************************/ -B5500CardPunch.prototype.removeClass = function removeClass(e, name) { - /* Removes the class "name" from the element "e"s class list */ - - e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), ""); -}; - /**************************************/ B5500CardPunch.prototype.setPunchReady = function setPunchReady(ready) { /* Controls the ready-state of the card punch */ if (ready && !this.ready) { this.statusChange(1); - this.addClass(this.$$("CPStartBtn"), "greenLit") - this.removeClass(this.$$("CPNotReadyLight"), "redLit"); + B5500Util.addClass(this.$$("CPStartBtn"), "greenLit") + B5500Util.removeClass(this.$$("CPNotReadyLight"), "whiteLit"); this.ready = true; if (this.runoutArmed) { if (this.stacker1Count || this.stacker2Count) { if (this.window.confirm("Empty both " + this.mnemonic + " stackers?")) { this.stacker1Count = this.stacker2Count = 0; this.$$("CPStacker1Bar").value = 0; - this.$$("CPStacker2Bar").value = 0; + B5500Util.removeClass(this.$$("CPStacker1Full"), "annunciatorLit"); while (this.stacker1.firstChild) { this.stacker1.removeChild(this.stacker1.firstChild); } + this.$$("CPStacker2Bar").value = 0; + B5500Util.removeClass(this.$$("CPStacker2Full"), "annunciatorLit"); while (this.stacker2.firstChild) { this.stacker2.removeChild(this.stacker2.firstChild); } @@ -129,22 +100,49 @@ B5500CardPunch.prototype.setPunchReady = function setPunchReady(ready) { } } else if (!ready && this.ready) { this.statusChange(0); - this.removeClass(this.$$("CPStartBtn"), "greenLit") - this.addClass(this.$$("CPNotReadyLight"), "redLit"); + B5500Util.removeClass(this.$$("CPStartBtn"), "greenLit") + B5500Util.addClass(this.$$("CPNotReadyLight"), "whiteLit"); this.ready = false; } }; +/**************************************/ +B5500CardPunch.prototype.setAlgolGlyphs = function setAlgolGlyphs(makeItPretty) { + /* Controls the display of Unicode glyphs for the special Algol characters */ + + if (makeItPretty) { + if (!this.useAlgolGlyphs) { + B5500Util.xlateDOMTreeText(this.stacker1, B5500Util.xlateASCIIToAlgol); + B5500Util.xlateDOMTreeText(this.stacker2, B5500Util.xlateASCIIToAlgol); + } + } else { + if (this.useAlgolGlyphs) { + B5500Util.xlateDOMTreeText(this.stacker1, B5500Util.xlateAlgolToASCII); + B5500Util.xlateDOMTreeText(this.stacker2, B5500Util.xlateAlgolToASCII); + } + } + this.$$("CPAlgolGlyphsCheck").checked = makeItPretty; + this.useAlgolGlyphs = makeItPretty; +}; + +/**************************************/ +B5500CardPunch.prototype.appendLine = function appendLine(stacker, text) { + /* Appends a new
 element to the 
+    
+ + + + +
diff --git a/webUI/B5500CardReader.js b/webUI/B5500CardReader.js index 714f161..d0aa436 100644 --- a/webUI/B5500CardReader.js +++ b/webUI/B5500CardReader.js @@ -16,9 +16,8 @@ "use strict"; /**************************************/ -function B5500CardReader(mnemonic, unitIndex, designate, statusChange, signal) { +function B5500CardReader(mnemonic, unitIndex, designate, statusChange, signal, options) { /* Constructor for the CardReader object */ - var that = this; var x = (mnemonic == "CRA" ? 0 : 1)*110; this.mnemonic = mnemonic; // Unit mnemonic @@ -39,12 +38,11 @@ function B5500CardReader(mnemonic, unitIndex, designate, statusChange, signal) { } this.doc = null; this.window = window.open("../webUI/B5500CardReader.html", mnemonic, - "scrollbars=no,resizable,width=560,height=160,left=0,top="+x); - this.window.addEventListener("load", function windowLoad() { - that.readerOnload(); - }, false); + "location=no,scrollbars=no,resizable,width=560,height=160,left=0,top="+x); + this.window.addEventListener("load", + B5500CentralControl.bindMethod(this, B5500CardReader.prototype.readerOnload), false); - this.progressBar = null; + this.hopperBar = null; this.outHopperFrame = null; this.outHopper = null; } @@ -59,7 +57,7 @@ B5500CardReader.prototype.cardFilter = [ // Filter ASCII character values to val 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x3F,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, // 20-2F 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, // 30-3F 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 40-4F - 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x3F,0x5D,0x3F,0x3F, // 50-5F + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x3F,0x5D,0x3F,0x7E, // 50-5F 0x3F,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 60-6F 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x3F]; // 70-7F @@ -84,36 +82,6 @@ B5500CardReader.prototype.$$ = function $$(e) { return this.doc.getElementById(e); }; -/**************************************/ -B5500CardReader.prototype.hasClass = function hasClass(e, name) { - /* returns true if element "e" has class "name" in its class list */ - var classes = e.className; - - if (!e) { - return false; - } else if (classes == name) { - return true; - } else { - return (classes.search("\\b" + name + "\\b") >= 0); - } -}; - -/**************************************/ -B5500CardReader.prototype.addClass = function addClass(e, name) { - /* Adds a class "name" to the element "e"s class list */ - - if (!this.hasClass(e, name)) { - e.className += (" " + name); - } -}; - -/**************************************/ -B5500CardReader.prototype.removeClass = function removeClass(e, name) { - /* Removes the class "name" from the element "e"s class list */ - - e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), ""); -}; - /**************************************/ B5500CardReader.prototype.setReaderReady = function setReaderReady(ready) { /* Controls the ready-state of the card reader */ @@ -122,12 +90,12 @@ B5500CardReader.prototype.setReaderReady = function setReaderReady(ready) { this.ready = ready; if (ready) { this.statusChange(1); - this.addClass(this.$$("CRStartBtn"), "greenLit") - this.removeClass(this.$$("CRNotReadyLight"), "redLit"); + B5500Util.addClass(this.$$("CRStartBtn"), "greenLit") + B5500Util.removeClass(this.$$("CRNotReadyLight"), "whiteLit"); } else { this.statusChange(0); - this.removeClass(this.$$("CRStartBtn"), "greenLit") - this.addClass(this.$$("CRNotReadyLight"), "redLit"); + B5500Util.removeClass(this.$$("CRStartBtn"), "greenLit") + B5500Util.addClass(this.$$("CRNotReadyLight"), "whiteLit"); } }; @@ -138,16 +106,15 @@ B5500CardReader.prototype.armEOF = function armEOF(armed) { this.eofArmed = armed; if (armed) { - this.addClass(this.$$("CREOFBtn"), "redLit"); + B5500Util.addClass(this.$$("CREOFBtn"), "whiteLit"); } else { - this.removeClass(this.$$("CREOFBtn"), "redLit"); + B5500Util.removeClass(this.$$("CREOFBtn"), "whiteLit"); } }; /**************************************/ B5500CardReader.prototype.CRStartBtn_onclick = function CRStartBtn_onclick(ev) { /* Handle the click event for the START button */ - var that = this; if (!this.ready) { if (this.bufIndex < this.bufLength) { @@ -176,8 +143,8 @@ B5500CardReader.prototype.CREOFBtn_onclick = function CREOFBtn_onclick(ev) { }; /**************************************/ -B5500CardReader.prototype.CRProgressBar_onclick = function CRProgressBar_onclick(ev) { - /* Handle the click event for the "input hopper" progress bar */ +B5500CardReader.prototype.CRHopperBar_onclick = function CRHopperBar_onclick(ev) { + /* Handle the click event for the "input hopper" meter bar */ if (this.bufIndex < this.bufLength && !this.ready) { if (this.window.confirm((this.bufLength-this.bufIndex).toString() + " of " + this.bufLength.toString() + @@ -185,7 +152,7 @@ B5500CardReader.prototype.CRProgressBar_onclick = function CRProgressBar_onclick this.buffer = ""; this.bufLength = 0; this.bufIndex = 0; - this.progressBar.value = 0; + this.hopperBar.value = 0; this.$$("CRFileSelector").value = null; // reset the control while (this.outHopper.childNodes.length > 0) { this.outHopper.removeChild(this.outHopper.firstChild); @@ -223,8 +190,8 @@ B5500CardReader.prototype.fileSelector_onChange = function fileSelector_onChange that.bufIndex = 0; that.bufLength = that.buffer.length; - that.$$("CRProgressBar").value = that.bufLength; - that.$$("CRProgressBar").max = that.bufLength; + that.$$("CRHopperBar").value = that.bufLength; + that.$$("CRHopperBar").max = that.bufLength; } for (x=f.length-1; x>=0; x--) { @@ -260,10 +227,24 @@ B5500CardReader.prototype.readCardAlpha = function readCardAlpha(buffer, length) } for (x=0; x 0) { // an actual "?" + if (c == 0x3F) { // an actual "?" buffer[x] = 0x3F; - } else if (c > 0x7F) { // Unicode R Us -- NOT! - this.errorMask |= 0x08; + if (x == 0) { // it's an invalid char only if in first column + this.errorMask |= 0x08; + } + } else if (c > 0x7F) { // possibly a Unicode Algol glyph + switch (c) { + case 0x00A0: buffer[x] = 0x20; break; // non-breaking space + case 0x00D7: buffer[x] = 0x7C; break; // multiply + case 0x2190: buffer[x] = 0x7E; break; // left-arrow + case 0x2260: buffer[x] = 0x21; break; // not-equal + case 0x2264: buffer[x] = 0x7B; break; // less-than-or-equal + case 0x2265: buffer[x] = 0x7D; break; // greater-than-or-equal + default: + buffer[x] = 0x3F; + this.errorMask |= 0x08; + break; + } } else if ((buffer[x] = this.cardFilter[c]) == 0x3F) { // intentional assignment this.errorMask |= 0x08; } @@ -325,44 +306,34 @@ B5500CardReader.prototype.beforeUnload = function beforeUnload(ev) { /**************************************/ B5500CardReader.prototype.readerOnload = function readerOnload() { /* Initializes the reader window and user interface */ - var that = this; + var de; this.doc = this.window.document; - this.doc.title = "retro-B5500 " + this.mnemonic; + de = this.doc.documentElement; + this.doc.title = "retro-B5500 Card Reader " + this.mnemonic; - this.progressBar = this.$$("CRProgressBar"); + this.hopperBar = this.$$("CRHopperBar"); this.outHopperFrame = this.$$("CROutHopperFrame"); - this.outHopperFrame.contentDocument.head.innerHTML += ""; - this.outHopper = this.doc.createElement("pre"); - this.outHopperFrame.contentDocument.body.appendChild(this.outHopper); - - this.window.addEventListener("beforeunload", this.beforeUnload, false); + this.outHopper = this.outHopperFrame.contentDocument.getElementById("Paper"); this.armEOF(false); this.setReaderReady(false); - this.$$("CRFileSelector").addEventListener("change", function fileSelectorChange(ev) { - that.fileSelector_onChange(ev); - }, false); + this.window.addEventListener("beforeunload", + B5500CardReader.prototype.beforeUnload, false); + this.$$("CRFileSelector").addEventListener("change", + B5500CentralControl.bindMethod(this, B5500CardReader.prototype.fileSelector_onChange), false); + this.$$("CRStartBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500CardReader.prototype.CRStartBtn_onclick), false); + this.$$("CRStopBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500CardReader.prototype.CRStopBtn_onclick), false); + this.$$("CREOFBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500CardReader.prototype.CREOFBtn_onclick), false); + this.hopperBar.addEventListener("click", + B5500CentralControl.bindMethod(this, B5500CardReader.prototype.CRHopperBar_onclick), false); - this.$$("CRStartBtn").addEventListener("click", function startClick(ev) { - that.CRStartBtn_onclick(ev); - }, false); - - this.$$("CRStopBtn").addEventListener("click", function stopClick(ev) { - that.CRStopBtn_onclick(ev); - }, false); - - this.$$("CREOFBtn").addEventListener("click", function eofClick(ev) { - that.CREOFBtn_onclick(ev); - }, false); - - this.progressBar.addEventListener("click", function progressClick(ev) { - that.CRProgressBar_onclick(ev); - }, false); + this.window.resizeBy(de.scrollWidth - this.window.innerWidth + 4, // kludge for right-padding/margin + de.scrollHeight - this.window.innerHeight); }; /**************************************/ @@ -398,9 +369,9 @@ B5500CardReader.prototype.read = function read(finish, buffer, length, mode, con }); if (this.bufIndex < this.bufLength) { - this.progressBar.value = this.bufLength-this.bufIndex; + this.hopperBar.value = this.bufLength-this.bufIndex; } else { - this.progressBar.value = 0; + this.hopperBar.value = 0; this.buffer = ""; // discard the input buffer this.bufLength = 0; this.bufIndex = 0; @@ -472,6 +443,6 @@ B5500CardReader.prototype.shutDown = function shutDown() { if (this.timer) { clearCallback(this.timer); } - this.window.removeEventListener("beforeunload", this.beforeUnload, false); + this.window.removeEventListener("beforeunload", B5500CardReader.prototype.beforeUnload, false); this.window.close(); }; diff --git a/webUI/B5500ColdLoader.html b/webUI/B5500ColdLoader.html index e70440b..15492c8 100644 --- a/webUI/B5500ColdLoader.html +++ b/webUI/B5500ColdLoader.html @@ -1,12 +1,9 @@ + + B5500 Disk Subsystem Coldstart Loader - - - - - - + + + + + - + - - + -
+
- - + + - - - - - + + + + + - + + +
+
+ Using (none)+(none) +
?.??
- diff --git a/webUI/B5500Console.js b/webUI/B5500Console.js new file mode 100644 index 0000000..95a54a1 --- /dev/null +++ b/webUI/B5500Console.js @@ -0,0 +1,699 @@ +/*********************************************************************** +* retro-b5500/emulator B5500Console.js +************************************************************************ +* Copyright (c) 2012,2014, Nigel Williams and Paul Kimpel. +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* B5500 Operations Console Javascript module. +* +* Implements event handlers and control functions for the B5500 emulator +* operations console. +* +************************************************************************ +* 2014-07-20 P.Kimpel +* Original version, extracted from B5500Console.html. +***********************************************************************/ +"use strict"; + +window.addEventListener("load", function() { + var aControl; // A-Control button/light + var aNormal; // A-Normal button/light + var bControl; // B-Control button/light + var bNormal; // B-Normal button/light + var cc; // B5500CentralControl object + var ccLatches = [0, 0, 0]; // I/O- & interrupt-reporting latches + var ccLightsMap = new Array(6); // Misc annunciator DOM objects + var intLightsMap = new Array(48); // Interrupt annunciator DOM objects + var lastInterruptMask = 0; // Prior mask of interrupt annunciator lights + var lastCCMask = 0; // Prior mask of misc annunciator lights + var lastUnitBusyMask = 0; // Prior mask of unit-busy annunciator lights + var lastPANormalRate = -1; // Prior PA normal-state busy rate + var lastPAControlRate = -1; // Prior PA control-state busy rate + var lastPBNormalRate = -1; // Prior PB normal-state busy rate + var lastPBControlRate = -1; // Prior PB normal-state busy rate + var perf = performance; // (it's faster if locally cached) + var perLightsMap = new Array(48); // Peripheral I/O annunciator DOM objects + var procDelay; // Current average P1 delay [ms] + var procSlack; // Current average P1 slack time [%] + var showAnnunciators = true; // Display non-purist console mode (annunciators) + var timer = 0; // Console display update timer control cookie + var timerInterval = 50; // Console display update interval [ms] + + function $$(id) { + return document.getElementById(id); + } + + function setAnnunciators(showEm) { + $$("CentralControl").style.display = (showEm && cc.poweredUp ? "block" : "none"); + $$("RetroVersion").style.visibility = (showEm ? "visible" : "hidden"); + $$("RetroLogoImage").style.display = (showEm ? "inline" : "none"); + $$("B5500LogoImage").style.display = (showEm ? "none" : "inline"); + $$("ConfigLabel").style.display = (showEm ? "inline" : "none"); + } + + function evaluateNotReady(config) { + /* Evaluates the system configuration to determine whether the + NOT READY lamp should be illuminated */ + var lampClass = "whiteButton"; + + switch (false) { + case config.PA.enabled || config.PA.enabled: + case (config.PA.enabled && !config.PB1L) || (config.PB.enabled && config.PB1L): + case config.IO1.enabled || config.IO2.enabled || config.IO3.enabled || config.IO4.enabled: + case config.memMod[0].enabled: + case config.units.SPO.enabled: + case config.units.DKA.enabled: + lampClass += " whiteLit"; + } + + $$("NotReadyBtn").className = lampClass; + } + + function BurroughsLogo_Click(ev) { + showAnnunciators = !showAnnunciators; + setAnnunciators(showAnnunciators); + } + + function B5500Logo_Click(ev) { + var sysConfig = new B5500SystemConfig(); + + if (cc.poweredUp) { + alert("System configuration changes are\nnot allowed while power is on."); + } else { + $$("ConfigLabel").style.display = "none"; + sysConfig.openConfigUI(); + } + } + + function PowerOnBtn_Click(ev) { + var sysConfig = new B5500SystemConfig(); + + function applyPower(config) { + $$("HaltBtn").className = "redButton redLit"; + $$("PowerOnBtn").disabled = true; + $$("PowerOffBtn").disabled = false; + $$("LoadSelectBtn").disabled = false; + $$("LoadBtn").disabled = false; + $$("HaltBtn").disabled = true; + $$("MemoryCheckBtn").disabled = false; + cc.powerOn(config); + $$("LoadSelectBtn").className = "yellowButton" + (cc.cardLoadSelect ? " yellowLit" : ""); + evaluateNotReady(config); + setAnnunciators(showAnnunciators); + } + + function youMayPowerOnWhenReady_Gridley(config) { + /* Called-back by sysConfig.getSystemConfig with the requested configuration */ + + if (!config) { + alert("No System Configuration found\nCANNOT POWER ON."); + } else { + $$("PowerOnBtn").className = "greenButton greenLit"; + $$("SysConfigName").textContent = config.configName; + $$("StorageName").textContent = config.units.DKA.storageName; + if (showAnnunciators) { + lampTest(applyPower, config); + } else { + applyPower(config); + } + } + } + + sysConfig.getSystemConfig(null, youMayPowerOnWhenReady_Gridley); // get current system config + return true; + } + + function PowerOffBtn_Click(ev) { + $$("PowerOnBtn").className = "greenButton"; + $$("ANormalBtn").className = "yellowButton"; + $$("AControlBtn").className = "yellowButton"; + $$("BNormalBtn").className = "yellowButton"; + $$("BControlBtn").className = "yellowButton"; + $$("LoadSelectBtn").className = "yellowButton"; + $$("MemoryCheckBtn").className = "redButton"; + $$("NotReadyBtn").className = "whiteButton"; + $$("HaltBtn").className = "redButton"; + cc.powerOff(); + $$("PowerOnBtn").disabled = false; + $$("PowerOffBtn").disabled = true; + $$("LoadSelectBtn").disabled = true; + $$("LoadBtn").disabled = true; + $$("HaltBtn").disabled = true; + $$("MemoryCheckBtn").disabled = true; + $$("CentralControl").style.display = "none"; + if (timer) { + clearInterval(timer); + timer = 0; + } + return true; + } + + function HaltBtn_Click(ev) { + $$("HaltBtn").className = "redButton redLit"; + cc.halt(); + $$("HaltBtn").disabled = true; + $$("LoadBtn").disabled = false; + dasBlinkenlicht(); + if (timer) { + clearInterval(timer); + timer = 0; + } + } + + function LoadBtn_Click(ev) { + var result; + + window.open("", "SPO").focus(); // re-focus the SPO window + result = cc.load(false); + switch (result) { + case 0: // load initiated successfully + $$("HaltBtn").className = "redButton"; + $$("HaltBtn").disabled = false; + $$("LoadBtn").disabled = true; + timer = setInterval(dasBlinkenlicht, timerInterval); + break; + case 1: + alert("P1 busy or not available"); + break; + case 2: + alert("SPO is not ready"); + break; + case 3: + alert("SPO is busy"); + break; + case 4: + alert("DKA is not ready"); + break; + case 5: + alert("DKA is busy"); + break; + default: + alert("cc.load() result = " + result); + break; + } + } + + function LoadSelectBtn_Click(ev) { + if (cc.cardLoadSelect) { + cc.cardLoadSelect = 0; + $$("LoadSelectBtn").className = "yellowButton"; + } else { + cc.cardLoadSelect = 1; + $$("LoadSelectBtn").className = "yellowButton yellowLit"; + } + } + + function dumpState(caption) { + /* Generates a dump of the processor states and all of memory */ + var doc; + var lastPhase = -2; + var win = window.open("", "", "location=no,resizable,scrollbars,status"); + var x; + + var htmlMatch = /[<>&"]/g; // regular expression for escaping HTML text + + function htmlFilter(c) { + /* Used to escape HTML-sensitive characters in a string */ + switch (c) { + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + case "\"": + return """; + default: + return c; + } + } + + function escapeHTML(text) { + /* Returns "text" as escaped HTML */ + + return text.replace(htmlMatch, htmlFilter); + } + + function writer(phase, text) { + /* Call-back function for cc.dumpSystemState */ + + switch (phase) { + case 0: + lastPhase = phase; + doc.writeln(escapeHTML(text)); + doc.writeln("User Agent: " + navigator.userAgent); + break; + + case 1: + case 2: + if (phase == lastPhase) { + doc.writeln(escapeHTML(text)); + } else { + lastPhase = phase; + doc.writeln(); + doc.writeln(escapeHTML(text)); + doc.writeln(); + } + break; + + case 32: + if (phase != lastPhase) { + lastPhase = phase; + doc.writeln(); + } + doc.writeln(); + doc.writeln(escapeHTML(text)); + break; + + case -1: + break; + } // switch + } + + doc = win.document; + doc.open(); + doc.writeln("retro-B5500 Console State Dump"); + doc.writeln(""); + doc.write("
");
+
+        cc.dumpSystemState(caption, writer);
+
+        doc.writeln("
") + doc.close(); + win.focus(); + } + + function displayCallbacks() { + /* Builds a table of outstanding callbacks */ + var cb; + var cbs = clearCallback(0); + var cookie; + var e; + var body = document.createElement("tbody"); + var oldBody = $$("CallbackBody"); + var row; + + for (cookie in cbs) { + cb = cbs[cookie]; + row = document.createElement("tr"); + + e = document.createElement("td"); + e.appendChild(document.createTextNode(cookie.toString())); + row.appendChild(e); + + e = document.createElement("td"); + e.appendChild(document.createTextNode(cb.delay.toFixed(2))); + row.appendChild(e); + + e = document.createElement("td"); + e.appendChild(document.createTextNode(cb.context.mnemonic || "??")); + row.appendChild(e); + + e = document.createElement("td"); + e.appendChild(document.createTextNode((cb.args ? cb.args.length : 0).toString())); + row.appendChild(e); + body.appendChild(row); + } + + body.id = oldBody.id; + oldBody.parentNode.replaceChild(body, oldBody); + } + + function displayCentralControl() { + /* Displays the I/O and interrupt status in CentralControl */ + var cells; + var s; + var interruptMask; + var interruptChange; + var ccMask; + var ccChange; + var unitBusyMask; + var unitBusyChange; + var x; + + cc.fetchCCLatches(ccLatches); + ccMask = ccLatches[0]; + ccChange = lastCCMask ^ ccMask; + lastCCMask = ccMask; + + interruptMask = ccLatches[1] % 0x4000; + interruptChange = lastInterruptMask ^ interruptMask; + lastInterruptMask = interruptMask; + + unitBusyMask = ccLatches[2]; + unitBusyChange = lastUnitBusyMask ^ unitBusyMask; + lastUnitBusyMask = unitBusyMask; + + x = 0; + while (ccChange) { + if (ccChange & 0x01) { + if (ccLightsMap[x]) { + ccLightsMap[x].style.visibility = (ccMask & 0x01 ? "visible" : "hidden"); + } + } + ccMask >>>= 1; + ccChange >>>= 1; + x++; + } + + x = 47; + while (interruptChange) { + if (interruptChange & 0x01) { + if (intLightsMap[x]) { + intLightsMap[x].style.visibility = (interruptMask & 0x01 ? "visible" : "hidden"); + } + } + interruptMask >>>= 1; + interruptChange >>>= 1; + x--; + } + + x = 47; + while (unitBusyChange) { + if (unitBusyChange & 0x01) { + if (perLightsMap[x]) { + perLightsMap[x].style.visibility = (unitBusyMask & 0x01 ? "visible" : "hidden"); + } + } + unitBusyMask >>>= 1; + unitBusyChange >>>= 1; + x--; + } + } + + function dasBlinkenlicht() { + var cycles; + var pa = cc.PA; + var pb = cc.PB; + var p1 = cc.P1; + var stateRate; + + cycles = p1.normalCycles+p1.controlCycles; + + if (pa) { + if (pa.normalCycles+pa.controlCycles <= 0) { + if (lastPAControlRate != -1) { + lastPAControlRate = -1; + aControl.className = "yellowButton"; + aNormal.className = "yellowButton"; + } + } else { + stateRate = Math.round(pa.normalCycles/cycles*6 + 0.25); + if (stateRate != lastPANormalRate) { + lastPANormalRate = stateRate; + switch (stateRate) { + case 0: + aNormal.className = "yellowButton"; + break; + case 1: + //aNormal.className = "yellowButton yellowLit1"; + //break; + case 2: + aNormal.className = "yellowButton yellowLit2"; + break; + case 3: + //aNormal.className = "yellowButton yellowLit3"; + //break; + case 4: + aNormal.className = "yellowButton yellowLit4"; + break; + case 5: + //aNormal.className = "yellowButton yellowLit5"; + //break; + default: + aNormal.className = "yellowButton yellowLit"; + break; + } + } + + stateRate = Math.round(pa.controlCycles/cycles*6 + 0.25); + if (stateRate != lastPAControlRate) { + lastPAControlRate = stateRate; + switch (stateRate) { + case 0: + aControl.className = "yellowButton"; + break; + case 1: + //aControl.className = "yellowButton yellowLit1"; + //break; + case 2: + aControl.className = "yellowButton yellowLit2"; + break; + case 3: + //aControl.className = "yellowButton yellowLit3"; + //break; + case 4: + aControl.className = "yellowButton yellowLit4"; + break; + case 5: + //aControl.className = "yellowButton yellowLit5"; + //break; + default: + aControl.className = "yellowButton yellowLit"; + break; + } + } + + pa.controlCycles = pa.normalCycles = 0; + } + } + + if (pb) { + if (pb.normalCycles+pb.controlCycles <= 0) { + if (lastPBControlRate != -1) { + bControl.className = "yellowButton"; + bNormal.className = "yellowButton"; + lastPBControlRate = -1; + } + } else { + stateRate = Math.round(pb.normalCycles/cycles*6 + 0.25); + if (stateRate != lastPBNormalRate) { + lastPBNormalRate = stateRate; + switch (stateRate) { + case 0: + bNormal.className = "yellowButton"; + break; + case 1: + //bNormal.className = "yellowButton yellowLit1"; + //break; + case 2: + bNormal.className = "yellowButton yellowLit2"; + break; + case 3: + //bNormal.className = "yellowButton yellowLit3"; + //break; + case 4: + bNormal.className = "yellowButton yellowLit4"; + break; + case 5: + //bNormal.className = "yellowButton yellowLit5"; + //break; + default: + bNormal.className = "yellowButton yellowLit"; + break; + } + } + + stateRate = Math.round(pb.controlCycles/cycles*6 + 0.25); + if (stateRate != lastPBControlRate) { + lastPBControlRate = stateRate; + switch (stateRate) { + case 0: + bControl.className = "yellowButton"; + break; + case 1: + //bControl.className = "yellowButton yellowLit1"; + //break; + case 2: + bControl.className = "yellowButton yellowLit2"; + break; + case 3: + //bControl.className = "yellowButton yellowLit3"; + //break; + case 4: + bControl.className = "yellowButton yellowLit4"; + break; + case 5: + //bControl.className = "yellowButton yellowLit5"; + //break; + default: + bControl.className = "yellowButton yellowLit"; + break; + } + } + + pb.controlCycles = pb.normalCycles = 0; + } + } + + procDelay.innerHTML = p1.delayDeltaAvg.toFixed(1); + procSlack.innerHTML = (p1.procSlackAvg/p1.procRunAvg*100).toFixed(1); + + if (showAnnunciators) { + displayCentralControl(); + } + // displayCallbacks(); + } + + function buildLightMaps() { + /* Builds tables of the DOM entries for the annunciator lights, for efficient access */ + var mnem; + var spec; + var x; + + ccLightsMap[0] = $$("AD1F"); + ccLightsMap[1] = $$("AD2F"); + ccLightsMap[2] = $$("AD3F"); + ccLightsMap[3] = $$("AD4F"); + ccLightsMap[4] = $$("P2BF"); + ccLightsMap[5] = $$("HP2F"); + + for (x=3; x<=16; x++) { + intLightsMap[50-x] = $$("CCI" + (x+100).toString().substring(1) + "F"); + } + + for (mnem in B5500CentralControl.unitSpecs) { + spec = B5500CentralControl.unitSpecs[mnem]; + perLightsMap[spec.unitIndex] = $$(mnem); + } + } + + function lampTest(callback, callbackParam) { + /* Lights up the operator console, waits a bit, then turns everything + off and calls the "callback" function, passing "callbackParam". + The Power On lamp is not affected */ + + function switchEm(mode) { + var visibility = (mode ? "visible" : "hidden"); + var x; + + $$("ANormalBtn").className = "yellowButton" + (mode ? " yellowLit" : ""); + $$("AControlBtn").className = "yellowButton" + (mode ? " yellowLit" : ""); + $$("BNormalBtn").className = "yellowButton" + (mode ? " yellowLit" : ""); + $$("BControlBtn").className = "yellowButton" + (mode ? " yellowLit" : ""); + $$("LoadSelectBtn").className = "yellowButton" + (mode ? " yellowLit" : ""); + $$("MemoryCheckBtn").className = "redButton" + (mode ? " redLit" : ""); + $$("NotReadyBtn").className = "whiteButton" + (mode ? " whiteLit" : ""); + $$("HaltBtn").className = "redButton" + (mode ? " redLit" : ""); + + for (x in ccLightsMap) { + if (ccLightsMap[x]) { + ccLightsMap[x].style.visibility = visibility; + } + } + + for (x in intLightsMap) { + if (intLightsMap[x]) { + intLightsMap[x].style.visibility = visibility; + } + } + + for (x in perLightsMap) { + if (perLightsMap[x]) { + perLightsMap[x].style.visibility = visibility; + } + } + + if (!mode) { + setAnnunciators(showAnnunciators); + setTimeout(callback, 1000, callbackParam); + } + } + + setAnnunciators(true); + $$("CentralControl").style.display = "block"; // overrides if !cc.poweredUp + switchEm(1); + setTimeout(switchEm, 2000, 0); + } + + function clearStatus(inSeconds) { + /* Delays for "inSeconds" seconds, then clears the StatusLabel element */ + + setTimeout(function(ev) { + $$("StatusLabel").textContent = ""; + }, inSeconds*1000); + } + + function checkBrowser() { + /* Checks whether this browser can support the necessary stuff */ + var missing = ""; + + if (!window.indexedDB) {missing += ", IndexedDB"} + if (!window.ArrayBuffer) {missing += ", ArrayBuffer"} + if (!window.DataView) {missing += ", DataView"} + if (!window.Blob) {missing += ", Blob"} + if (!window.File) {missing += ", File"} + if (!window.FileReader) {missing += ", FileReader"} + if (!window.FileList) {missing += ", FileList"} + if (!window.postMessage) {missing += ", window.postMessage"} + if (!(window.performance && "now" in performance)) {missing += ", performance.now"} + + if (missing.length == 0) { + return false; + } else { + alert("No can do... your browser does not support the following features:\n" + + missing.substring(2)); + return true; + } + } + + /***** window.onload() outer block *****/ + + $$("RetroVersion").innerHTML = B5500CentralControl.version; + if (!checkBrowser()) { + window.name = "B5500Console"; + $$("BurroughsLogo").addEventListener("click", BurroughsLogo_Click, false); + $$("B5500Logo").addEventListener("click", B5500Logo_Click, false); + $$("PowerOnBtn").addEventListener("click", PowerOnBtn_Click, false); + $$("PowerOffBtn").addEventListener("click", PowerOffBtn_Click, false); + $$("HaltBtn").addEventListener("click", HaltBtn_Click, false); + $$("LoadBtn").addEventListener("click", LoadBtn_Click, false); + $$("LoadSelectBtn").addEventListener("click", LoadSelectBtn_Click, false); + $$("MemoryCheckBtn").addEventListener("click", function(ev) { + dumpState("Memory-Check Button"); + }, false); + + window.applicationCache.addEventListener("checking", function(ev) { + $$("StatusLabel").textContent = "Checking for emulator update..."; + }, false); + window.applicationCache.addEventListener("noupdate", function(ev) { + $$("StatusLabel").textContent = "Emulator version is current."; + clearStatus(5); + }, false); + window.applicationCache.addEventListener("obsolete", function(ev) { + $$("StatusLabel").textContent = "Emulator off-line installation has been disabled."; + clearStatus(15); + }, false); + window.applicationCache.addEventListener("downloading", function(ev) { + $$("StatusLabel").textContent = "Initiating download for emulator update..."; + }, false); + window.applicationCache.addEventListener("progress", function(ev) { + var text = (ev.loaded && ev.total ? ev.loaded.toString() + "/" + ev.total.toString() : "Unknown number of"); + $$("StatusLabel").textContent = text + " resources downloaded thus far..."; + }, false); + window.applicationCache.addEventListener("updateready", function(ev) { + $$("StatusLabel").textContent = "Emulator update completed. Reload this page to activate the new version."; + clearStatus(10); + }, false); + window.applicationCache.addEventListener("cached", function(ev) { + $$("StatusLabel").textContent = "Emulator is now installed for off-line use."; + clearStatus(10); + }, false); + window.applicationCache.addEventListener("error", function(ev) { + $$("StatusLabel").textContent = "Unable to check for emulator update."; + clearStatus(10); + }, false); + + aControl = $$("AControlBtn"); + aNormal = $$("ANormalBtn"); + bControl = $$("BControlBtn"); + bNormal = $$("BNormalBtn"); + procDelay = $$("procDelay"); + procSlack = $$("procSlack"); + buildLightMaps(); + + cc = new B5500CentralControl(window); + window.dumpState = dumpState; + } +}, false); diff --git a/webUI/B5500DatacomUnit.css b/webUI/B5500DatacomUnit.css index f663e13..21e45a6 100644 --- a/webUI/B5500DatacomUnit.css +++ b/webUI/B5500DatacomUnit.css @@ -2,7 +2,8 @@ * retro-b5500/emulator B5500DatacomUnit.css ************************************************************************ * Copyright (c) 2013, Nigel Williams and Paul Kimpel. -* Licensed under the MIT License, see http://www.opensource.org/licenses/mit-license.php +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php ************************************************************************ * B5500 emulator Datacom web interface style sheet. ************************************************************************ @@ -10,49 +11,66 @@ * Original version, from B5500SPOUnit.css. ***********************************************************************/ -BODY { - position: relative; - margin: 4px} +#DatacomUnit { + height: 100%; + min-height: 100%; + overflow: hidden; + padding: 0} -PRE { - font-family: Lucida Sans Typewriter, Courier New, Courier, monospace; - font-size: 8pt; - margin: 0} - -IFRAME#TermOut { - height: 480px; - width: 560px; - border: 1px solid black} - -BODY.TermOut { - background-color: white; - font-family: Lucida Sans Typewriter, Courier New, Courier, monospace; - font-size: 8pt} - -DIV#TermControlsDiv { - margin-bottom: 2px; - text-align: left} - -BUTTON.greenButton { - background-color: #060; - color: white; - height: 24px; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: bold; - border: 1px solid black; - border-radius: 4px} - -BUTTON.greenLit { +#TermOut { + position: absolute; + left: 8px; + right: 8px; + top: 40px; + bottom: 8px; + min-height: 120px; + min-width: 120px; + overflow-x: hidden; + overflow-y: scroll; color: black; - background-color: #0F0} + background-color: white; + padding: 4px; + border: 1px solid gray} -BUTTON.blackBorder { - border: 1px solid black} +#Paper { + margin-left: 0; + margin-right: 0; + margin-top: 2000px; + margin-bottom: 0; + padding: 0} + +#EndOfPaper { + display: block; + margin: 0; + padding: 0; + opacity: 0} +#EndOfPaper.hidden { + display: none} + +#InputBox { + display: none; + margin: 0; + padding: 0; + border: none; + background-color: #FF9} +#InputBox.visible { + display: block} +#InputBox:focus { + border: none; + outline: none} /* to suppress Chrome's default outline */ + +#TermControlsDiv { + white-space: nowrap; + overflow: hidden; + margin: 8px} + +#TermConnectBtn { + height: 24px} SPAN.annunciator { + width: 2em; + text-align: right; color: black; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; font-size: 8pt; font-weight: bold; visibility: hidden} diff --git a/webUI/B5500DatacomUnit.html b/webUI/B5500DatacomUnit.html index 909b810..d482177 100644 --- a/webUI/B5500DatacomUnit.html +++ b/webUI/B5500DatacomUnit.html @@ -1,43 +1,79 @@ + + B5500 Emulator Datacom Unit + - - - + + - +
- +    - NR   - IDLE   - RR   - WR   - IBZ   - OBZ   - AB   - INT   - FB   + NR   + IDLE   + RR   + WR   + IBZ   + OBZ   + AB   + INT   + FB   Offset: - 0   + 0   Length: - 0   + 0   Col: - 0   + 0  
- +
+
 
+ +
 
+
\ No newline at end of file diff --git a/webUI/B5500DatacomUnit.js b/webUI/B5500DatacomUnit.js index 7ba219f..8ca6517 100644 --- a/webUI/B5500DatacomUnit.js +++ b/webUI/B5500DatacomUnit.js @@ -24,7 +24,7 @@ "use strict"; /**************************************/ -function B5500DatacomUnit(mnemonic, unitIndex, designate, statusChange, signal) { +function B5500DatacomUnit(mnemonic, unitIndex, designate, statusChange, signal, options) { /* Constructor for the DatacomUnit object */ this.maxScrollLines = 1500; // Maximum amount of printer scrollback @@ -53,9 +53,9 @@ function B5500DatacomUnit(mnemonic, unitIndex, designate, statusChange, signal) this.paper = null; this.endOfPaper = null; this.window = window.open("../webUI/B5500DatacomUnit.html", mnemonic, - "scrollbars,resizable,width=580,height=540"); - this.window.moveTo((screen.availWidth-this.window.outerWidth)/2, (screen.availHeight-this.window.outerHeight)/2); - this.window.addEventListener("load", B5500CentralControl.bindMethod(B5500DatacomUnit.prototype.datacomOnload, this), false); + "location=no,scrollbars,resizable,width=520,height=540"); + this.window.addEventListener("load", B5500CentralControl.bindMethod(this, + B5500DatacomUnit.prototype.datacomOnload), false); } // this.bufState enumerations @@ -90,6 +90,7 @@ B5500DatacomUnit.prototype.clear = function clear() { this.abnormal = false; // buffer in abnormal state this.bufIndex = 0; // current offset into buffer + this.bufCheckPoint = 0; // last bufIndex when input overflowed a line this.bufLength = 0; // current buffer length this.connected = false; // buffer/adapter is currently connected this.errorMask = 0; // error mask for finish() @@ -102,36 +103,6 @@ B5500DatacomUnit.prototype.clear = function clear() { this.bufState = this.bufNotReady; // Current state of datacom buffer }; -/**************************************/ -B5500DatacomUnit.prototype.hasClass = function hasClass(e, name) { - /* returns true if element "e" has class "name" in its class list */ - var classes = e.className; - - if (!e) { - return false; - } else if (classes == name) { - return true; - } else { - return (classes.search("\\b" + name + "\\b") >= 0); - } -}; - -/**************************************/ -B5500DatacomUnit.prototype.addClass = function addClass(e, name) { - /* Adds a class "name" to the element "e"s class list */ - - if (!this.hasClass(e, name)) { - e.className += (" " + name); - } -}; - -/**************************************/ -B5500DatacomUnit.prototype.removeClass = function removeClass(e, name) { - /* Removes the class "name" from the element "e"s class list */ - - e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), ""); -}; - /**************************************/ B5500DatacomUnit.prototype.showBufferIndex = function showBufferIndex() { /* Formats the buffer index and length, and the column counter, for display */ @@ -148,63 +119,63 @@ B5500DatacomUnit.prototype.setState = function setState(newState) { this.showBufferIndex(); if (this.abnormal) { - this.addClass(this.$$("Abnormal"), "textLit") + B5500Util.addClass(this.$$("Abnormal"), "textLit") } else { - this.removeClass(this.$$("Abnormal"), "textLit"); + B5500Util.removeClass(this.$$("Abnormal"), "textLit"); } if (this.interrupt) { - this.addClass(this.$$("Interrupt"), "textLit") + B5500Util.addClass(this.$$("Interrupt"), "textLit") } else { - this.removeClass(this.$$("Interrupt"), "textLit"); + B5500Util.removeClass(this.$$("Interrupt"), "textLit"); } if (this.fullBuffer) { - this.addClass(this.$$("FullBuffer"), "textLit") + B5500Util.addClass(this.$$("FullBuffer"), "textLit") } else { - this.removeClass(this.$$("FullBuffer"), "textLit"); + B5500Util.removeClass(this.$$("FullBuffer"), "textLit"); } if (this.bufState != newState) { switch (this.bufState) { case this.bufNotReady: - this.removeClass(this.$$("NotReadyState"), "textLit"); + B5500Util.removeClass(this.$$("NotReadyState"), "textLit"); break; case this.bufIdle: - this.removeClass(this.$$("IdleState"), "textLit"); + B5500Util.removeClass(this.$$("IdleState"), "textLit"); break; case this.bufInputBusy: - this.removeClass(this.$$("InputBusyState"), "textLit"); + B5500Util.removeClass(this.$$("InputBusyState"), "textLit"); break; case this.bufReadReady: - this.removeClass(this.$$("ReadReadyState"), "textLit"); + B5500Util.removeClass(this.$$("ReadReadyState"), "textLit"); break; case this.bufOutputBusy: - this.removeClass(this.$$("OutputBusyState"), "textLit"); + B5500Util.removeClass(this.$$("OutputBusyState"), "textLit"); break; case this.bufWriteReady: - this.removeClass(this.$$("WriteReadyState"), "textLit"); + B5500Util.removeClass(this.$$("WriteReadyState"), "textLit"); break; } switch (newState) { case this.bufNotReady: - this.addClass(this.$$("NotReadyState"), "textLit"); + B5500Util.addClass(this.$$("NotReadyState"), "textLit"); break; case this.bufIdle: - this.addClass(this.$$("IdleState"), "textLit"); + B5500Util.addClass(this.$$("IdleState"), "textLit"); break; case this.bufInputBusy: - this.addClass(this.$$("InputBusyState"), "textLit"); + B5500Util.addClass(this.$$("InputBusyState"), "textLit"); break; case this.bufReadReady: - this.addClass(this.$$("ReadReadyState"), "textLit"); + B5500Util.addClass(this.$$("ReadReadyState"), "textLit"); break; case this.bufOutputBusy: - this.addClass(this.$$("OutputBusyState"), "textLit"); + B5500Util.addClass(this.$$("OutputBusyState"), "textLit"); break; case this.bufWriteReady: - this.addClass(this.$$("WriteReadyState"), "textLit"); + B5500Util.addClass(this.$$("WriteReadyState"), "textLit"); break; } @@ -219,7 +190,7 @@ B5500DatacomUnit.prototype.termDisconnect = function termDisconnect() { if (this.connected) { this.bufLength = 0; this.bufIndex = 0; - this.removeClass(this.$$("TermConnectBtn"), "greenLit"); + B5500Util.removeClass(this.$$("TermConnectBtn"), "greenLit"); this.interrupt = true; this.abnormal = true; this.setState(this.bufIdle); @@ -233,7 +204,7 @@ B5500DatacomUnit.prototype.termConnect = function termConnect() { /* Sets the status of the datacom unit to connected */ if (!this.connected) { - this.addClass(this.$$("TermConnectBtn"), "greenLit"); + B5500Util.addClass(this.$$("TermConnectBtn"), "greenLit"); this.interrupt = true; this.abnormal = true; this.setState(this.bufWriteReady); @@ -243,17 +214,17 @@ B5500DatacomUnit.prototype.termConnect = function termConnect() { }; /**************************************/ -B5500DatacomUnit.prototype.appendEmptyLine = function appendEmptyLine() { +B5500DatacomUnit.prototype.appendEmptyLine = function appendEmptyLine(text) { /* Removes excess lines already printed, then appends a new
 element
     to the 
+
+ + + \ No newline at end of file diff --git a/webUI/B5500LinePrinter.js b/webUI/B5500LinePrinter.js new file mode 100644 index 0000000..f78ede3 --- /dev/null +++ b/webUI/B5500LinePrinter.js @@ -0,0 +1,427 @@ +/*********************************************************************** +* retro-b5500/emulator B5500LinePrinter.js +************************************************************************ +* Copyright (c) 2014, Nigel Williams and Paul Kimpel. +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php +************************************************************************ +* B5500 Line Printer Peripheral Unit module. +* +* Defines a Line Printer peripheral unit type. +* +************************************************************************ +* 2014-08-31 P.Kimpel +* Original version, cloned from B5500DummyPrinter.js and B5500CardPunch.js. +***********************************************************************/ +"use strict"; + +/**************************************/ +function B5500LinePrinter(mnemonic, unitIndex, designate, statusChange, signal, options) { + /* Constructor for the LinePrinter object */ + var h = screen.availHeight*0.60; + var w = 900; + + this.mnemonic = mnemonic; // Unit mnemonic + this.unitIndex = unitIndex; // Ready-mask bit number + this.designate = designate; // IOD unit designate number + this.statusChange = statusChange; // external function to call for ready-status change + this.signal = signal; // external function to call for special signals (e.g,. Printer Finished) + + this.timer = 0; // setCallback() token + this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit) + this.useAlgolGlyphs = options.algolGlyphs; // format Unicode for special Algol chars + this.useGreenbar = true; // format "greenbar" shading on the paper + this.lpi = 6; // lines/inch (actually, lines per greenbar group, should be even) + + this.clear(); + + this.window = window.open("", mnemonic); + if (this.window) { + this.shutDown(); // destroy the previously-existing window + this.window = null; + } + this.doc = null; + this.barGroup = null; // current greenbar line group + this.paperDoc = null; // the content document for the paper frame + this.paper = null; // the "paper" we print on + this.endOfPaper = null; // dummy element used to control scrolling + this.paperMeter = null; // element showing amount of paper remaining + this.window = window.open("../webUI/B5500LinePrinter.html", mnemonic, + "location=no,scrollbars,resizable,width=" + w + ",height=" + h + + ",left=0,top=" + (screen.availHeight - h)); + this.window.addEventListener("load", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.printerOnload), false); +} +B5500LinePrinter.prototype.linesPerMinute = 1040; // B329 line printer +B5500LinePrinter.prototype.maxPaperLines = 150000; // maximum printer scrollback (about a box of paper) +B5500LinePrinter.prototype.rtrimRex = /\s+$/g; // regular expression for right-trimming lines +B5500LinePrinter.prototype.theColorGreen = "#CFC"; // for greenbar shading + +/**************************************/ +B5500LinePrinter.prototype.$$ = function $$(e) { + return this.doc.getElementById(e); +}; + +/**************************************/ +B5500LinePrinter.prototype.clear = function clear() { + /* Initializes (and if necessary, creates) the printer unit state */ + + this.ready = false; // ready status + this.busy = false; // busy status + + this.errorMask = 0; // error mask for finish() + this.finish = null; // external function to call for I/O completion + + this.paperLeft = this.maxPaperLines;// lines remaining in paper supply + this.formFeedCount = 0; // counter for triple-formfeed => rip paper + this.groupLinesLeft = 0; // lines remaining in current greenbar group + this.topOfForm = false; // start new page flag +}; + +/**************************************/ +B5500LinePrinter.prototype.setPrinterReady = function setPrinterReady(ready) { + /* Controls the ready-state of the line printer */ + + this.formFeedCount = 0; + if (ready && !this.ready) { + this.statusChange(1); + B5500Util.addClass(this.$$("LPStartBtn"), "greenLit") + B5500Util.removeClass(this.$$("LPNotReadyLight"), "whiteLit"); + this.ready = true; + } else if (!ready && this.ready) { + this.statusChange(0); + B5500Util.removeClass(this.$$("LPStartBtn"), "greenLit") + B5500Util.addClass(this.$$("LPNotReadyLight"), "whiteLit"); + this.ready = false; + } +}; + +/**************************************/ +B5500LinePrinter.prototype.ripPaper = function ripPaper(ev) { + /* Handles an event to clear the "paper" from the printer */ + + this.formFeedCount = 0; + if (this.window.confirm("Do you want to clear the \"paper\" from the printer?")) { + B5500Util.removeClass(this.$$("LPEndOfPaperBtn"), "whiteLit"); + this.paperMeter.value = this.paperLeft = this.maxPaperLines; + while (this.paper.firstChild) { + this.paper.removeChild(this.paper.firstChild); + } + } +}; + +/**************************************/ +B5500LinePrinter.prototype.appendLine = function appendLine(text) { + /* Appends one line, with a trailing new-line character, to the current + greenbar group, this.barGroup. This handles top-of-form and greenbar + highlighting */ + var feed = "\n"; + + if (this.groupLinesLeft <= 0) { + // Start the green half of a greenbar group + this.barGroup = this.doc.createElement("pre"); + this.paper.appendChild(this.barGroup); + this.groupLinesLeft = this.lpi; + if (!this.atTopOfForm) { + this.barGroup.className = "paper greenBar"; + } else { + this.atTopOfForm = false; + this.barGroup.className = "paper greenBar topOfForm"; + } + } else if (this.groupLinesLeft*2 == this.lpi) { + // Start the white half of a greenbar group + this.barGroup = this.doc.createElement("pre"); + this.paper.appendChild(this.barGroup); + this.barGroup.className = "paper whiteBar"; + } else if (this.groupLinesLeft == 1) { + feed = ""; // no linefeed at end of a bar group + } else if ((this.groupLinesLeft-1)*2 == this.lpi) { + feed = ""; // ditto + } + + this.barGroup.appendChild(this.doc.createTextNode(text + feed)); + --this.groupLinesLeft; +}; + +/**************************************/ +B5500LinePrinter.prototype.printLine = function printLine(text, control) { + /* Prints one line to the "paper", handling carriage control and greenbar + group completion. For now, SPACE 0 (overprintng) is treated as single-spacing */ + var lines = 1; + + this.appendLine(text || "\xA0"); + if (control > 1) { + ++lines; + this.appendLine("\xA0"); + } else if (control < 0) { + while(this.groupLinesLeft > 0) { + ++lines; + this.appendLine("\xA0"); + } + this.atTopOfForm = true; + } + + if (this.paperLeft > 0) { + this.paperMeter.value = this.paperLeft -= lines; + } else { + this.setPrinterReady(false); + B5500Util.addClass(this.$$("LPEndOfPaperBtn"), "whiteLit"); + } +}; + +/**************************************/ +B5500LinePrinter.prototype.setAlgolGlyphs = function setAlgolGlyphs(makeItPretty) { + /* Controls the display of Unicode glyphs for the special Algol characters */ + + if (makeItPretty) { + if (!this.useAlgolGlyphs) { + B5500Util.xlateDOMTreeText(this.paper, B5500Util.xlateASCIIToAlgol); + } + } else { + if (this.useAlgolGlyphs) { + B5500Util.xlateDOMTreeText(this.paper, B5500Util.xlateAlgolToASCII); + } + } + this.$$("LPAlgolGlyphsCheck").checked = makeItPretty; + this.useAlgolGlyphs = makeItPretty; +}; + +/**************************************/ +B5500LinePrinter.prototype.setGreenbar = function setGreenbar(useGreen) { + /* Controls the display of "greenbar" shading on the paper */ + var rule = null; + var rules = null; + var sheet; + var ss = this.paperDoc.styleSheets; + var x; + + // First, find the embedded style sheet for the paper frame. + for (x=ss.length-1; x>=0; --x) { + sheet = ss[x]; + if (sheet.ownerNode.id == "PaperFrameStyles") { + rules = sheet.cssRules; + // Next, search through the rules for the one that controls greenbar shading. + for (x=rules.length-1; x>=0; --x) { + rule = rules[x]; + if (rule.selectorText == "PRE.greenBar") { + // Found it: now flip the background color. + rule.style.backgroundColor = (useGreen ? this.theColorGreen : "white"); + } + } + break; // out of for loop + } + } + this.$$("LPGreenbarCheck").checked = useGreen; + this.useGreenbar = useGreen; +}; + +/**************************************/ +B5500LinePrinter.prototype.LPStartBtn_onclick = function LPStartBtn_onclick(ev) { + /* Handle the click event for the START button */ + + if (!this.ready && this.paperLeft > 0) { + this.formFeedCount = 0; + this.setPrinterReady(true); + } +}; + +/**************************************/ +B5500LinePrinter.prototype.LPStopBtn_onclick = function LPStopBtn_onclick(ev) { + /* Handle the click event for the STOP button */ + + if (this.ready) { + this.formFeedCount = 0; + this.setPrinterReady(false); + } +}; + +/**************************************/ +B5500LinePrinter.prototype.LPSpaceBtn_onclick = function LPSpaceBtn_onclick(ev) { + /* Handle the click event for the Skip To Heading button */ + + if (!this.ready) { + this.formFeedCount = 0; + this.printLine("", 1); + this.endOfPaper.scrollIntoView(); + } +}; + +/**************************************/ +B5500LinePrinter.prototype.LPFormFeedBtn_onclick = function LPFormFeedBtn_onclick(ev) { + /* Handle the click event for the Skip To Heading button */ + + if (!this.ready) { + this.printLine("", -1); + this.endOfPaper.scrollIntoView(); + if (++this.formFeedCount >= 3) { + this.ripPaper(); + } + } +}; + +/**************************************/ +B5500LinePrinter.prototype.LPEndOfPaperBtn_onclick = function LPEndOfPaperBtn_onclick(ev) { + /* Handle the click event for the End Of Paper button. If the printer is in + and end-of-paper condition, this will make the printer ready, but it will + still be in an EOP condition. The next time a print line is received, the + EOP condition will force it not-ready again. You can print only one line + at a time (presumably to the end of the current page. The EOP condition can + be cleared by clicking Skip To Heading three times to "rip" the paper */ + + if (this.paperLeft <= 0 && !this.ready) { + this.formFeedCount = 0; + B5500Util.removeClass(this.$$("LPEndOfPaperBtn"), "whiteLit"); + this.setPrinterReady(true); + } +}; + +/**************************************/ +B5500LinePrinter.prototype.LPAlgolGlyphsCheck_onclick = function LPAlgolGlyphsCheck_onclick(ev) { + /* Handle the click event for the Algol Glyphs checkbox */ + + this.setAlgolGlyphs(ev.target.checked); +}; + +/**************************************/ +B5500LinePrinter.prototype.LPGreenbarCheck_onclick = function LPGreenbarCheck_onclick(ev) { + /* Handle the click event for the Greenbar checkbox */ + + this.setGreenbar(ev.target.checked); +}; + +/**************************************/ +B5500LinePrinter.prototype.beforeUnload = function beforeUnload(ev) { + var msg = "Closing this window will make the device unusable.\n" + + "Suggest you stay on the page and minimize this window instead"; + + ev.preventDefault(); + ev.returnValue = msg; + return msg; +}; + +/**************************************/ +B5500LinePrinter.prototype.printerOnload = function printerOnload() { + /* Initializes the line printer window and user interface */ + var newChild; + + this.doc = this.window.document; + this.doc.title = "retro-B5500 Line Printer " + this.mnemonic; + + this.paperDoc = this.$$("LPPaperFrame").contentDocument; + this.paper = this.paperDoc.getElementById("Paper"); + this.endOfPaper = this.paperDoc.getElementById("EndOfPaper"); + this.paperMeter = this.$$("LPPaperMeter"); + + newChild = this.paperDoc.createElement("div"); + newChild.id = this.paper.id; + this.paper.parentNode.replaceChild(newChild, this.paper); + this.paper = newChild; + + this.setAlgolGlyphs(this.useAlgolGlyphs); + this.setGreenbar(this.useGreenbar); + this.paperMeter.max = this.maxPaperLines; + this.paperMeter.low = this.maxPaperLines*0.1; + this.paperMeter.value = this.paperLeft = this.maxPaperLines; + this.setPrinterReady(true); + + this.window.addEventListener("beforeunload", + B5500LinePrinter.prototype.beforeUnload, false); + this.$$("LPEndOfPaperBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPEndOfPaperBtn_onclick), false); + this.$$("LPFormFeedBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPFormFeedBtn_onclick), false); + this.$$("LPSpaceBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPSpaceBtn_onclick), false); + this.$$("LPAlgolGlyphsCheck").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPAlgolGlyphsCheck_onclick), false); + this.$$("LPGreenbarCheck").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPGreenbarCheck_onclick), false); + this.$$("LPStopBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPStopBtn_onclick), false); + this.$$("LPStartBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.LPStartBtn_onclick), false); +}; + +/**************************************/ +B5500LinePrinter.prototype.read = function read(finish, buffer, length, mode, control) { + /* Initiates a read operation on the unit */ + + finish(0x04, 0); +}; + +/**************************************/ +B5500LinePrinter.prototype.space = function space(finish, length, control) { + /* Initiates a space operation on the unit */ + + finish(0x04, 0); +}; + +/**************************************/ +B5500LinePrinter.prototype.write = function write(finish, buffer, length, mode, control) { + /* Initiates a write operation on the unit */ + var text; + + this.errorMask = 0; + if (length > 0) { + text = String.fromCharCode.apply(null, buffer.subarray(0, length)).replace(this.rtrimRex, ''); + if (this.useAlgolGlyphs) { + text = B5500Util.xlateASCIIToAlgol(text); + } + } + + if (control || length) { + this.printLine(text, control); + } + + this.timer = setCallback(this.mnemonic, this, + 60000/this.linesPerMinute + this.initiateStamp - performance.now(), + this.signal); + finish(this.errorMask, 0); + this.endOfPaper.scrollIntoView(); +}; + +/**************************************/ +B5500LinePrinter.prototype.erase = function erase(finish, length) { + /* Initiates an erase operation on the unit */ + + finish(0x04, 0); +}; + +/**************************************/ +B5500LinePrinter.prototype.rewind = function rewind(finish) { + /* Initiates a rewind operation on the unit */ + + finish(0x04, 0); +}; + +/**************************************/ +B5500LinePrinter.prototype.readCheck = function readCheck(finish, length, control) { + /* Initiates a read check operation on the unit */ + + finish(0x04, 0); +}; + +/**************************************/ +B5500LinePrinter.prototype.readInterrogate = function readInterrogate(finish, control) { + /* Initiates a read interrogate operation on the unit */ + + finish(0x04, 0); +}; + +/**************************************/ +B5500LinePrinter.prototype.writeInterrogate = function writeInterrogate(finish, control) { + /* Initiates a write interrogate operation on the unit */ + + finish(0x04, 0); +}; + +/**************************************/ +B5500LinePrinter.prototype.shutDown = function shutDown() { + /* Shuts down the device */ + + if (this.timer) { + clearCallback(this.timer); + } + this.window.removeEventListener("beforeunload", B5500LinePrinter.prototype.beforeUnload, false); + this.window.close(); +}; \ No newline at end of file diff --git a/webUI/B5500MagTapeDrive.css b/webUI/B5500MagTapeDrive.css index 6fbb94c..d4dda47 100644 --- a/webUI/B5500MagTapeDrive.css +++ b/webUI/B5500MagTapeDrive.css @@ -10,70 +10,22 @@ * Original version, from B5500CardReader.css. ***********************************************************************/ -BODY { - position: relative; - background-color: black; +#magTapeBody { + height: 100%; + min-height: 100%; + overflow: hidden; + padding: 0} + +#MTDiv { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; margin: 4px} -DIV#MTDiv { - position: relative; - background-color: #666; - width: 550px; - height: 110px; - border: 1px solid black; - border-radius: 8px; - padding: 0; - vertical-align: top} - -BUTTON.yellowButton { - background-color: #990; - color: black; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: normal; - width: 60px; - height: 40px; - border: 1px solid #DDD; - border-radius: 4px} - -BUTTON.blackButton { - background-color: black; - color: white; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: normal; - width: 60px; - height: 40px; - border: 1px solid #DDD; - border-radius: 4px} - -BUTTON.redButton { - background-color: #900; - color: white; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: normal; - width: 60px; - height: 40px; - border: 1px solid #DDD; - border-radius: 4px} - -BUTTON.yellowLit { - background-color: #FF0} - -BUTTON.redLit { - color: black; - background-color: #F00} - SPAN.annunciator { - position: absolute; - color: #666; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 7pt; - font-weight: bold} - -SPAN.whiteLit { - color: white} + position: absolute} #MTUnloadBtn { position: absolute; @@ -105,7 +57,7 @@ SPAN.whiteLit { top: 8px; left: 348px;} -IMG#MTReel { +#MTReel { position: absolute; height: 40px; visibility: hidden; @@ -129,36 +81,47 @@ IMG#MTReel { top: 34px; right: 8px} -#MTFileName { +#MTStatusDiv { position: absolute; - border: none; - background-color: #666; - color: white; - width: 530px; top: 58px; - left: 8px} + left: 8px; + right: 8px; + bottom: auto} -#MTProgressBar { +#MTFileName { + width: 100%; + font-family: DejaVuSansWeb, sans-serif; + color: white; + background-color: inherit; + border: none} + +#MTReelBar { + margin-top: 4px; + width: 100%; + height: 16px; + border: 1px solid white} + +#MTReelBarCaption { position: absolute; - border: 1px solid white; - width: 530px; - top: 84px; - left: 8px} + right: 4px; + top: 24px; + z-index: 1; + height: 12px; + color: black; + font-weight: bold} /* Tape Load Panel */ -DIV#MTLoaderDiv { +#MTLoaderDiv { background-color: #666; width: 480px; height: 86px; border-radius: 8px; padding: 8px; color: white; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: bold} + font-size: 8pt} -DIV#MTLoaderInnerDiv { +#MTLoaderInnerDiv { position: relative; height: 100%; width: 100%} @@ -195,10 +158,12 @@ DIV#MTLoaderInnerDiv { #MTLoadCancelBtn { position: absolute; + height: 20px; bottom: 0; right: 64px} #MTLoadOKBtn { position: absolute; + height: 20px; bottom: 0; right: 0} diff --git a/webUI/B5500MagTapeDrive.html b/webUI/B5500MagTapeDrive.html index 368850d..cde9610 100644 --- a/webUI/B5500MagTapeDrive.html +++ b/webUI/B5500MagTapeDrive.html @@ -1,34 +1,54 @@ + + B5500 Emulator Magnetic Tape Drive + - - - + + - + -
- - +
+ + - - + + - + UNLOADED AT BOT AT EOT REWINDING - - - +
+ + + +
diff --git a/webUI/B5500MagTapeDrive.js b/webUI/B5500MagTapeDrive.js index 407d9e3..607e07b 100644 --- a/webUI/B5500MagTapeDrive.js +++ b/webUI/B5500MagTapeDrive.js @@ -28,10 +28,8 @@ "use strict"; /**************************************/ -function B5500MagTapeDrive(mnemonic, unitIndex, designate, statusChange, signal) { +function B5500MagTapeDrive(mnemonic, unitIndex, designate, statusChange, signal, options) { /* Constructor for the MagTapeDrive object */ - var that = this; - var x = ((mnemonic.charCodeAt(2) - "A".charCodeAt(0))*30); this.mnemonic = mnemonic; // Unit mnemonic this.unitIndex = unitIndex; // Ready-mask bit number @@ -44,20 +42,20 @@ function B5500MagTapeDrive(mnemonic, unitIndex, designate, statusChange, signal) this.clear(); + this.loadWindow = null; // handle for the tape loader window + this.reelBar = null; // handle for tape-full meter + this.reelIcon = null; // handle for the reel spinner + this.window = window.open("", mnemonic); if (this.window) { - this.shutDown(); // destroy the previously-existing window + this.shutDown(); // destroy any previously-existing window this.window = null; } this.doc = null; this.window = window.open("../webUI/B5500MagTapeDrive.html", mnemonic, - "scrollbars=no,resizable,width=560,height=120,left=280,top="+x); - this.window.addEventListener("load", function windowLoad() { - that.tapeDriveOnLoad(); - }, false); - - this.progressBar = null; - this.reelIcon = null; + "location=no,scrollbars=no,resizable,width=560,height=120,left=280,top=0"); + this.window.addEventListener("load", + B5500CentralControl.bindMethod(this, B5500MagTapeDrive.prototype.tapeDriveOnload), false); } // this.tapeState enumerations @@ -87,7 +85,9 @@ B5500MagTapeDrive.prototype.bcdTapeMark = 0x8F; // .bcd image EOF code B5500MagTapeDrive.prototype.reelCircumference = 10*Math.PI; // max circumference of tape [inches] -B5500MagTapeDrive.prototype.maxSpinAngle = 33; +B5500MagTapeDrive.prototype.spinUpdateInterval = 15; + // milliseconds between reel icon angle updates +B5500MagTapeDrive.prototype.maxSpinAngle = 25; // max angle to rotate reel image [degrees] B5500MagTapeDrive.prototype.bcdXlateInOdd = [ // Translate odd parity BIC to ASCII @@ -166,52 +166,76 @@ B5500MagTapeDrive.prototype.clear = function clear() { this.bufIndex = 0; // IOUnit buffer current offset }; -/**************************************/ -B5500MagTapeDrive.prototype.hasClass = function hasClass(e, name) { - /* returns true if element "e" has class "name" in its class list */ - var classes = e.className; - - if (!e) { - return false; - } else if (classes == name) { - return true; - } else { - return (classes.search("\\b" + name + "\\b") >= 0); - } -}; - -/**************************************/ -B5500MagTapeDrive.prototype.addClass = function addClass(e, name) { - /* Adds a class "name" to the element "e"s class list */ - - if (!this.hasClass(e, name)) { - e.className += (" " + name); - } -}; - -/**************************************/ -B5500MagTapeDrive.prototype.removeClass = function removeClass(e, name) { - /* Removes the class "name" from the element "e"s class list */ - - e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), ""); -}; - /**************************************/ B5500MagTapeDrive.prototype.spinReel = function spinReel(inches) { - /* Rotates the reel image icon an appropriate amount based on the number of - inches of tape movement. The rotation is limited to this.maxSpinAngle degrees + /* Rotates the reel image icon an appropriate amount based on the "inches" + of tape to be moved. The rotation is limited to this.maxSpinAngle degrees in either direction so that movement remains apparent to the viewer */ var circumference = this.reelCircumference*(1 - this.tapeInches/this.maxTapeLength/2); var degrees = inches/circumference*360; - if (degrees >= this.maxSpinAngle) { + if (degrees > this.maxSpinAngle) { degrees = this.maxSpinAngle; } else if (degrees < -this.maxSpinAngle) { degrees = -this.maxSpinAngle; } this.reelAngle = (this.reelAngle + degrees)%360; + this.reelIcon.style["-webkit-transform"] = "rotate(" + this.reelAngle.toFixed(0) + "deg)"; // temp for Chrome this.reelIcon.style.transform = "rotate(" + this.reelAngle.toFixed(0) + "deg)"; + + if (this.tapeInches < this.imgMaxInches) { + this.reelBar.value = this.imgMaxInches - this.tapeInches; + } else { + this.reelBar.value = 0; + } +}; + +/**************************************/ +B5500MagTapeDrive.prototype.moveTape = function moveTape(inches, delay, callBack) { + /* Delays the I/O during tape motion, during which it animates the reel image + icon. At the completion of the "delay" time in milliseconds, "callBack" is + called with no parameters. */ + var delayLeft = delay; // milliseconds left to delay + var direction = (inches < 0 ? -1 : 1); + var inchesLeft = inches; // inches left to move tape + var lastStamp = performance.now(); // last timestamp for spinDelay + + function spinFinish() { + this.timer = 0; + if (inchesLeft != 0) { + this.spinReel(inchesLeft); + } + callBack.call(this); + } + + function spinDelay() { + var motion; + var stamp = performance.now(); + var interval = stamp - lastStamp; + + if (interval <= 0) { + interval = this.spinUpdateInterval/2; + if (interval > delayLeft) { + interval = delayLeft; + } + } + + if ((delayLeft -= interval) > this.spinUpdateInterval) { + lastStamp = stamp; + this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, spinDelay); + } else { + this.timer = setCallback(this.mnemonic, this, delayLeft, spinFinish); + } + motion = this.tapeSpeed*interval/1000*direction; + inchesLeft -= motion; + if (inchesLeft*direction < 0) { // inchesLeft crossed zero + inchesLeft = direction = 0; + } + this.spinReel(motion); + } + + spinDelay.call(this); }; /**************************************/ @@ -223,11 +247,12 @@ B5500MagTapeDrive.prototype.setAtBOT = function setAtBOT(atBOT) { if (atBOT) { this.imgIndex = 0; this.tapeInches = 0; - this.addClass(this.$$("MTAtBOTLight"), "whiteLit"); - this.progressBar.value = this.imgMaxInches; + B5500Util.addClass(this.$$("MTAtBOTLight"), "annunciator"); + this.reelBar.value = this.imgMaxInches; this.reelIcon.style.transform = "rotate(0deg)"; + this.reelIcon.style["-webkit-transform"] = "rotate(0deg)"; // temp for Chrome } else { - this.removeClass(this.$$("MTAtBOTLight"), "whiteLit"); + B5500Util.removeClass(this.$$("MTAtBOTLight"), "annunciatorLit"); } } }; @@ -239,10 +264,10 @@ B5500MagTapeDrive.prototype.setAtEOT = function setAtEOT(atEOT) { if (atEOT ^ this.atEOT) { this.atEOT = atEOT; if (atEOT) { - this.addClass(this.$$("MTAtEOTLight"), "whiteLit"); - this.progressBar.value = 0; + B5500Util.addClass(this.$$("MTAtEOTLight"), "annunciatorLit"); + this.reelBar.value = 0; } else { - this.removeClass(this.$$("MTAtEOTLight"), "whiteLit"); + B5500Util.removeClass(this.$$("MTAtEOTLight"), "annunciatorLit"); } } }; @@ -265,13 +290,13 @@ B5500MagTapeDrive.prototype.setTapeUnloaded = function setTapeUnloaded() { this.$$("MTRewindBtn").disabled = true; this.$$("MTWriteRingBtn").disabled = true; this.$$("MTFileName").value = ""; - this.removeClass(this.$$("MTRemoteBtn"), "yellowLit"); - this.addClass(this.$$("MTLocalBtn"), "yellowLit"); - this.removeClass(this.$$("MTWriteRingBtn"), "redLit"); - this.addClass(this.$$("MTUnloadedLight"), "whiteLit"); + B5500Util.removeClass(this.$$("MTRemoteBtn"), "yellowLit"); + B5500Util.addClass(this.$$("MTLocalBtn"), "yellowLit"); + B5500Util.removeClass(this.$$("MTWriteRingBtn"), "redLit"); + B5500Util.addClass(this.$$("MTUnloadedLight"), "annunciatorLit"); this.setAtBOT(false); this.setAtEOT(false); - this.progressBar.value = 0; + this.reelBar.value = 0; this.reelIcon.style.visibility = "hidden"; if (this.timer) { clearCallback(this.timer); @@ -295,13 +320,13 @@ B5500MagTapeDrive.prototype.setTapeRemote = function setTapeRemote(ready) { if (ready) { this.tapeState = this.tapeRemote; this.statusChange(1); - this.removeClass(this.$$("MTLocalBtn"), "yellowLit"); - this.addClass(this.$$("MTRemoteBtn"), "yellowLit"); + B5500Util.removeClass(this.$$("MTLocalBtn"), "yellowLit"); + B5500Util.addClass(this.$$("MTRemoteBtn"), "yellowLit"); } else { this.tapeState = this.tapeLocal; this.statusChange(0); - this.removeClass(this.$$("MTRemoteBtn"), "yellowLit"); - this.addClass(this.$$("MTLocalBtn"), "yellowLit"); + B5500Util.removeClass(this.$$("MTRemoteBtn"), "yellowLit"); + B5500Util.addClass(this.$$("MTLocalBtn"), "yellowLit"); } } }; @@ -316,7 +341,7 @@ B5500MagTapeDrive.prototype.setWriteRing = function setWriteRing(writeRing) { case this.tapeRemote: if (this.writeRing && !writeRing) { this.writeRing = false; - this.removeClass(this.$$("MTWriteRingBtn"), "redLit"); + B5500Util.removeClass(this.$$("MTWriteRingBtn"), "redLit"); } break; } @@ -337,7 +362,8 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { var tapeInches = 0; // selected tape length in inches var tapeLengthSelect = null; // tape length list element var win = this.window.open("B5500MagTapeLoadPanel.html", this.mnemonic + "Load", - "scrollbars=no,resizable,width=508,height=112"); + "location=no,scrollbars=no,resizable,width=508,height=112,left=" + + (this.window.screenX+16) +",top=" + (this.window.screenY+16)); var writeRing = false; // true if write-enabled var writeRingCheck = null; // tape write ring checkbox element @@ -382,27 +408,27 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { mt.tapeInches = 0; mt.imgEOTInches = eotInches; mt.imgMaxInches = tapeInches; - mt.progressBar.max = mt.imgMaxInches; - mt.progressBar.value = mt.imgMaxInches; - mt.removeClass(mt.$$("MTUnloadedLight"), "whiteLit"); + mt.reelBar.max = mt.imgMaxInches; + mt.reelBar.value = mt.imgMaxInches; mt.setAtEOT(false); mt.setAtBOT(true); mt.tapeState = mt.tapeLocal; // setTapeRemote() requires it not be unloaded mt.setTapeRemote(false); mt.reelIcon.style.visibility = "visible"; + B5500Util.removeClass(mt.$$("MTUnloadedLight"), "annunciatorLit"); mt.imgWritten = false; mt.writeRing = writeRing; if (writeRing) { - mt.addClass(mt.$$("MTWriteRingBtn"), "redLit"); + B5500Util.addClass(mt.$$("MTWriteRingBtn"), "redLit"); } else { - mt.removeClass(mt.$$("MTWriteRingBtn"), "redLit"); + B5500Util.removeClass(mt.$$("MTWriteRingBtn"), "redLit"); } win.close(); } - function bcdLoader_onLoad(ev) { + function bcdLoader_onload(ev) { /* Loads a ".bcd" tape image into the drive */ var blockLength; var image = new Uint8Array(ev.target.result); @@ -456,13 +482,13 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { finishLoad(); } - function tapLoader_onLoad(ev) { + function tapLoader_onload(ev) { /* Loads a ".tap" tape image into the drive */ /* To be Provided */ } - function textLoader_onLoad(ev) { + function textLoader_onload(ev) { /* Loads a text image as either odd or even parity bcd data */ var block; // ANSI text of current block var blockLength; // length of current ASCII block @@ -536,17 +562,17 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { case "aod": case "aev": tape = new FileReader(); - tape.onload = textLoader_onLoad; + tape.onload = textLoader_onload; tape.readAsText(file); break; case "bcd": tape = new FileReader(); - tape.onload = bcdLoader_onLoad; + tape.onload = bcdLoader_onload; tape.readAsArrayBuffer(file); break; case "tap": tape = new FileReader(); - tape.onload = tapLoader_onLoad; + tape.onload = tapLoader_onload; tape.readAsArrayBuffer(file); break; default: @@ -557,10 +583,13 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { } } - function tapeLoadOnLoad (ev) { + function tapeLoadOnload (ev) { /* Driver for the tape loader window */ + var de; doc = win.document; + de = doc.documentElement; + win.focus(); $$$ = function $$$(id) { return doc.getElementById(id); }; @@ -589,19 +618,25 @@ B5500MagTapeDrive.prototype.loadTape = function loadTape() { }, false); $$$("MTLoadOKBtn").addEventListener("click", tapeLoadOK, false); - $$$("MTLoadCancelBtn").addEventListener("click", function loadCancelBtn(ev) { file = null; mt.$$("MTFileName").value = ""; win.close(); }, false); + + win.resizeBy(de.scrollWidth - win.innerWidth, + de.scrollHeight - win.innerHeight); } + // Outer block of loadTape + if (this.loadWindow && !this.loadWindow.closed) { + this.loadWindow.close(); + } + this.loadWindow = win; mt.$$("MTLoadBtn").disabled = true; - win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2); - win.focus(); - win.addEventListener("load", tapeLoadOnLoad, false); + win.addEventListener("load", tapeLoadOnload, false); win.addEventListener("unload", function tapeLoadUnload(ev) { + this.loadWindow = null; if (win.closed) { mt.$$("MTLoadBtn").disabled = (mt.tapeState != mt.tapeUnloaded); } @@ -614,8 +649,8 @@ B5500MagTapeDrive.prototype.unloadTape = function unloadTape() { window so the user can save or copy/paste it elsewhere */ var doc = null; // loader window.document var mt = this; // tape drive object - var win = this.window.open("", this.mnemonic + "Unload", - "scrollbars=no,resizable,width=800,height=600"); + var win = this.window.open("./B5500FramePaper.html", this.mnemonic + "-Unload", + "location=no,scrollbars=yes,resizable,width=800,height=600"); function unloadDriver() { /* Converts the tape image to ASCII once the window has displayed the @@ -627,11 +662,14 @@ B5500MagTapeDrive.prototype.unloadTape = function unloadTape() { var image = mt.image; // tape image data var imgLength = mt.imgTopIndex; // tape image active length var table; // even/odd parity translate table - var text = doc.getElementById("TapeText"); + var tape; //
 element to receive tape data
         var x = 0;                      // image data index
 
-        while (text.firstChild) {       // delete the please-wait message
-            text.removeChild(text.firstChild);
+        doc = win.document;
+        doc.title = "B5500 " + mt.mnemonic + " Unload Tape";
+        tape = doc.getElementById("Paper");
+        while (tape.firstChild) {               // delete any existing 
 content
+            tape.removeChild(tape.firstChild);
         }
 
         c = image[x];
@@ -641,7 +679,8 @@ B5500MagTapeDrive.prototype.unloadTape = function unloadTape() {
             bufIndex = 0;
             do {
                 if (bufIndex >= bufLength) { // ASCII block size exceeded
-                    text.appendChild(doc.createTextNode(String.fromCharCode.apply(null, buf.subarray(0, bufIndex))));
+                    tape.appendChild(doc.createTextNode(
+                            String.fromCharCode.apply(null, buf.subarray(0, bufIndex))));
                     bufIndex = 0;
                 }
                 if (c > 0) {            // drop any unrecorded tape frames
@@ -654,20 +693,26 @@ B5500MagTapeDrive.prototype.unloadTape = function unloadTape() {
                 }
             } while (c < 0x80);
             buf[bufIndex++] = 0x0A;
-            text.appendChild(doc.createTextNode(String.fromCharCode.apply(null, buf.subarray(0, bufIndex))));
+            tape.appendChild(doc.createTextNode(
+                    String.fromCharCode.apply(null, buf.subarray(0, bufIndex))));
         } while (x < imgLength);
 
         mt.setTapeUnloaded();
     }
 
+    function unloadSetup() {
+        /* Loads a status message into the "paper" rendering area, then calls
+        unloadDriver after a short wait to allow the message to appear */
+
+        win.document.getElementById("Paper").appendChild(
+                win.document.createTextNode("Rendering tape image... please wait..."));
+        setTimeout(unloadDriver, 50);
+    }
+
+    // Outer block of unloadTape
     win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
     win.focus();
-    doc = win.document;
-    doc.open();
-    doc.write("
Converting... please wait...
"); - doc.close(); - doc.title = "B5500 " + this.mnemonic + " Unload Tape"; - setCallback(this.mnemonic, this, 50, unloadDriver); // give the message time to display + win.addEventListener("load", unloadSetup, false); }; /**************************************/ @@ -677,12 +722,11 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) { called from this.rewind()], then readies the unit again when the rewind is complete */ var inches; var lastStamp = performance.now(); - var updateInterval = 15; // ms function rewindFinish() { this.timer = 0; this.busy = false; - this.removeClass(this.$$("MTRewindingLight"), "whiteLit"); + B5500Util.removeClass(this.$$("MTRewindingLight"), "annunciatorLit"); if (makeReady && this.tapeState == this.tapeRemote) { this.ready = true; this.statusChange(1); @@ -694,15 +738,14 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) { var interval = stamp - lastStamp; if (interval <= 0) { - interval = updateInterval/2; + interval = this.spinUpdateInterval/2; } if (this.tapeInches > 0) { inches = interval/1000*this.rewindSpeed; this.tapeInches -= inches; lastStamp = stamp; - this.timer = setCallback(this.mnemonic, this, updateInterval, rewindDelay); + this.timer = setCallback(this.mnemonic, this, this.spinUpdateInterval, rewindDelay); this.spinReel(-inches); - this.progressBar.value = this.imgMaxInches - this.tapeInches; } else { this.setAtBOT(true); this.timer = setCallback(this.mnemonic, this, 2000, rewindFinish); @@ -719,7 +762,7 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) { this.ready = false; this.statusChange(0); this.setAtEOT(false); - this.addClass(this.$$("MTRewindingLight"), "whiteLit"); + B5500Util.addClass(this.$$("MTRewindingLight"), "annunciatorLit"); this.timer = setCallback(this.mnemonic, this, 1000, rewindDelay); } }; @@ -728,8 +771,9 @@ B5500MagTapeDrive.prototype.tapeRewind = function tapeRewind(makeReady) { B5500MagTapeDrive.prototype.MTUnloadBtn_onclick = function MTUnloadBtn_onclick(ev) { /* Handle the click event for the UNLOAD button */ - if (this.imgWritten && this.window.confirm("Do you want to save the tape image data?")) { - this.unloadTape(); // will do setTapeUnloaded() afterwards + if (this.imgWritten && this.window.confirm( + "Do you want to save the tape image data?\n(CANCEL discards the image)")) { + this.unloadTape(); // it will do setTapeUnloaded() afterwards } else { this.setTapeUnloaded(); } @@ -1080,7 +1124,7 @@ B5500MagTapeDrive.prototype.bcdWrite = function bcdWrite(oddParity) { }; /**************************************/ -B5500MagTapeDrive.prototype.tapeDriveBeforeUnload = function tapeDriveBeforeUnload(ev) { +B5500MagTapeDrive.prototype.beforeUnload = function beforeUnload(ev) { var msg = "Closing this window will make the device unusable.\n" + "Suggest you stay on the page and minimize this window instead"; @@ -1090,49 +1134,40 @@ B5500MagTapeDrive.prototype.tapeDriveBeforeUnload = function tapeDriveBeforeUnlo }; /**************************************/ -B5500MagTapeDrive.prototype.tapeDriveOnLoad = function tapeDriveOnLoad() { +B5500MagTapeDrive.prototype.tapeDriveOnload = function tapeDriveOnload() { /* Initializes the reader window and user interface */ - var that = this; + var de; + var y = ((this.mnemonic.charCodeAt(2) - "A".charCodeAt(0))*30); this.doc = this.window.document; - this.doc.title = "retro-B5500 " + this.mnemonic; + de = this.doc.documentElement; + this.doc.title = "retro-B5500 Tape Drive " + this.mnemonic; - this.progressBar = this.$$("MTProgressBar"); + this.reelBar = this.$$("MTReelBar"); this.reelIcon = this.$$("MTReel"); - this.window.addEventListener("beforeunload", this.tapeDriveBeforeUnload, false); - this.tapeState = this.tapeLocal; // setTapeUnloaded() requires it to be in local this.atBOT = true; // and also at BOT this.setTapeUnloaded(); - this.$$("MTUnloadBtn").addEventListener("click", function unloadBtn(ev) { - that.MTUnloadBtn_onclick(ev); - }, false); + this.window.addEventListener("beforeunload", + B5500MagTapeDrive.prototype.beforeUnload, false); + this.$$("MTUnloadBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500MagTapeDrive.prototype.MTUnloadBtn_onclick), false); + this.$$("MTLoadBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500MagTapeDrive.prototype.MTLoadBtn_onclick), false); + this.$$("MTRemoteBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500MagTapeDrive.prototype.MTRemoteBtn_onclick), false); + this.$$("MTLocalBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500MagTapeDrive.prototype.MTLocalBtn_onclick), false); + this.$$("MTWriteRingBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500MagTapeDrive.prototype.MTWriteRingBtn_onclick), false); + this.$$("MTRewindBtn").addEventListener("click", + B5500CentralControl.bindMethod(this, B5500MagTapeDrive.prototype.MTRewindBtn_onclick), false); - this.$$("MTLoadBtn").addEventListener("click", function loadBtn(ev) { - that.MTLoadBtn_onclick(ev); - }, false); - - this.$$("MTRemoteBtn").addEventListener("click", function remoteBtn(ev) { - that.MTRemoteBtn_onclick(ev); - }, false); - - this.$$("MTLocalBtn").addEventListener("click", function localBtn(ev) { - that.MTLocalBtn_onclick(ev); - }, false); - - this.$$("MTWriteRingBtn").addEventListener("click", function writeRingBtn(ev) { - that.MTWriteRingBtn_onclick(ev); - }, false); - - this.$$("MTRewindBtn").addEventListener("click", function rewindBtn(ev) { - that.MTRewindBtn_onclick(ev); - }, false); - - this.progressBar.addEventListener("click", function progressClick(ev) { - that.MTProgressBar_onclick(ev); - }, false); + this.window.resizeBy(de.scrollWidth - this.window.innerWidth + 4, // kludge for right-padding/margin + de.scrollHeight - this.window.innerHeight); + this.window.moveTo(280, y); }; /**************************************/ @@ -1179,19 +1214,13 @@ B5500MagTapeDrive.prototype.read = function read(finish, buffer, length, mode, c } this.buildErrorMask(residue); - this.timer = setCallback(this.mnemonic, this, + this.moveTape(inches, (imgCount/this.charsPerSec + this.startStopTime)*1000 + this.initiateStamp - performance.now(), function readDelay() { this.busy = false; finish(this.errorMask, count); }); - this.spinReel(inches); - if (this.tapeInches < this.imgMaxInches) { - this.progressBar.value = this.imgMaxInches - this.tapeInches; - } else { - this.progressBar.value = 0; - } this.buffer = null; } //console.log(this.mnemonic + " read: c=" + control + ", length=" + length + ", mode=" + mode + @@ -1237,19 +1266,12 @@ B5500MagTapeDrive.prototype.space = function space(finish, length, control) { } this.buildErrorMask(0); - this.timer = setCallback(this.mnemonic, this, + this.moveTape(inches, (imgCount/this.charsPerSec + this.startStopTime)*1000 + this.initiateStamp - performance.now(), function readDelay() { this.busy = false; finish(this.errorMask, 0); }); - - this.spinReel(inches); - if (this.tapeInches < this.imgMaxInches) { - this.progressBar.value = this.imgMaxInches - this.tapeInches; - } else { - this.progressBar.value = 0; - } } //console.log(this.mnemonic + " space: c=" + control + ", length=" + length + // ", count=" + imgCount + ", inches=" + this.tapeInches + @@ -1290,19 +1312,13 @@ B5500MagTapeDrive.prototype.write = function write(finish, buffer, length, mode, this.imgWritten = true; this.buildErrorMask(residue); - this.timer = setCallback(this.mnemonic, this, + this.moveTape(inches, (imgCount/this.charsPerSec + this.startStopTime)*1000 + this.initiateStamp - performance.now(), function writeDelay() { this.busy = false; finish(this.errorMask, count); }); - this.spinReel(inches); - if (this.tapeInches < this.imgMaxInches) { - this.progressBar.value = this.imgMaxInches - this.tapeInches; - } else { - this.progressBar.value = 0; - } this.buffer = null; } //console.log(this.mnemonic + " write: c=" + control + ", length=" + length + ", mode=" + mode + @@ -1338,20 +1354,13 @@ B5500MagTapeDrive.prototype.erase = function erase(finish, length) { this.imgWritten = true; this.buildErrorMask(0); - this.timer = setCallback(this.mnemonic, this, + this.moveTape(inches, (length/this.charsPerSec + this.startStopTime)*1000 + this.initiateStamp - performance.now(), function eraseDelay() { this.busy = false; finish(this.errorMask, 0); }); - - this.spinReel(inches); - if (this.tapeInches < this.imgMaxInches) { - this.progressBar.value = this.imgMaxInches - this.tapeInches; - } else { - this.progressBar.value = 0; - } - } + } //console.log(this.mnemonic + " erase: c=" + control + ", length=" + length + // ", inches=" + this.tapeInches + // ", index=" + this.imgIndex + ", mask=" + this.errorMask.toString(8)); @@ -1419,6 +1428,9 @@ B5500MagTapeDrive.prototype.shutDown = function shutDown() { if (this.timer) { clearCallback(this.timer); } - this.window.removeEventListener("beforeunload", this.tapeDriveBeforeUnload, false); + this.window.removeEventListener("beforeunload", B5500MagTapeDrive.prototype.beforeUnload, false); this.window.close(); + if (this.loadWindow && !this.loadWindow.closed) { + this.loadWindow.close(); + } }; diff --git a/webUI/B5500MagTapeLoadPanel.html b/webUI/B5500MagTapeLoadPanel.html index d68a89c..60c1043 100644 --- a/webUI/B5500MagTapeLoadPanel.html +++ b/webUI/B5500MagTapeLoadPanel.html @@ -1,17 +1,36 @@ + + B5500 Emulator Magnetic Tape Loader + - - - + + - + -
+
@@ -39,8 +58,8 @@
- - + +
diff --git a/webUI/B5500Manifest.appcache b/webUI/B5500Manifest.appcache new file mode 100644 index 0000000..013c011 --- /dev/null +++ b/webUI/B5500Manifest.appcache @@ -0,0 +1,56 @@ +CACHE MANIFEST +# retro-B5500 emulator 1.00, 2014-09-27 + +CACHE: +../emulator/B5500CentralControl.js +../emulator/B5500IOUnit.js +../emulator/B5500Processor.js +../emulator/B5500SystemConfiguration.js +B5500BlankPaper.html +B5500CardPunch.css +B5500CardPunch.html +B5500CardPunch.js +B5500CardReader.css +B5500CardReader.html +B5500CardReader.js +B5500ColdLoader.html +B5500Common.css +B5500Console.css +B5500Console.html +B5500Console.js +B5500DatacomUnit.css +B5500DatacomUnit.html +B5500DatacomUnit.js +B5500DiskStorageConfig.html +B5500DiskStorageConfig.js +B5500DiskUnit.js +B5500DummyUnit.js +B5500FramePaper.html +B5500LinePrinter.css +B5500LinePrinter.html +B5500LinePrinter.js +B5500MagTapeDrive.css +B5500MagTapeDrive.html +B5500MagTapeDrive.js +B5500MagTapeLoadPanel.html +B5500SetCallback.js +B5500SPOUnit.css +B5500SPOUnit.html +B5500SPOUnit.js +B5500SyllableDebugger.css +B5500SyllableDebugger.html +B5500SystemConfig.css +B5500SystemConfig.html +B5500SystemConfig.js +B5500Util.js +resources/B5500Logo.jpg +resources/Burroughs-Logo-Neg.jpg +resources/DejaVuSans-Bold-webfont.ttf +resources/DejaVuSans-Bold-webfont.woff +resources/DejaVuSans-webfont.ttf +resources/DejaVuSans-webfont.woff +resources/DejaVuSansMono-webfont.ttf +resources/DejaVuSansMono-webfont.woff +resources/MagTapeReel.jpg +resources/retro-B5500-Logo.png +resources/TeletypeLogo.gif diff --git a/webUI/B5500SPOUnit.css b/webUI/B5500SPOUnit.css index 435a6ee..e27e855 100644 --- a/webUI/B5500SPOUnit.css +++ b/webUI/B5500SPOUnit.css @@ -2,7 +2,8 @@ * retro-b5500/emulator B5500SPOUnit.css ************************************************************************ * Copyright (c) 2012, Nigel Williams and Paul Kimpel. -* Licensed under the MIT License, see http://www.opensource.org/licenses/mit-license.php +* Licensed under the MIT License, see +* http://www.opensource.org/licenses/mit-license.php ************************************************************************ * B5500 emulator SPO web interface style sheet. ************************************************************************ @@ -10,117 +11,125 @@ * Original version, from B5500ConsoleUnit.css. ***********************************************************************/ -BODY { - position: relative; - background-color: black; - margin: 4px} +#SPOUnit { + height: 100%; + min-height: 100%; + overflow: hidden; + padding: 0} -PRE { - font-family: Lucida Sans Typewriter, Courier New, Courier, monospace; - font-size: 8pt; - margin: 0} - - -DIV#SPODiv { - position: relative; - width: 680px; - height: 500px; +#SPODiv { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: 4px; background-color: #FFC; - border-radius: 16px; - text-align: left} + border-radius: 16px} -IFRAME#SPOUT { +#SPOUT { position: absolute; left: 16px; + right: 168px; + top: 16px; bottom: 16px; - height: 468px; - width: 490px; - border: 1px solid black} - -BODY.SPOUT { + min-height: 280px; + min-width: 120px; + overflow-x: hidden; + overflow-y: scroll; + color: black; background-color: white; - font-family: Lucida Sans Typewriter, Courier New, Courier, monospace; - font-size: 8pt} + padding: 4px; + border: 1px solid gray} -DIV#SPOControlsDiv { +#Paper { + margin-left: 0; + margin-right: 0; + margin-top: 2000px; + margin-bottom: 0; + padding: 0} + +#EndOfPaper { + display: block; + margin: 0; + padding: 0; + opacity: 0} +#EndOfPaper.hidden { + display: none} + +#InputBox { + display: none; + margin: 0; + padding: 0; + border: none; + outline: none; + background-color: #FF9} +#InputBox.visible { + display: block} +#InputBox:focus { + border: none; + outline: none} /* to suppress Chrome's default outline */ + +#SPOControlsDiv { position: absolute; text-align: center; - height: 468px; - width: 136px; - right: 16px; - bottom: 16px} - -IMG#TeletypeLogo { left: auto; - right: auto} + right: 16px; + top: 16px; + bottom: 16px; + min-height: 280px; + width: 136px} -BUTTON.greenButton { +#TeletypeLogo { + margin-left: auto; + margin-right: auto} + +#SPOReadyBtn { position: absolute; - background-color: #060; - color: white; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: normal; - width: 60px; - height: 40px; - border: 1px solid black; - border-radius: 4px} - -BUTTON.yellowButton { - position: absolute; - background-color: #990; - color: black; - font-family: Arial Rounded, Arial, Helvetica, sans-serif; - font-size: 8pt; - font-weight: normal; - width: 60px; - height: 40px; - border: 1px solid black; - border-radius: 4px} - -BUTTON.greenLit { - color: black; - background-color: #0F0} - -BUTTON.yellowLit { - background-color: #FF0} - -BUTTON#SPOReadyBtn { bottom: 224px; left: 0px} -BUTTON#SPOPowerBtn { +#SPOPowerBtn { + position: absolute; bottom: 224px; right: 0px} -BUTTON#SPORemoteBtn { +#SPORemoteBtn { + position: absolute; bottom: 168px; left: 0px} -BUTTON#SPOLocalBtn { +#SPOLocalBtn { + position: absolute; bottom: 168px; right: 0px} -BUTTON#SPOInputRequestBtn { +#SPOInputRequestBtn { + position: absolute; bottom: 112px; left: 0px} -BUTTON#SPOEndOfMessageBtn { +#SPOEndOfMessageBtn { + position: absolute; bottom: 112px; right: 0px} -BUTTON#SPOBlank1Btn { +#SPOBlank1Btn { + position: absolute; bottom: 56px; left: 0px} -BUTTON#SPOErrorBtn { +#SPOErrorBtn { + position: absolute; bottom: 56px; right: 0px} -BUTTON#SPOBlank2Btn { +#SPOAlgolGlyphsBtn { + position: absolute; bottom: 0px; left: 0px} -BUTTON#SPOBlank3Btn { +#SPOBlank3Btn { + position: absolute; bottom: 0px; right: 0px} diff --git a/webUI/B5500SPOUnit.html b/webUI/B5500SPOUnit.html index b2d6216..5fd4f0c 100644 --- a/webUI/B5500SPOUnit.html +++ b/webUI/B5500SPOUnit.html @@ -1,30 +1,53 @@ + + B5500 Emulator SPO Unit + - - - + + - +
- +
+
 
+ +
 
+
+
- - - - - - - - - - - + + + + + + + + + + +
diff --git a/webUI/B5500SPOUnit.js b/webUI/B5500SPOUnit.js index 555fba9..2ced523 100644 --- a/webUI/B5500SPOUnit.js +++ b/webUI/B5500SPOUnit.js @@ -17,7 +17,7 @@ "use strict"; /**************************************/ -function B5500SPOUnit(mnemonic, unitIndex, designate, statusChange, signal) { +function B5500SPOUnit(mnemonic, unitIndex, designate, statusChange, signal, options) { /* Constructor for the SPOUnit object */ this.maxScrollLines = 1500; // Maximum amount of printer scrollback @@ -32,6 +32,7 @@ function B5500SPOUnit(mnemonic, unitIndex, designate, statusChange, signal) { this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit) this.inTimer = 0; // input setCallback() token this.outTimer = 0; // output setCallback() token + this.useAlgolGlyphs = options.algolGlyphs; // format Unicode for special Algol chars this.clear(); @@ -42,11 +43,12 @@ function B5500SPOUnit(mnemonic, unitIndex, designate, statusChange, signal) { } this.doc = null; this.paper = null; + this.inputBox = null; this.endOfPaper = null; this.window = window.open("../webUI/B5500SPOUnit.html", mnemonic, - "scrollbars,resizable,width=688,height=508"); - this.window.moveTo(screen.availWidth-this.window.outerWidth, screen.availHeight-this.window.outerHeight); - this.window.addEventListener("load", B5500CentralControl.bindMethod(B5500SPOUnit.prototype.spoOnload, this), false); + "location=no,scrollbars=no,resizable,width=688,height=508"); + this.window.addEventListener("load", B5500CentralControl.bindMethod(this, + B5500SPOUnit.prototype.spoOnload), false); } // this.spoState enumerations @@ -61,7 +63,7 @@ B5500SPOUnit.prototype.keyFilter = [ // Filter keyCode values to valid BIC on 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x3F,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, // 20-2F 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, // 30-3F 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 40-4F - 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x3F,0x5D,0x3F,0x3F, // 50-5F + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x3F,0x5D,0x3F,0x7E, // 50-5F 0x3F,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, // 60-6F 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x3F]; // 70-7F @@ -81,53 +83,26 @@ B5500SPOUnit.prototype.clear = function clear() { this.finish = null; // external function to call for I/O completion this.buffer = null; this.bufLength = 0; - this.bufIndex = 0; - this.printCol = 0; + this.bufIndex = 0; // current index into I/O buffer + this.printCol = 0; // current print column (0-relative) this.nextCharTime = 0; this.spoState = this.spoLocal; // Current state of SPO interface this.spoLocalRequested = false; // LOCAL button pressed while active }; -/**************************************/ -B5500SPOUnit.prototype.hasClass = function hasClass(e, name) { - /* returns true if element "e" has class "name" in its class list */ - var classes = e.className; - - if (!e) { - return false; - } else if (classes == name) { - return true; - } else { - return (classes.search("\\b" + name + "\\b") >= 0); - } -}; - -/**************************************/ -B5500SPOUnit.prototype.addClass = function addClass(e, name) { - /* Adds a class "name" to the element "e"s class list */ - - if (!this.hasClass(e, name)) { - e.className += (" " + name); - } -}; - -/**************************************/ -B5500SPOUnit.prototype.removeClass = function removeClass(e, name) { - /* Removes the class "name" from the element "e"s class list */ - - e.className = e.className.replace(new RegExp("\\b" + name + "\\b\\s*", "g"), ""); -}; - /**************************************/ B5500SPOUnit.prototype.setLocal = function setLocal() { - /* Sets the status of the SPO to Local */ + /* Sets the status of the SPO to Local and enables the input element */ this.spoLocalRequested = false; this.spoState = this.spoLocal; - this.addClass(this.$$("SPOLocalBtn"), "yellowLit"); - this.removeClass(this.$$("SPORemoteBtn"), "yellowLit"); - this.removeClass(this.$$("SPOInputRequestBtn"), "yellowLit"); + this.endOfPaper.scrollIntoView(); + B5500Util.addClass(this.$$("SPOLocalBtn"), "yellowLit"); + B5500Util.addClass(this.inputBox, "visible"); + this.inputBox.focus(); + B5500Util.removeClass(this.$$("SPORemoteBtn"), "yellowLit"); + B5500Util.removeClass(this.$$("SPOInputRequestBtn"), "yellowLit"); this.statusChange(0); // Set up to echo characters from the keyboard @@ -138,48 +113,76 @@ B5500SPOUnit.prototype.setLocal = function setLocal() { this.finish = null; }; +/**************************************/ +B5500SPOUnit.prototype.requestLocal = function requestLocal(ev) { + /* Handler for the Local button click. If the SPO is idle and in remote + status, sets it to local; otherwise flags it to go local once the current + I/O completes */ + + if (this.spoState == this.spoRemote) { + this.setLocal(); + } else { + this.spoLocalRequested = true; + } +}; + /**************************************/ B5500SPOUnit.prototype.setRemote = function setRemote() { - /* Sets the status of the SPO to Remote */ + /* Sets the status of the SPO to Remote and disabled the input element */ + var text; if (this.spoState == this.spoLocal) { this.spoState = this.spoRemote; this.spoLocalRequested = false; - this.addClass(this.$$("SPORemoteBtn"), "yellowLit"); - this.removeClass(this.$$("SPOLocalBtn"), "yellowLit"); + B5500Util.addClass(this.$$("SPORemoteBtn"), "yellowLit"); + B5500Util.removeClass(this.$$("SPOLocalBtn"), "yellowLit"); + B5500Util.removeClass(this.inputBox, "visible"); + this.window.focus(); + text = this.inputBox.value; + if (text.length > 0) { + this.appendEmptyLine(text.substring(0, 72)); + this.inputBox.value = ""; + } + this.endOfPaper.scrollIntoView(); + this.nextCharTime = performance.now(); this.statusChange(1); } }; /**************************************/ -B5500SPOUnit.prototype.appendEmptyLine = function appendEmptyLine() { - /* Removes excess lines already printed, then appends a new text node - to the
 element within the 
     
- + diff --git a/webUI/tools/B5500TestArithmetics.html b/webUI/prototypes/B5500TestArithmetics.html similarity index 93% rename from webUI/tools/B5500TestArithmetics.html rename to webUI/prototypes/B5500TestArithmetics.html index 9797d0a..1d047d2 100644 --- a/webUI/tools/B5500TestArithmetics.html +++ b/webUI/prototypes/B5500TestArithmetics.html @@ -1,18 +1,18 @@ B5500 Emulator Arithmetic Testbed - + + - @@ -180,7 +180,7 @@ window.onload = function() { document.getElementById("BReg").onchange = reg_onChange; cc = new B5500CentralControl(); - cc.powerOn(); + cc.powerOn(new B5500SystemConfiguration().systemConfig); cc.clear(); //cc.MemMod[0][0x10] = 0x00400C215415;// LITC 1, LITC 3, XCH, DUP @@ -204,7 +204,7 @@ window.onload = function() {
diff --git a/webUI/tools/B5500TestLoader.html b/webUI/prototypes/B5500TestLoader.html similarity index 91% rename from webUI/tools/B5500TestLoader.html rename to webUI/prototypes/B5500TestLoader.html index 679bc1b..6abfba6 100644 --- a/webUI/tools/B5500TestLoader.html +++ b/webUI/prototypes/B5500TestLoader.html @@ -1,18 +1,18 @@ B5500 Test Loader - + + - @@ -79,7 +79,7 @@ window.onload = function() { /* Start of window.onload() */ if (checkBrowser()) { - cc.powerOn(); + cc.powerOn(new B5500SystemConfiguration().systemConfig); document.getElementById("FileSelector").addEventListener("change", fileSelector_onChange, false); document.getElementById("RunBtn").addEventListener("click", runBtn_onClick, false); diff --git a/webUI/tools/B5500Testbed.html b/webUI/prototypes/B5500Testbed.html similarity index 90% rename from webUI/tools/B5500Testbed.html rename to webUI/prototypes/B5500Testbed.html index 2a27094..7f81b53 100644 --- a/webUI/tools/B5500Testbed.html +++ b/webUI/prototypes/B5500Testbed.html @@ -1,17 +1,17 @@ B5500 Emulator Operator Console - + + - @@ -23,7 +23,7 @@ var cc; function testIt(e) { /* Simple test driver for the Processor module */ - cc.powerOn(); + cc.powerOn(new B5500SystemConfiguration().systemConfig); cc.clear(); e.style.backgroundColor = "white"; // a kludge, for now @@ -97,7 +97,7 @@ window.onload = function() {
diff --git a/webUI/prototypes/PanelTest.html b/webUI/prototypes/PanelTest.html index 307b41d..7f44c8a 100644 --- a/webUI/prototypes/PanelTest.html +++ b/webUI/prototypes/PanelTest.html @@ -1,7 +1,7 @@ B5500 Panel Prototype tests - +