mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-14 20:16:18 +00:00
1. Move project from Google Code to GitHub (https://github.com/pkimpel/retro-b5500/). Update links and help pages; convert wiki pages to GitHub's MarkDown format. 2. Implement emulator-hosted memory dump to a tape image that can be saved and input into the B5500 DUMP/ANALYZE utility for analysis. Activated by clicking the NOT READY button on the Console. 3. Fix bad assignments to Processor X register in arithmetic ops (affected only SyllableDebugger script). 4. Remove IndexedDB.openDatabase() version parameter so the B5500ColdLoader and tools/ scripts will work in non-Firefox browsers. 5. Add a "?db" query string parameter to the tools/scripts so these scripts can open disk subsystems other than B5500DiskUnit. 6. Correct pre-allocated file locations and ESU card in tools/COLDSTART-XIII.card. 7. Implement new double-click mechanism to copy and clear the contents of card punch, datacom terminal, and line-printer output areas to a temporary window for subsequent copying or saving. 8. Correct handling of Ctrl-B (break), Ctrl-D (disconnect request), Ctrl-E (WRU), Ctrl-L (clear input buffer), and Ctrl-Q (alternate end-of-message) in B5500DatacomUnit. 9. Implement reporting of Model IB (slow, bulk) disk in B5500DiskUnit readInterrogate. 10. Implement detection of browser IndexedDB quota-exceeded errors in B5500DiskUnit (primarily to handle the fixed 2GB limit for off-line storage in Firefox). 11. Correct problem when line printer exhausted paper and FORM FEED triple-click did not clear the condition. 12. Eliminate BOT marker sensed in result for tape drive Write Interrogate operation -- Mark XIII and XV MCPs treat this as an error and will not purge blank tapes because of it. 13. Fix double-click of SPO INPUT REQUEST button either sending a duplicate interrupt to the system or the second click moving focus from the SPO input box. 14. Further tuning of delay-deviation adjustment mechanism in B5500SetCallback.js. 15. Reinstate ability of SPO to wrap long outputs to additional lines (apparently lost with new SPO input mechanism in 1.00). 16. Commit preliminary COOLSTART-XIII.card and MCPTAPEDISK-XIII.card decks.
459 lines
18 KiB
JavaScript
459 lines
18 KiB
JavaScript
/***********************************************************************
|
|
* 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; // <meter> 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+$/; // 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 */
|
|
|
|
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;
|
|
B5500Util.removeClass(this.$$("LPEndOfPaperBtn"), "whiteLit");
|
|
this.paperMeter.value = this.paperLeft = this.maxPaperLines;
|
|
while (this.paper.firstChild) {
|
|
this.paper.removeChild(this.paper.firstChild);
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500LinePrinter.prototype.copyPaper = function copyPaper(ev) {
|
|
/* Copies the text contents of the "paper" area of the device, opens a new
|
|
temporary window, and pastes that text into the window so it can be copied
|
|
or saved by the user */
|
|
var barGroup = this.paper.firstChild;
|
|
var text = "";
|
|
var title = "B5500 " + this.mnemonic + " Paper Snapshot";
|
|
var win = window.open("./B5500FramePaper.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;
|
|
|
|
doc = win.document;
|
|
doc.title = title;
|
|
doc.getElementById("Paper").textContent = text;
|
|
});
|
|
|
|
this.ripPaper();
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
};
|
|
|
|
/**************************************/
|
|
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 (overprinting) 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.toLowerCase() == "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 Space 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) {
|
|
if (this.window.confirm("Do you want to clear the \"paper\" from the printer?")) {
|
|
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.paper.addEventListener("dblclick",
|
|
B5500CentralControl.bindMethod(this, B5500LinePrinter.prototype.copyPaper));
|
|
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();
|
|
}; |