Initial commit (a clone of the jsmachines project as of v1.15.3)
This commit is contained in:
commit
a5e3e6a59d
714 changed files with 130602 additions and 0 deletions
39
my_modules/c1pjs-client/.jshintrc
Normal file
39
my_modules/c1pjs-client/.jshintrc
Normal 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
|
||||
}
|
||||
}
|
||||
353
my_modules/c1pjs-client/lib/computer.js
Normal file
353
my_modules/c1pjs-client/lib/computer.js
Normal 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);
|
||||
3891
my_modules/c1pjs-client/lib/cpu.js
Normal file
3891
my_modules/c1pjs-client/lib/cpu.js
Normal file
File diff suppressed because it is too large
Load diff
2226
my_modules/c1pjs-client/lib/debugger.js
Normal file
2226
my_modules/c1pjs-client/lib/debugger.js
Normal file
File diff suppressed because it is too large
Load diff
53
my_modules/c1pjs-client/lib/defines.js
Normal file
53
my_modules/c1pjs-client/lib/defines.js
Normal 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
|
||||
1266
my_modules/c1pjs-client/lib/disk.js
Normal file
1266
my_modules/c1pjs-client/lib/disk.js
Normal file
File diff suppressed because it is too large
Load diff
1105
my_modules/c1pjs-client/lib/keyboard.js
Normal file
1105
my_modules/c1pjs-client/lib/keyboard.js
Normal file
File diff suppressed because it is too large
Load diff
47
my_modules/c1pjs-client/lib/nodebugger.js
Normal file
47
my_modules/c1pjs-client/lib/nodebugger.js
Normal 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;
|
||||
97
my_modules/c1pjs-client/lib/outline.xml
Normal file
97
my_modules/c1pjs-client/lib/outline.xml
Normal 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>
|
||||
130
my_modules/c1pjs-client/lib/panel.js
Normal file
130
my_modules/c1pjs-client/lib/panel.js
Normal 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);
|
||||
96
my_modules/c1pjs-client/lib/ram.js
Normal file
96
my_modules/c1pjs-client/lib/ram.js
Normal 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);
|
||||
244
my_modules/c1pjs-client/lib/rom.js
Normal file
244
my_modules/c1pjs-client/lib/rom.js
Normal 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);
|
||||
360
my_modules/c1pjs-client/lib/serial.js
Normal file
360
my_modules/c1pjs-client/lib/serial.js
Normal 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);
|
||||
600
my_modules/c1pjs-client/lib/video.js
Normal file
600
my_modules/c1pjs-client/lib/video.js
Normal 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 <canvas> 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);
|
||||
14
my_modules/c1pjs-client/templates/README.md
Normal file
14
my_modules/c1pjs-client/templates/README.md
Normal 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.
|
||||
106
my_modules/c1pjs-client/templates/components.css
Normal file
106
my_modules/c1pjs-client/templates/components.css
Normal 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;
|
||||
}
|
||||
564
my_modules/c1pjs-client/templates/components.xsl
Normal file
564
my_modules/c1pjs-client/templates/components.xsl
Normal 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>
|
||||
Loading…
Reference in a new issue