1
0
mirror of https://github.com/pkimpel/retro-b5500.git synced 2026-02-14 20:16:18 +00:00
Files
pkimpel.retro-b5500/webUI/B5500LinePrinter.js
Paul Kimpel f1fe18dab3 Commit release 1.02:
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.
2015-06-14 19:06:27 -07:00

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();
};