diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a9b992b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,33 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.html text +*.css text +*.js text +*.md text +*.txt text +*.lst text +*.svg text +*.card text +*.tape text +*.pt text +*.bacg text +*.baca text +*.cmd text +*.bat text +*.wsf text +*.vbs text + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.gif binary +*.jpg binary +*.ttf binary +*.woff binary +*.pdf binary +*.doc binary +*.docx binary +*.xls binary +*.xlsx binary diff --git a/.gitignore b/.gitignore index bb889df..6693367 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,19 @@ -############# -## Windows detritus -############# - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -#IIS config file -web.config - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Mac crap -.DS_Store +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +#IIS config file +web.config + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store diff --git a/LICENSE.txt b/LICENSE.txt index 7c53e05..e4f40ef 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,21 +1,21 @@ -Copyright (c) 2016 Paul Kimpel - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +Copyright (c) 2016 Paul Kimpel + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index d7a31b1..ea0fef1 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -The Burroughs 220 was a late-1950s, decimal, vacuum-tube, core-memory computer system. Some consider it to be the last of the major vacuum-tube computers. - -The 220 was the follow-on product to the ElectroData/Burroughs Datatron 205. It was initially developed as the ElectroData Datatron 220 but renamed after Burroughs bought ElectroData in 1956. The system was initially released in 1958. It did well with both scientific and commercial applications, but being a vacuum-tube system at the beginning of the transistorized era, was only modestly successful. - -The ElectroData Division of Burroughs went on to create a number of successful systems after the 220, including the B100/200/300 series, the B1700/1800/1900 series, the B2000/3000/4000/V Series, the B5000/5500, and finally the B6000/7000/A Series, which are still produced and sold today as Unisys ClearPath MCP systems. - -The main goal of this project is creation of a web browser-based emulator for the 220. - -A second goal is reconstruction of the Burroughs Algebraic Compiler (BALGOL), an Algol-58 compiler written for the 220 by a team from Burroughs that included Joel Erdwinn, Jack Merner, Donald Knuth, Dave Dahm, and Clark Oliphint. - -The contents of this project are licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php). - -| Related Sites | URL | -| ------------- | ----- | -| Emulator hosting site | http://www.phkimpel.us/Burroughs-220/ | -| Burroughs 205/220 blog | http://datatron.blogspot.com | -| Datatron 205 site | http://www.phkimpel.us/ElectroData-205/ | -| Documents at bitsavers | http://bitsavers.org/pdf/burroughs/electrodata/220/ | -| BALGOL compiler listing | http://archive.computerhistory.org/resources/text/Knuth_Don_X4100/PDF_index/k-1-pdf/k-1-u2196-balgol220compiler.pdf | +The Burroughs 220 was a late-1950s, decimal, vacuum-tube, core-memory computer system. Some consider it to be the last of the major vacuum-tube computers. + +The 220 was the follow-on product to the ElectroData/Burroughs Datatron 205. It was initially developed as the ElectroData Datatron 220 but renamed after Burroughs bought ElectroData in 1956. The system was initially released in 1958. It did well with both scientific and commercial applications, but being a vacuum-tube system at the beginning of the transistorized era, was only modestly successful. + +The ElectroData Division of Burroughs went on to create a number of successful systems after the 220, including the B100/200/300 series, the B1700/1800/1900 series, the B2000/3000/4000/V Series, the B5000/5500, and finally the B6000/7000/A Series, which are still produced and sold today as Unisys ClearPath MCP systems. + +The main goal of this project is creation of a web browser-based emulator for the 220. + +A second goal is reconstruction of the Burroughs Algebraic Compiler (BALGOL), an Algol-58 compiler written for the 220 by a team from Burroughs that included Joel Erdwinn, Jack Merner, Donald Knuth, Dave Dahm, and Clark Oliphint. + +The contents of this project are licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php). + +| Related Sites | URL | +| ------------- | ----- | +| Emulator hosting site | http://www.phkimpel.us/Burroughs-220/ | +| Burroughs 205/220 blog | http://datatron.blogspot.com | +| Datatron 205 site | http://www.phkimpel.us/ElectroData-205/ | +| Documents at bitsavers | http://bitsavers.org/pdf/burroughs/electrodata/220/ | +| BALGOL compiler listing | http://archive.computerhistory.org/resources/text/Knuth_Don_X4100/PDF_index/k-1-pdf/k-1-u2196-balgol220compiler.pdf | diff --git a/emulator/B220Processor.js b/emulator/B220Processor.js index 1fb043f..9c9b7e9 100644 --- a/emulator/B220Processor.js +++ b/emulator/B220Processor.js @@ -61,6 +61,7 @@ /**************************************/ function B220Processor(config, devices) { /* Constructor for the 220 Processor module object */ + var staticLampGlow = false; // compute fractional lamp glow (experimental) this.mnemonic = "CPU"; B220Processor.instance = this; // externally-available object reference (for DiagMonitor) @@ -101,14 +102,14 @@ function B220Processor(config, devices) { this.delayRequested = 0; // last requested setCallback() delay, ms // Primary Registers - this.A = new B220Processor.Register(11*4, this, false); - this.B = new B220Processor.Register( 4*4, this, false); - this.C = new B220Processor.Register(10*4, this, false); - this.D = new B220Processor.Register(11*4, this, false); - this.E = new B220Processor.Register( 4*4, this, false); - this.P = new B220Processor.Register( 4*4, this, false); - this.R = new B220Processor.Register(11*4, this, false); - this.S = new B220Processor.Register( 4*4, this, false); + this.A = new B220Processor.Register(11*4, this, staticLampGlow); + this.B = new B220Processor.Register( 4*4, this, staticLampGlow); + this.C = new B220Processor.Register(10*4, this, staticLampGlow); + this.D = new B220Processor.Register(11*4, this, staticLampGlow); + this.E = new B220Processor.Register( 4*4, this, staticLampGlow); + this.P = new B220Processor.Register( 4*4, this, staticLampGlow); + this.R = new B220Processor.Register(11*4, this, staticLampGlow); + this.S = new B220Processor.Register( 4*4, this, staticLampGlow); // Register E decrements modulo the system memory size, so override dec(). this.E.dec = function decE() { @@ -119,14 +120,14 @@ function B220Processor(config, devices) { }; // Control Console Lamps - this.digitCheckAlarm = new B220Processor.FlipFlop(this, false); + this.digitCheckAlarm = new B220Processor.FlipFlop(this, staticLampGlow); - this.systemNotReady = new B220Processor.FlipFlop(this, false); - this.computerNotReady = new B220Processor.FlipFlop(this, false); + this.systemNotReady = new B220Processor.FlipFlop(this, staticLampGlow); + this.computerNotReady = new B220Processor.FlipFlop(this, staticLampGlow); - this.compareLowLamp = new B220Processor.FlipFlop(this, false); - this.compareEqualLamp = new B220Processor.FlipFlop(this, false); - this.compareHighLamp = new B220Processor.FlipFlop(this, false); + this.compareLowLamp = new B220Processor.FlipFlop(this, staticLampGlow); + this.compareEqualLamp = new B220Processor.FlipFlop(this, staticLampGlow); + this.compareHighLamp = new B220Processor.FlipFlop(this, staticLampGlow); // Control Console Switches this.PC1SW = 0; // program control switches 1-10 @@ -159,29 +160,29 @@ function B220Processor(config, devices) { this.HOLDSEQUENCE8SW = 0; // Left-Hand Maintenance Panel Registers & Flip-Flops - this.CI = new B220Processor.Register(5, this, false); // carry inverters - this.DC = new B220Processor.Register(6, this, false); // digit counter (modulo 20) - this.SC = new B220Processor.Register(4, this, false); // sequence counter - this.SI = new B220Processor.Register(4, this, false); // sum inverters - this.X = new B220Processor.Register(4, this, false); // adder X (augend) input - this.Y = new B220Processor.Register(4, this, false); // adder Y (addend) input - this.Z = new B220Processor.Register(4, this, false); // decimal sum inverters, adder output + this.CI = new B220Processor.Register(5, this, staticLampGlow); // carry inverters + this.DC = new B220Processor.Register(6, this, staticLampGlow); // digit counter (modulo 20) + this.SC = new B220Processor.Register(4, this, staticLampGlow); // sequence counter + this.SI = new B220Processor.Register(4, this, staticLampGlow); // sum inverters + this.X = new B220Processor.Register(4, this, staticLampGlow); // adder X (augend) input + this.Y = new B220Processor.Register(4, this, staticLampGlow); // adder Y (addend) input + this.Z = new B220Processor.Register(4, this, staticLampGlow); // decimal sum inverters, adder output this.CI.checkFC = B220Processor.emptyFunction; // these registers generate A-F undigits this.SI.checkFC = B220Processor.emptyFunction; - this.C10 = new B220Processor.FlipFlop(this, false); // decimal carry toggle - this.DST = new B220Processor.FlipFlop(this, false); // D-sign toggle - this.LT1 = new B220Processor.FlipFlop(this, false); // logical toggle 1 - this.LT2 = new B220Processor.FlipFlop(this, false); // logical toggle 2 - this.LT3 = new B220Processor.FlipFlop(this, false); // logical toggle 3 - this.SCI = new B220Processor.FlipFlop(this, false); // sequence counter inverter - this.SGT = new B220Processor.FlipFlop(this, false); // sign toggle - this.SUT = new B220Processor.FlipFlop(this, false); // subtract toggle - this.TBT = new B220Processor.FlipFlop(this, false); // tape busy toggle - this.TCT = new B220Processor.FlipFlop(this, false); // tape clock toggle - this.TPT = new B220Processor.FlipFlop(this, false); // tape pulse toggle - this.TWT = new B220Processor.FlipFlop(this, false); // tape write toggle + this.C10 = new B220Processor.FlipFlop(this, staticLampGlow); // decimal carry toggle + this.DST = new B220Processor.FlipFlop(this, staticLampGlow); // D-sign toggle + this.LT1 = new B220Processor.FlipFlop(this, staticLampGlow); // logical toggle 1 + this.LT2 = new B220Processor.FlipFlop(this, staticLampGlow); // logical toggle 2 + this.LT3 = new B220Processor.FlipFlop(this, staticLampGlow); // logical toggle 3 + this.SCI = new B220Processor.FlipFlop(this, staticLampGlow); // sequence counter inverter + this.SGT = new B220Processor.FlipFlop(this, staticLampGlow); // sign toggle + this.SUT = new B220Processor.FlipFlop(this, staticLampGlow); // subtract toggle + this.TBT = new B220Processor.FlipFlop(this, staticLampGlow); // tape busy toggle + this.TCT = new B220Processor.FlipFlop(this, staticLampGlow); // tape clock toggle + this.TPT = new B220Processor.FlipFlop(this, staticLampGlow); // tape pulse toggle + this.TWT = new B220Processor.FlipFlop(this, staticLampGlow); // tape write toggle // Right-Hand Maintenance Panel Switches this.MULTIPLEACCESSSW = 0; @@ -197,33 +198,33 @@ function B220Processor(config, devices) { this.FETCHEXECUTELOCKSW = 0; // Right-Hand Maintenance Panel Registers & Flip-Flops - this.AX = new B220Processor.Register(10, this, false); // A exponent register - this.BI = new B220Processor.Register( 8, this, false); // paper tape buffer inverters - this.DX = new B220Processor.Register( 8, this, false); // D exponent register - this.PA = new B220Processor.Register( 8, this, false); // PA register + this.AX = new B220Processor.Register(10, this, staticLampGlow); // A exponent register + this.BI = new B220Processor.Register( 8, this, staticLampGlow); // paper tape buffer inverters + this.DX = new B220Processor.Register( 8, this, staticLampGlow); // D exponent register + this.PA = new B220Processor.Register( 8, this, staticLampGlow); // PA register - this.ALT = new B220Processor.FlipFlop(this, false); // program check alarm toggle - this.AST = new B220Processor.FlipFlop(this, false); // asynchronous toggle - this.CCT = new B220Processor.FlipFlop(this, false); // ?? toggle - this.CRT = new B220Processor.FlipFlop(this, false); // Cardatron alarm toggle - this.DPT = new B220Processor.FlipFlop(this, false); // decimal point toggle (SPO) - this.EWT = new B220Processor.FlipFlop(this, false); // end of word toggle - this.EXT = new B220Processor.FlipFlop(this, false); // fetch(0)/execute(1) toggle - this.HAT = new B220Processor.FlipFlop(this, false); // high-speed printer alarm toggle - this.HCT = new B220Processor.FlipFlop(this, false); // halt control toggle, for SOR, SOH, IOM - this.HIT = new B220Processor.FlipFlop(this, false); // high comparison toggle - this.MAT = new B220Processor.FlipFlop(this, false); // multiple access toggle - this.MET = new B220Processor.FlipFlop(this, false); // memory (storage) alarm toggle - this.MNT = new B220Processor.FlipFlop(this, false); // manual toggle - this.OFT = new B220Processor.FlipFlop(this, false); // overflow toggle - this.PAT = new B220Processor.FlipFlop(this, false); // paper tape alarm toggle - this.PRT = new B220Processor.FlipFlop(this, false); // paper tape read toggle - this.PZT = new B220Processor.FlipFlop(this, false); // paper tape zone toggle - this.RPT = new B220Processor.FlipFlop(this, false); // repeat toggle - this.RUT = new B220Processor.FlipFlop(this, false); // run toggle - this.SST = new B220Processor.FlipFlop(this, false); // single-step toggle - this.TAT = new B220Processor.FlipFlop(this, false); // magnetic tape alarm toggle - this.UET = new B220Processor.FlipFlop(this, false); // unequal comparison toggle (HIT=UET=0 => off) + this.ALT = new B220Processor.FlipFlop(this, staticLampGlow); // program check alarm toggle + this.AST = new B220Processor.FlipFlop(this, staticLampGlow); // asynchronous toggle + this.CCT = new B220Processor.FlipFlop(this, staticLampGlow); // ?? toggle + this.CRT = new B220Processor.FlipFlop(this, staticLampGlow); // Cardatron alarm toggle + this.DPT = new B220Processor.FlipFlop(this, staticLampGlow); // decimal point toggle (SPO) + this.EWT = new B220Processor.FlipFlop(this, staticLampGlow); // end of word toggle + this.EXT = new B220Processor.FlipFlop(this, staticLampGlow); // fetch(0)/execute(1) toggle + this.HAT = new B220Processor.FlipFlop(this, staticLampGlow); // high-speed printer alarm toggle + this.HCT = new B220Processor.FlipFlop(this, staticLampGlow); // halt control toggle, for SOR, SOH, IOM + this.HIT = new B220Processor.FlipFlop(this, staticLampGlow); // high comparison toggle + this.MAT = new B220Processor.FlipFlop(this, staticLampGlow); // multiple access toggle + this.MET = new B220Processor.FlipFlop(this, staticLampGlow); // memory (storage) alarm toggle + this.MNT = new B220Processor.FlipFlop(this, staticLampGlow); // manual toggle + this.OFT = new B220Processor.FlipFlop(this, staticLampGlow); // overflow toggle + this.PAT = new B220Processor.FlipFlop(this, staticLampGlow); // paper tape alarm toggle + this.PRT = new B220Processor.FlipFlop(this, staticLampGlow); // paper tape read toggle + this.PZT = new B220Processor.FlipFlop(this, staticLampGlow); // paper tape zone toggle + this.RPT = new B220Processor.FlipFlop(this, staticLampGlow); // repeat toggle + this.RUT = new B220Processor.FlipFlop(this, staticLampGlow); // run toggle + this.SST = new B220Processor.FlipFlop(this, staticLampGlow); // single-step toggle + this.TAT = new B220Processor.FlipFlop(this, staticLampGlow); // magnetic tape alarm toggle + this.UET = new B220Processor.FlipFlop(this, staticLampGlow); // unequal comparison toggle (HIT=UET=0 => off) // Left/Right Maintenance Panel this.leftPanelOpen = false; @@ -257,7 +258,7 @@ function B220Processor(config, devices) { * Global Constants * ***********************************************************************/ -B220Processor.version = "0.06"; +B220Processor.version = "0.07"; B220Processor.tick = 1000/200000; // milliseconds per clock cycle (200KHz) B220Processor.cyclesPerMilli = 1/B220Processor.tick; diff --git a/webUI/B220.js b/webUI/B220.js index b6abccf..8b34132 100644 --- a/webUI/B220.js +++ b/webUI/B220.js @@ -96,11 +96,15 @@ window.addEventListener("load", function() { /**************************************/ function openDiagPanel(ev) { /* Opens the emulator's diagnostic monitor panel in a new sub-window */ + var global = window; - diagWindow = window.open("B220DiagMonitor.html", "DiagPanel", - "resizable,width=300,height=500,left=0,top=" + screen.availHeight-500); - diagWindow.global = window; // give it access to our globals. - diagWindow.focus(); + B220Util.openPopup(window, "B220DiagMonitor.html", "DiagPanel", + "resizable,width=300,height=500,left=0,top=" + screen.availHeight-500, + this, function(ev) { + diagWindow = ev.target.defaultView; + diagWindow.global = global; // give it access to our globals. + diagWindow.focus(); + }); } /**************************************/ diff --git a/webUI/B220CardatronControl.js b/webUI/B220CardatronControl.js index 8ee745b..84727d6 100644 --- a/webUI/B220CardatronControl.js +++ b/webUI/B220CardatronControl.js @@ -26,11 +26,11 @@ function B220CardatronControl(p) { // Do not call this.clear() here -- call from onLoad instead this.doc = null; - this.window = window.open("../webUI/B220CardatronControl.html", this.mnemonic, + this.window = null; + B220Util.openPopup(window, "../webUI/B220CardatronControl.html", this.mnemonic, "location=no,scrollbars=no,resizable,width=140,height=140,left=" + left + - ",top=" + (screen.availHeight-140)); - this.window.addEventListener("load", - B220CardatronControl.prototype.cardatronOnLoad.bind(this), false); + ",top=" + (screen.availHeight-140), + this, B220CardatronControl.prototype.cardatronOnLoad); // Set up the I/O devices from the system configuration this.inputUnit = [ @@ -127,14 +127,15 @@ B220CardatronControl.prototype.beforeUnload = function beforeUnload(ev) { }; /**************************************/ -B220CardatronControl.prototype.cardatronOnLoad = function cardatronOnLoad() { +B220CardatronControl.prototype.cardatronOnLoad = function cardatronOnLoad(ev) { /* Initializes the Cardatron Control window and user interface */ var body; var box; var e; var x; - this.doc = this.window.document; + this.doc = ev.target; + this.window = this.doc.defaultView; body = this.$$("PanelSurface"); this.bufferReadLamp = new NeonLampBox(body, null, null, "BufferReadLamp"); diff --git a/webUI/B220CardatronInput.js b/webUI/B220CardatronInput.js index cb1b73e..a7a986a 100644 --- a/webUI/B220CardatronInput.js +++ b/webUI/B220CardatronInput.js @@ -45,17 +45,22 @@ function B220CardatronInput(mnemonic, unitIndex, config) { new Uint8Array(this.bufferDrum, tks*6, tks), // format band 6 (fixed) new Uint8Array(this.bufferDrum, tks*7, tks)]; // format band 7 (dummy) - // Initialize format band 6 for all-numeric transfer - // (note that ArrayBuffer storage is initialized to zero, so band[160..233] == 0) - for (x=0; x<160; x+=2) { + // Initialize format band 6 for all-numeric transfer. + for (x=0; x<160; x+=2) { // transfer 80 numeric digits this.formatBand[6][x] = 1; this.formatBand[6][x+1] = 3; } - for (x=234; x 0) { this.outHopper.removeChild(this.outHopper.firstChild); @@ -439,33 +446,40 @@ B220CardatronInput.prototype.determineFormatBand = function determineFormatBand( break; case "`": // 1-8 punch format = 1; + this.noReload = true; this.setFormatLockout(true); break; case ":": // 2-8 punch format = 2; + this.noReload = true; this.setFormatLockout(true); break; case "#": // 3-8 punch format = 3; + this.noReload = true; this.setFormatLockout(true); break; case "@": // 4-8 punch format = 4; + this.noReload = true; this.setFormatLockout(true); break; case "'": // 5-8 punch case "|": // translates to a 5-numeric digit format = 5; + this.noReload = true; this.setFormatLockout(true); break; case "=": // 6-8 punch case "}": // translates to a 6-numeric digit format = 6; + this.noReload = true; this.setFormatLockout(true); break; case "\"": // 7-8 punch -- reject plus lockout case "~": // translates to a 7-numeric digit format = 7+8; + this.noReload = true; this.setFormatLockout(true); break; default: @@ -573,13 +587,14 @@ B220CardatronInput.prototype.beforeUnload = function beforeUnload(ev) { }; /**************************************/ -B220CardatronInput.prototype.readerOnLoad = function readerOnLoad() { +B220CardatronInput.prototype.readerOnLoad = function readerOnLoad(ev) { /* Initializes the reader window and user interface */ var body; var de; var prefs = this.config.getNode("Cardatron.units", this.unitIndex); - this.doc = this.window.document; + this.doc = ev.target; + this.window = this.doc.defaultView; de = this.doc.documentElement; this.doc.title = "retro-220 Cardatron Reader " + this.mnemonic; @@ -742,7 +757,8 @@ B220CardatronInput.prototype.inputStop = function inputStop() { /* Terminates data transfer from the input unit and releases the card */ this.setFormatSelectLamps(0); - if (this.rDigit % 2) { // set reload-lockout + if (this.noReload) { // set reload-lockout + this.noReload = false; if (!this.reloadLockout) { this.setReloadLockout(true); } @@ -785,6 +801,7 @@ B220CardatronInput.prototype.inputInitiate = function inputInitiate(rDigit, word } } else { this.rDigit = rDigit; + this.noReload |= (rDigit%2 == 1); this.infoIndex = 0; // start at the beginning of the info band this.digitCount = 0; this.pendingInputWord = 0; @@ -831,7 +848,16 @@ B220CardatronInput.prototype.inputFormatTransfer = function inputFormatTransfer( this.pendingFinish(); // call signalFinished(); this.pendingFinish = null; - this.inputStop(); + this.setFormatSelectLamps(0); + if (this.noReload) { // set reload-lockout + this.noReload = false; + if (!this.reloadLockout) { + this.setReloadLockout(true); + } + } else if (this.reloadLockout) { // reset reload-lockout + this.setReloadLockout(false); + this.initiateCardRead(); + } }; /**************************************/ @@ -849,6 +875,7 @@ B220CardatronInput.prototype.inputFormatInitiate = function inputFormatInitiate( signalFinished(); } else { this.rDigit = rDigit; + this.noReload |= (rDigit%2 == 1); this.selectedFormat = ((rDigit >>> 1) & 0x07) + 1; this.pendingFinish = signalFinished; // stash the call-back function this.setFormatSelectLamps(this.selectedFormat); @@ -872,7 +899,7 @@ B220CardatronInput.prototype.clearUnit = function clearUnit() { // If there is a pending read, confirm that this.pendingParams[1] is a // function and call it with the end-of-data signal. We assume it's the - // Processor's wordReceiver function. This will prevent the Processor + // Processor's wordReceiver function. This will prevent the Processor // from hanging on an I/O to a cleared input unit. if (this.readRequested) { if (Object.prototype.toString.call(this.pendingParams) === "[object Array]") { diff --git a/webUI/B220CardatronOutput.js b/webUI/B220CardatronOutput.js index 5c06bf1..3dfda05 100644 --- a/webUI/B220CardatronOutput.js +++ b/webUI/B220CardatronOutput.js @@ -59,18 +59,19 @@ function B220CardatronOutput(mnemonic, unitIndex, config) { // Device window this.doc = null; + this.window = null; this.barGroup = null; // current greenbar line group this.supplyDoc = null; // the content document for the supply frame this.supply = null; // the "paper" or "cards" we print/punch on this.endOfSupply = null; // dummy element used to control scrolling this.supplyMeter = null; // element showing amount of paper/card supply remaining w = (this.isPrinter ? 790 : 608); - this.window = window.open("../webUI/B220CardatronOutput.html", mnemonic, + + B220Util.openPopup(window, "../webUI/B220CardatronOutput.html", mnemonic, "location=no,scrollbars,resizable,width=" + w + ",height=" + h + - ",left=" + (screen.availWidth - w) + - ",top=" + (screen.availHeight - h - (unitIndex-3)*32)); - this.window.addEventListener("load", - B220CardatronOutput.prototype.deviceOnLoad.bind(this), false); + ",left=" + (screen.availWidth - w) + + ",top=" + (screen.availHeight - h - (unitIndex-3)*32), + this, B220CardatronOutput.prototype.deviceOnLoad); } /**************************************/ @@ -93,8 +94,8 @@ B220CardatronOutput.prototype.outputXlate = [ [0x2D,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x20,0x24,0x2A], // zone digit 2 [0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20], // zone digit 3 [0x30,0x2F,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x20,0x2C,0x25], // zone digit 4 - [0x40,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20], // zone digit 5 - [0x2D,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20], // zone digit 6 + [0x26,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x20,0x2E,0xA4], // zone digit 5 + [0x2D,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x20,0x24,0x2A], // zone digit 6 [0x20,0x20,0x39,0x2E,0xA4,0xA4,0x2E,0xA4,0x20,0x20,0x20,0x2E,0xA4], // zone digit 7 [0x20,0x4A,0x49,0x24,0x2A,0x2A,0x24,0x2A,0x20,0x20,0x20,0x24,0x2A], // zone digit 8 [0x20,0x20,0x52,0x27,0x25,0x25,0x2C,0x25,0x20,0x20,0x20,0x27,0x25], // zone digit 9 @@ -102,9 +103,9 @@ B220CardatronOutput.prototype.outputXlate = [ [0x20,0x26,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20], // zone digit 11 [0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20]]; // zone digit 12 -// Translate buffer zone digits to internal zone decades. -// Each row is indexed by the zone digit from the buffer drum info band; -// each column is indexed by the PREVIOUS numeric digit from the info band. +// Translate internal zone decades to buffer zone digits. +// Each row is indexed by the zone digit from the internal character code; +// each column is indexed by the PREVIOUS numeric digit from the character code. // See U.S. Patent 3,072,328, January 8, 1963, L.L. Bewley et al, Figure 12; // and ElectroData Technical Newsletter #5 of February 14, 1958. B220CardatronOutput.prototype.zoneXlate = [ // numeric digit:0 1 2 3 4 5 6 7 8 9 @@ -157,7 +158,7 @@ B220CardatronOutput.prototype.clear = function clear() { this.supplyLeft = this.maxSupplyLines; // lines/cards remaining in output supply this.runoutSupplyCount = 0; // counter for triple-formfeed => rip paper/empty hopper this.groupLinesLeft = 0; // lines remaining in current greenbar group - this.topOfForm = false; // start new page flag + this.atTopOfForm = false; // start new page flag this.pendingSpaceBefore = -1; // pending carriage control (eat the initial space-before) this.pendingCall = null; // stashed pending function reference @@ -244,22 +245,22 @@ B220CardatronOutput.prototype.copySupply = function copySupply(ev) { var barGroup = this.supply.firstChild; var text = ""; var title = "B220 " + this.mnemonic + " Text Snapshot"; - var win = window.open("./B220FramePaper.html", this.mnemonic + "-Snapshot", - "scrollbars,resizable,width=500,height=500"); while (barGroup) { text += barGroup.textContent + "\n"; barGroup = barGroup.nextSibling; } - win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2); - win.addEventListener("load", function() { - var doc; + B220Util.openPopup(this.window, "./B220FramePaper.html", this.mnemonic + "-Snapshot", + "scrollbars,resizable,width=500,height=500", + this, function(ev) { + var doc = ev.target; + var win = doc.defaultView; - doc = win.document; doc.title = title; + win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2); doc.getElementById("Paper").textContent = text; - }, false); + }); this.runoutSupply(); ev.preventDefault(); @@ -303,30 +304,36 @@ B220CardatronOutput.prototype.appendLine = function appendLine(text) { --this.groupLinesLeft; }; +/**************************************/ +B220CardatronOutput.prototype.skipToChannel = function skipToChannel() { + /* Finishes the current page and sets up for top-of-form formatting on the + next line printed. Adjusts the supply of forms left */ + var lines = 0; + + while(this.groupLinesLeft > 0) { + ++lines; + this.appendLine("\xA0"); + } + + this.atTopOfForm = true; + this.supplyMeter.value = this.supplyLeft -= lines; +}; + /**************************************/ B220CardatronOutput.prototype.printLine = function printLine(text, spaceBefore) { /* Prints one line to the output, handling carriage control and greenbar group completion. For now, SPACE 0 (overprinting) is treated as single-spacing */ - var lines = 0; + var lines = spaceBefore; - if (spaceBefore < 0) { // skip to channel 1 - while(this.groupLinesLeft > 0) { - ++lines; - this.appendLine("\xA0"); - } - this.atTopOfForm = true; - } else { // space before print - lines = spaceBefore; - while (lines > 1) { - --lines; - --this.supplyLeft; - this.appendLine("\xA0"); - } + while (lines > 1) { // space before print + --lines; + --this.supplyLeft; + this.appendLine("\xA0"); } this.appendLine(text || "\xA0"); if (this.supplyLeft > 0) { - this.supplyMeter.value = this.supplyLeft -= lines; + this.supplyMeter.value = this.supplyLeft -= 1; } else { this.setDeviceReady(false); B220Util.addClass(this.$$("COEndOfSupplyBtn"), "redLit"); @@ -433,7 +440,8 @@ B220CardatronOutput.prototype.initiateWrite = function initiateWrite() { case 1: // Relay 1 (eject page after printing) case 9: // same as 1 this.printLine(line, this.pendingSpaceBefore); - this.pendingSpaceBefore = -99; + this.skipToChannel(); + this.pendingSpaceBefore = 0; break; case 2: // Relay 2 (single space before and after printing) this.printLine(line, this.pendingSpaceBefore+1); @@ -442,7 +450,8 @@ B220CardatronOutput.prototype.initiateWrite = function initiateWrite() { case 3: // Relay 3 (eject page before printing) case 5: // Relay 5 (skip to channel 2 before printing) case 7: // Relay 3+5 (skip to channel 3 before printing) - this.printLine(line, -1); + this.skipToChannel(); + this.printLine(line, 0); this.pendingSpaceBefore = 0; break; case 4: // Relay 4 (double space before printing) @@ -627,11 +636,9 @@ B220CardatronOutput.prototype.parseZeroSuppressList = function parseZeroSuppress B220CardatronOutput.prototype.COSetZSBtn_onClick = function COSetZSBtn_onClick(ev) { /* Displays the Zero Suppress Panel window to capture a list of column numbers */ var $$$ = null; // getElementById shortcut for loader window - var doc = null; // loader window.document + var doc = null; // zero-suppress window.document var tron = this; // this B220CardatronOutput device instance - var win = this.window.open("B220CardatronZeroSuppressPanel.html", this.mnemonic + "ZS", - "location=no,scrollbars=no,resizable,width=508,height=120,left=" + - (this.window.screenX+16) +",top=" + (this.window.screenY+16)); + var win = null; // zero-suppress window defaultView function zsOK(ev) { /* Handler for the OK button. Parses the list of column numbers; if successful, @@ -660,7 +667,9 @@ B220CardatronOutput.prototype.COSetZSBtn_onClick = function COSetZSBtn_onClick(e /* Driver for the tape loader window */ var de; - doc = win.document; + doc = ev.target; + win = doc.defaultView; + this.zsWindow = win; doc.title = "retro-220 " + tron.mnemonic + " Zero-Suppress Panel"; de = doc.documentElement; $$$ = function $$$(id) { @@ -676,17 +685,20 @@ B220CardatronOutput.prototype.COSetZSBtn_onClick = function COSetZSBtn_onClick(e win.resizeBy(de.scrollWidth - win.innerWidth, de.scrollHeight - win.innerHeight); $$$("COZSColumnList").focus(); + win.addEventListener("unload", function zsUnload(ev) { + this.zsWindow = null; + }, false); } // Outer block of COSetZSBtn_onClick if (this.zsWindow && !this.zsWindow.closed) { this.zsWindow.close(); } - this.zsWindow = win; - win.addEventListener("load", zsOnload, false); - win.addEventListener("unload", function zsUnload(ev) { - this.zsWindow = null; - }, false); + + B220Util.openPopup(this.window, "B220CardatronZeroSuppressPanel.html", this.mnemonic + "ZS", + "location=no,scrollbars=no,resizable,width=508,height=120,left=" + + (this.window.screenX+16) +",top=" + (this.window.screenY+16), + this, zsOnload); }; /**************************************/ @@ -700,7 +712,7 @@ B220CardatronOutput.prototype.beforeUnload = function beforeUnload(ev) { }; /**************************************/ -B220CardatronOutput.prototype.deviceOnLoad = function deviceOnLoad() { +B220CardatronOutput.prototype.deviceOnLoad = function deviceOnLoad(ev) { /* Initializes the printer/punch window and user interface */ var body; var de; @@ -708,7 +720,8 @@ B220CardatronOutput.prototype.deviceOnLoad = function deviceOnLoad() { var prefs = this.config.getNode("Cardatron.units", this.unitIndex); var zsCol; - this.doc = this.window.document; + this.doc = ev.target; + this.window = this.doc.defaultView; de = this.doc.documentElement; this.doc.title = "retro-220 Cardatron " + (this.isPrinter ? "Printer " : "Punch ") + this.mnemonic; @@ -841,11 +854,13 @@ B220CardatronOutput.prototype.outputWord = function outputWord(requestNextWord) nu = true; // next is a numeric digit if (x > 9) { // For a zone digit in the sign position, store a 5 (+) or 6 (-) - // so that the sign will be printed/punched as a zone 11/12. - if (d == 0 && this.suppress12Mode) { + // so that the sign will be printed/punched as a zone 11/12, UNLESS + // the sign is positive and Suppress-12 mode is in effect. In that + // case, store a zero to the zone so the sign will not print/punch. + if (d%2 == 0 && this.suppress12Mode) { info[ix] = 0; // suppress the 12-zone output } else { - info[ix] = (d & 0x01) + 5; + info[ix] = d%2 + 5; } } else if (d > 3) { info[ix] = this.zoneXlate[d][lastNumeric]; diff --git a/webUI/B220ConsoleKeyboard.js b/webUI/B220ConsoleKeyboard.js index c15a1e8..bb5f099 100644 --- a/webUI/B220ConsoleKeyboard.js +++ b/webUI/B220ConsoleKeyboard.js @@ -25,7 +25,6 @@ function B220ConsoleKeyboard(p) { this.boundKeypress = B220ConsoleKeyboard.prototype.keypress.bind(this); this.boundButton_Click = B220ConsoleKeyboard.prototype.button_Click.bind(this); - this.boundKeyboard_OnLoad = B220ConsoleKeyboard.prototype.keyboardOnLoad.bind(this); this.boundKeyboard_Unload = B220ConsoleKeyboard.prototype.keyboardUnload.bind(this); this.clear(); @@ -188,10 +187,10 @@ B220ConsoleKeyboard.prototype.keyboardOpen = function keyboardOpen() { var w = 240; if (!this.window) { - this.window = window.open("../webUI/B220ConsoleKeyboard.html", this.mnemonic, + B220Util.openPopup(window, "../webUI/B220ConsoleKeyboard.html", this.mnemonic, "resizable,width=" + w + ",height=" + h + - ",left=" + (screen.availWidth - w) + ",top=" + (screen.availHeight - h)); - this.window.addEventListener("load", this.boundKeyboard_OnLoad, false); + ",left=" + (screen.availWidth - w) + ",top=" + (screen.availHeight - h), + this, B220ConsoleKeyboard.prototype.keyboardOnLoad); } }; @@ -207,13 +206,13 @@ B220ConsoleKeyboard.prototype.keyboardUnload = function keyboardUnload(ev) { }; /**************************************/ -B220ConsoleKeyboard.prototype.keyboardOnLoad = function keyboardOnLoad() { +B220ConsoleKeyboard.prototype.keyboardOnLoad = function keyboardOnLoad(ev) { /* Initializes the Control Console window and user interface */ var body; var de; - this.window.removeEventListener("load", this.boundKeyboard_OnLoad, false); - this.doc = this.window.document; + this.doc = ev.target; + this.window = this.doc.defaultView; body = this.$$("KeyboardCase"); de = this.doc.documentElement; @@ -237,7 +236,7 @@ B220ConsoleKeyboard.prototype.keyboardOnLoad = function keyboardOnLoad() { // Kludge for Chrome window.outerWidth/Height timing bug setCallback(null, this, 100, function chromeBug() { - this.window.moveTo(screen.availWidth-this.window.outerWidth, + this.window.moveTo(screen.availWidth - this.window.outerWidth, screen.availHeight - this.window.outerHeight); }); }; diff --git a/webUI/B220ConsolePrinter.js b/webUI/B220ConsolePrinter.js index bba3519..ab8ab5c 100644 --- a/webUI/B220ConsolePrinter.js +++ b/webUI/B220ConsolePrinter.js @@ -49,11 +49,10 @@ function B220ConsolePrinter(mnemonic, unitIndex, config) { this.printerEOP = null; this.printerLine = 0; this.printerCol = 0; - this.window = window.open("../webUI/B220ConsolePrinter.html", mnemonic, + B220Util.openPopup(window, "../webUI/B220ConsolePrinter.html", mnemonic, "location=no,scrollbars=no,resizable,width=640,height=240," + - "left=" + left + ",top=" + top); - this.window.addEventListener("load", - B220ConsolePrinter.prototype.printerOnLoad.bind(this), false); + "left=" + left + ",top=" + top, + this, B220ConsolePrinter.prototype.printerOnLoad); } /**************************************/ @@ -210,17 +209,17 @@ B220ConsolePrinter.prototype.copyPaper = function copyPaper(ev) { or saved by the user */ var text = this.paper.textContent; var title = "B220 " + this.mnemonic + " Text Snapshot"; - var win = window.open("./B220FramePaper.html", "TTY-Snapshot", - "scrollbars,resizable,width=500,height=500"); - win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2); - win.addEventListener("load", function() { - var doc; + B220Util.openPopup(this.window, "./B220FramePaper.html", this.mnemonic + "-Snapshot", + "scrollbars,resizable,width=500,height=500", + this, function(ev) { + var doc = ev.target; + var win = doc.defaultView; - doc = win.document; doc.title = title; + win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2); doc.getElementById("Paper").textContent = text; - }, false); + }); this.emptyPaper(); this.emptyLine(); @@ -381,7 +380,7 @@ B220ConsolePrinter.prototype.parseTabStops = function parsetabStops(text, alertW }; /**************************************/ -B220ConsolePrinter.prototype.printerOnLoad = function printerOnLoad() { +B220ConsolePrinter.prototype.printerOnLoad = function printerOnLoad(ev) { /* Initializes the Teletype printer window and user interface */ var body; var id; @@ -390,7 +389,8 @@ B220ConsolePrinter.prototype.printerOnLoad = function printerOnLoad() { var tabStop; var x; - this.doc = this.window.document; + this.doc = ev.target; + this.window = this.doc.defaultView; this.doc.title = "retro-220 Printer - " + this.mnemonic; this.paper = this.$$("Paper"); this.printerEOP = this.$$("EndOfPaper"); diff --git a/webUI/B220ControlConsole.js b/webUI/B220ControlConsole.js index cc7f5e6..aa55737 100644 --- a/webUI/B220ControlConsole.js +++ b/webUI/B220ControlConsole.js @@ -95,11 +95,11 @@ function B220ControlConsole(p, systemShutdown) { // Create the Console window this.doc = null; - this.window = window.open("../webUI/B220ControlConsole.html", mnemonic, + this.window = null; + B220Util.openPopup(window, "../webUI/B220ControlConsole.html", mnemonic, "location=no,scrollbars,resizable,width=" + w + ",height=" + h + - ",top=0,left=" + (screen.availWidth - w)); - this.window.addEventListener("load", - B220ControlConsole.prototype.consoleOnLoad.bind(this), false); + ",top=0,left=" + (screen.availWidth - w), + this, B220ControlConsole.prototype.consoleOnLoad); } /**************************************/ @@ -172,12 +172,11 @@ B220ControlConsole.prototype.beforeUnload = function beforeUnload(ev) { B220ControlConsole.prototype.meatballMemdump = function meatballMemdump() { /* Opens a temporary window and formats the current processor and memory state to it */ - var doc = null; // loader window.document + var doc = null; // dump window.document var p = this.p; // local copy of Processor object var paper = null; //
 element to receive dump lines
     var trimRightRex = /[\s\uFEFF\xA0]+$/;
-    var win = this.window.open("./B220FramePaper.html", this.mnemonic + "-MEMDUMP",
-            "location=no,scrollbars=yes,resizable,width=800,height=600");
+    var win = null;                     // dump window
     var xlate = B220ControlConsole.codeXlate; // local copy
 
     function formatWord(w) {
@@ -378,22 +377,24 @@ B220ControlConsole.prototype.meatballMemdump = function meatballMemdump() {
         writer("End dump, memory size: " + (top+1).toString() + " words");
     }
 
-    function memdumpSetup() {
+    function memdumpSetup(ev) {
         /* Loads a status message into the "paper" rendering area, then calls
         dumpDriver after a short wait to allow the message to appear */
 
-        win.removeEventListener("load", memdumpSetup, false);
-        doc = win.document;
+        doc = ev.target;
+        win = doc.defaultView
         doc.title = "retro-220 Console: Meatball Memdump";
+        win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
         paper = doc.getElementById("Paper");
         writer("Rendering the dump... please wait...");
         setTimeout(memdumpDriver, 50);
+        win.focus();
     }
 
     // Outer block of meatBallMemdump
-    win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
-    win.focus();
-    win.addEventListener("load", memdumpSetup, false);
+    B220Util.openPopup(this.window, "./B220FramePaper.html", this.mnemonic + "-MEMDUMP",
+            "location=no,scrollbars=yes,resizable,width=800,height=600",
+            this, memdumpSetup);
 };
 
 /**************************************/
@@ -789,7 +790,7 @@ B220ControlConsole.prototype.switch_Click = function switch_Click(ev) {
 };
 
 /**************************************/
-B220ControlConsole.prototype.consoleOnLoad = function consoleOnLoad() {
+B220ControlConsole.prototype.consoleOnLoad = function consoleOnLoad(ev) {
     /* Initializes the Supervisory Panel window and user interface */
     var body;
     var p = this.p;                     // local copy of processor object
@@ -797,7 +798,8 @@ B220ControlConsole.prototype.consoleOnLoad = function consoleOnLoad() {
     var prefs = this.config.getNode("ControlConsole");
     var x;
 
-    this.doc = this.window.document;
+    this.doc = ev.target;
+    this.window = this.doc.defaultView;
     body = this.$$("PanelSurface");
 
     this.intervalTimer = this.$$("IntervalTimer");
diff --git a/webUI/B220MagTapeControl.js b/webUI/B220MagTapeControl.js
index cf99d7d..656f387 100644
--- a/webUI/B220MagTapeControl.js
+++ b/webUI/B220MagTapeControl.js
@@ -16,7 +16,7 @@
 function B220MagTapeControl(p) {
     /* Constructor for the MagTapeControl object */
     var left = 0;                       // control window left position
-    var top = 412;                      // control window top-from-bottom position
+    var top = 432;                      // control window top-from-bottom position
     var u;                              // unit configuration object
     var x;                              // unit index
 
@@ -28,11 +28,11 @@ function B220MagTapeControl(p) {
     // Do not call this.clear() here -- call this.clearUnit() from onLoad instead
 
     this.doc = null;
-    this.window = window.open("../webUI/B220MagTapeControl.html", this.mnemonic,
+    this.window = null;
+    B220Util.openPopup(window, "../webUI/B220MagTapeControl.html", this.mnemonic,
             "location=no,scrollbars=no,resizable,width=712,height=144,top=" +
-            (screen.availHeight - top) + ",left=" + left);
-    this.window.addEventListener("load",
-        B220MagTapeControl.prototype.magTapeOnLoad.bind(this), false);
+                (screen.availHeight - top) + ",left=" + left,
+            this, B220MagTapeControl.prototype.magTapeOnLoad);
 
     this.boundReleaseControl = B220MagTapeControl.prototype.releaseControl.bind(this);
     this.boundCancelIO = B220MagTapeControl.prototype.cancelIO.bind(this);
@@ -212,7 +212,7 @@ B220MagTapeControl.prototype.findDesignate = function findDesignate(u) {
     var unit;
     var x;
 
-    for (x=this.tapeUnit.length-1; x>=0; --x) {
+    for (x=this.tapeUnit.length-1; x>0; --x) {
         unit = this.tapeUnit[x];
         if (unit && unit.ready) {
             if (unit.unitDesignate == u) {
@@ -523,14 +523,15 @@ B220MagTapeControl.prototype.beforeUnload = function beforeUnload(ev) {
 };
 
 /**************************************/
-B220MagTapeControl.prototype.magTapeOnLoad = function magTapeOnLoad() {
+B220MagTapeControl.prototype.magTapeOnLoad = function magTapeOnLoad(ev) {
     /* Initializes the MagTape Control window and user interface */
     var body;
     var box;
     var e;
     var x;
 
-    this.doc = this.window.document;
+    this.doc = ev.target;
+    this.window = this.doc.defaultView;
     body = this.$$("PanelSurface");
 
     // Misc Register
@@ -656,7 +657,7 @@ B220MagTapeControl.prototype.scan = function scan(dReg, bReg) {
                 this.releaseControl();
             } else {
                 // Start the scan after changing lane, as appropriate
-                this.T = searchWord;
+                this.T = searchWord%0x10000000000;
                 this.regT.update(this.T);
                 this.releaseProcessor(false, 0);
                 this.TFLamp.set(1);
@@ -773,7 +774,7 @@ B220MagTapeControl.prototype.search = function search(dReg, bReg) {
                 this.releaseControl();
             } else {
                 // Start the search after changing lane, as appropriate
-                this.T = searchWord;
+                this.T = searchWord%0x10000000000;
                 this.regT.update(this.T);
                 this.releaseProcessor(false, 0);
                 this.TFLamp.set(1);
diff --git a/webUI/B220MagTapeDrive.js b/webUI/B220MagTapeDrive.js
index 80e6d5f..e3fc334 100644
--- a/webUI/B220MagTapeDrive.js
+++ b/webUI/B220MagTapeDrive.js
@@ -141,10 +141,10 @@ function B220MagTapeDrive(mnemonic, unitIndex, tcu, config) {
     this.boundStartUpForward = B220MagTapeDrive.prototype.startUpForward.bind(this);
 
     this.doc = null;
-    this.window = window.open("../webUI/B220MagTapeDrive.html", mnemonic,
-            "location=no,scrollbars=no,resizable,width=384,height=184,left=0,top=" + y);
-    this.window.addEventListener("load",
-            B220MagTapeDrive.prototype.tapeDriveOnload.bind(this), false);
+    this.window = null;
+    B220Util.openPopup(window, "../webUI/B220MagTapeDrive.html", mnemonic,
+            "location=no,scrollbars=no,resizable,width=384,height=184,left=0,top=" + y,
+            this, B220MagTapeDrive.prototype.tapeDriveOnload);
 }
 
 // this.tapeState enumerations
@@ -554,9 +554,7 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
     var file = null;                    // FileReader instance
     var fileSelect = null;              // file picker element
     var mt = this;                      // this B220MagTapeDrive instance
-    var win = this.window.open("B220MagTapeLoadPanel.html", this.mnemonic + "Load",
-            "location=no,scrollbars=no,resizable,width=508,height=112,left=" +
-            (this.window.screenX+16) +",top=" + (this.window.screenY+16));
+    var win = null;                     // loader window
 
     function fileSelector_onChange(ev) {
         /* Handle the  onchange event when a file is selected */
@@ -891,14 +889,16 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
         /* Driver for the tape loader window */
         var de;
 
-        doc = win.document;
+        doc = ev.target;
+        win = doc.defaultView;
         doc.title = "retro-220 " + mt.mnemonic + " Tape Loader";
+        this.loadWindow = win;
         de = doc.documentElement;
         $$$ = function $$$(id) {
             return doc.getElementById(id);
         };
 
-        win.removeEventListener("load", tapeLoadOnload, false);
+        win.addEventListener("unload", tapeLoadUnload, false);
 
         fileSelect = $$$("MTLoadFileSelector");
         fileSelect.addEventListener("change", fileSelector_onChange, false);
@@ -921,20 +921,21 @@ B220MagTapeDrive.prototype.loadTape = function loadTape() {
     if (this.loadWindow && !this.loadWindow.closed) {
         this.loadWindow.close();
     }
-    this.loadWindow = win;
-    win.addEventListener("load", tapeLoadOnload, false);
-    win.addEventListener("unload", tapeLoadUnload, false);
+
+    B220Util.openPopup(this.window, "B220MagTapeLoadPanel.html", this.mnemonic + "Load",
+            "location=no,scrollbars=no,resizable,width=508,height=112,left=" +
+                (this.window.screenX+16) +",top=" + (this.window.screenY+16),
+            this, tapeLoadOnload);
 };
 
 /**************************************/
 B220MagTapeDrive.prototype.unloadTape = function unloadTape() {
     /* Reformats the tape image data as ASCII text and displays it in a new
     window so the user can save or copy/paste it elsewhere */
-    var doc = null;                     // loader window.document
+    var doc = null;                     // unloader window.document
     var image;                          // 
 element to receive tape image data
     var mt = this;                      // tape drive object
-    var win = this.window.open("./B220FramePaper.html", this.mnemonic + "-Unload",
-            "location=no,scrollbars=yes,resizable,width=800,height=600");
+    var win = null;                     // unloader window
 
     function findBlockStart() {
         /* Searches forward in the tape image on the currently-selected lane for the
@@ -1071,23 +1072,25 @@ B220MagTapeDrive.prototype.unloadTape = function unloadTape() {
         mt.setTapeUnloaded();
     }
 
-    function unloadSetup() {
+    function unloadSetup(ev) {
         /* Loads a status message into the "paper" rendering area, then calls
         unloadDriver after a short wait to allow the message to appear */
 
-        win.removeEventListener("load", unloadSetup, false);
-        doc = win.document;
+        doc = ev.target;
+        win = doc.defaultView;
         doc.title = "retro-220 " + mt.mnemonic + " Unload Tape";
+        win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
         image = doc.getElementById("Paper");
         image.appendChild(win.document.createTextNode(
                 "Rendering tape image... please wait..."));
         setTimeout(unloadDriver, 50);
+        win.focus();
     }
 
     // Outer block of unloadTape
-    win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
-    win.focus();
-    win.addEventListener("load", unloadSetup, false);
+    B220Util.openPopup(this.window, "./B220FramePaper.html", this.mnemonic + "-Unload",
+            "location=no,scrollbars=yes,resizable,width=800,height=600",
+            this, unloadSetup);
 };
 
 /**************************************/
@@ -1174,12 +1177,13 @@ B220MagTapeDrive.prototype.beforeUnload = function beforeUnload(ev) {
 };
 
 /**************************************/
-B220MagTapeDrive.prototype.tapeDriveOnload = function tapeDriveOnload() {
+B220MagTapeDrive.prototype.tapeDriveOnload = function tapeDriveOnload(ev) {
     /* Initializes the reader window and user interface */
     var body;
     var prefs = this.config.getNode("MagTape.units", this.unitIndex);
 
-    this.doc = this.window.document;
+    this.doc = ev.target;
+    this.window = this.doc.defaultView;
     this.doc.title = "retro-220 Tape Drive " + this.mnemonic;
 
     body = this.$$("MTDiv");
diff --git a/webUI/B220PaperTapePunch.js b/webUI/B220PaperTapePunch.js
index aa10502..9172ed0 100644
--- a/webUI/B220PaperTapePunch.js
+++ b/webUI/B220PaperTapePunch.js
@@ -34,13 +34,13 @@ function B220PaperTapePunch(mnemonic, unitIndex, config) {
 
     // Create the punch window and onload event
     this.doc = null;
+    this.window = null;
     this.punchTape = null;
     this.punchEOP = null;
-    this.window = window.open("../webUI/B220PaperTapePunch.html", mnemonic,
+    B220Util.openPopup(window, "../webUI/B220PaperTapePunch.html", mnemonic,
             "location=no,scrollbars=no,resizable,width=240,height=160," +
-            "left=" + left + ",top=" + top);
-    this.window.addEventListener("load",
-            B220PaperTapePunch.prototype.punchOnLoad.bind(this), false);
+                "left=" + left + ",top=" + top,
+            this, B220PaperTapePunch.prototype.punchOnLoad);
 }
 
 /**************************************/
@@ -156,15 +156,15 @@ B220PaperTapePunch.prototype.punchCopyTape = function punchCopyTape(ev) {
     or saved by the user */
     var text = this.punchTape.textContent;
     var title = "B220 " + this.mnemonic + " Text Snapshot";
-    var win = window.open("./B220FramePaper.html", "PaperTape-Snapshot",
-            "scrollbars,resizable,width=500,height=500");
 
-    win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
-    win.addEventListener("load", function() {
-        var doc;
+    B220Util.openPopup(this.window, "./B220FramePaper.html", this.mnemonic + "-Snapshot",
+            "scrollbars,resizable,width=500,height=500",
+            this, function(ev) {
+        var doc = ev.target;
+        var win = doc.defaultView;
 
-        doc = win.document;
         doc.title = title;
+        win.moveTo((screen.availWidth-win.outerWidth)/2, (screen.availHeight-win.outerHeight)/2);
         doc.getElementById("Paper").textContent = text;
     });
 
@@ -205,14 +205,15 @@ B220PaperTapePunch.prototype.flipSwitch = function flipSwitch(ev) {
 };
 
 /**************************************/
-B220PaperTapePunch.prototype.punchOnLoad = function punchOnLoad() {
+B220PaperTapePunch.prototype.punchOnLoad = function punchOnLoad(ev) {
     /* Initializes the Paper Tape Punch window and user interface */
     var body;
     var mask;
     var prefs = this.config.getNode("ConsoleOutput.units", this.unitIndex);
     var x;
 
-    this.doc = this.window.document;
+    this.doc = ev.target;
+    this.window = this.doc.defaultView;
     this.doc.title = "retro-220 Punch - " + this.mnemonic;
 
     this.punchTape = this.$$("Paper");
diff --git a/webUI/B220PaperTapeReader.js b/webUI/B220PaperTapeReader.js
index f3cbb5a..99cab34 100644
--- a/webUI/B220PaperTapeReader.js
+++ b/webUI/B220PaperTapeReader.js
@@ -43,12 +43,12 @@ function B220PaperTapeReader(mnemonic, unitIndex, config) {
 
     // Create the reader window and onload event
     this.doc = null;
+    this.window = null;
     this.tapeSupplyBar = null;
     this.tapeView = null;
-    this.window = window.open("../webUI/B220PaperTapeReader.html", mnemonic,
-            "location=no,scrollbars=no,resizable,width=420,height=160,left=" + left + ",top=" + top);
-    this.window.addEventListener("load",
-            B220PaperTapeReader.prototype.readerOnload.bind(this), false);
+    B220Util.openPopup(window, "../webUI/B220PaperTapeReader.html", mnemonic,
+            "location=no,scrollbars=no,resizable,width=420,height=160,left=" + left + ",top=" + top,
+            this, B220PaperTapeReader.prototype.readerOnload);
 }
 
 /**************************************/
@@ -229,14 +229,15 @@ B220PaperTapeReader.prototype.flipSwitch = function flipSwitch(ev) {
 };
 
 /**************************************/
-B220PaperTapeReader.prototype.readerOnload = function readerOnload() {
+B220PaperTapeReader.prototype.readerOnload = function readerOnload(ev) {
     /* Initializes the reader window and user interface */
     var body;
     var mask;
     var prefs = this.config.getNode("ConsoleInput.units", this.unitIndex);
     var x;
 
-    this.doc = this.window.document;
+    this.doc = ev.target;
+    this.window = this.doc.defaultView;
     //de = this.doc.documentElement;
     this.doc.title = "retro-220 - Reader - " + this.mnemonic;
 
diff --git a/webUI/B220SetCallback.js b/webUI/B220SetCallback.js
index 136a84f..88f9db8 100644
--- a/webUI/B220SetCallback.js
+++ b/webUI/B220SetCallback.js
@@ -1,275 +1,275 @@
-/***********************************************************************
-* retro-220/webUI B220SetCallback.js
-************************************************************************
-* Copyright (c) 2014,2017, Paul Kimpel.
-* Licensed under the MIT License, see
-*       http://www.opensource.org/licenses/mit-license.php
-************************************************************************
-* B220 emulator universal function call-back module.
-*
-* Implements a combination setTimeout() and setImmediate() facility for the
-* B220 emulator web-based user interface. setCallback() is used in a manner
-* similar to setTimeout(), except that for low values of the timeout parameter
-* it merely yields control to any other pending events and timers before calling
-* the call-back function.
-*
-* This facility is needed because modern browsers implement a minimum delay
-* when calling setTimeout(). HTML5 specs require 4ms, but browsers vary in the
-* minimum they use, and their precision in activating the call-back function
-* once the actual delay is established varies even more. This module will use
-* setTimeout() if the requested delay time is above a certain threshold, and
-* a setImmediate-like mechanism (based on Promise) if the requested delay is
-* below that threshold.
-*
-* To help compensate for the fact that the call-back function may be called
-* sooner than requested, and that due either to other activity or to browser
-* limitations the delay may be longer than requested, the timing behavior of
-* setCallback() may be divided into "categories." For each category, a separate
-* record is kept of the current total deviation between the requested delay and
-* the actual delay. A portion of this deviation is then applied to the requested
-* delay on subsequent calls in an attempt to smooth out the differences. We are
-* going for good average behavior here, and some too-quick call-backs are better
-* than consistently too-long callbacks in this environment, particularly so that
-* I/Os can be initiated and their finish detected in finer-grained time increments.
-*
-* The SetCallback mechanism defines three functions that become members of the
-* global (window) object:
-*
-*   token = setCallback(category, context, delay, fcn[, arg])
-*
-*       Requests that the function "fcn" be called after "delay" milliseconds.
-*       The function will be called as a method of "context", passing a
-*       single optional argument "arg". The call-back "fcn" may be called
-*       earlier or later than the specified delay. The string "category" (which
-*       may be empty, null, or undefined) defines the category under which the
-*       average delay difference will be maintained. setCallBack returns a
-*       numeric token identifying the call-back event, which can be used with
-*       clearCallback() to cancel the callback. Note that passing a string in
-*       lieu of a function object is not permitted.
-*
-*   clearCallBack(token)
-*
-*       Cancels a pending call-back event, if in fact it is still pending.
-*       The "token" parameter is a value returned from setCallback().
-*
-*   object = getCallbackState(optionMask)
-*
-*       This is a diagnostic function intended for use in monitoring the callback
-*       mechanism. It returns an object that, depending upon bits set in its mask
-*       parameter, contains copies of the lastTokenNr value, poolLength
-*       value, current delayDev hash, pendingCallbacks hash, and pool array.
-*       The optionMask parameter supports the following bit values:
-*           bit 0x01: delayDev hash
-*           bit 0x02: pendingCallbacks hash
-*           bit 0x04: pool array
-*       The lastTokenNr and poolLength values are always returned. If no mask
-*       is supplied, no additional items are returned.
-*
-* This implementation has been inspired by Domenic Denicola's shim for the
-* setImmediate() API at https://github.com/NobleJS/setImmediate, and
-* David Baron's setZeroTimeout() implemenmentation described in his blog
-* at http://dbaron.org/log/20100309-faster-timeouts.
-*
-* I stole a little of their code, too.
-*
-************************************************************************
-* 2017-01-01  P.Kimpel
-*   Original version, cloned from retro-205 emulator D205SetCallback.js.
-* 2017-02-18  P.Kimpel
-*   Redesign yet again the delay adjustment mechanism with one from the
-*   retro-205 project.
-* 2017-10-16  P.Kimpel
-*   Replace window.postMessage yield mechanism with one based on Promise().
-***********************************************************************/
-"use strict";
-
-(function (global) {
-    /* Define a closure for the setCallback() mechanism */
-    var alpha = 0.25;                   // decay factor for delay deviation adjustment
-    var delayDev = {NUL: 0};            // hash of delay time deviations by category
-    var minTimeout = 4;                 // minimum setTimeout() threshold, milliseconds
-    var lastTokenNr = 0;                // last setCallback token return value
-    var pendingCallbacks = {};          // hash of pending callbacks, indexed by token as a string
-    var perf = global.performance;      // cached window.performance object
-    var pool = [];                      // pool of reusable callback objects
-    var poolLength = 0;                 // length of active entries in pool
-
-    /**************************************/
-    function activateCallback(token) {
-        /* Activates a callback after its delay period has expired */
-        var endStamp = perf.now();
-        var thisCallback;
-        var tokenName = token.toString();
-
-        thisCallback = pendingCallbacks[tokenName];
-        if (thisCallback) {
-            delete pendingCallbacks[tokenName];
-            delayDev[thisCallback.category] += endStamp - thisCallback.startStamp - thisCallback.delay;
-            try {
-                thisCallback.fcn.call(thisCallback.context, thisCallback.arg);
-            } catch (err) {
-                console.log("B220SetCallback.activateCallback: " + err.name + ", " + err.message);
-            }
-
-            thisCallback.context = null;
-            thisCallback.fcn = null;
-            thisCallback.arg = null;
-            pool[poolLength++] = thisCallback;
-        }
-    }
-
-    /**************************************/
-    function clearCallback(token) {
-        /* Disables a pending callback, if it still exists and is still pending */
-        var thisCallback;
-        var tokenName = token.toString();
-
-        thisCallback = pendingCallbacks[tokenName];
-        if (thisCallback) {
-            delete pendingCallbacks[tokenName];
-            if (thisCallback.cancelToken) {
-                global.clearTimeout(thisCallback.cancelToken);
-            }
-
-            thisCallback.context = null;
-            thisCallback.fcn = null;
-            thisCallback.arg = null;
-            pool[poolLength++] = thisCallback;
-        }
-    }
-
-    /**************************************/
-    function setCallback(category, context, callbackDelay, fcn, arg) {
-        /* Sets up and schedules a callback for function "fcn", called with context
-        "context", after a delay of "delay" ms. An optional "arg" value will be passed
-        to "fcn". If the delay is less than "minTimeout", a setImmediate-like mechanism
-        based on DOM Promise() will be used; otherwise the environment's standard
-        setTimeout mechanism will be used */
-        var adj = 0;                    // adjustment to delay and delayDev[]
-        var categoryName = (category || "NUL").toString();
-        var delay = callbackDelay || 0; // actual delay to be generated
-        var delayBias;                  // current amount of delay deviation
-        var thisCallback;               // call-back object to be used
-        var token = ++lastTokenNr;      // call-back token number
-        var tokenName = token.toString(); // call-back token ID
-
-        // Allocate a call-back object from the pool.
-        if (poolLength <= 0) {
-            thisCallback = {};
-        } else {
-            thisCallback = pool[--poolLength];
-            pool[poolLength] = null;
-        }
-
-        thisCallback.startStamp = perf.now();
-
-        // Adjust the requested delay based on the current delay deviation
-        // for this category.
-        delayBias = delayDev[categoryName];
-        if (!delayBias) {
-            delayDev[categoryName] = 0;         // bias was zero, or got a new one: no adjustment
-        } else {
-            if (delayBias > 0) {
-                // We are delaying too much and should try to delay less.
-                if (delay < 0) {
-                    adj = 0;            // don't make delay any more negative
-                } else {
-                    adj = -Math.min(delay, delayBias, minTimeout)*alpha;
-                }
-            } else { // delayBias < 0
-                // We are delaying too little and should try to delay more.
-                if (delay < 0) {
-                    if (delay - minTimeout < delayBias) {
-                        adj = -delayBias;
-                    } else {
-                        adj = minTimeout - delay;
-                    }
-                } else {
-                    if (delay > minTimeout) {
-                        adj = 0;
-                    } else if (delay - minTimeout < delayBias) {
-                        adj = -delayBias;
-                    } else {
-                        adj = minTimeout - delay;
-                    }
-                }
-            }
-
-            delay += adj;
-            delayDev[categoryName] += adj;
-        }
-
-        // Fill in the call-back object and tank it in pendingCallbacks.
-        thisCallback.category = categoryName;
-        thisCallback.delay = delay;
-        thisCallback.context = context || this;
-        thisCallback.fcn = fcn;
-        thisCallback.arg = arg;
-        pendingCallbacks[tokenName] = thisCallback;
-
-        // Decide whether to do a time wait or just a yield.
-        if (delay > minTimeout) {
-            thisCallback.cancelToken = global.setTimeout(activateCallback, delay, token);
-        } else {
-            thisCallback.cancelToken = 0;
-            Promise.resolve(token).then(activateCallback);
-        }
-
-        return token;
-    }
-
-    /**************************************/
-    function getCallbackState(optionMask) {
-        /* Diagnostic function. Returns an object that, depending upon bits in
-        the option mask, contains copies of the lastTokenNr value, poolLength
-        value, current delayDev hash, pendingCallbacks hash, and pool array.
-            bit 0x01: delayDev hash
-            bit 0x02: pendingCallbacks hash
-            bit 0x04: pool array
-        If no mask is supplied, no additional items are returned */
-        var e;
-        var mask = optionMask || 0;
-        var state = {
-            lastTokenNr: lastTokenNr,
-            poolLength: poolLength,
-            delayDev: {},
-            pendingCallbacks: {},
-            pool: []};
-
-        if (mask & 0x01) {
-            for (e in delayDev) {
-                state.delayDev[e] = delayDev[e];
-            }
-        }
-        if (mask & 0x02) {
-            for (e in pendingCallbacks) {
-                state.pendingCallbacks[e] = pendingCallbacks[e];
-            }
-        }
-        if (mask & 0x04) {
-            for (e=0; e 0) {
+                // We are delaying too much and should try to delay less.
+                if (delay < 0) {
+                    adj = 0;            // don't make delay any more negative
+                } else {
+                    adj = -Math.min(delay, delayBias, minTimeout)*alpha;
+                }
+            } else { // delayBias < 0
+                // We are delaying too little and should try to delay more.
+                if (delay < 0) {
+                    if (delay - minTimeout < delayBias) {
+                        adj = -delayBias;
+                    } else {
+                        adj = minTimeout - delay;
+                    }
+                } else {
+                    if (delay > minTimeout) {
+                        adj = 0;
+                    } else if (delay - minTimeout < delayBias) {
+                        adj = -delayBias;
+                    } else {
+                        adj = minTimeout - delay;
+                    }
+                }
+            }
+
+            delay += adj;
+            delayDev[categoryName] += adj;
+        }
+
+        // Fill in the call-back object and tank it in pendingCallbacks.
+        thisCallback.category = categoryName;
+        thisCallback.delay = delay;
+        thisCallback.context = context || this;
+        thisCallback.fcn = fcn;
+        thisCallback.arg = arg;
+        pendingCallbacks[tokenName] = thisCallback;
+
+        // Decide whether to do a time wait or just a yield.
+        if (delay > minTimeout) {
+            thisCallback.cancelToken = global.setTimeout(activateCallback, delay, token);
+        } else {
+            thisCallback.cancelToken = 0;
+            Promise.resolve(token).then(activateCallback);
+        }
+
+        return token;
+    }
+
+    /**************************************/
+    function getCallbackState(optionMask) {
+        /* Diagnostic function. Returns an object that, depending upon bits in
+        the option mask, contains copies of the lastTokenNr value, poolLength
+        value, current delayDev hash, pendingCallbacks hash, and pool array.
+            bit 0x01: delayDev hash
+            bit 0x02: pendingCallbacks hash
+            bit 0x04: pool array
+        If no mask is supplied, no additional items are returned */
+        var e;
+        var mask = optionMask || 0;
+        var state = {
+            lastTokenNr: lastTokenNr,
+            poolLength: poolLength,
+            delayDev: {},
+            pendingCallbacks: {},
+            pool: []};
+
+        if (mask & 0x01) {
+            for (e in delayDev) {
+                state.delayDev[e] = delayDev[e];
+            }
+        }
+        if (mask & 0x02) {
+            for (e in pendingCallbacks) {
+                state.pendingCallbacks[e] = pendingCallbacks[e];
+            }
+        }
+        if (mask & 0x04) {
+            for (e=0; eSystem Properties: