From bfb5bd520e65631ce67d25959b5fa5b5bb61fa37 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 14 Sep 2016 15:46:17 -0700 Subject: [PATCH] Remove uncompiled file from versions folder --- versions/pc8080/1.23.2/pc8080-dbg.js | 20559 ------------------------- 1 file changed, 20559 deletions(-) delete mode 100644 versions/pc8080/1.23.2/pc8080-dbg.js diff --git a/versions/pc8080/1.23.2/pc8080-dbg.js b/versions/pc8080/1.23.2/pc8080-dbg.js deleted file mode 100644 index 49d1ce444..000000000 --- a/versions/pc8080/1.23.2/pc8080-dbg.js +++ /dev/null @@ -1,20559 +0,0 @@ -"use strict"; - -// ./modules/shared/lib/defines.js - -/** - * @fileoverview Compile-time definitions used by C1Pjs and PCjs. - * @author Jeff Parsons - * @version 1.0 - * Created 2014-May-08 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -/** - * @define {string} - */ -var APPVERSION = "1.x.x"; // this @define is overridden by the Closure Compiler with the version in package.json - -var XMLVERSION = null; // this is set in non-COMPILED builds by embedMachine() if a version number was found in the machine XML - -var COPYRIGHT = "Copyright © 2012-2016 Jeff Parsons "; - -var LICENSE = "License: GPL version 3 or later "; - -var CSSCLASS = "pcjs"; - -/** - * @define {string} - */ -var SITEHOST = "localhost:8088";// this @define is overridden by the Closure Compiler with "www.pcjs.org" - -/** - * @define {boolean} - */ -var COMPILED = false; // this @define is overridden by the Closure Compiler (to true) - -/** - * @define {boolean} - */ -var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code - -/** - * @define {boolean} - */ -var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code - -/** - * @define {boolean} - */ -var PRIVATE = false; // this @define is overridden by the Closure Compiler (to false) to enable PRIVATE code - -/* - * NODE should be true if we're running under NodeJS (eg, command-line), false if not (eg, web browser) - */ -var NODE = false; - - -// ./modules/shared/lib/dumpapi.js - -/** - * @fileoverview Disk APIs, as defined by diskdump.js and consumed by disk.js - * @author Jeff Parsons - * @version 1.0 - * Created 2014-May-08 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -/* - * Our "DiskDump API", such as it was, used to look like: - * - * http://jsmachines.net/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img - * - * To make it (a bit) more "REST-like", the above request now looks like: - * - * http://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img - * - * Similarly, our "FileDump API" used to look like: - * - * http://jsmachines.net/bin/convrom.php?rom=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json - * - * and that request now looks like: - * - * http://www.pcjs.org/api/v1/dump?file=/devices/pc/rom/5150/1981-04-24/PCBIOS-REV1.rom&format=json - * - * I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the - * the rest of the URL would be (a) confusing, and (b) more work to parse. - */ -var DumpAPI = { - ENDPOINT: "/api/v1/dump", - QUERY: { - DIR: "dir", // value is path of a directory (DiskDump only) - DISK: "disk", // value is path of a disk image (DiskDump only) - FILE: "file", // value is path of a ROM image file (FileDump only) - IMG: "img", // alias for DISK - PATH: "path", // value is path of a one or more files (DiskDump only) - FORMAT: "format", // value is one of FORMAT values below - COMMENTS: "comments", // value is either "true" or "false" - DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise - MBHD: "mbhd", // value is hard drive size in Mb (formerly "mbsize") (DiskDump only) (DEPRECATED) - SIZE: "size" // value is target disk size in Kb (supersedes "mbhd") (DiskDump only) - }, - FORMAT: { - JSON: "json", // default - JSON_GZ: "gz", // gzip is currently used ONLY for compressed JSON - DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only) - HEX: "hex", // deprecated - BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled - IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only) - ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only) - } -}; - -/* - * Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must - * also provide a list of commands which, when combined with the endpoint, define a unique request. - */ -DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH]; -DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE]; - - -// ./modules/shared/lib/reportapi.js - -/** - * @fileoverview Report API, as defined by httpapi.js - * @author Jeff Parsons - * @version 1.0 - * Created 2014-May-13 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -var ReportAPI = { - ENDPOINT: "/api/v1/report", - QUERY: { - APP: "app", - VER: "ver", - URL: "url", - USER: "user", - TYPE: "type", - DATA: "data" - }, - TYPE: { - BUG: "bug" - }, - RES: { - OK: "Thank you" - } -}; - - -// ./modules/shared/lib/userapi.js - -/** - * @fileoverview User API, as defined by httpapi.js - * @author Jeff Parsons - * @version 1.0 - * Created 2014-May-13 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -/* - * Examples of User API requests: - * - * web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser; - */ - -var UserAPI = { - ENDPOINT: "/api/v1/user", - QUERY: { - REQ: "req", // specifies a request - USER: "user", // specifies a user ID - STATE: "state", // specifies a state ID - DATA: "data" // specifies state data - }, - REQ: { - CREATE: "create", // creates a user ID - VERIFY: "verify", // requests verification of a user ID - STORE: "store", // stores a machine state on the server - LOAD: "load" // loads a machine state from the server - }, - RES: { - CODE: "code", - DATA: "data" - }, - CODE: { - OK: "ok", - FAIL: "error" - }, - FAIL: { - DUPLICATE: "user already exists", - VERIFY: "unable to verify user", - BADSTATE: "invalid state parameter", - NOSTATE: "no machine state", - BADLOAD: "unable to load machine state", - BADSTORE: "unable to save machine state" - } -}; - - -// ./modules/shared/lib/strlib.js - -/** - * @fileoverview String-related helper functions - * @author Jeff Parsons (@jeffpar) - * @version 1.0 - * Created 2014-03-09 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -var str = {}; - -/** - * isValidInt(s, base) - * - * The built-in parseInt() function has the annoying feature of returning a partial value (ie, - * up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf. - * So use this function to validate the entire string. - * - * @param {string} s is the string representation of some number - * @param {number} [base] is the radix of the number represented above (only 2, 10 and 16 are supported) - * @return {boolean} true if valid, false if invalid (or the specified base isn't supported) - */ -str.isValidInt = function(s, base) -{ - if (!base || base == 10) return s.match(/^[0-9]+$/) !== null; - if (base == 16) return s.match(/^[0-9a-f]+$/i) !== null; - if (base == 2) return s.match(/^[01]+$/i) !== null; - return false; -}; - -/** - * parseInt(s, base) - * - * This is a wrapper around the built-in parseInt() function, which recognizes certain prefixes (eg, - * '$' or "0x" for hex) and suffixes (eg, 'h' for hex, or '.' for decimal), and then calls isValidInt() - * to ensure we don't convert strings that contain partial values (see isValidInt() for details). - * - * We don't support multiple prefix/suffix combinations, nor do we support the "0b" prefix (or "b" suffix) - * for binary, because 1) it's not commonly used, and 2) it conflicts with valid hex sequences. - * - * @param {string} s is the string representation of some number - * @param {number} [base] is the default radix to use (default is 16); can be overridden by prefixes/suffixes - * @return {number|undefined} corresponding value, or undefined if invalid - */ -str.parseInt = function(s, base) -{ - var value; - if (s) { - if (!base) base = 16; - if (s.charAt(0) == '$') { - base = 16; - s = s.substr(1); - } else if (s.substr(0, 2) == "0x") { - base = 16; - s = s.substr(2); - } else { - var chSuffix = s.charAt(s.length-1).toLowerCase(); - if (chSuffix == 'h') { - base = 16; - chSuffix = null; - } - else if (chSuffix == '.') { - base = 10; - chSuffix = null; - } - if (chSuffix == null) s = s.substr(0, s.length-1); - } - var v; - if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) { - value = v|0; - } - } - return value; -}; - -/** - * toBin(n, cch) - * - * Converts an integer to binary, with the specified number of digits (up to the default of 32). - * - * @param {number|null|undefined} n is a 32-bit value - * @param {number} [cch] is the desired number of binary digits (32 is both the default and the maximum) - * @return {string} the binary representation of n - */ -str.toBin = function(n, cch) -{ - var s = ""; - if (cch === undefined) { - cch = 32; - } else { - if (cch > 32) cch = 32; - } - /* - * An initial "falsey" check for null takes care of both null and undefined; - * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough. - * - * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN, - * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those - * values displayed differently. - */ - if (n == null || isNaN(n)) { - while (cch-- > 0) s = '?' + s; - } else { - while (cch-- > 0) { - s = ((n & 0x1)? '1' : '0') + s; - n >>= 1; - } - } - return s; -}; - -/** - * toBinBytes(n, cb) - * - * Converts an integer to binary, with the specified number of bytes (up to the default of 4). - * - * @param {number|null|undefined} n is a 32-bit value - * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum) - * @return {string} the binary representation of n - */ -str.toBinBytes = function(n, cb) -{ - var s = ""; - if (!cb || cb > 4) cb = 4; - for (var i = 0; i < cb; i++) { - if (s) s = ',' + s; - s = str.toBin(n & 0xff, 8) + 'b' + s; - n >>= 8; - } - return s; -}; - -/** - * toHex(n, cch) - * - * Converts an integer to hex, with the specified number of digits (up to the default of 8). - * - * You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it - * doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16) - * will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will - * throw an exception, whereas this function will return '?' characters. - * - * NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution, - * taking care of negative values, zero-padding, and upper-casing, but not null/undefined/NaN values: - * - * s = (n < 0? n + 0x100000000 : n).toString(16); - * s = "00000000".substr(0, 8 - s.length) + s; - * s = s.substr(0, cch).toUpperCase(); - * - * @param {number|null|undefined} n is a 32-bit value - * @param {number} [cch] is the desired number of hex digits (8 is both the default and the maximum) - * @return {string} the hex representation of n - */ -str.toHex = function(n, cch) -{ - var s = ""; - if (cch === undefined) { - cch = 8; - } else { - if (cch > 8) cch = 8; - } - /* - * An initial "falsey" check for null takes care of both null and undefined; - * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough. - * - * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN, - * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those - * values displayed differently. - */ - if (n == null || isNaN(n)) { - while (cch-- > 0) s = '?' + s; - } else { - while (cch-- > 0) { - var d = n & 0xf; - d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10); - s = String.fromCharCode(d) + s; - n >>= 4; - } - } - return s; -}; - -/** - * toHexByte(b) - * - * Alias for "0x" + str.toHex(b, 2) - * - * @param {number|null|undefined} b is a byte value - * @return {string} the hex representation of b - */ -str.toHexByte = function(b) -{ - return "0x" + str.toHex(b, 2); -}; - -/** - * toHexWord(w) - * - * Alias for "0x" + str.toHex(w, 4) - * - * @param {number|null|undefined} w is a word (16-bit) value - * @return {string} the hex representation of w - */ -str.toHexWord = function(w) -{ - return "0x" + str.toHex(w, 4); -}; - -/** - * toHexLong(l) - * - * Alias for "0x" + toHex(l) - * - * @param {number|null|undefined} l is a dword (32-bit) value - * @return {string} the hex representation of w - */ -str.toHexLong = function(l) -{ - return "0x" + str.toHex(l); -}; - -/** - * getBaseName(sFileName, fStripExt) - * - * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead. - * - * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips the extension only - * if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux"). - * - * @param {string} sFileName - * @param {boolean} [fStripExt] - * @return {string} - */ -str.getBaseName = function(sFileName, fStripExt) -{ - var sBaseName = sFileName; - - var i = sFileName.lastIndexOf('/'); - if (i >= 0) sBaseName = sFileName.substr(i + 1); - - /* - * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters. - */ - i = sBaseName.indexOf('&'); - if (i > 0) sBaseName = sBaseName.substr(0, i); - - if (fStripExt) { - i = sBaseName.lastIndexOf("."); - if (i > 0) { - sBaseName = sBaseName.substring(0, i); - } - } - return sBaseName; -}; - -/** - * getExtension(sFileName) - * - * This is a poor-man's version of Node's path.extname(), which Node-only components should use instead. - * - * Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it. - * - * @param {string} sFileName - * @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string - */ -str.getExtension = function(sFileName) -{ - var sExtension = ""; - var i = sFileName.lastIndexOf("."); - if (i >= 0) { - sExtension = sFileName.substr(i + 1).toLowerCase(); - } - return sExtension; -}; - -/** - * endsWith(s, sSuffix) - * - * @param {string} s - * @param {string} sSuffix - * @return {boolean} true if s ends with sSuffix, false if not - */ -str.endsWith = function(s, sSuffix) -{ - return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1; -}; - -str.aHTMLEscapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' -}; - -/** - * escapeHTML(sHTML) - * - * @param {string} sHTML - * @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars() - */ -str.escapeHTML = function(sHTML) -{ - return sHTML.replace(/[&<>"']/g, function(m) { - return str.aHTMLEscapeMap[m]; - }); -}; - -/** - * replaceAll(sFind, sReplace, s) - * - * @param {string} sFind - * @param {string} sReplace - * @param {string} s - * @return {string} - */ -str.replaceAll = function(sFind, sReplace, s) -{ - var a = {}; - a[sFind] = sReplace; - return str.replaceArray(a, s); -}; - -/** - * replaceArray(a, s) - * - * @param {Object} a - * @param {string} s - * @return {string} - */ -str.replaceArray = function(a, s) -{ - var sMatch = ""; - for (var k in a) { - /* - * As noted in: - * - * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/ - * - * inside character classes, only backslash, caret, hyphen and the closing bracket need to be - * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first, - * and the hyphen is last, you can avoid escaping those as well. - */ - k = k.replace(/([\\[\]*{}().+?])/g, "\\$1"); - sMatch += (sMatch? '|' : '') + k; - } - return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) { - return a[m]; - }); -}; - -/** - * pad(s, cch, fPadLeft) - * - * NOTE: the maximum amount of padding currently supported is 40 spaces. - * - * @param {string} s is a string - * @param {number} cch is desired length - * @param {boolean} [fPadLeft] (default is padding on the right) - * @returns {string} the original string (s) with spaces padding it to the specified length - */ -str.pad = function(s, cch, fPadLeft) -{ - var sPadding = " "; - return fPadLeft? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch); -}; - -/** - * trim(s) - * - * @param {string} s - * @returns {string} - */ -str.trim = function(s) -{ - if (String.prototype.trim) { - return s.trim(); - } - return s.replace(/^\s+|\s+$/g, ""); -}; - - -// ./modules/shared/lib/usrlib.js - -/** - * @fileoverview Assorted helper functions - * @author Jeff Parsons (@jeffpar) - * @version 1.0 - * Created 2014-03-09 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -var usr = {}; - -/** - * binarySearch(a, v, fnCompare) - * - * @param {Array} a is an array - * @param {number|string|Array|Object} v - * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] - * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point - */ -usr.binarySearch = function(a, v, fnCompare) { - var left = 0; - var right = a.length; - var found = 0; - if (fnCompare === undefined) { - fnCompare = function(a, b) { - return a > b? 1 : a < b? -1 : 0; - }; - } - while (left < right) { - var middle = (left + right) >> 1; - var compareResult; - compareResult = fnCompare(v, a[middle]); - if (compareResult > 0) { - left = middle + 1; - } else { - right = middle; - found = !compareResult; - } - } - return found? left : ~left; -}; - -/** - * binaryInsert(a, v, fnCompare) - * - * If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the - * element is inserted into the array at the appropriate index. - * - * @param {Array} a is an array - * @param {number|string|Array|Object} v is the value to insert - * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare] - */ -usr.binaryInsert = function(a, v, fnCompare) { - var index = usr.binarySearch(a, v, fnCompare); - if (index < 0) { - a.splice(-(index + 1), 0, v); - } -}; - -/** - * getTime() - * - * @return {number} the current time, in milliseconds - */ -usr.getTime = Date.now || function() { return +new Date(); }; - -/** - * getTimestamp() - * - * @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss") - */ -usr.getTimestamp = function() { - var date = new Date(); - var padNum = function(n) { - return (n < 10? "0" : "") + n; - }; - return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate()) + " " + padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds()); -}; - -/** - * getMonthDays(nMonth, nYear) - * - * Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100), - * so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails - * the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap - * year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year. - * - * TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD, - * so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and - * we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what - * a real RTC actually does. - * - * @param {number} nMonth (1-12) - * @param {number} nYear (normally a 4-digit year, but it may also be mod 100) - * @return {number} the maximum (1-based) day allowed for the specified month and year - */ -usr.getMonthDays = function(nMonth, nYear) -{ - var nDays = usr.aMonthDays[nMonth - 1]; - if (nDays == 28) { - if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) { - nDays++; - } - } - return nDays; -}; - -usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; -usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; -usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - -/** - * formatDate(sFormat, date) - * - * @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s") - * @param {Date} [date] (default is the current time) - * @return {string} - * - * Supported identifiers in sFormat include: - * - * a: lowercase ante meridiem and post meridiem (am or pm) - * d: day of the month, 2 digits with leading zeros (01,...,31) - * g: hour in 12-hour format, without leading zeros (1,...,12) - * i: minutes, with leading zeros (00,...,59) - * j: day of the month, without leading zeros (1,...,31) - * l: day of the week ("Sunday",...,"Saturday") - * m: month, with leading zeros (01,...,12) - * s: seconds, with leading zeros (00,...,59) - * F: month ("January",...,"December") - * H: hour in 24-hour format, with leading zeros (00,...,23) - * Y: year (eg, 2014) - * - * For more inspiration, see: http://php.net/manual/en/function.date.php - */ -usr.formatDate = function(sFormat, date) { - var sDate = ""; - if (!date) date = new Date(); - var iHour = date.getHours(); - var iDay = date.getDate(); - var iMonth = date.getMonth() + 1; - for (var i = 0; i < sFormat.length; i++) { - var ch; - switch((ch = sFormat.charAt(i))) { - case 'a': - sDate += (iHour < 12? "am" : "pm"); - break; - case 'd': - sDate += ('0' + iDay).slice(-2); - break; - case 'g': - sDate += (!iHour? 12 : (iHour > 12? iHour - 12 : iHour)); - break; - case 'i': - sDate += ('0' + date.getMinutes()).slice(-2); - break; - case 'j': - sDate += iDay; - break; - case 'l': - sDate += usr.asDays[date.getDay()]; - break; - case 'm': - sDate += ('0' + iMonth).slice(-2); - break; - case 's': - sDate += ('0' + date.getSeconds()).slice(-2); - break; - case 'F': - sDate += usr.asMonths[iMonth - 1]; - break; - case 'H': - sDate += ('0' + iHour).slice(-2); - break; - case 'Y': - sDate += date.getFullYear(); - break; - default: - sDate += ch; - break; - } - } - return sDate; -}; - -/** - * @typedef {{ - * mask: number, - * shift: number - * }} - */ -var BitField; - -/** - * @typedef {Object.} - */ -var BitFields; - -/** - * defineBitFields(bfs) - * - * Prepares a bit field definition for use with getBitField() and setBitField(); eg: - * - * var bfs = usr.defineBitFields({num:20, count:8, btmod:1, type:3}); - * - * The above defines a set of bit fields containg four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31). - * - * usr.setBitField(bfs.num, n, 1); - * - * The above set bit field "bfs.num" in numeric variable "n" to the value 1. - * - * @param {Object} bfs - * @return {BitFields} - */ -usr.defineBitFields = function(bfs) -{ - var bit = 0; - for (var f in bfs) { - var width = bfs[f]; - var mask = ((1 << width) - 1) << bit; - bfs[f] = {mask: mask, shift: bit}; - bit += width; - } - // - return bfs; -}; - -/** - * initBitFields(bfs, ...) - * - * @param {BitFields} bfs - * @param {...number} var_args - * @return {number} a value containing all supplied bit fields - */ -usr.initBitFields = function(bfs, var_args) -{ - var v = 0, i = 1; - for (var f in bfs) { - if (i >= arguments.length) break; - v = usr.setBitField(bfs[f], v, arguments[i++]); - } - return v; -}; - -/** - * getBitField(bf, v) - * - * @param {BitField} bf - * @param {number} v is a value containing bit fields - * @return {number} the value of the bit field in v defined by bf - */ -usr.getBitField = function(bf, v) -{ - return (v & bf.mask) >> bf.shift; -}; - -/** - * setBitField(bf, v, n) - * - * @param {BitField} bf - * @param {number} v is a value containing bit fields - * @param {number} n is a value to store in v in the bit field defined by bf - * @return {number} updated v - */ -usr.setBitField = function(bf, v, n) -{ - // - return (v & ~bf.mask) | ((n << bf.shift) & bf.mask); -}; - -/** - * indexOf(a, t, i) - * - * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it. - * - * @param {Array} a - * @param {*} t - * @param {number} [i] - * @returns {number} - */ -usr.indexOf = function(a, t, i) -{ - if (Array.prototype.indexOf) { - return a.indexOf(t, i); - } - i = i || 0; - if (i < 0) i += a.length; - if (i < 0) i = 0; - for (var n = a.length; i < n; i++) { - if (i in a && a[i] === t) return i; - } - return -1; -}; - - -// ./modules/shared/lib/weblib.js - -/** - * @fileoverview Browser-related helper functions - * @author Jeff Parsons (@jeffpar) - * @version 1.0 - * Created 2014-05-08 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -/* - * According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties - * and functions of JavaScript-in-the-Browser: - * - * Property Description - * --- - * Infinity A numeric value that represents positive/negative infinity - * NaN "Not-a-Number" value - * undefined Indicates that a variable has not been assigned a value - * - * Function Description - * --- - * decodeURI() Decodes a URI - * decodeURIComponent() Decodes a URI component - * encodeURI() Encodes a URI - * encodeURIComponent() Encodes a URI component - * escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead - * eval() Evaluates a string and executes it as if it was script code - * isFinite() Determines whether a value is a finite, legal number - * isNaN() Determines whether a value is an illegal number - * Number() Converts an object's value to a number - * parseFloat() Parses a string and returns a floating point number - * parseInt() Parses a string and returns an integer - * String() Converts an object's value to a string - * unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead - * - * And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions - * of the *window* object. - * - * Property Description - * --- - * closed Returns a Boolean value indicating whether a window has been closed or not - * defaultStatus Sets or returns the default text in the statusbar of a window - * document Returns the Document object for the window (See Document object) - * frames Returns an array of all the frames (including iframes) in the current window - * history Returns the History object for the window (See History object) - * innerHeight Returns the inner height of a window's content area - * innerWidth Returns the inner width of a window's content area - * length Returns the number of frames (including iframes) in a window - * location Returns the Location object for the window (See Location object) - * name Sets or returns the name of a window - * navigator Returns the Navigator object for the window (See Navigator object) - * opener Returns a reference to the window that created the window - * outerHeight Returns the outer height of a window, including toolbars/scrollbars - * outerWidth Returns the outer width of a window, including toolbars/scrollbars - * pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window - * pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window - * parent Returns the parent window of the current window - * screen Returns the Screen object for the window (See Screen object) - * screenLeft Returns the x coordinate of the window relative to the screen - * screenTop Returns the y coordinate of the window relative to the screen - * screenX Returns the x coordinate of the window relative to the screen - * screenY Returns the y coordinate of the window relative to the screen - * self Returns the current window - * status Sets or returns the text in the statusbar of a window - * top Returns the topmost browser window - * - * Method Description - * --- - * alert() Displays an alert box with a message and an OK button - * atob() Decodes a base-64 encoded string - * blur() Removes focus from the current window - * btoa() Encodes a string in base-64 - * clearInterval() Clears a timer set with setInterval() - * clearTimeout() Clears a timer set with setTimeout() - * close() Closes the current window - * confirm() Displays a dialog box with a message and an OK and a Cancel button - * createPopup() Creates a pop-up window - * focus() Sets focus to the current window - * moveBy() Moves a window relative to its current position - * moveTo() Moves a window to the specified position - * open() Opens a new browser window - * print() Prints the content of the current window - * prompt() Displays a dialog box that prompts the visitor for input - * resizeBy() Resizes the window by the specified pixels - * resizeTo() Resizes the window to the specified width and height - * scroll() This method has been replaced by the scrollTo() method. - * scrollBy() Scrolls the content by the specified number of pixels - * scrollTo() Scrolls the content to the specified coordinates - * setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds) - * setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds - * stop() Stops the window from loading - */ - -/* global window: true, setTimeout: false, clearTimeout: false, SITEHOST: false */ - - -var web = {}; - -/* - * We must defer loading the Component module until the function(s) requiring it are - * called; otherwise, we create an initialization cycle in which Component requires weblib - * and weblib requires Component. - * - * In an ideal world, weblib would not be dependent on Component, but we really want to use - * its I/O functions. The simplest solution is to create wrapper functions. - */ - -/** - * log(s, type) - * - * For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line) - * for Component.log() to display anything. - * - * @param {string} [s] is the message text - * @param {string} [type] is the message type - */ -web.log = function(s, type) -{ - Component.log(s, type); -}; - -/** - * notice(s, fPrintOnly, id) - * - * If Component.notice() calls web.alertUser(), it will fall back to web.log() if all else fails. - * - * @param {string} s is the message text - * @param {boolean} [fPrintOnly] - * @param {string} [id] is the caller's ID, if any - */ -web.notice = function(s, fPrintOnly, id) -{ - Component.notice(s, fPrintOnly, id); -}; - -/** - * getResource(sURL, dataPost, fAsync, done) - * - * Request the specified resource (sURL), and once the request is complete, notify done(). - * - * Also, if dataPost is set to a string, that string can be used to control the response format; - * by default, the response format is plain text, but you can specify "bytes" to request arbitrary - * binary data, which should come back as a string of bytes. - * - * TODO: The "bytes" option works by calling overrideMimeType(), which was never a best practice. - * Instead, we should implement supported response types ("text" and "arraybuffer", at a minimum) - * by setting xmlHTTP.responseType to one of those values before calling xmlHTTP.send(). - * - * @param {string} sURL - * @param {string|Object|null} [dataPost] for a POST request (default is a GET request) - * @param {boolean} [fAsync] is true for an asynchronous request - * @param {function(string,string,number)} [done] - * @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet - */ -web.getResource = function(sURL, dataPost, fAsync, done) -{ - var nErrorCode = 0, sResource = null, response = null; - - if (typeof resources == 'object' && (sResource = resources[sURL])) { - if (done) done(sURL, sResource, nErrorCode); - return [sResource, nErrorCode]; - } - else if (fAsync && typeof resources == 'function') { - resources(sURL, function(sResource, nErrorCode) { - if (done) done(sURL, sResource, nErrorCode); - }); - return response; - } - - if (DEBUG) { - /* - * The larger resources that we put on archive.pcjs.org should also be available locally... - */ - sURL = sURL.replace("http://archive.pcjs.org", ""); - } - - - var xmlHTTP = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP")); - if (fAsync) { - xmlHTTP.onreadystatechange = function() { - if (xmlHTTP.readyState === 4) { - /* - * The following line was recommended for WebKit, as a work-around to prevent the handler firing multiple - * times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when - * debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests - * pending, any debugging activity means most of them simply get dropped on floor, so what may actually be - * happening are mis-notifications rather than redundant notifications. - * - * xmlHTTP.onreadystatechange = undefined; - */ - sResource = xmlHTTP.responseText; - /* - * The normal "success" case is an HTTP status code of 200, but when testing with files loaded - * from the local file system (ie, when using the "file:" protocol), we have to be a bit more "flexible". - */ - if (xmlHTTP.status == 200 || !xmlHTTP.status && sResource.length && web.getHostProtocol() == "file:") { - if (MAXDEBUG) web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sResource.length + " bytes"); - } - else { - nErrorCode = xmlHTTP.status || -1; - web.log("xmlHTTP.onreadystatechange(" + sURL + "): error code " + nErrorCode); - } - if (done) done(sURL, sResource, nErrorCode); - } - }; - } - - if (dataPost && typeof dataPost == "object") { - var sDataPost = ""; - for (var p in dataPost) { - if (!dataPost.hasOwnProperty(p)) continue; - if (sDataPost) sDataPost += "&"; - sDataPost += p + '=' + encodeURIComponent(dataPost[p]); - } - sDataPost = sDataPost.replace(/%20/g, '+'); - if (MAXDEBUG) web.log("web.getResource(POST " + sURL + "): " + sDataPost.length + " bytes"); - xmlHTTP.open("POST", sURL, !!fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it) - xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xmlHTTP.send(sDataPost); - } else { - if (MAXDEBUG) web.log("web.getResource(GET " + sURL + ")"); - xmlHTTP.open("GET", sURL, !!fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it) - if (dataPost == "bytes") { - xmlHTTP.overrideMimeType("text/plain; charset=x-user-defined"); - } - xmlHTTP.send(); - } - - if (!fAsync) { - sResource = xmlHTTP.responseText; - if (xmlHTTP.status == 200) { - if (MAXDEBUG) web.log("web.getResource(" + sURL + "): returned " + sResource.length + " bytes"); - } else { - nErrorCode = xmlHTTP.status || -1; - web.log("web.getResource(" + sURL + "): error code " + nErrorCode); - } - if (done) done(sURL, sResource, nErrorCode); - response = [sResource, nErrorCode]; - } - return response; -}; - -/** - * sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName) - * - * Send a report (eg, bug report) to the server. - * - * @param {string} sApp (eg, "PCjs") - * @param {string} sVer (eg, "1.02") - * @param {string} sURL (eg, "/devices/pc/machine/5150/mda/64kb/machine.xml") - * @param {string} sUser (ie, the user key, if any) - * @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.* - * @param {string} sReport (eg, unparsed state data) - * @param {string} [sHostName] (default is http://SITEHOST) - */ -web.sendReport = function(sApp, sVer, sURL, sUser, sType, sReport, sHostName) -{ - var dataPost = {}; - dataPost[ReportAPI.QUERY.APP] = sApp; - dataPost[ReportAPI.QUERY.VER] = sVer; - dataPost[ReportAPI.QUERY.URL] = sURL; - dataPost[ReportAPI.QUERY.USER] = sUser; - dataPost[ReportAPI.QUERY.TYPE] = sType; - dataPost[ReportAPI.QUERY.DATA] = sReport; - var sReportURL = (sHostName? sHostName : "http://" + SITEHOST) + ReportAPI.ENDPOINT; - web.getResource(sReportURL, dataPost, true); -}; - -/** - * getHost() - * - * @return {string} - */ -web.getHost = function() -{ - return ("http://" + (window? window.location.host : SITEHOST)); -}; - -/** - * getHostURL() - * - * @return {string|null} - */ -web.getHostURL = function() -{ - return (window? window.location.href : null); -}; - -/** - * getHostProtocol() - * - * @return {string} - */ -web.getHostProtocol = function() -{ - return (window? window.location.protocol : "file:"); -}; - -/** - * getUserAgent() - * - * @return {string} - */ -web.getUserAgent = function() -{ - return (window? window.navigator.userAgent : ""); -}; - -/** - * alertUser(sMessage) - * - * @param {string} sMessage - */ -web.alertUser = function(sMessage) -{ - if (window) window.alert(sMessage); else web.log(sMessage); -}; - -/** - * confirmUser(sPrompt) - * - * @param {string} sPrompt - * @returns {boolean} true if the user clicked OK, false if Cancel/Close - */ -web.confirmUser = function(sPrompt) -{ - var fResponse = false; - if (window) { - fResponse = window.confirm(sPrompt); - } - return fResponse; -}; - -/** - * promptUser() - * - * @param {string} sPrompt - * @param {string} [sDefault] - * @returns {string|null} - */ -web.promptUser = function(sPrompt, sDefault) -{ - var sResponse = null; - if (window) { - sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault); - } - return sResponse; -}; - -/** - * fLocalStorage - * - * true if localStorage support exists, is enabled, and works; "falsey" otherwise - * - * @type {boolean|null} - */ -web.fLocalStorage = null; - -/** - * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This - * isn't cutting it. - * - * @const {string} - */ -web.sLocalStorageTest = "PCjs.localStorage"; - -/** - * hasLocalStorage - * - * true if localStorage support exists, is enabled, and works; false otherwise - * - * @return {boolean} - */ -web.hasLocalStorage = function() { - if (web.fLocalStorage == null) { - var f = false; - if (window) { - try { - window.localStorage.setItem(web.sLocalStorageTest, web.sLocalStorageTest); - f = (window.localStorage.getItem(web.sLocalStorageTest) == web.sLocalStorageTest); - window.localStorage.removeItem(web.sLocalStorageTest); - } catch(e) { - web.logLocalStorageError(e); - f = false; - } - } - web.fLocalStorage = f; - } - return web.fLocalStorage; -}; - -/** - * logLocalStorageError(e) - * - * @param {Error} e is an exception - */ -web.logLocalStorageError = function(e) -{ - web.log(e.message, "localStorage error"); -}; - -/** - * getLocalStorageItem(sKey) - * - * Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available - * - * @param {string} sKey - * @return {string|null|undefined} sValue - */ -web.getLocalStorageItem = function(sKey) -{ - var sValue; - if (window) { - try { - sValue = window.localStorage.getItem(sKey); - } catch(e) { - web.logLocalStorageError(e); - } - } - return sValue; -}; - -/** - * setLocalStorageItem(sKey, sValue) - * - * @param {string} sKey - * @param {string} sValue - * @return {boolean} true if localStorage is available, false if not - */ -web.setLocalStorageItem = function(sKey, sValue) -{ - try { - window.localStorage.setItem(sKey, sValue); - return true; - } catch(e) { - web.logLocalStorageError(e); - } - return false; -}; - -/** - * removeLocalStorageItem(sKey) - * - * @param {string} sKey - */ -web.removeLocalStorageItem = function(sKey) -{ - try { - window.localStorage.removeItem(sKey); - } catch(e) { - web.logLocalStorageError(e); - } -}; - -/** - * getLocalStorageKeys() - * - * @return {Array} - */ -web.getLocalStorageKeys = function() -{ - var a = []; - try { - for (var i = 0, c = window.localStorage.length; i < c; i++) { - a.push(window.localStorage.key(i)); - } - } catch(e) { - web.logLocalStorageError(e); - } - return a; -}; - -/** - * reloadPage() - */ -web.reloadPage = function() -{ - if (window) window.location.reload(); -}; - -/** - * isUserAgent(s) - * - * Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can - * use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE"). - * - * 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating - * the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx); - * they say "public websites should rely on feature detection, rather than browser detection, in order to design - * their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent - * that tries to fool apps into thinking the browser is more like WebKit or Gecko: - * - * Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko - * - * That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained - * some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because - * that's a sledgehammer solution which restores the old user-agent string but also disables other features like - * HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or - * "Trident". - * - * UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't - * any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll - * need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture. - * - * @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values - * @return {boolean} is true if the string was found, false if not - */ -web.isUserAgent = function(s) -{ - if (window) { - var userAgent = web.getUserAgent(); - /* - * Here's one case where we have to be careful with Component, because when isUserAgent() is called by - * the init code below, component.js hasn't been loaded yet. The simple solution for now is to remove the call. - * - * web.log("agent: " + userAgent); - * - * And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure - * Compiler (v20130823) failing to detect the entire expression as a boolean. - */ - return (s == "iOS" && userAgent.match(/(iPod|iPhone|iPad)/) && userAgent.match(/AppleWebKit/) || s == "MSIE" && userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0))? true : false; - } - return false; -}; - -/** - * isMobile() - * - * Check the browser's user-agent string for the substring "Mobi", as per Mozilla recommendation: - * - * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent - * - * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not - */ -web.isMobile = function() -{ - return web.isUserAgent("Mobi"); -}; - -/** - * getURLParameters(sParms) - * - * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?') - * @return {Object} containing properties for each parameter found - */ -web.getURLParameters = function(sParms) -{ - var aParms = {}; - if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used - if (!sParms) { - /* - * Note that window.location.href returns the entire URL, whereas window.location.search - * returns only the parameters, if any (starting with the '?', which we skip over with a substr() call). - */ - sParms = window.location.search.substr(1); - } - var match; - var pl = /\+/g; // RegExp for replacing addition symbol with a space - var search = /([^&=]+)=?([^&]*)/g; - var decode = function(s) { return decodeURIComponent(s.replace(pl, " ")); }; - - while ((match = search.exec(sParms))) { - aParms[decode(match[1])] = decode(match[2]); - } - } - return aParms; -}; - -/** - * downloadFile(sData, sType, fBase64, sFileName) - * - * @param {string} sData - * @param {string} sType - * @param {boolean} [fBase64] - * @param {string} [sFileName] - */ -web.downloadFile = function(sData, sType, fBase64, sFileName) -{ - var link = null, sAlert; - var sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ","; - - if (!web.isUserAgent("Firefox")) { - sURI += (fBase64? sData : encodeURI(sData)); - } else { - sURI += (fBase64? sData : encodeURIComponent(sData)); - } - if (sFileName) { - link = document.createElement('a'); - if (typeof link.download != 'string') link = null; - } - if (link) { - link.href = sURI; - link.download = sFileName; - document.body.appendChild(link); // Firefox allegedly requires the link to be in the body - link.click(); - document.body.removeChild(link); - sAlert = 'Check your Downloads folder for ' + sFileName + '.'; - } else { - window.open(sURI); - sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.'; - } - return sAlert; -}; - -/** - * onCountRepeat(n, fnRepeat, fnComplete, msDelay) - * - * Call fnRepeat() n times with an msDelay millisecond delay between calls, - * then call fnComplete() when n has been exhausted OR fnRepeat() returns false. - * - * @param {number} n - * @param {function()} fnRepeat - * @param {function()} fnComplete - * @param {number} [msDelay] - */ -web.onCountRepeat = function(n, fnRepeat, fnComplete, msDelay) -{ - var fnTimeout = function doCountRepeat() { - n -= 1; - if (n >= 0) { - if (!fnRepeat()) n = 0; - } - if (n > 0) { - setTimeout(fnTimeout, msDelay || 0); - return; - } - fnComplete(); - }; - fnTimeout(); -}; - -/** - * onClickRepeat(e, msDelay, msRepeat, fn) - * - * Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter, - * as long as HTML control Object e has an active "down" event and fn() returns true. - * - * @param {Object} e - * @param {number} msDelay - * @param {number} msRepeat - * @param {function(boolean)} fn is passed false on the first call, true on all repeated calls - */ -web.onClickRepeat = function(e, msDelay, msRepeat, fn) -{ - var ms = 0, timer = null, fIgnoreMouseEvents = false; - - var fnRepeat = function doClickRepeat() { - if (fn(ms === msRepeat)) { - timer = setTimeout(fnRepeat, ms); - ms = msRepeat; - } - }; - e.onmousedown = function() { - // web.log("onMouseDown()"); - if (!fIgnoreMouseEvents) { - if (!timer) { - ms = msDelay; - fnRepeat(); - } - } - }; - e.ontouchstart = function() { - // web.log("onTouchStart()"); - if (!timer) { - ms = msDelay; - fnRepeat(); - } - }; - e.onmouseup = e.onmouseout = function() { - // web.log("onMouseUp()/onMouseOut()"); - if (timer) { - clearTimeout(timer); - timer = null; - } - }; - e.ontouchend = e.ontouchcancel = function() { - // web.log("onTouchEnd()/onTouchCancel()"); - if (timer) { - clearTimeout(timer); - timer = null; - } - /* - * Devices that generate ontouch* events ALSO generate onmouse* events, - * and generally do so immediately after all the touch events are complete, - * so unless we want double the action, we need to ignore mouse events. - */ - fIgnoreMouseEvents = true; - }; -}; - -web.aPageEventHandlers = { - 'init': [], // list of window 'onload' handlers - 'show': [], // list of window 'onpageshow' handlers - 'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible) -}; - -web.fPageReady = false; // set once the browser's first page initialization has occurred -web.fPageEventsEnabled = true; - -/** - * onPageEvent(sName, fn) - * - * @param {string} sFunc - * @param {function()} fn - * - * Use this instead of setting window['onload'], window['onunload'], etc. - * Allows multiple JavaScript modules to define a handler for the same event. - * - * Moreover, it's risky to refer to obscure event handlers with "dot" names, because - * the Closure Compiler may erroneously replace them (eg, window.onpageshow is a good example). - */ -web.onPageEvent = function(sFunc, fn) -{ - if (window) { - var fnPrev = window[sFunc]; - if (typeof fnPrev !== 'function') { - window[sFunc] = fn; - } else { - /* - * TODO: Determine whether there's any value in receiving/sending the Event object that the - * browser provides when it generates the original event. - */ - window[sFunc] = function onWindowEvent() { - if (fnPrev) fnPrev(); - fn(); - }; - } - } -}; - -/** - * onInit(fn) - * - * @param {function()} fn - * - * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler. - */ -web.onInit = function(fn) -{ - web.aPageEventHandlers['init'].push(fn); -}; - -/** - * onShow(fn) - * - * @param {function()} fn - * - * Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler. - */ -web.onShow = function(fn) -{ - web.aPageEventHandlers['show'].push(fn); -}; - -/** - * onExit(fn) - * - * @param {function()} fn - * - * Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler. - */ -web.onExit = function(fn) -{ - web.aPageEventHandlers['exit'].push(fn); -}; - -/** - * doPageEvent(afn) - * - * @param {Array.} afn - */ -web.doPageEvent = function(afn) -{ - if (web.fPageEventsEnabled) { - try { - for (var i = 0; i < afn.length; i++) { - afn[i](); - } - } catch(e) { - web.notice("An unexpected exception occurred:\n\n" + e.message + "\n\nPlease send this information to support@pcjs.org. Thanks."); - } - } -}; - -/** - * enablePageEvents(fEnable) - * - * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default) - */ -web.enablePageEvents = function(fEnable) -{ - if (!web.fPageEventsEnabled && fEnable) { - web.fPageEventsEnabled = true; - if (web.fPageReady) web.sendPageEvent('init'); - return; - } - web.fPageEventsEnabled = fEnable; -}; - -/** - * sendPageEvent(sEvent) - * - * This allows us to manually trigger page events. - * - * @param {string} sEvent (one of 'init', 'show' or 'exit') - */ -web.sendPageEvent = function(sEvent) -{ - if (web.aPageEventHandlers[sEvent]) { - web.doPageEvent(web.aPageEventHandlers[sEvent]); - } -}; - -web.onPageEvent('onload', function onPageLoad() { web.fPageReady = true; web.doPageEvent(web.aPageEventHandlers['init']); }); -web.onPageEvent('onpageshow', function onPageShow() { web.doPageEvent(web.aPageEventHandlers['show']); }); -web.onPageEvent(web.isUserAgent("Opera") || web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() { web.doPageEvent(web.aPageEventHandlers['exit']); }); - - -// ./modules/shared/lib/component.js - -/** - * @fileoverview The Component class used by C1Pjs and PCx86. - * @author Jeff Parsons - * @version 1.0 - * Created 2012-May-14 - * - * Copyright © 2012-2016 Jeff Parsons - * - * This file is part of PCjs, a computer emulation software project at . - * - * PCjs 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. - * - * PCjs 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 PCjs. If not, - * see . - * - * 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 COPYRIGHT in /modules/shared/lib/defines.js). - * - * Some PCjs 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 PCjs - * for purposes of the GNU General Public License, and the author does not claim any copyright - * as to their contents. - */ - -/* - * All the C1Pjs and PCjs components now use JSDoc types, primarily so that Google's Closure Compiler - * will compile everything with ZERO warnings. For more information about the JSDoc types supported by - * the Closure Compiler: - * - * https://developers.google.com/closure/compiler/docs/js-for-compiler#types - * - * I also attempted to use JSLint, but it's excessively strict for my taste, so this is the only file - * I tried massaging for JSLint's sake. I gave up when it complained about my use of "while (true)"; - * replacing "true" with an assignment expression didn't make it any happier. - * - * I wasn't thrilled about replacing all "++" and "--" operators with "+= 1" and "-= 1", nor about using - * "(s || '')" instead of "(s? s : '')", because while the former may seem simpler, it is NOT more portable. - * It's not that I'm trying to write "portable JavaScript", but some of this code was ported from C code I'd - * written about 14 years earlier, and portability is good, so I'm not going to rewrite if there's no need. - * - * UPDATE: I've since switched to JSHint, which seems to have more reasonable defaults. - */ - -/* global window: true, DEBUG: true */ - - -/** - * Component(type, parms, constructor, bitsMessage) - * - * A Component object requires: - * - * type: a user-defined type name (eg, "CPU") - * - * and accepts any or all of the following (parms) properties: - * - * id: component ID (default is "") - * name: component name (default is ""; if blank, toString() will use the type name only) - * comment: component comment string (default is undefined) - * - * Subclasses that use Component.subclass() to extend Component will likely have additional (parms) properties. - * - * @constructor - * @param {string} type - * @param {Object} [parms] - * @param {Object} [constructor] - * @param {number} [bitsMessage] selects message(s) that the component wants to enable (default is 0) - */ -function Component(type, parms, constructor, bitsMessage) -{ - this.type = type; - - if (!parms) parms = {'id': "", 'name': ""}; - - this.id = parms['id']; - this.name = parms['name']; - this.comment = parms['comment']; - this.parms = parms; - if (this.id === undefined) this.id = ""; - - var i = this.id.indexOf('.'); - if (i > 0) { - this.idMachine = this.id.substr(0, i); - this.idComponent = this.id.substr(i + 1); - } else { - this.idComponent = this.id; - } - - /* - * Recording the constructor is really just a debugging aid, because many of our constructors - * have class constants, but they're hard to find when the constructors are buried among all the - * other globals. - */ - this[type] = constructor; - - /* - * Gather all the various component flags (booleans) into a single "flags" object, and encourage - * subclasses to do the same, to reduce the property clutter we have to wade through while debugging. - */ - this.flags = { - fReady: false, - fBusy: false, - fBusyCancel: false, - fPowered: false, - fError: false - }; - - this.fnReady = null; - this.clearError(); - this.bindings = {}; - this.dbg = null; // by default, no connection to a Debugger - this.bitsMessage = bitsMessage || 0; - - /* - * TODO: Consider adding another parameter to the Component() constructor that allows components to tell - * us if they support single or multiple instances per machine. For example, there can be multiple SerialPort - * components per machine, but only one CPU component (well, OK, an FPU is also supported, but that's considered - * a different component). - * - * It's not critical, but it would help catch machine configuration errors; for example, a machine that mistakenly - * includes two CPU components may, aside from wasting memory, end up with odd side-effects, like unresponsive - * CPU controls. - */ - Component.add(this); -} - -/** - * Component.parmsURL - * - * Initialized to the set of URL parameters, if any, for the current web page. - * - * @type {Object} - */ -Component.parmsURL = web.getURLParameters(); - -/** - * Component.inherit(p) - * - * Returns a newly created object that inherits properties from the prototype object p. - * It uses the ECMAScript 5 function Object.create() if it is defined, and otherwise falls back to an older technique. - * - * See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-1) - * - * @param {Object} p - */ -Component.inherit = function(p) -{ - if (window) { - if (!p) throw new TypeError(); - if (Object.create) { - return Object.create(p); - } - var t = typeof p; - if (t !== "object" && t !== "function") throw new TypeError(); - } - /** - * @constructor - */ - function F() {} - F.prototype = p; - return new F(); -}; - -/** - * Component.extend(o, p) - * - * Copies the enumerable properties of p to o and returns o. - * If o and p have a property by the same name, o's property is overwritten. - * - * See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-2) - * - * @param {Object} o - * @param {Object} p - */ -Component.extend = function(o, p) -{ - for (var prop in p) { - o[prop] = p[prop]; - } - return o; -}; - -/** - * Component.subclass(subclass, superclass, methods, statics) - * - * See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 9-11) - * - * @param {Object} subclass is the constructor for the new subclass - * @param {Object} [superclass] is the constructor of the superclass (default is Component) - * @param {Object} [methods] contains all instance methods - * @param {Object} [statics] contains all class properties and methods - */ -Component.subclass = function(subclass, superclass, methods, statics) -{ - if (!superclass) superclass = Component; - subclass.prototype = Component.inherit(superclass.prototype); - subclass.prototype.constructor = subclass; - subclass.prototype.parent = superclass.prototype; - if (methods) { - Component.extend(subclass.prototype, methods); - } - if (statics) { - Component.extend(subclass, statics); - } - return subclass; -}; - -/* - * Every component created on the current page is recorded in this array (see Component.add()). - * - * This enables any component to locate another component by ID (see Component.getComponentByID()) - * or by type (see Component.getComponentByType()). - */ -Component.components = []; - -/** - * Component.add(component) - * - * @param {Component} component - */ -Component.add = function(component) -{ - /* - * This just generates a lot of useless noise, handy in the early days, not so much these days.... - * - * if (DEBUG) Component.log("Component.add(" + component.type + "," + component.id + ")"); - */ - Component.components.push(component); -}; - -/* - * Every machine on the page are now recorded as well, by their machine ID. We then record the various resources - * used by that machine. - */ -Component.machines = {}; - -/** - * Component.addMachine(idMachine) - * - * @param {string} idMachine - */ -Component.addMachine = function(idMachine) -{ - Component.machines[idMachine] = {}; -}; - -/** - * Component.addMachineResource(idMachine, sName, data) - * - * @param {string} idMachine - * @param {string|null} sName (name of the resource) - * @param {*} data - */ -Component.addMachineResource = function(idMachine, sName, data) -{ - /* - * I used to assert(Component.machines[idMachine]), but when we're running as a Node app, embed.js is not used, - * so addMachine() is never called, so resources do not need to be recorded. - */ - if (Component.machines[idMachine] && sName) { - Component.machines[idMachine][sName] = data; - } -}; - -/** - * Component.getMachineResources(idMachine) - * - * @param {string} idMachine - * @return {Object|undefined} - */ -Component.getMachineResources = function(idMachine) -{ - return Component.machines[idMachine]; -}; - -/** - * Component.log(s, type) - * - * For diagnostic output only. - * - * @param {string} [s] is the message text - * @param {string} [type] is the message type - */ -Component.log = function(s, type) -{ - if (DEBUG) { - if (s) { - var sElapsed = "", sMsg = (type? (type + ": ") : "") + s; - if (typeof usr != "undefined") { - if (Component.msStart === undefined) { - Component.msStart = usr.getTime(); - } - sElapsed = (usr.getTime() - Component.msStart) + "ms: "; - } - if (window && window.console) console.log(sElapsed + sMsg.replace(/\n/g, " ")); - } - } -}; - -/** - * Component.assert(f, s) - * - * Verifies conditions that must be true (for DEBUG builds only). - * - * The Closure Compiler should automatically remove all references to Component.assert() in non-DEBUG builds. - * - * TODO: Add a task to the build process that "asserts" there are no instances of "assertion failure" in RELEASE builds. - * - * @param {boolean} f is the expression we are asserting to be true - * @param {string} [s] is description of the assertion on failure - */ -Component.assert = function(f, s) -{ - if (DEBUG) { - if (!f) { - if (!s) s = "assertion failure"; - Component.log(s); - throw new Error(s); - } - } -}; - -/** - * Component.println(s, type, id) - * - * For non-diagnostic messages, which components may override to control the destination/appearance of their output. - * - * Components that inherit from this class should use the instance method, this.println(), rather than Component.println(), - * because if a Control Panel is loaded, it will override only the instance method, not the class method (overriding the class - * method would improperly affect any other machines loaded on the same page). - * - * @param {string} [s] is the message text - * @param {string} [type] is the message type - * @param {string} [id] is the caller's ID, if any - */ -Component.println = function(s, type, id) -{ - if (DEBUG) { - Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type); - } -}; - -/** - * Component.notice(s, fPrintOnly, id) - * - * notice() is like println() but implies a need for user notification, so we alert() as well. - * - * @param {string} s is the message text - * @param {boolean} [fPrintOnly] - * @param {string} [id] is the caller's ID, if any - */ -Component.notice = function(s, fPrintOnly, id) -{ - if (DEBUG) { - Component.println(s, "notice", id); - } - if (!fPrintOnly) web.alertUser(s); -}; - -/** - * Component.warning(s) - * - * @param {string} s describes the warning - */ -Component.warning = function(s) -{ - if (DEBUG) { - Component.println(s, "warning"); - } - web.alertUser(s); -}; - -/** - * Component.error(s) - * - * @param {string} s describes the error; an alert() is displayed as well - */ -Component.error = function(s) -{ - if (DEBUG) { - Component.println(s, "error"); - } - web.alertUser(s); -}; - -/** - * Component.getComponents(idRelated) - * - * We could store components as properties, using the component's ID, and change - * this linear lookup into a property lookup, but some components may have no ID. - * - * @param {string} [idRelated] of related component - * @return {Array} of components - */ -Component.getComponents = function(idRelated) -{ - var i; - var aComponents = []; - /* - * getComponentByID(id, idRelated) - * - * If idRelated is provided, we check it for a machine prefix, and use any - * existing prefix to constrain matches to IDs with the same prefix, in order to - * avoid matching components belonging to other machines. - */ - if (idRelated) { - if ((i = idRelated.indexOf('.')) > 0) - idRelated = idRelated.substr(0, i + 1); - else - idRelated = ""; - } - for (i = 0; i < Component.components.length; i++) { - var component = Component.components[i]; - if (!idRelated || !component.id.indexOf(idRelated)) { - aComponents.push(component); - } - } - return aComponents; -}; - -/** - * Component.getComponentByID(id, idRelated) - * - * We could store components as properties, using the component's ID, and change - * this linear lookup into a property lookup, but some components may have no ID. - * - * @param {string} id of the desired component - * @param {string} [idRelated] of related component - * @return {Component|null} - */ -Component.getComponentByID = function(id, idRelated) -{ - if (id !== undefined) { - var i; - /* - * If idRelated is provided, we check it for a machine prefix, and use any - * existing prefix to constrain matches to IDs with the same prefix, in order to - * avoid matching components belonging to other machines. - */ - if (idRelated && (i = idRelated.indexOf('.')) > 0) { - id = idRelated.substr(0, i + 1) + id; - } - for (i = 0; i < Component.components.length; i++) { - if (Component.components[i].id === id) { - return Component.components[i]; - } - } - if (Component.components.length) { - Component.log("Component ID '" + id + "' not found", "warning"); - } - } - return null; -}; - -/** - * Component.getComponentByType(sType, idRelated, componentPrev) - * - * @param {string} sType of the desired component - * @param {string} [idRelated] of related component - * @param {Component|null} [componentPrev] of previously returned component, if any - * @return {Component|null} - */ -Component.getComponentByType = function(sType, idRelated, componentPrev) -{ - if (sType !== undefined) { - var i; - /* - * If idRelated is provided, we check it for a machine prefix, and use any - * existing prefix to constrain matches to IDs with the same prefix, in order to - * avoid matching components belonging to other machines. - */ - if (idRelated) { - if ((i = idRelated.indexOf('.')) > 0) { - idRelated = idRelated.substr(0, i + 1); - } else { - idRelated = ""; - } - } - for (i = 0; i < Component.components.length; i++) { - if (componentPrev) { - if (componentPrev == Component.components[i]) componentPrev = null; - continue; - } - if (sType == Component.components[i].type && (!idRelated || !Component.components[i].id.indexOf(idRelated))) { - return Component.components[i]; - } - } - Component.log("Component type '" + sType + "' not found", "warning"); - } - return null; -}; - -/** - * Component.getComponentParms(element) - * - * @param {Object} element from the DOM - */ -Component.getComponentParms = function(element) -{ - var parms = null; - var sParms = element.getAttribute("data-value"); - if (sParms) { - try { - parms = eval("(" + sParms + ")"); // jshint ignore:line - /* - * We can no longer invoke removeAttribute() because some components (eg, Panel) need - * to run their initXXX() code more than once, to avoid initialization-order dependencies. - * - * if (!DEBUG) { - * element.removeAttribute("data-value"); - * } - */ - } catch(e) { - Component.error(e.message + " (" + sParms + ")"); - } - } - return parms; -}; - -/** - * Component.bindExternalControl(component, sControl, sBinding, sType) - * - * @param {Component} component - * @param {string} sControl - * @param {string} sBinding - * @param {string} [sType] is the external component type - */ -Component.bindExternalControl = function(component, sControl, sBinding, sType) -{ - if (sControl) { - if (sType === undefined) sType = "Panel"; - var target = Component.getComponentByType(sType, component.id); - if (target) { - var eBinding = target.bindings[sControl]; - if (eBinding) { - component.setBinding(null, sBinding, eBinding); - } - } - } -}; - -/** - * Component.bindComponentControls(component, element, sAppClass) - * - * @param {Component} component - * @param {Object} element from the DOM - * @param {string} sAppClass - */ -Component.bindComponentControls = function(component, element, sAppClass) -{ - var aeControls = Component.getElementsByClass(element.parentNode, sAppClass + "-control"); - - for (var iControl = 0; iControl < aeControls.length; iControl++) { - - var aeChildNodes = aeControls[iControl].childNodes; - - for (var iNode = 0; iNode < aeChildNodes.length; iNode++) { - var control = aeChildNodes[iNode]; - if (control.nodeType !== 1 /* document.ELEMENT_NODE */) { - continue; - } - var sClass = control.getAttribute("class"); - if (!sClass) continue; - var aClasses = sClass.split(" "); - for (var iClass = 0; iClass < aClasses.length; iClass++) { - var parms; - sClass = aClasses[iClass]; - switch (sClass) { - case sAppClass + "-binding": - parms = Component.getComponentParms(control); - if (parms && parms['binding']) { - component.setBinding(parms['type'], parms['binding'], control, parms['value']); - } else if (!parms || parms['type'] != "description") { - Component.log("Component '" + component.toString() + "' missing binding" + (parms? " for " + parms['type'] : ""), "warning"); - } - iClass = aClasses.length; - break; - default: - // if (DEBUG) Component.log("Component.bindComponentControls(" + component.toString() + "): unrecognized control class \"" + sClass + "\"", "warning"); - break; - } - } - } - } -}; - -/** - * Component.getElementsByClass(element, sClass, sObjClass) - * - * This is a cross-browser helper function, since not all browser's support getElementsByClassName() - * - * TODO: This should probably be moved into weblib.js at some point, along with the control binding functions above, - * to keep all the browser-related code together. - * - * @param {Object} element from the DOM - * @param {string} sClass - * @param {string} [sObjClass] - * @return {Array|NodeList} - */ -Component.getElementsByClass = function(element, sClass, sObjClass) -{ - if (sObjClass) sClass += '-' + sObjClass + "-object"; - /* - * Use the browser's built-in getElementsByClassName() if it appears to be available - * (for example, it's not available in IE8, but it should be available in IE9 and up) - */ - if (element.getElementsByClassName) { - return element.getElementsByClassName(sClass); - } - var i, j, ae = []; - var aeAll = element.getElementsByTagName("*"); - var re = new RegExp('(^| )' + sClass + '( |$)'); - for (i = 0, j = aeAll.length; i < j; i++) { - if (re.test(aeAll[i].className)) { - ae.push(aeAll[i]); - } - } - if (!ae.length) { - Component.log('No elements of class "' + sClass + '" found'); - } - return ae; -}; - -Component.prototype = { - constructor: Component, - parent: null, - /** - * toString() - * - * @this {Component} - * @return {string} - */ - toString: function() { - return (this.name? this.name : (this.id || this.type)); - }, - /** - * getMachineNum() - * - * @this {Component} - * @return {number} unique machine number - */ - getMachineNum: function() { - var nMachine = 1; - if (this.idMachine) { - var aDigits = this.idMachine.match(/\d+/); - if (aDigits !== null) - nMachine = parseInt(aDigits[0], 10); - } - return nMachine; - }, - /** - * setBinding(sHTMLType, sBinding, control, sValue) - * - * Component's setBinding() method is intended to be overridden by subclasses. - * - * @this {Component} - * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas") - * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset") - * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) - * @param {string} [sValue] optional data value - * @return {boolean} true if binding was successful, false if unrecognized binding request - */ - setBinding: function(sHTMLType, sBinding, control, sValue) { - switch (sBinding) { - case "clear": - if (!this.bindings[sBinding]) { - this.bindings[sBinding] = control; - control.onclick = (function(component) { - return function clearPanel() { - if (component.bindings['print']) { - component.bindings['print'].value = ""; - } - }; - }(this)); - } - return true; - case "print": - if (!this.bindings[sBinding]) { - this.bindings[sBinding] = control; - /* - * HACK: Save this particular HTML element so that the Debugger can access it, too - */ - this.controlPrint = control; - /* - * This was added for Firefox (Safari automatically clears the