/**************************************************************************** Virtual nixie tube display, clock & calculator DHTML components v 1.05, 20080214a (c) 2007-08 Cestmir Hybl, cestmir.hybl@nustep.net http://cestmir.freeside.sk/projects/dhtml-nixie-display license: free for non-commercial use, copyright must be preserved ****************************************************************************/ /* NixieDisplay */ // public class NixieDisplay function NixieDisplay() { // public this.id = 'nixie'; this.elContainer = null; this.charCount = 10; this.autoDecimalPoint = true; // automatically extracts decimal point index in setText() call this.align = 'left'; // alignment of text via setText() call this.afterUpdate = null; // after display update callback this.charWidth = 62; this.charHeight = 150; this.charGapWidth = 0; this.extraGapsWidths = []; this.createCharElements = true; this.text = ''; this.decimalPoint = -1; this.urlCharsetImage = 'nixie/zm1082_l1_09bdm_62x150_8b.png'; this.charMap = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, ' ': 10, '-': 11, 'default': 10 }; // maps displayable chars onto glyph matrix indexes // protected function _drawChar(index) { var el = document.getElementById(this.id + '_d' + index); var charIndex = this.charMap[this.text.charAt(index)]; if (!charIndex && charIndex !== 0) charIndex = this.charMap['default']; var x = - (charIndex * this.charWidth); var y = (index === this.decimalPoint ? - this.charHeight : 0); el.style.backgroundPosition = x + 'px ' + y + 'px'; } this._drawChar = _drawChar; // Shows given string on display // public function setText(text, updateDecimalPoint) { // force string type this.text = text + ''; // extract decimal point updateDecimalPoint = (typeof(updateDecimalPoint) != 'undefined' ? updateDecimalPoint : this.autoDecimalPoint); if (updateDecimalPoint) { var i = this.text.indexOf('.'); if (i >= 0) { this.decimalPoint = i - 1; // alert(this.decimalPoint); this.text = this.text.substr(0, i) + this.text.substr(i + 1); } else this.decimalPoint = -1; } // pad up to display width (from left/right acording to this.align) if (this.text.length < this.charCount) { var pad = ''; var padWidth = this.charCount - this.text.length; for (var i = 0; i < padWidth; i++) pad += ' '; if (this.align == 'left') this.text = this.text + pad; else { if (this.decimalPoint >= 0) this.decimalPoint += padWidth; this.text = pad + this.text; } } if (this.text.length > this.charCount) this.text = this.text.substr(0, this.charCount); // draw chars for (var i = 0; i < this.text.length; i++) { this._drawChar(i); } if (this.afterUpdate) this.afterUpdate(this); } this.setText = setText; // Sets char at given display position // public function setChar(index, chr) { // alert(chr); this.text = this.text.substring(0, index) + chr + this.text.substring(index + 1); this.setText(this.text, false); } this.setChar = setChar; function setDecimalPoint(index) { var oldDecimalPoint = this.decimalPoint; this.decimalPoint = ((!index && index !== 0) ? -1 : index); if (oldDecimalPoint != this.decimalPoint) { if (oldDecimalPoint >= 0) this._drawChar(oldDecimalPoint); if (this.decimalPoint >= 0) this._drawChar(this.decimalPoint); } } this.setDecimalPoint = setDecimalPoint; // Clears display - fills all positions with given char (space by default). // public function clear(chr) { chr = (typeof(chr) == 'undefined' ? ' ' : chr); this.text = ''; for (var i = 0; i < this.charCount; i++) this.text += chr; this.decimalPoint = -1; this.setText(this.text); } this.clear = clear; // Shifts display contents left or right // public function shift(direction, step) { step = (!step && step !== 0 ? 1 : step); direction = (!direction ? 'left' : direction); if (this.decimalPoint >= 0) { this.decimalPoint += (direction == 'left' ? - step : + step); if (this.decimalPoint >= this.charCount) this.decimalPoint = -1; } if (direction == 'left') this.text = this.text.substr(step) + ' '; // @todo padding for step != +/-1 else if (direction == 'right') this.text = ' ' + this.text.substr(0, this.text.length - 1); // @todo padding for step != +/-1 this.setText(this.text, false); } this.shift = shift; // public function init() { if (!this.elContainer) { this.elContainer = document.getElementById(this.id); if (!this.elContainer) throw "Container element '" + this.id + "' not found"; } this.elContainer.style.position = 'relative'; if (this.createCharElements) { var totalWidth = 0; for (var i = 0; i < this.charCount; i++) { var charWidthIncludingGap = (this.charWidth + this.charGapWidth); var elId = this.id + '_d' + i; var el0 = document.getElementById(elId); var el = (el0 ? el0 : document.createElement('div')); el.id = this.id + '_d' + i; el.className = 'digit d' + i; el.style.position = 'absolute'; el.style.left = totalWidth + 'px'; el.style.width = this.charWidth + 'px'; el.style.height = this.charHeight + 'px'; el.style.background = 'url(' + this.urlCharsetImage + ')'; if (!el.parentNode) this.elContainer.appendChild(el); totalWidth += charWidthIncludingGap + (this.extraGapsWidths[i] ? this.extraGapsWidths[i] : 0); } this.elContainer.style.width = totalWidth + 'px'; this.elContainer.style.height = this.charHeight + 'px'; } if (this.text) this.setText(this.text) else this.clear(); } this.init = init; } /* NixieClock */ // public class NixieClock : NixieDisplay function NixieClock() { // public // private this.lastSeconds = -1; // Show current time on "display" // public function showCurrentTime(refreshAfterChangeOnly) { var d = new Date(); var s = d.getSeconds(); if (refreshAfterChangeOnly && s == this.lastSeconds) return; else this.lastSeconds = s; var h = d.getHours(); var m = d.getMinutes(); var digits = ''; digits += (h / 10) | 0; digits += h % 10; digits += (m / 10) | 0; digits += m % 10; digits += (s / 10) | 0; digits += s % 10; this.setText(digits); } this.showCurrentTime = showCurrentTime; // Run clock (via scheduling a periodic callback to showCurrentTime()) // public function run() { if (!this.elContainer) this.init(); var __nixieClock = this; window.setInterval(function() { __nixieClock.showCurrentTime(true); }, 100); } this.run = run; this.ancestor = NixieDisplay; this.ancestor(); this.charCount = 6; this.extraGapsWidths[1] = 20; this.extraGapsWidths[3] = 20; } /* NixieCalculator */ // @todo rounding of rightmost digit // public class NixieCalculator : NixieDisplay function NixieCalculator() { // public this.id = 'nixieCalc'; this.digitCount = 13; this.display = new NixieDisplay(); // private this.operandStack = []; this.newValueAtNextChar = false; this.fullPrecisionValue = 0; // private function push(value) { this.operandStack[this.operandStack.length] = value; // JS50 compatible .push() } this.push = push; // private function pop() { if (!this.operandStack.length) return null; var v = this.operandStack[this.operandStack.length - 1]; this.operandStack = this.operandStack.slice(0, this.operandStack.length - 1); // JS50 compatible .pop() return v; } this.pop = pop; // public function getValue() { if (this.fullPrecisionValue !== null) return this.fullPrecisionValue; var v = this.display.text; // insert decimal point if (this.display.decimalPoint >= 0 && this.display.decimalPoint < this.digitCount - 1) v = v.substr(0, this.display.decimalPoint + 1) + '.' + v.substr(this.display.decimalPoint + 1); // remove padding spaces var i = 0; while (i < v.length && v.charAt(i) == ' ') i++; v = v.substr(i); // convert to number v = parseFloat(v); return v; } this.getValue = getValue; // public function setValue(v) { if (typeof(v) != 'number') v = parseFloat(v); if (isNaN(v) || v > this.maxNumber || v < -this.maxNumber) this.error(); else { this.fullPrecisionValue = v; if (v.toFixed) { // force fixed-point notation (JS5.5+) var s = (v >= 0 ? ' ' : '') + v.toFixed(1); s = s.substring(0, s.length - 2); // (s now contains string with integer part of value, prefixed by either ' ' or '-') v = v.toFixed(this.digitCount - s.length); // to fixed point + round rightmost digit } else { v = v.toString(); if (v.toLowerCase().indexOf('e') >= 0) { // we won't handle exp notation in JS<5.5 this.error(); return; } } if (v !== '0') { if (v.charAt(0) != '-') v = ' ' + v; var c = this.digitCount + (v.indexOf('.') >= 0 ? 1 : 0); if (v.length > c) v = v.substr(0, c); v = v.replace(/^(.{1,}?)\.?0+$/g, '$1'); // strip zero's from right } this.display.setText(v); } } this.setValue = setValue; // private function eval(v1, o, v2) { try { switch (o) { case '+': return v1 + v2; case '-': return v1 - v2; case '*': return v1 * v2; case '/': return v1 / v2; case '^': return Math.pow(v1, v2); case 'sqrt': return Math.sqrt(v1); case 'sqr': return v1 * v1; default: throw "Unsupported operand: '" + o + "'"; } } catch(e) { this.error(); } } this.eval = eval; // public function error() { var s= ''; for (var i = 0; i < this.digitCount; i++) s += '-'; this.operandStack = []; this.newValueAtNextChar = true; this.fullPrecisionValue = null; this.display.setText(s); } this.error = error; // public function clear() { this.display.clear(); this.setValue(0); this.operandStack = []; this.fullPrecisionValue = null; } this.clear = clear; // public function keyDown(event0) { var e = (event0 ? event0 : event); var k = e.keyCode; var cancelEvent = true; if (k == 8) { // backspace if (this.display.text.charAt(this.digitCount - 2) == ' ') this.display.setChar(this.digitCount - 1, '0'); else this.display.shift('right'); this.fullPrecisionValue = null; } else if (k == 27) { // escape this.clear(); } else cancelEvent = false; return !cancelEvent; } this.keyDown = keyDown; // public function keyPress(event0) { var e = (event0 ? event0 : event); var k = (e.keyCode ? e.keyCode : e.which); // IE: .keyCode, FF: .which var chr = String.fromCharCode(k); var cancelEvent = true; var newValueAtThisChar = this.newValueAtNextChar; this.newValueAtNextChar = true; if (chr >= '0' && chr <= '9') { this.fullPrecisionValue = null; if (newValueAtThisChar) { this.display.clear(); } if (this.display.text.charAt(1) == ' ' || this.display.text.charAt(1) == '-') { if (this.display.text.charAt(this.digitCount - 1) == '0' && this.display.text.charAt(this.digitCount - 2) == ' ' && this.display.decimalPoint < 0) ; else this.display.shift('left'); this.display.setChar(this.digitCount - 1, chr); } this.newValueAtNextChar = false; } else if (chr == '.' || chr == ',') { this.fullPrecisionValue = null; if (newValueAtThisChar) this.display.setText(0); if (this.display.decimalPoint < 0) this.display.setDecimalPoint(this.digitCount - 1); this.newValueAtNextChar = false; } else if (chr == '+' || chr == '-' || chr == '*' || chr == '/' || chr == '^') { if (this.operandStack.length > 2) // cancel repeated evaluation this.operandStack = []; if (this.operandStack.length == 2) { // previous expression without explicit '=', evaluate it this.setValue(this.eval(this.operandStack[this.operandStack.length - 2], this.operandStack[this.operandStack.length - 1], this.getValue())); this.operandStack = []; } // push left operand this.push(this.getValue()); // push operator this.push(chr); } else if (chr == 'm' || chr == 'M') { this.setValue(- this.getValue()); this.newValueAtNextChar = false; } else if (chr == 'p' || chr == 'P') { this.setValue(Math.PI); } else if (chr == 'q') { this.setValue(this.eval(this.getValue(), 'sqrt', null)); } else if (chr == 'Q') { this.setValue(this.eval(this.getValue(), 'sqr', null)); } else if (k == 13 || chr == '=') { if (this.operandStack.length >= 2) { if (this.operandStack.length <= 2) // push right operand this.push(this.getValue()) else // repeated evaluation (e.g. [1] [+] [1] [=] [=] ...), replace left operand with current display value this.operandStack[this.operandStack.length - 3] = this.getValue(); // alert(this.operandStack); var result = this.eval(this.operandStack[this.operandStack.length - 3], this.operandStack[this.operandStack.length - 2], this.operandStack[this.operandStack.length - 1]); this.setValue(result); // this.operandStack = []; } } // else if (k == 27) { // Fix: chromium: Esc not handled in keyPress(), http://code.google.com/p/chromium/issues/detail?id=12744 // // escape // this.clear(); // } else { cancelEvent = false; this.newValueAtNextChar = false; } return !cancelEvent; } this.keyPress = keyPress; // public function init() { this.display.id = this.id; this.display.charCount = this.digitCount; this.display.align = 'right'; this.display.init(); this.display.setText('0'); this.maxNumber = Math.pow(10, this.digitCount - 1) - 1; } this.init = init; // assign default glyph matrix this.display.urlCharsetImage = 'nixie/zm1080_l2_09bdm_45x75_8b.png'; this.display.charWidth = 45; this.display.charHeight = 75; this.display.charGapWidth = 5; }