Initial commit (a clone of the jsmachines project as of v1.15.3)

This commit is contained in:
jeffpar 2014-09-27 14:52:57 -07:00
commit a5e3e6a59d
714 changed files with 130602 additions and 0 deletions

View file

@ -0,0 +1,39 @@
{
"boss": true,
"eqnull": true,
"evil": true,
"loopfunc": true,
"sub": true,
"globalstrict": true,
"globals": {
"window": true,
"APPNAME": false,
"APPVERSION": false,
"SITEHOST": false,
"DEBUG": false,
"MAXDEBUG": false,
"C1PJSCLASS": true,
"DEBUGGER": true,
"Component": true,
"State": true,
"C1PComputer": true,
"C1PCPU": true,
"C1PDebugger": true,
"C1PDiskController": true,
"C1PKeyboard": true,
"C1PPanel": true,
"C1PRAM": true,
"C1PROM": true,
"C1PSerialPort": true,
"C1PVideo": true,
"str": true,
"usr": true,
"web": true,
"global": true,
"module": true,
"require": true,
"setTimeout": false,
"clearTimeout": false,
"Image": false
}
}

View file

@ -0,0 +1,353 @@
/**
* @fileoverview This file implements the C1Pjs Computer component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Jun-15
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
/*
* BUILD INSTRUCTIONS
*
* To build C1Pjs (c1p.js), run Google's Closure Compiler, replacing "*.js" with
* the input file sequence defined by the "c1pJSFiles" property in package.json:
*
* java -jar compiler.jar
* --compilation_level ADVANCED_OPTIMIZATIONS
* --define='DEBUG=false'
* --warning_level=VERBOSE
* --js *.js
* --js_output_file c1p.js
*
* Google's Closure Compiler (compiler.jar) is documented at
* https://developers.google.com/closure/compiler/ and is available
* for download here:
*
* http://closure-compiler.googlecode.com/files/compiler-latest.zip
*
* The C1Pjs JavaScript files do have some initialization-order dependencies.
* If you load the files individually, it's recommended that you load them in
* the same order that they're compiled (see above).
*
* Generally speaking, component.js should be first, computer.js should be
* last (of the files based on component.js), and panel.js should be listed
* early so that the Control Panel is ready as soon as possible.
*/
"use strict";
/**
* C1PComputer(parmsComputer, modules)
*
* The C1PComputer component expects the following (parmsComputer) properties:
*
* modules[{}] (from the <module> definition(s) for the computer)
*
* This component processes all the <module> "start" and "end" specifications
* and "wires" everything to a common "address buffer"; namely, the abMemory array.
* abMemory encompasses the computer's entire address space, but every component must
* play nice and use only its assigned section of abMemory -- and pretend it's an array
* of bytes, when in fact it's an array of floating-point values (the only primitive
* numeric data type that JavaScript provides).
*
* This component also insures that all the other components are ready; in particular,
* this means that the ROM and Video components have finished loading their resources
* and are ready for operation. Other components become ready as soon as we call their
* setBuffer() method (eg, CPU, RAM, Keyboard, Debugger, SerialPort, DiskController), and
* others, like Panel, become ready even earlier, at the end of their initialization.
*
* Once every component has indicated it's ready, we call its setPower() notification
* function (if it has one; it's optional). We call the CPU's setPower() function last,
* so that the CPU is assured that all other components are ready and "powered".
*
* @constructor
* @extends Component
*/
function C1PComputer(parmsComputer, modules)
{
Component.call(this, "C1PComputer", parmsComputer);
this.modules = modules;
}
C1PComputer.sAppName = APPNAME || "C1Pjs";
C1PComputer.sAppVer = APPVERSION;
C1PComputer.sCopyright = "Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>";
Component.subclass(Component, C1PComputer);
/**
* @this {C1PComputer}
* @param {boolean} [fPowerOn] is true to indicate that we should start the CPU running
*/
C1PComputer.prototype.reset = function(fPowerOn)
{
var cpu = null;
for (var sType in this.modules) {
for (var i=0; i < this.modules[sType].length; i++) {
var component = this.modules[sType][i];
if (component && component.reset) {
if (DEBUG) this.println("resetting " + sType);
component.reset();
if (sType == "cpu") cpu = component;
}
}
}
if (cpu) {
cpu.update();
if (fPowerOn) cpu.run();
}
};
/**
* @this {C1PComputer}
*
* Called by the CPU to notify all component start() handlers
*/
C1PComputer.prototype.start = function()
{
for (var sType in this.modules) {
if (sType == "cpu") continue;
for (var i=0; i < this.modules[sType].length; i++) {
var component = this.modules[sType][i];
if (component && component.start) {
component.start();
}
}
}
};
/**
* @this {C1PComputer}
* @param {number} msStart
* @param {number} nCycles
*
* Called by the CPU to notify all component stop() handlers
*/
C1PComputer.prototype.stop = function(msStart, nCycles)
{
for (var sType in this.modules) {
if (sType == "cpu") continue;
for (var i=0; i < this.modules[sType].length; i++) {
var component = this.modules[sType][i];
if (component && component.stop) {
component.stop(msStart, nCycles);
}
}
}
};
/**
* @this {C1PComputer}
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PComputer.prototype.setBinding = function(c, t, s, e)
{
switch(s) {
case "reset":
this.bindings[s] = e;
e.onclick = function(computer) {
return function() {
computer.reset();
};
}(this);
return true;
default:
break;
}
return false;
};
/**
* @this {C1PComputer}
* @param {string} sType
* @return {Component}
*
* NOTE: If there are multiple components for a given type, we may need to provide a means of discriminating.
*/
C1PComputer.prototype.getComponentByType = function(sType)
{
if (this.modules[sType]) {
return this.modules[sType][0];
}
return null;
};
C1PComputer.power = function(computer)
{
/*
* Insure that the ROMs, Video and CPU are all ready before "powering" everything; always "power"
* the CPU last, to make sure it doesn't start asking other components to do things before they're ready.
*/
var cpu = null;
for (var sType in computer.modules) {
for (var i=0; i < computer.modules[sType].length; i++) {
var component = computer.modules[sType][i];
if (!component) continue;
if (!component.isReady()) {
component.isReady(function(computer) {
return function() {
C1PComputer.power(computer);
};
}(computer)); // jshint ignore:line
return;
}
/*
* The CPU component's setPower() notification handler is a special case: we don't want
* to call it until the end (below), after all others have been called.
*/
if (sType == "cpu")
cpu = component;
else if (component.setPower) {
component.setPower(true, computer);
}
}
}
/*
* The entire computer is finally ready; we call our own setReady() for completeness, not because any
* other component actually cares when we're ready.
*/
computer.setReady();
computer.println(C1PComputer.sAppName + " v" + C1PComputer.sAppVer + "\n" + C1PComputer.sCopyright);
/*
* Once we get to this point, we're guaranteed that all components are ready, so it's safe to "power" the CPU;
* setPower() includes an automatic reset(fPowerOn), so the CPU should begin executing immediately, unless a debugger
* is attached.
*/
if (cpu) cpu.setPower(true, computer);
};
/*
* C1PComputer.init()
*
* This function operates on every element (e) of class "computer", and initializes
* all the necessary HTML to construct the C1PComputer(s) as spec'ed.
*
* Note that each element (e) of class "computer" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the C1PComputer constructor
* expects.
*/
C1PComputer.init = function()
{
var aeComputers = Component.getElementsByClass(window.document, C1PJSCLASS, "computer");
for (var iComputer=0; iComputer < aeComputers.length; iComputer++) {
var eComputer = aeComputers[iComputer];
var parmsComputer = Component.getComponentParms(eComputer);
var component;
var modules = {};
var abMemory;
var addrStart = 0, addrEnd = 0;
for (var iAddr=0; iAddr < parmsComputer['modules'].length; iAddr++) {
var addrInfo = parmsComputer['modules'][iAddr];
/*
* The first address range (ie, the CPU range) must specify the range for the entire
* address space (abMemory), which we allocate and zero-initialize.
*
* NOTE: We might consider doing what the Video component does on first reset: initializing
* the entire memory buffer to random values. However, a constant (eg, 0xA5) might be
* more useful, acting as a crude indicator of memory the client code hasn't written yet.
*/
if (!iAddr) {
if (addrInfo['type'] != "cpu") break;
addrStart = addrInfo['start'];
addrEnd = addrInfo['end'];
abMemory = new Array(addrEnd+1 - addrStart);
for (var addr=addrStart; addr < abMemory.length; addr++) {
abMemory[addr] = 0;
}
}
component = Component.getComponentByID(addrInfo['refID'], parmsComputer['id']);
if (component) {
var sType = addrInfo['type'];
if (modules[sType] === undefined)
modules[sType] = [];
modules[sType].push(component);
if (component.setBuffer && addrInfo['start'] !== undefined) {
component.setBuffer(abMemory, addrInfo['start'], addrInfo['end'], modules['cpu'][0]);
}
}
else {
Component.error("no component for <module refid=\"" + addrInfo['refID'] + "\">");
return;
}
}
if (abMemory === undefined) {
Component.error("<module type=\"cpu\"> definition must appear first in the <computer> specification");
return;
}
/*
* Let's see if the Debugger is installed (NOTE: its ID must be "debugger", and only one per machine is supported);
* the Debugger needs our setBuffer(), setPower() and reset() notifications, and this relieves us from having an explicit
* <module> entry for type="debugger".
*/
component = Component.getComponentByID('debugger', parmsComputer['id']);
if (component) {
modules['debugger'] = [component];
if (component.setBuffer) {
component.setBuffer(abMemory, addrStart, addrEnd, modules['cpu'][0]);
}
}
/*
* Let's see if the Control Panel is installed (NOTE: its ID must be "panel", and only one per machine is supported);
* the Panel needs our setPower() notifications, and this relieves us from having an explicit <module> entry for type="panel".
*/
component = Component.getComponentByID('panel', parmsComputer['id']);
if (component) {
modules['panel'] = [component];
}
var computer = new C1PComputer(parmsComputer, modules);
/*
* We may eventually add a "Power" button, but for now, all we have is a "Reset" button
*/
Component.bindComponentControls(computer, eComputer, C1PJSCLASS);
/*
* "Power" the computer automatically
*/
C1PComputer.power(computer);
}
};
/*
* Initialize every Computer on the page.
*/
web.onInit(C1PComputer.init);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,53 @@
/**
* @fileoverview C1Pjs-specific compile-time definitions.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-08
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* @define {string}
*/
var C1PJSCLASS = "c1pjs"; // this @define is the default application class (formerly APPCLASS) to use for C1Pjs
/**
* @define {boolean}
*
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate. When it's *false*,
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
* debugger.js from the compilation process altogether.
*
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
* I would like to skip loading debugger.js altogether. When doing that, we must ALSO arrange for an additional file
* (nodebugger.js) to be loaded immediately after this file, which *explicitly* overrides DEBUGGER with *false*.
*/
var DEBUGGER = true; // this @define is overridden by the Closure Compiler to remove Debugger-related support

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
/**
* @fileoverview Compile-time definitions for Debugger-less configurations.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-08
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/*
* WARNING: DEBUGGER needs to accurately reflect whether or not the Debugger component is (or will be) loaded.
* In the compiled case, we rely on the Closure Compiler to override DEBUGGER as appropriate; when it's *false*,
* nearly all of debugger.js will be conditionally removed by the compiler, reducing it to little more than a
* "type skeleton", which also solves some type-related warnings we would otherwise have if we tried to remove
* debugger.js from the compilation process altogether.
*
* However, when we're in "development mode" and running uncompiled code in debugger-less configurations,
* I would like to skip loading debugger.js altogether. To do that, we must arrange for this additional file
* (nodebugger.js) to be loaded as early as possible, which *explicitly* overrides the previously defined value
* of DEBUGGER with *false*.
*/
DEBUGGER = false;

View file

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/my_modules/c1pjs-client/templates/outline-debug.xsl"?>
<outline id="osi-6502-overview">
<title>C1Pjs</title>
<excerpt>The following document describes the C1Pjs source code.</excerpt>
<content>
<machine id="c1psim" class="c1p" border="1" width="272px" pos="right" padleft="50px" padright="50px">
<computer id="c1p" name="Challenger 1P">
<module type="cpu" refid="cpu6502" start="0x0000" end="0xffff"/>
<module type="ram" refid="ram8K" start="0x0000" end="0x1fff"/>
<module type="rom" refid="romNull" start="0x2000" end="0x9fff"/>
<module type="rom" refid="romBasic" start="0xa000" end="0xbfff"/>
<module type="video" refid="video" start="0xd000" end="0xd3ff"/>
<module type="keyboard" refid="keyboard" start="0xdf00" end="0xdfff"/>
<module type="serial" refid="serialPort" start="0xf000" end="0xf0ff"/>
<module type="rom" refid="romSystem" start="0xf800" end="0xffff"/>
</computer>
<cpu id="cpu6502"/>
<ram id="ram8K" size="0x2000"/>
<rom id="romNull" size="0x8000"/>
<rom id="romBasic" size="0x2000" image="/devices/c1p/roms/basic-gcpatch.hex"/>
<rom id="romSystem" size="0x0800" image="/devices/c1p/roms/system.hex"/>
<video id="video" screenwidth="256" screenheight="192" cols="32" rows="32" charset="/devices/c1p/video/chargen1x.png" padding="8px"/>
<keyboard id="keyboard">
<control type="button" class="input" binding="ctrl-c">CTRL-C</control>
<control type="button" class="input" binding="break">BREAK</control>
</keyboard>
<serial id="serialPort" demo="true"/>
</machine>
<p>C1Pjs is a simulation of the <a href="/devices/c1p/">Challenger 1P</a> micro-computer, a 6502-based system built by Ohio Scientific in the late 1970's.
This simulation is implemented entirely in JavaScript, and it is built from the following files:
<ul>
<li><a href="../../shared/lib/component.js">Component</a></li>
<li><a href="computer.js">Computer</a></li>
<li><a href="panel.js">Control Panel</a></li>
<li><a href="cpu.js">CPU</a></li>
<li><a href="debugger.js">Debugger</a></li>
<li><a href="disk.js">Disk Controller</a></li>
<li><a href="keyboard.js">Keyboard</a></li>
<li><a href="ram.js">RAM</a></li>
<li><a href="rom.js">ROM</a></li>
<li><a href="serial.js">Serial Port</a></li>
<li><a href="video.js">Video Display</a></li>
<li><a href="embed.js">Embed Support</a></li>
</ul>
</p>
<p>The <a href="../../shared/lib/component.js">Component</a> file defines a generic Component object with some common functionality, and
each of the other files copy and extend the Component object to define new components that simulate various pieces of computer
hardware, such as the 6502 CPU, the keyboard, the video display, etc. Each file implements exactly one type of component,
which are all "compiled" into a single JavaScript file using Google's <a href="https://developers.google.com/closure/compiler/">Closure Compiler</a>.</p>
<p>Most of the components are optional. You assemble them into a virtual machine by creating a virtual machine definition file, which is
an XML file that lists the components to be used, along with the HTML elements that will visually represent those components, and all
the "bindings" that connect the components to the visual elements.</p>
<p>Here are some working examples:
<ul>
<li><a href="/configs/c1p/machines/8kb/large/debugger/">Challenger 1P w/Debugger</a></li>
<li><a href="/configs/c1p/machines/8kb/array/">Challenger 1P "Server Array" Demo</a></li>
</ul>
</p>
<p>Here's what a machine definition XML file typically looks like:
<pre>
<lt/>?xml version="1.0" encoding="UTF-8"?<gt/>
<lt/>?xml-stylesheet type="text/xsl" href="/versions/c1pjs/1.12.1/machine.xsl"?<gt/>
<lt/>machine id="c1psim" class="c1p" border="1" width="272px"<gt/>
<lt/>name<gt/>Challenger 1P Simulation<lt/>/name<gt/>
<lt/>computer id="c1p" name="Challenger 1P"<gt/>
<lt/>module type="cpu" refid="cpu6502" start="0x0000" end="0xffff"/<gt/>
<lt/>module type="ram" refid="ram8K" start="0x0000" end="0x1fff"/<gt/>
<lt/>module type="rom" refid="romNull" start="0x2000" end="0x9fff"/<gt/>
<lt/>module type="rom" refid="romBasic" start="0xa000" end="0xbfff"/<gt/>
<lt/>module type="video" refid="video" start="0xd000" end="0xd3ff"/<gt/>
<lt/>module type="keyboard" refid="keyboard" start="0xdf00" end="0xdfff"/<gt/>
<lt/>module type="rom" refid="romSystem" start="0xf800" end="0xffff"/<gt/>
<lt/>/computer<gt/>
<lt/>cpu id="cpu6502"/<gt/>
<lt/>ram id="ram8K" size="0x2000"/<gt/>
<lt/>rom id="romNull" size="0x8000"/<gt/>
<lt/>rom id="romBasic" size="0x2000" image="/devices/c1p/roms/basic-gcpatch.hex"/<gt/>
<lt/>rom id="romSystem" size="0x0800" image="/devices/c1p/roms/system.hex"/<gt/>
<lt/>video id="video" screenwidth="256" screenheight="192" cols="32" rows="32"
charset="/devices/c1p/video/chargen1x.png" width="256px" padding="8px"/<gt/>
<lt/>keyboard id="keyboard"<gt/>
<lt/>control type="button" class="input" binding="ctrl-c"<gt/>CTRL-C<lt/>/control<gt/>
<lt/>control type="button" class="input" binding="ctrl-o"<gt/>CTRL-O<lt/>/control<gt/>
<lt/>control type="button" class="input" binding="break"<gt/>BREAK<lt/>/control<gt/>
<lt/>/keyboard<gt/>
<lt/>/machine<gt/>
</pre>
</p>
<p>The order of the components (and any controls they define) determines the order in which the corresponding HTML elements are laid out on the page.
It has no bearing on the initialization sequence of the JavaScript components, which is determined entirely by the order in which the JavaScript files
were "compiled" (or by the order the individual JavaScript files are listed on the page).</p>
<p>NOTE: If you're debugging a simulation using the browser's Developer Tools, then obviously you'll want to load the original, individual JavaScript files,
but in general, you'll get much better performance -- both load-time and run-time -- by loading a single "compiled" JavaScript file.</p>
<p class="noindent"><em>Jeff Parsons<br/>August 28, 2012</em></p>
</content>
</outline>

View file

@ -0,0 +1,130 @@
/**
* @fileoverview This file implements the C1Pjs Panel component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Jun-19
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* C1PPanel(parmsPanel)
*
* The Panel component has no required (parmsPanel) properties.
*
* @constructor
* @extends Component
*/
function C1PPanel(parmsPanel)
{
Component.call(this, "C1PPanel", parmsPanel);
this.fPower = false;
}
Component.subclass(Component, C1PPanel);
/**
* The Panel doesn't have any bindings of its own; it passes along all binding requests to
* the Computer, CPU, Keyboard and Debugger components. The order shouldn't matter, since any
* component that doesn't recognize the specified binding should simply ignore it.
*
* @this {C1PPanel}
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PPanel.prototype.setBinding = function(c, t, s, e)
{
if (this.cmp && this.cmp.setBinding(c, t, s, e)) return true;
if (this.cpu && this.cpu.setBinding(c, t, s, e)) return true;
if (this.kbd && this.kbd.setBinding(c, t, s, e)) return true;
if (DEBUGGER && this.dbg && this.dbg.setBinding(c, t, s, e)) return true;
return Component.prototype.setBinding.call(this, c, t, s, e);
};
/**
* @this {C1PPanel}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*/
C1PPanel.prototype.setPower = function(fOn, cmp)
{
if (fOn && !this.fPower) {
this.fPower = true;
this.cmp = cmp;
this.cpu = cmp.getComponentByType("cpu");
this.kbd = cmp.getComponentByType("keyboard");
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
C1PPanel.init();
}
};
/**
* C1PPanel.init()
*
* This function operates on every element (e) of class "panel", and initializes
* all the necessary HTML to construct the Panel module(s) as spec'ed.
*
* Note that each element (e) of class "panel" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the Panel constructor
* expects.
*
* NOTE: Unlike most other component init() functions, this one is designed to be
* called multiple times: once at load time, so that we can binding our print()
* function to the panel's output control ASAP, and again when the Computer component
* is verifying that all components are ready and invoking their setPower() functions.
*
* Our setPower() method gives us a second opportunity to notify any components that
* that might care (eg, CPU, Keyboard, and Debugger) that we have some controls they
* might want to use.
*/
C1PPanel.init = function()
{
var fReady = false;
var aePanels = Component.getElementsByClass(window.document, C1PJSCLASS, "panel");
for (var iPanel=0; iPanel < aePanels.length; iPanel++) {
var ePanel = aePanels[iPanel];
var parmsPanel = Component.getComponentParms(ePanel);
var panel = Component.getComponentByID(parmsPanel['id']);
if (!panel) {
fReady = true;
panel = new C1PPanel(parmsPanel);
}
Component.bindComponentControls(panel, ePanel, C1PJSCLASS);
if (fReady) panel.setReady();
}
};
/*
* Initialize every Panel module on the page.
*/
web.onInit(C1PPanel.init);

View file

@ -0,0 +1,96 @@
/**
* @fileoverview This file implements the C1Pjs RAM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Jun-15
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* C1PRAM(parmsRAM)
*
* The RAM component expects the following (parmsRAM) properties:
*
* size: amount of RAM, in bytes
*
* NOTE: We may make a note of the specified size, but we will not actually allocate
* any memory for the RAM; we wait for the Computer object to tell us where our RAM is,
* using the setBuffer() method.
*
* @constructor
* @extends Component
*/
function C1PRAM(parmsRAM)
{
Component.call(this, "C1PRAM", parmsRAM);
}
Component.subclass(Component, C1PRAM);
/**
* @this {C1PRAM}
* @param {Array} abMemory
* @param {number} start
* @param {number} end
* @param {C1PCPU} cpu
*/
C1PRAM.prototype.setBuffer = function(abMemory, start, end, cpu)
{
this.abMem = abMemory;
// this.offRAM = start;
// this.cbRAM = end - start + 1;
this.setReady();
};
/**
* C1PRAM.init()
*
* This function operates on every element (e) of class "ram", and initializes
* all the necessary HTML to construct the RAM module(s) as spec'ed.
*
* Note that each element (e) of class "ram" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the RAM constructor
* expects.
*/
C1PRAM.init = function()
{
var aeRAM = Component.getElementsByClass(window.document, C1PJSCLASS, "ram");
for (var iRAM=0; iRAM < aeRAM.length; iRAM++) {
var eRAM = aeRAM[iRAM];
var parmsRAM = Component.getComponentParms(eRAM);
var ram = new C1PRAM(parmsRAM);
Component.bindComponentControls(ram, eRAM, C1PJSCLASS);
}
};
/*
* Initialize all the RAM modules on the page.
*/
web.onInit(C1PRAM.init);

View file

@ -0,0 +1,244 @@
/**
* @fileoverview This file implements the C1Pjs ROM component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Jun-15
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* C1PROM(parmsROM)
*
* The ROM component expects the following (parmsROM) properties:
*
* size: amount of ROM, in bytes
* image: name of ROM image file
*
* NOTE: The final location for the ROM image, once loaded, will be specified
* by the Computer object, using the setBuffer() method.
*
* @constructor
* @extends Component
* @property {function()} convertImage
*/
function C1PROM(parmsROM)
{
Component.call(this, "C1PROM", parmsROM);
this.abMem = null;
this.abImage = null;
this.cbROM = parmsROM['size'];
this.sImage = parmsROM['image'];
if (this.sImage) {
var sFileURL = this.sImage;
/**
* If the selected ROM image has a ".json" extension, then we assume it's a pre-converted
* JSON-encoded ROM image, so we load it as-is; ditto for files with a ".hex" extension. Otherwise,
* we ask our server-side ROM image converter to return the corresponding JSON-encoded data,
* in compact form (ie, minimal whitespace, no ASCII data comments, etc).
*/
var sFileExt = str.getExtension(this.sImage);
if (sFileExt != "json" && sFileExt != "hex") {
/**
* TODO: This code was using a deprecated parameter (compact=1); make sure things still work.
*
* TODO: Convert this code to use the new shared File API definitions and weblib functions; eg:
*
* sFileURL = web.getHost() + DumpAPI.ENDPOINT + "?" + DumpAPI.QUERY.FILE + "=" + this.sImage;
*/
sFileURL = "http://" + window.location.host + "/api/v1/dump?file=" + this.sImage;
}
web.loadResource(sFileURL, true, null, this, this.convertImage);
}
}
Component.subclass(Component, C1PROM);
/**
* @this {C1PROM}
* @param {Array} abMemory
* @param {number} start
* @param {number} end
* @param {C1PCPU} cpu
*/
C1PROM.prototype.setBuffer = function(abMemory, start, end, cpu)
{
this.abMem = abMemory;
this.offROM = start;
var cbROM = end - start + 1;
/*
* It's possible that the ROM component didn't specify a size,
* in which case just use the size the Computer component has specified.
*/
if (!this.cbROM)
this.cbROM = cbROM;
if (cbROM != this.cbROM) {
this.setError("computer-specified ROM size (" + str.toHexWord(cbROM) + ") does not match component-specified size (" + str.toHexWord(this.cbROM) + ")");
return;
}
if (cpu) {
this.cpu = cpu;
cpu.addWriteNotify(start, end, this, this.setByte);
}
this.copyImage();
};
/**
* @this {C1PROM}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*/
C1PROM.prototype.setPower = function(fOn, cmp)
{
if (fOn && !this.fPower) {
this.fPower = true;
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
}
};
/**
* @this {C1PROM}
* @param {number} addr
* @param {number|undefined} [addrFrom]
*/
C1PROM.prototype.setByte = function(addr, addrFrom)
{
/*
* Beyond reporting this write, we need to "repair" the ROM, using the original image data,
* but only if addrFrom is defined (undefined implies this is a write from the Debugger, and
* we need to allow the Debugger to modify ROM contents).
*/
if (addrFrom !== undefined) {
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_PORT, true);
var offset = (addr - this.offROM);
Component.assert(offset >= 0 && offset < this.cbROM);
if (!this.abImage)
this.abMem[this.offROM + offset] = 0;
else
this.abMem[this.offROM + offset] = this.abImage[offset];
}
};
/**
* @this {C1PROM}
* @param {string} sImageName
* @param {string} sImageData
* @param {number} nErrorCode (response from server if anything other than 200)
*/
C1PROM.prototype.convertImage = function(sImageName, sImageData, nErrorCode)
{
if (nErrorCode) {
this.println("ROM load error (" + nErrorCode + ")");
return;
}
if (sImageData[0] == "[") {
try {
/*
* The most likely source of any exception will be right here, where we're parsing
* the JSON-encoded ROM data.
*/
this.abImage = eval("(" + sImageData + ")");
} catch (e) {
//
// System exceptions throw an object with a message property, whereas exceptions I throw myself do not (they're just strings)
//
this.println("ROM data error: " + (e.message || e));
return;
}
}
else {
/*
* Parse the ROM image data manually; we assume it's in "simplified" hex form (a series of hex byte-values separated by whitespace)
*/
var sData = sImageData.replace(/\n/gm, " ").replace(/ +$/, "");
var asData = sData.split(" ");
this.abImage = new Array(asData.length);
for (var i=0; i < asData.length; i++) {
this.abImage[i] = parseInt(asData[i], 16);
}
}
this.copyImage();
};
/**
* @this {C1PROM}
*/
C1PROM.prototype.copyImage = function()
{
/*
* The Computer object may give us the address of the ROM image before we've finished downloading the image,
* so both setBuffer() and convertImage() call copyImage(), which in turn will copy the image ONLY when both
* pieces are in place. At that point, the component becomes "ready", in much the same way that other components
* (eg, CPU and Screen) become "ready" when all their prerequisites are satisfied.
*/
if (!this.isReady()) {
if (!this.sImage) {
this.setReady();
}
else
if (this.abImage && this.abMem) {
var cbImage = this.abImage.length;
if (cbImage != this.cbROM) {
this.setError("ROM image size (" + str.toHexWord(cbImage) + ") does not match component-specified size (" + str.toHexWord(this.cbROM) + ")");
return;
}
if (DEBUG) this.log("copyImage(): copying ROM to " + str.toHexAddr(this.offROM) + " (0x" + str.toHexWord(cbImage) + " bytes)");
for (var i=0; i < cbImage; i++) {
this.abMem[this.offROM + i] = this.abImage[i];
}
this.setReady();
}
}
};
/**
* C1PROM.init()
*
* This function operates on every element (e) of class "rom", and initializes
* all the necessary HTML to construct the ROM module(s) as spec'ed.
*
* Note that each element (e) of class "rom" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the ROM constructor
* expects.
*/
C1PROM.init = function()
{
var aeROM = Component.getElementsByClass(window.document, C1PJSCLASS, "rom");
for (var iROM=0; iROM < aeROM.length; iROM++) {
var eROM = aeROM[iROM];
var parmsROM = Component.getComponentParms(eROM);
var rom = new C1PROM(parmsROM);
Component.bindComponentControls(rom, eROM, C1PJSCLASS);
}
};
/*
* Initialize all the ROM modules on the page.
*/
web.onInit(C1PROM.init);

View file

@ -0,0 +1,360 @@
/**
* @fileoverview This file implements the C1Pjs SerialPort component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Jul-01
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* C1PSerialPort(parmsSerial)
*
* The SerialPort component has no component-specific parameters.
*
* @constructor
* @extends Component
*/
function C1PSerialPort(parmsSerial)
{
Component.call(this, "C1PSerialPort", parmsSerial);
this.fPower = false;
this.fDemo = parmsSerial['demo'];
this.STATUS_NONE = 0x00;
this.STATUS_DATA = 0x01; // indicates data available
this.reset();
}
Component.subclass(Component, C1PSerialPort);
/**
* @this {C1PSerialPort}
*/
C1PSerialPort.prototype.reset = function()
{
/*
* Because we reset the machine at the start of a 6502 HEX command file auto-load,
* we must avoid tossing the serial port's input buffer in that particular case (2).
*/
if (this.autoLoad != 2) {
this.bInput = 0;
this.cbInput = 0;
this.sInput = "10 PRINT \"HELLO OSI #" + this.getMachineNum() + "\"\n";
this.iInputNext = 0;
// this.sOutput = new Array(0);
// this.iOutputNext = 0;
/*
* Values for autoLoad:
*
* 0: no auto-load active
* 1: BASIC command file auto-load in progress
* 2: 6502 HEX command file auto-load in progress
*/
this.autoLoad = 0;
}
};
/**
* @this {C1PSerialPort}
*/
C1PSerialPort.prototype.start = function()
{
if (this.kbd && this.fDemo) {
this.kbd.injectKeys(" C\n\n", 3000); // override the default injection delay (currently 300ms)
setTimeout(function(serial) { return function() {serial.startLoad();}; }(this), 12000);
}
this.fDemo = false;
};
/**
* @this {C1PSerialPort}
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "listSerial")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PSerialPort.prototype.setBinding = function(c, t, s, e)
{
switch(s) {
case "listSerial":
this.bindings[s] = e;
return true;
case "loadSerial":
this.bindings[s] = e;
e.onclick = function(serial) {
return function() {
if (serial.bindings["listSerial"]) {
var sFile = serial.bindings["listSerial"].value;
// serial.println("loading " + sFile + "...");
web.loadResource(sFile, true, null, serial, serial.loadFile);
}
};
}(this);
return true;
default:
break;
}
return false;
};
/**
* @this {C1PSerialPort}
* @param {Array} abMemory
* @param {number} start
* @param {number} end
* @param {C1PCPU} cpu
*/
C1PSerialPort.prototype.setBuffer = function(abMemory, start, end, cpu)
{
this.abMem = abMemory;
this.offPort = start;
this.cbPort = end - start + 1;
this.offPortLimit = this.offPort + this.cbPort;
if ((this.cpu = cpu)) {
cpu.addReadNotify(start, end, this, this.getByte);
cpu.addWriteNotify(start, end, this, this.setByte);
}
this.setReady();
};
/**
* @this {C1PSerialPort}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*
* We make a note of the Computer component, so that we can invoke its reset() method whenever we need to
* simulate a warm start, and we query the Keyboard component so that we can use its injectKeys() function.
*/
C1PSerialPort.prototype.setPower = function(fOn, cmp)
{
if (fOn && !this.fPower) {
this.fPower = true;
this.cmp = cmp;
this.kbd = cmp.getComponentByType("keyboard");
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
}
};
/**
* @this {C1PSerialPort}
*/
C1PSerialPort.prototype.startLoad = function()
{
this.autoLoad = 1;
this.kbd.injectKeys("LOAD\n");
};
/**
* @this {C1PSerialPort}
* @param {String} sFileName
* @param {string} sFileData (null if loadResource() encountered an error)
* @param {number} nResponse from server
*/
C1PSerialPort.prototype.loadFile = function(sFileName, sFileData, nResponse)
{
if (!sFileData) {
this.println(sFileName + " load error (" + nResponse + ")");
return;
}
this.sInput = sFileData;
this.iInputNext = 0;
this.autoLoad = 0;
if (this.cmp && this.kbd && this.cpu.isRunning()) {
this.println("auto-loading " + sFileName);
/*
* QUESTION: Is this setFocus() call strictly necessary? We're being called in the
* context of loadResource(), not some user action. If there was an original user action,
* then the handler for THAT action should take care to switch focus back, not us.
*/
this.cpu.setFocus();
/*
* We interpret the presence of a "." at the beginning of the file as a "65V Monitor"
* address-mode command, and consequently treat the file as 6502 HEX command file.
*
* Anything else is treated as commands for the BASIC interpreter, which we re-initialize
* with "NEW" and "LOAD" commands. To prevent that behavior, halt the CPU, perform the load,
* and then start it running again. BASIC will start reading the data as soon as you type
* LOAD.
*/
if (this.sInput.charAt(0) != ".") {
this.autoLoad = 1;
this.kbd.injectKeys("NEW\nLOAD\n");
}
else {
/*
* Set autoLoad to 2 before the reset, so that when our reset() method is called,
* we'll take care to preserve all the data we just loaded.
*/
this.autoLoad = 2;
/*
* Although the Keyboard allows us to inject any key, even the BREAK key, like so:
*
* this.kbd.injectKeys(String.fromCharCode(this.kbd.CHARCODE_BREAK))
*
* it's easier to initiate a reset() ourselves and then start the machine-language load process
*/
this.cmp.reset(true);
this.kbd.injectKeys("ML");
}
}
else {
this.println(sFileName + " ready to load");
}
};
/**
* @this {C1PSerialPort}
* @param {number} addr
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
*/
C1PSerialPort.prototype.getByte = function(addr, addrFrom)
{
/*
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
*/
if (addrFrom !== undefined) {
/*
* WARNING: All I need to do for now is load the COM interface's "data byte"
* with the next byte from the virtual cassette data stream -JP
*/
if (!(addr & 0x01)) {
/*
* An EVEN address implies they're looking, so if we have a fresh buffer,
* then prime the pump.
*/
if (this.sInput && !this.iInputNext)
this.advanceInput();
} else {
/*
* An ODD address implies they just grabbed a data byte, so prep the next data byte.
*/
this.advanceInput();
}
}
};
/**
* @this {C1PSerialPort}
* @param {number} addr
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
*/
C1PSerialPort.prototype.setByte = function(addr, addrFrom)
{
/*
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
* the Debugger performed this write (need a special Debugger I/O command if/when you really want to do that).
*/
if (addrFrom !== undefined) {
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_SERIAL, true);
/*
* WARNING: I don't yet care what state the CPU puts the port into. When it's time to support serial output,
* obviously that will become an issue.
*/
}
};
/**
* @this {C1PSerialPort}
*/
C1PSerialPort.prototype.advanceInput = function()
{
if (this.sInput !== undefined) {
this.bInput = 0;
this.cbInput = 0;
if (this.iInputNext < this.sInput.length) {
var b = this.sInput.charCodeAt(this.iInputNext++);
if (b == 0x0a) b = 0x0d;
this.bInput = b;
this.cbInput = 1;
// if (DEBUG) this.log("advanceInput(" + str.toHexByte(b) + ")");
}
else {
if (DEBUG) this.log("advanceInput(): out of data");
if (this.autoLoad == 1 && this.kbd) {
this.kbd.injectKeys(" \nRUN\n");
}
this.autoLoad = 0;
}
this.updateMemory();
}
// else if (DEBUG) this.log("advanceInput(): no input");
};
/**
* @this {C1PSerialPort}
*/
C1PSerialPort.prototype.updateMemory = function()
{
var offset;
/*
* Update all the status (even) bytes
*/
for (offset = this.offPort+0; offset < this.offPortLimit; offset+=2)
this.abMem[offset] = (this.cbInput? this.STATUS_DATA : this.STATUS_NONE);
/*
* Update all the data (odd) bytes
*/
for (offset = this.offPort+1; offset < this.offPortLimit; offset+=2)
this.abMem[offset] = (this.cbInput? this.bInput : 0);
};
/**
* C1PSerialPort.init()
*
* This function operates on every element (e) of class "serial", and initializes
* all the necessary HTML to construct the SerialPort module(s) as spec'ed.
*
* Note that each element (e) of class "serial" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the SerialPort constructor
* expects.
*/
C1PSerialPort.init = function()
{
var aeSerial = Component.getElementsByClass(window.document, C1PJSCLASS, "serial");
for (var iSerial=0; iSerial < aeSerial.length; iSerial++) {
var eSerial = aeSerial[iSerial];
var parmsSerial = Component.getComponentParms(eSerial);
var serial = new C1PSerialPort(parmsSerial);
Component.bindComponentControls(serial, eSerial, C1PJSCLASS);
}
};
/*
* Initialize every SerialPort module on the page.
*/
web.onInit(C1PSerialPort.init);

View file

@ -0,0 +1,600 @@
/**
* @fileoverview This file implements the C1Pjs Video component
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Jun-15
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* C1Pjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with C1Pjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some C1Pjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* C1Pjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* C1PVideo(parmsVideo, eCanvas, context, imgChars)
*
* The Video component can be configured with the following (parmsVideo) properties:
*
* model: model number (one of: 540 or 600; 600 is the default)
* screenWidth: width of the screen window, in pixels
* screenHeight: height of the screen window, in pixels
* charCols: number of character columns
* charRows: number of character rows
* charWidth: width of charSet characters, in pixels (default is 0)
* charHeight: height of charSet characters, in pixels (default is 0)
* charSet: path to image (eg, PNG) file that defines the character set
* screenColor: background color of the screen window (default is black)
*
* The Video object assumes that the video buffer is organized such that offset 0 is mapped
* to the left-most column and top-most row (col=0,row=0), offset 1 is (1,0), offset 2
* is (2,0), and so on.
*
* The Video object initially contains no underlying video buffer; memory for the buffer
* must be given to it by the Computer object. We allocate a separate buffer, called
* the screen buffer, into which we periodically copy the contents of the video buffer
* via updateScreen(); any differences between the two buffers are then rendered in the
* associated window, via updateWindow().
*
* When updateScreen() finds a byte in the screen buffer must be redisplayed, it converts
* the offset of that byte into a (col,row) character position for the updateWindow() function,
* which then converts (col,row) into (x,y) pixel offsets within the underlying canvas.
*
* Regarding the C1P (aka Model 600): The C1P has a 1K video buffer located at 0xD000-0xD3FF.
* The ROM draws the initial "D/C/W/M ?" prompt at the "bottom" of the video buffer at location
* 0xD365. That row really begins at 0xD360, but the C1P "indents" everything by 5 columns due
* to the lack of a "guard band feature." Similarly, BASIC defaults to a width of 24 columns
* avoid display problems near the right edge. BASIC will let you choose a width SMALLER than
* 24 but not larger. So, while the video buffer supports a theoretical maximum of 32 rows x 32
* columns, the practical maximum is 24 rows x 24 columns; the last 4 rows of the video buffer
* are never used, and while content scrolls through the top 4 lines of the buffer, it is never
* assumed that you can see the top 4 lines.
*
* This is partially confirmed by the "C1P Character Graphics Reference Manual", p3, which says
* that the "the visible character field consists of 25 lines of 25 columns" and that the "first
* visible character in the upper left of the screen is accessed via address 53379," or 0xD083,
* confirming that the first 4 lines are not assumed to be visible. However, the comment
* regarding "25 lines of 25 columns" seems to be off by one in both dimensions. And why would
* they say that the first visible address is 0xD083 instead of 0xD085? An indentation of 5 bytes,
* rather than 3, would be more consistent with how the C1P ROMs use video memory.
*
* Model 540 Video Board vs. Model 600 "Superboard II"
* ---------------------------------------------------
* This emulation was originally written for the Model 600 "Superboard II" (eg, Challenger 1P).
* Support for the Model 540 video board (as used in the Challenger II-4P and II-8P) was added
* later.
*
* NOTE: When Model 540 video emulation is enabled, Model 542 keyboard emulation must also be
* enabled, because the former always came with the latter keyboard interface; this is why when
* we call this.setModel(540), we must also notify the Keyboard via kbd.setModel(542).
*
* Key features/differences of the Model 540 video board include:
*
* 2K (8 pages) of video memory located at 0xD000-0xD7FF
* Two display modes: 32 rows x 64 cols (default on power up), and 32 rows x 32 cols
* 64 bytes per screen row, regardless which display mode is selected
* The following options can be selected via WRITE to port address 0xDE00:
* Bit 0: clear to enable 32/64 mode (default on power up), set to enable 32/32
* Bit 1: 1=tone on (542 keyboard)
* Bit 2: 1=color on (Rev. B only?)
* bit 3: 1=enable 38-40Khz AC Home control output (Rev. B only?)
* Video timing counter status via READ from port address 0xDE00:
* Bit 7: 0 for 1/120 second, then 1 for 1/120 second, based on video clock (60Hz)
*
* @constructor
* @extends Component
*/
function C1PVideo(parmsVideo, eCanvas, context, imgChars)
{
Component.call(this, "C1PVideo", parmsVideo);
this.nDefaultModel = parmsVideo['model'];
this.nDefaultCols = parmsVideo['charCols'];
this.nDefaultRows = parmsVideo['charRows'];
this.cxScreen = parmsVideo['screenWidth'];
this.cyScreen = parmsVideo['screenHeight'];
/*
* These (source) character dimensions are tentative, and may not even be provided,
* but they will become definitive once imgChars has finished loading and setReady() is called.
*/
this.cxChar = parmsVideo['charWidth'];
this.cyChar = parmsVideo['charHeight'];
/*
* This is a preliminary call to setDimensions(), to initialize default screen buffer and
* window dimensions. A more extensive call to setDimensions() will take place when setModel()
* is called later, from reset() and possibly via the tripGuard() handler.
*
* This preliminary call merely establishes a default screen buffer size, so that when
* setBuffer() is called, it's able to verify the assigned address space is at least as big
* as the screen buffer.
*/
this.setDimensions();
this.eCanvas = eCanvas;
this.context = context;
this.imgChars = imgChars;
/*
* QUESTION: Does this video port exist only on the Model 540?
*/
this.addrVideoPort = 0xDE00; // WARNING: Hard-coded port address -JP
}
Component.subclass(Component, C1PVideo);
/**
* @this {C1PVideo}
* @param {boolean} [fPowerOn] is true for the initial reset, so that we have
* the option of rendering "random" graphic characters, just like the real machine would do.
*/
C1PVideo.prototype.reset = function(fPowerOn)
{
this.setModel(this.nDefaultModel);
if (this.abMem) {
/*
* Let's treat every reset like a power-cycle, just for fun.
* If you don't think that's fun, then simply remove the next line.
*
fPowerOn = true;
*/
for (var offset = this.offVideo; offset < this.offVideoLimit; offset++) {
var b = (fPowerOn? Math.floor(Math.random() * 256) : 0x20);
Component.assert(b >= 0 && b <= 255);
this.abMem[offset] = b;
}
}
};
/**
* @this {C1PVideo}
* @param {string|null} c is the class of the HTML control (eg, "input", "output")
* @param {string|null} t is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} s is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "refresh")
* @param {Object} e is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
C1PVideo.prototype.setBinding = function(c, t, s, e)
{
switch(s) {
case "refresh":
this.bindings[s] = e;
e.onclick = function(video) {
return function() {
if (DEBUG) video.println("refreshScreen()");
video.initScreen();
video.updateScreen();
};
}(this);
return true;
default:
break;
}
return false;
};
/**
* @this {C1PVideo}
* @param {Array} abMemory
* @param {number} start
* @param {number} end
* @param {C1PCPU} cpu
*/
C1PVideo.prototype.setBuffer = function(abMemory, start, end, cpu)
{
this.abMem = abMemory;
this.offVideo = start;
this.cbVideo = end - start + 1;
this.offVideoLimit = this.offVideo + this.cbVideo;
Component.assert(this.cbScreen <= this.cbVideo, "screen size (0x" + this.cbScreen.toString(16) + ") exceeds video buffer size (0x" + this.cbVideo.toString(16) + ")");
if (cpu) {
this.cpu = cpu;
if (this.addrVideoPort !== undefined) {
cpu.addReadNotify(this.addrVideoPort, this.addrVideoPort, this, this.getByte);
cpu.addWriteNotify(this.addrVideoPort, this.addrVideoPort, this, this.setByte);
}
}
this.reset(true);
};
/**
* @this {C1PVideo}
* @param {number|undefined} [nCols] (default is nDefaultCols)
* @param {number|undefined} [nRows] (default is nDefaultRows)
* @param {number|undefined} [iRowTop] (eg, 4; default is 0)
* @param {number|undefined} [nRowsVisible] (eg, 24; default is nRows)
*/
C1PVideo.prototype.setDimensions = function(nCols, nRows, iRowTop, nRowsVisible)
{
this.nCols = (nCols !== undefined? nCols : this.nDefaultCols);
this.nRows = (nRows !== undefined? nRows : this.nDefaultRows);
this.cbScreen = this.nCols * this.nRows;
this.offVideoLimit = this.offVideo + this.cbScreen;
/*
* Set the first visible row and total visible rows next
*/
this.iRowTop = (iRowTop !== undefined? iRowTop : 0);
this.nRowsVisible = (nRowsVisible !== undefined? nRowsVisible : nRows);
this.setDrawingDimensions();
};
/**
* @this {C1PVideo}
*
* cxScreen and cyScreen give us the overall dimensions of the destination surface. Dividing that by the number of
* columns and rows yields a target cell size (cxCharDst,cyCharDst), which may or may not map 1-1 to the source cell size
* (cxChar,cyChar).
*/
C1PVideo.prototype.setDrawingDimensions = function()
{
this.cxCharDst = Math.floor(this.cxScreen / this.nCols);
this.cyCharDst = Math.floor(this.cyScreen / this.nRowsVisible);
};
/**
* @this {C1PVideo}
*/
C1PVideo.prototype.setFocus = function()
{
this.eCanvas.focus();
};
/**
* @this {C1PVideo}
* @param {number} nModel
*/
C1PVideo.prototype.setModel = function(nModel)
{
this.nModel = nModel;
/*
* Default to model 600 behavior (1K video buffer);
* the only other supported model is 540 (2K video buffer).
*/
if (this.nModel == 600) {
this.setDimensions(this.nDefaultCols, this.nDefaultRows, 4, 24);
if (this.cbScreen == 1024 && this.cpu) {
/*
* NOTE: We deliberately set the guard address to the LAST byte of the 2K
* buffer range, not the FIRST byte, which has the same effect but with the
* added benefit of deferring any screen update until after the "Model 540"
* screen initialization code has completely blanked the entire 2K buffer,
* avoiding a brief flicker of unsightly characters.
*/
this.addrGuard = this.offVideoLimit + this.cbScreen - 1;
this.cpu.addWriteNotify(this.addrGuard, this.addrGuard, this, this.tripGuard);
}
}
else {
this.println("updated video model: " + this.nModel);
this.setDimensions(64, 32);
}
this.initScreen();
this.updateScreen();
};
/**
* @this {C1PVideo}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*/
C1PVideo.prototype.setPower = function(fOn, cmp)
{
/*
* NOTE: No one should be calling power(true) before first checking isReady(), but we check
* it ourselves, too. This also means that updateScreen() need check only fPower and not isReady(),
* since we guarantee that the former implies the latter.
*/
if (fOn && !this.fPower && this.isReady()) {
this.fPower = true;
if (DEBUGGER) this.dbg = cmp.getComponentByType("debugger");
/*
* If we have an associated keyboard, then ensure that the keyboard will be notified whenever
* the canvas gets focus and receives input.
*
* Also, when simulating a Model 540 video board, we need to access to the Keyboard component due
* to some shared I/O responsibilities; ie, bit 1 of the video control port at 0xDE00 enables whatever
* tone has been selected via the keyboard frequency port at 0xDF01 (frequency == 49152/n, where n
* is the value stored at 0xDF01).
*/
this.kbd = cmp.getComponentByType("keyboard");
if (this.kbd) {
this.kbd.setBinding("input", "canvas", "keyDown", this.eCanvas);
this.kbd.setBinding("input", "canvas", "keyPress", this.eCanvas);
this.kbd.setBinding("input", "canvas", "keyUp", this.eCanvas);
}
}
else
if (!fOn && this.fPower) {
this.fPower = false;
/*
* This is where we would add some method of blanking the display, without the disturbing the video
* buffer contents, and blocking all further updates to the display.
*/
}
};
/**
* @this {C1PVideo}
*
* cxChar and cyChar are the source cell size. Originally, those values came strictly from the parmsVideo
* 'charWidth' and 'charHeight' properties. Now, if those aren't defined (which is normally the case now),
* then we infer the source cell size from the dimensions of imgChars, which is expected to be a 16x16 array of
* character bitmaps. We could be even more flexible, by allowing imgChars to be any rectangular dimension
* (eg, 1x256) as long as we can assume it contains exactly 256 characters, but there's no need to get carried away....
*/
C1PVideo.prototype.setReady = function()
{
if (!this.cxChar) this.cxChar = Math.floor(this.imgChars.width / 16);
if (!this.cyChar) this.cyChar = Math.floor(this.imgChars.height / 16);
Component.prototype.setReady.call(this);
};
/**
* @this {C1PVideo}
* @param {number} addr (ie, addrVideoPort)
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
*
* NOTE: Ordinarily, I wouldn't allow Debugger writes (addrFrom === undefined) to interfere with the simulated
* hardware state, but for now, I find it useful to be able to prod the simulation code directly from the Debugger.
*/
C1PVideo.prototype.getByte = function(addr, addrFrom)
{
var b = this.cpu.getByte(addr);
if (addrFrom !== undefined) {
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_VIDEO);
}
/*
* The only documented READ bit in addrVideoPort is bit 7, which is supposed to alternate between
* 0 and 1 every 1/120 of a second. There's no way we're going to add special code to the emulator to update
* this stupid byte every 8,333 cycles (assuming 1Mhz operation), so clearly we're going to fake it.
*
* Faking it means that any polling code will unavoidably get a stale value the FIRST time it reads bit 7.
* However, we can still do a pretty good job of faking any EXTENSIVE polling: get the number of cycles
* executed so far, divide that by 8333, floor the quotient, and then set/clear bit 7 according to whether the
* result is odd/even.
*/
var nCyclesHigh = Math.floor(this.cpu.getCycles() / 8333);
this.cpu.setByte(addr, (b & 0x7F) | ((nCyclesHigh & 0x1)? 0x80 : 0));
};
/**
* @this {C1PVideo}
* @param {number} addr (ie, addrVideoPort)
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to write the specified addr)
*/
C1PVideo.prototype.setByte = function(addr, addrFrom)
{
if (addrFrom !== undefined) {
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_VIDEO);
}
};
/**
* @this {C1PVideo}
* @param {number} addr (ie, addrGuard)
* @param {number|undefined} addrFrom (not defined whenever the Debugger tries to read the specified addr)
*/
C1PVideo.prototype.tripGuard = function(addr, addrFrom)
{
/*
* Don't trigger any further hardware emulation (beyond what we've already stored in memory) if
* the Debugger performed this read (need a special Debugger I/O command if/when you really want to do that).
*/
if (addrFrom !== undefined) {
if (DEBUGGER && this.dbg) this.dbg.messagePort(this, addr, addrFrom, this.dbg.MESSAGE_VIDEO, true);
/*
* The CPU has just written to the guard address we established just beyond the video buffer's 1K boundary,
* implying that the system thinks we have a 2K buffer instead. So we bump our model to 540, bump the
* associated keyboard model to 542, and remove this guard handler.
*/
this.setModel(540);
if (this.kbd) this.kbd.setModel(542);
this.cpu.removeWriteNotify(this.addrGuard, this.addrGuard, this, this.tripGuard);
}
};
/**
* @this {C1PVideo}
*/
C1PVideo.prototype.initScreen = function()
{
this.abScreen = new Array(this.cbScreen);
for (var offset=0; offset <= this.cbScreen; offset++) {
this.abScreen[offset] = -1; // initialize every cell of the screen to an invalid value
}
};
/**
* updateScreen() updates the screen buffer from the video buffer and updates the window with any changes.
*
* @this {C1PVideo}
* @return {boolean}
*
* For every byte in the video buffer, this renders it if it differs from the byte stored in the screen buffer,
* and then updates the screen buffer to match. Since initScreen() sets every byte in the screen buffer
* to an illegal byte value (ie, a value which is outside the byte range 0x00-0xff), that assures the first call
* to updateScreen() will redraw every byte in the video buffer.
*/
C1PVideo.prototype.updateScreen = function()
{
var offset = 0;
if (this.fPower) {
while (offset < this.cbScreen) {
var b = this.abMem[this.offVideo + offset];
if (this.abScreen[offset] != b) {
if (!this.writeByte(offset, b)) {
break;
}
this.abScreen[offset] = b;
}
offset++;
}
}
return (offset == this.cbScreen);
};
/**
* @this {C1PVideo}
* @param {number} offset
* @param {number} b
* @return {boolean}
*/
C1PVideo.prototype.writeByte = function(offset, b)
{
var col = offset % this.nCols;
var row = Math.floor(offset / this.nCols);
// if (b == 0) this.cpu.halt(); // I must have been testing something here...
return this.updateWindow(col, row, b);
};
/**
* updateWindow() updates a particular position (row,col) in the associated window with the given byte (b)
*
* @this {C1PVideo}
* @param {number} col
* @param {number} row
* @param {number} b
* @return {boolean} true if successful, false if not
*
* I originally used (screenWidth,screenHeight) == (512,448) and (cols,rows) == (32,32) and (cxChar,cyChar) == (16,16),
* and I simply copied the source cells 1-to-1 to the destination (16,16), knowing that we would never try to display more
* than 28 rows (the last 4 rows of the 32 possible rows were never used to display any content). However, I should still
* have ignored any attempt to draw past row 28 (aka screenHeight 448). I now perform row clipping and biasing, according
* to the first visible row (iRowTop) and total visible rows (nRowsVisible).
*
* Moreover, I no longer copy the source cell images to the destination 1-to-1. I calculate (cxCharDst,cyCharDst) separately
* (see setDrawingDimensions). And I no longer assume that (cxChar,cyChar) are (16,16); once the source image file has finished
* loading, I calculate (cxChar,cyChar) based on the size of image file (see setReady). I made this change when I created
* chargen1x.png. In fact, at first I thought I might be able to eliminate chargen2x.png and just let drawImage() scale up
* the individual character images from (8,8) to (16,16) or whatever (cxCharDst,cyCharDst) size was needed, but the results were
* fuzzy, so it's still best to use chargen2x.png when using larger window sizes.
*/
C1PVideo.prototype.updateWindow = function(col, row, b)
{
if (row >= this.iRowTop) {
row -= this.iRowTop;
if (row < this.nRowsVisible) {
var xChar = (b * this.cxChar);
var ySrc = Math.floor(xChar / this.imgChars.width) * this.cyChar;
var xSrc = xChar % this.imgChars.width;
var xDst = col * this.cxCharDst;
var yDst = row * this.cyCharDst;
// if (DEBUG) this.log("updateWindow(" + col + "," + row + "," + b +"): drawing from " + xSrc + "," + ySrc + " to " + xDst + "," + yDst);
this.context.drawImage(this.imgChars, xSrc, ySrc, this.cxChar, this.cyChar, xDst, yDst, this.cxCharDst, this.cyCharDst);
}
}
return true;
};
/**
* C1PVideo.init()
*
* This function operates on every element (e) of class "video", and initializes
* all the necessary HTML to construct every Video module as spec'ed.
*
* Note that each element (e) of class "video" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the Video constructor
* expects.
*/
C1PVideo.init = function()
{
var aeVideo = Component.getElementsByClass(window.document, C1PJSCLASS, "video");
for (var iVideo=0; iVideo < aeVideo.length; iVideo++) {
var eVideo = aeVideo[iVideo];
var parmsVideo = Component.getComponentParms(eVideo);
/*
* As noted in keyboard.js, the keyboard on an iOS device pops up with the SHIFT key depressed,
* which is not the initial keyboard state that the C1P expects. I originally tried to fix that by
* adding an 'autocapitalize="off"' attribute alongside the 'contenteditable="true"' attribute
* on the <canvas> element, but apparently Safari honors that only inside certain elements (eg, <input>).
*
* I've since settled on a better work-around in keyboard.js, so I've stopped worrying about how to make
* "autocapitalize" work here.
*/
var eCanvas = window.document.createElement("canvas");
if (eCanvas === undefined) {
eVideo.innerHTML = "<br/>Missing &lt;canvas&gt; support; try a new web browser.";
return;
}
eCanvas.setAttribute("class", C1PJSCLASS + "-canvas");
eCanvas.setAttribute("width", parmsVideo['screenWidth']);
eCanvas.setAttribute("height", parmsVideo['screenHeight']);
eCanvas.setAttribute("contenteditable", "true");
eCanvas.setAttribute("autocapitalize", "off");
eCanvas.setAttribute("autocorrect", "off");
eCanvas.style.backgroundColor = parmsVideo['screenColor'];
/*
* HACK: A canvas style of "auto" provides for excellent responsive canvas scaling in EVERY browser
* except IE9/IE10, so I recalculate the appropriate CSS height every time the parent DIV is resized;
* IE11 works without this hack, so we take advantage of the fact that IE11 doesn't report itself as "MSIE".
*/
eCanvas.style.height = "auto";
if (web.getUserAgent().indexOf("MSIE") >= 0) {
eCanvas.style.height = (((eVideo.clientWidth * parmsVideo['screenHeight']) / parmsVideo['screenWidth']) | 0) + "px";
eVideo.onresize = function(eParent, eChild, cx, cy) {
return function() {
eChild.style.height = (((eParent.clientWidth * cy) / cx) | 0) + "px";
};
}(eVideo, eCanvas, parmsVideo['screenWidth'], parmsVideo['screenHeight']);
}
eVideo.appendChild(eCanvas);
/*
* Now we can create the Video object, record it, and wire it up to the associated document elements.
*
* Regarding "new Image()", see https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement.Image:
*
* This constructor exists for historical reasons only and returns an HTMLImageElement instance just as
* document.createElement('img') would.
*/
var imgCharSet = new Image();
var contextVideo = eCanvas.getContext("2d");
var video = new C1PVideo(parmsVideo, eCanvas, contextVideo, imgCharSet);
imgCharSet.onload = function(video, sCharSet) {
return function() {
if (DEBUG) video.log("onload(): finished loading " + sCharSet);
video.setReady();
};
}(video, parmsVideo['charSet']); // jshint ignore:line
imgCharSet.src = parmsVideo['charSet'];
/*
* Bind any video-specific controls (eg, the Refresh button). There are no essential controls, however;
* even the "Refresh" button is just a diagnostic tool, to verify that the screen contents are up-to-date.
*/
Component.bindComponentControls(video, eVideo, C1PJSCLASS);
}
};
/*
* Initialize every Video module on the page.
*/
web.onInit(C1PVideo.init);

View file

@ -0,0 +1,14 @@
C1Pjs Templates
===
Template folders contain a variety of XML and HTML templates and supporting files, including:
- DTD files (Document Type Definitions)
- XSD files (XML schemas -- eventually)
- XSL files (XML stylesheets)
- CSS files (stylesheets that the XSL files rely upon)
- HTML files (HTML fragments used to generate part or all of a web page)
[*components.xsl*](components.xsl) transforms all the elements of a machine XML file into an HTML fragment
that includes a series of **DIV** tags with corresponding *id* and *data-value* attributes that allow our
JavaScript components to bind themselves to visual elements (eg, virtual screen, virtual keyboard, control
panel) on a web page.

View file

@ -0,0 +1,106 @@
@CHARSET "UTF-8";
/* @author Jeff Parsons (@jeffpar)
@website http://www.pcjs.org/
@created 2013-05-05
@modified 2014-03-12
@license http://www.gnu.org/licenses/gpl.html
*/
*:not(input,textarea) {
-webkit-user-select: none;
}
.c1pjs-embed {
}
.c1pjs-embed:after {
clear:both;
}
.c1pjs-name {
clear: both;
font-weight: bold;
padding-bottom: 4px;
}
.c1pjs-canvas {
width: 100%;
height: auto;
}
.c1pjs-container {
color: #000000;
position: relative;
}
.c1pjs-label {
font-size: small;
line-height: 19px;
vertical-align: middle;
float: left;
font-family: "Lucida Console", monospace;
}
.c1pjs-control textarea {
font-family: Monaco, monospace;
font-size: x-small;
}
.c1pjs-flag {
font-family: "Lucida Console", monospace;
font-size: small;
text-align: center;
line-height: 19px;
vertical-align: middle;
}
.c1pjs-register {
font-family: "Lucida Console", monospace;
font-size: small;
text-align: center;
line-height: 19px;
vertical-align: middle;
border: 1px solid black;
}
.c1pjs-switches {
float: left;
}
.c1pjs-bitBucket {
float: left;
width: 19px;
height: 38px;
}
.c1pjs-bitCell {
float: left;
width: 19px;
height: 19px;
margin-right: -1px;
margin-bottom: -1px;
border: 1px solid black;
text-align: center;
line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */
}
.c1pjs-bitCellLeft {
border-left: 1px solid black;
}
.c1pjs-bitLabel {
font-size: xx-small;
text-align: center;
}
.c1pjs-description, .c1pjs-status {
font-size: small;
line-height: 2em;
}
.c1pjs-key {
border: 1px solid black;
font-size: x-small;
text-align: center;
position: absolute;
height: 34px;
line-height: 34px; /* the equivalent of "vertical-align: middle" for single-line elements */
}
.c1pjs-reference {
float: left;
font-size: x-small;
}
.c1pjs-reference a {
text-decoration: none;
}
.c1pjs-copyright {
float: right;
font-size: x-small;
}
.c1pjs-copyright a {
text-decoration: none;
}

View file

@ -0,0 +1,564 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2013-01-29" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other required entities may be defined below (see http://www.pcjs.org/my_modules/shared/templates/entities.dtd). -->
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="rootDir" select="''"/>
<xsl:param name="generator" select="'client'"/>
<xsl:variable name="MACHINECLASS">c1p</xsl:variable>
<xsl:variable name="APPCLASS">c1pjs</xsl:variable>
<xsl:variable name="APPVERSION">1.0.0</xsl:variable>
<xsl:variable name="SITEHOST">www.pcjs.org</xsl:variable>
<xsl:template name="componentStyles">
<link rel="stylesheet" type="text/css" href="/versions/{$APPCLASS}/{$APPVERSION}/components.css"/>
</xsl:template>
<xsl:template name="componentScripts">
<xsl:param name="component"/>
<script type="text/javascript" src="/versions/{$APPCLASS}/{$APPVERSION}/{$component}.js"></script>
</xsl:template>
<xsl:template name="componentIncludes">
<xsl:param name="component"/>
<xsl:call-template name="componentScripts"><xsl:with-param name="component" select="$component"/></xsl:call-template>
</xsl:template>
<xsl:template match="machine[@ref]">
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/machine"><xsl:with-param name="machine" select="@id"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="machine[not(@ref)]">
<xsl:param name="machine"><xsl:value-of select="@id"/></xsl:param>
<div id="{$machine}" class="machine {@class}js">
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="component" select="'machine'"/>
<xsl:with-param name="class"><xsl:value-of select="@class"/>js</xsl:with-param>
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
<xsl:with-param name="url"><xsl:value-of select="@url"/></xsl:with-param>
</xsl:call-template>
</div>
</xsl:template>
<xsl:template match="component[@ref]">
<xsl:param name="machine"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/component"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="component[not(@ref)]">
<xsl:param name="machine"/>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class" select="@class"/>
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="component">
<xsl:param name="machine" select="''"/>
<xsl:param name="component" select="name(.)"/>
<xsl:param name="class" select="''"/>
<xsl:param name="parms" select="''"/>
<xsl:param name="url" select="''"/>
<xsl:variable name="id">
<xsl:choose>
<xsl:when test="$component = 'machine'"><xsl:value-of select="$machine"/>.machine</xsl:when>
<xsl:when test="$machine != ''"><xsl:value-of select="$machine"/><xsl:if test="@id">.<xsl:value-of select="@id"/></xsl:if></xsl:when>
<xsl:when test="@id"><xsl:value-of select="@id"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="name">
<xsl:choose>
<xsl:when test="name"><xsl:value-of select="name"/></xsl:when>
<xsl:when test="@name"><xsl:value-of select="@name"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="border">
<xsl:choose>
<xsl:when test="@border = '1'">border:1px solid black;border-radius:10px;</xsl:when>
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="left">
<xsl:choose>
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="top">
<xsl:choose>
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="width">
<xsl:choose>
<xsl:when test="@width">
<xsl:choose>
<xsl:when test="$left != '' or $top != ''">width:<xsl:value-of select="@width"/>;</xsl:when>
<xsl:otherwise>width:auto;max-width:<xsl:value-of select="@width"/>;</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="height">
<xsl:choose>
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="padding">
<xsl:choose>
<xsl:when test="@padding">padding:<xsl:value-of select="@padding"/>;</xsl:when>
<xsl:otherwise>
<xsl:if test="@padtop">padding-top:<xsl:value-of select="@padtop"/>;</xsl:if>
<xsl:if test="@padright">padding-right:<xsl:value-of select="@padright"/>;</xsl:if>
<xsl:if test="@padbottom">padding-bottom:<xsl:value-of select="@padbottom"/>;</xsl:if>
<xsl:if test="@padleft">padding-left:<xsl:value-of select="@padleft"/>;</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="pos">
<xsl:choose>
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
<xsl:when test="@pos">position:<xsl:value-of select="@pos"/>;</xsl:when>
<xsl:when test="$left != '' or $top != ''">position:absolute;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="style">
<xsl:if test="$component = 'machine'">overflow:auto;width:100%;</xsl:if>
<xsl:if test="@style"><xsl:value-of select="@style"/></xsl:if>
</xsl:variable>
<xsl:variable name="componentClass">
<xsl:value-of select="$APPCLASS"/><xsl:text>-</xsl:text><xsl:value-of select="$component"/><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-component</xsl:text>
</xsl:variable>
<div id="{$id}" class="{$componentClass}" style="{$width}{$height}{$pos}{$left}{$top}{$padding}">
<xsl:if test="$component = 'machine'">
<xsl:apply-templates select="name" mode="machine"/>
</xsl:if>
<xsl:if test="$component != 'machine'">
<xsl:apply-templates select="name" mode="component"/>
</xsl:if>
<div class="{$APPCLASS}-container" style="{$border}{$style}">
<xsl:if test="$class != '' and $component != 'machine'">
<div class="{$APPCLASS}-{$class}-object" data-value="id:'{$id}',name:'{$name}'{$parms}"></div>
</xsl:if>
<xsl:if test="control">
<div class="{$APPCLASS}-controls">
<xsl:apply-templates select="control" mode="component"/>
</div>
</xsl:if>
<xsl:apply-templates><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</div>
<xsl:if test="$component = 'machine'">
<xsl:choose>
<xsl:when test="$url != ''"><div class="{$APPCLASS}-reference">[<a href="{$url}">XML</a>]</div></xsl:when>
<xsl:otherwise/>
</xsl:choose>
<div class="{$APPCLASS}-copyright">
<a href="http://{$SITEHOST}/{$APPCLASS}" target="_blank">C1Pjs</a> v<xsl:value-of select="$APPVERSION"/> © 2012-2014 by <a href="http://twitter.com/jeffpar" target="_blank">@jeffpar</a>
</div>
<div style="clear:both"></div>
</xsl:if>
</div>
</xsl:template>
<xsl:template match="name" mode="machine">
<xsl:variable name="pos">
<xsl:choose>
<xsl:when test="@pos = 'center'">text-align:center;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<h2 style="{$pos}"><xsl:apply-templates/></h2>
</xsl:template>
<xsl:template match="name" mode="component">
<div class="{$APPCLASS}-name"><xsl:apply-templates/></div>
</xsl:template>
<xsl:template match="control" mode="component">
<xsl:variable name="type">
type:'<xsl:value-of select="@type"/>'
</xsl:variable>
<xsl:variable name="binding">
binding:'<xsl:value-of select="@binding"/>'
</xsl:variable>
<xsl:variable name="border">
<xsl:choose>
<xsl:when test="@border = '1'">border:1px solid black;</xsl:when>
<xsl:when test="@border">border:<xsl:value-of select="@border"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="width">
<xsl:choose>
<xsl:when test="@width">width:<xsl:value-of select="@width"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="height">
<xsl:choose>
<xsl:when test="@height">height:<xsl:value-of select="@height"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="left">
<xsl:choose>
<xsl:when test="@left">left:<xsl:value-of select="@left"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="top">
<xsl:choose>
<xsl:when test="@top">top:<xsl:value-of select="@top"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="pos">
<xsl:choose>
<xsl:when test="$left != '' or $top != ''">position:absolute;</xsl:when>
<xsl:when test="@pos = 'left'">float:left;</xsl:when>
<xsl:when test="@pos = 'right'">float:right;</xsl:when>
<xsl:when test="@pos = 'center'">margin:0 auto;</xsl:when>
<xsl:when test="@pos"><xsl:value-of select="@pos"/>;</xsl:when>
<xsl:otherwise><xsl:if test="$left = ''">float:left;</xsl:if></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="style">
<xsl:choose>
<xsl:when test="@style"><xsl:value-of select="@style"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<div class="{$APPCLASS}-control" style="{$pos}{$left}{$top}">
<xsl:variable name="fontsize">
<xsl:choose>
<xsl:when test="@size = 'large'">font-size:<xsl:value-of select="@size"/>;</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="subclass">
<xsl:if test="@label"><xsl:text> </xsl:text><xsl:value-of select="$APPCLASS"/><xsl:text>-label</xsl:text></xsl:if>
</xsl:variable>
<xsl:variable name="labelwidth">
<xsl:if test="@labelwidth">width:<xsl:value-of select="@labelwidth"/>;</xsl:if>
</xsl:variable>
<xsl:variable name="labelstyle">
<xsl:if test="@labelstyle"><xsl:value-of select="@labelstyle"/></xsl:if>
</xsl:variable>
<xsl:if test="@label">
<xsl:if test="not(@labelpos) or @labelpos = 'left'">
<div class="{$APPCLASS}-label" style="{$labelwidth}{$labelstyle}"><xsl:value-of select="@label"/></div>
</xsl:if>
</xsl:if>
<xsl:choose>
<xsl:when test="@type = 'button'">
<button class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></button>
</xsl:when>
<xsl:when test="@type = 'list'">
<select class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$fontsize}{$style}" data-value="{$type},{$binding}">
<xsl:apply-templates select="item" mode="component"/>
</select>
</xsl:when>
<xsl:when test="@type = 'text'">
<input class="{$APPCLASS}-{@class}" type="text" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" value="" autocapitalize="off" autocorrect="off"/>
</xsl:when>
<xsl:when test="@type = 'submit'">
<input class="{$APPCLASS}-{@class}" type="submit" style="{$border}{$fontsize}{$style}" data-value="{$type},{$binding}" value="{.}"/>
</xsl:when>
<xsl:when test="@type = 'textarea'">
<textarea class="{$APPCLASS}-{@class}" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}" readonly="readonly"></textarea>
</xsl:when>
<xsl:when test="@type = 'heading'">
<div><xsl:value-of select="."/></div>
</xsl:when>
<xsl:when test="@type = 'separator'">
<hr/>
</xsl:when>
<xsl:when test="not(@type)">
<div style="clear:both"></div><br/>
</xsl:when>
<xsl:otherwise>
<div class="{$APPCLASS}-{@class}{$subclass}" style="{$border}{$width}{$height}{$style}" data-value="{$type},{$binding}"><xsl:apply-templates/></div>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@label">
<xsl:if test="@labelpos = 'right'">
<div class="{$APPCLASS}-label" style="{$labelwidth}{$labelstyle}"><xsl:value-of select="@label"/></div>
</xsl:if>
<div style="clear:both"></div>
</xsl:if>
</div>
</xsl:template>
<xsl:template match="item" mode="component">
<option value="{@ref}"><xsl:value-of select="."/></option>
</xsl:template>
<xsl:template match="name">
</xsl:template>
<xsl:template match="control">
</xsl:template>
<xsl:template match="cpu[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/cpu"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="cpu[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="autoStart">
<xsl:choose>
<xsl:when test="@autostart"><xsl:value-of select="@autostart"/></xsl:when>
<xsl:otherwise>null</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class" select="'cpu'"/>
<xsl:with-param name="parms">,autoStart:<xsl:value-of select="$autoStart"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="keyboard[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/keyboard"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="keyboard[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="model">
<xsl:choose>
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
<xsl:otherwise>600</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">keyboard</xsl:with-param>
<xsl:with-param name="parms">,model:<xsl:value-of select="$model"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="serial[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/serial"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="serial[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="demo">
<xsl:choose>
<xsl:when test="@demo"><xsl:value-of select="@demo"/></xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">serial</xsl:with-param>
<xsl:with-param name="parms">,demo:<xsl:value-of select="$demo"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="disk[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/disk"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="disk[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">disk</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="rom[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/rom"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="rom[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="size">
<xsl:choose>
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="image">
<xsl:choose>
<xsl:when test="@image"><xsl:value-of select="@image"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">rom</xsl:with-param>
<xsl:with-param name="parms">,size:<xsl:value-of select="$size"/>,image:'<xsl:value-of select="$image"/>'</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="ram[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/ram"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="ram[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="size">
<xsl:choose>
<xsl:when test="@size"><xsl:value-of select="@size"/></xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">ram</xsl:with-param>
<xsl:with-param name="parms">,size:<xsl:value-of select="$size"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="video[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/video"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="video[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="model">
<xsl:choose>
<xsl:when test="@model"><xsl:value-of select="@model"/></xsl:when>
<xsl:otherwise>600</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="screenWidth">
<xsl:choose>
<xsl:when test="@screenwidth"><xsl:value-of select="@screenwidth"/></xsl:when>
<xsl:otherwise>256</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="screenHeight">
<xsl:choose>
<xsl:when test="@screenheight"><xsl:value-of select="@screenheight"/></xsl:when>
<xsl:otherwise>224</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="charCols">
<xsl:choose>
<xsl:when test="@cols"><xsl:value-of select="@cols"/></xsl:when>
<xsl:otherwise>32</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="charRows">
<xsl:choose>
<xsl:when test="@rows"><xsl:value-of select="@rows"/></xsl:when>
<xsl:otherwise>32</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="charWidth">
<xsl:choose>
<xsl:when test="@charwidth"><xsl:value-of select="@charwidth"/></xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="charHeight">
<xsl:choose>
<xsl:when test="@charheight"><xsl:value-of select="@charheight"/></xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="charSet">
<xsl:choose>
<xsl:when test="@charset"><xsl:value-of select="@charset"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="screenColor">
<xsl:choose>
<xsl:when test="@screencolor"><xsl:value-of select="@screencolor"/></xsl:when>
<xsl:otherwise>black</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">video</xsl:with-param>
<xsl:with-param name="parms">,model:<xsl:value-of select="$model"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,charWidth:<xsl:value-of select="$charWidth"/>,charHeight:<xsl:value-of select="$charHeight"/>,charSet:'<xsl:value-of select="$charSet"/>',screenColor:'<xsl:value-of select="$screenColor"/>'</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="debugger[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/debugger"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="debugger[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">debugger</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="panel[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/panel"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="panel[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">panel</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="computer[@ref]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="componentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($componentFile)/computer"><xsl:with-param name="machine" select="$machine"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="computer[not(@ref)]">
<xsl:param name="machine" select="''"/>
<xsl:variable name="modules">
[<xsl:for-each select="module">
{<xsl:call-template name="module"/>}
<xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>]
</xsl:variable>
<xsl:call-template name="component">
<xsl:with-param name="machine" select="$machine"/>
<xsl:with-param name="class">computer</xsl:with-param>
<xsl:with-param name="parms">,modules:<xsl:value-of select="$modules"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="module">
type:'<xsl:value-of select="@type"/>',refID:'<xsl:value-of select="@refid"/>'<xsl:if test="@start">,start:<xsl:value-of select="@start"/>,end:<xsl:value-of select="@end"/></xsl:if>
</xsl:template>
</xsl:stylesheet>