2022-02-21 15:35:52 +00:00
|
|
|
"use strict";
|
2022-09-14 04:33:52 +00:00
|
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
|
|
};
|
2022-02-21 15:35:52 +00:00
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
exports.WaveformView = void 0;
|
2022-09-03 18:38:45 +00:00
|
|
|
const toolbar_1 = require("./toolbar");
|
2022-02-21 15:35:52 +00:00
|
|
|
const vlist_1 = require("../common/vlist");
|
2022-09-14 04:33:52 +00:00
|
|
|
const dompurify_1 = __importDefault(require("dompurify"));
|
2022-02-21 15:35:52 +00:00
|
|
|
const BUILTIN_INPUT_PORTS = [
|
|
|
|
'clk', 'reset',
|
|
|
|
];
|
|
|
|
class WaveformView {
|
|
|
|
constructor(parent, wfp) {
|
|
|
|
this.lines = [];
|
|
|
|
this.zoom = 8;
|
|
|
|
this.t0 = 0;
|
|
|
|
this.tsel = -1;
|
|
|
|
this.tnow = -1;
|
|
|
|
this.hexformat = true;
|
|
|
|
this.scrollbarWidth = 12;
|
|
|
|
this.parent = parent;
|
|
|
|
this.wfp = wfp;
|
|
|
|
this.recreate();
|
|
|
|
}
|
|
|
|
recreate() {
|
|
|
|
clearTimeout(this.wtimer);
|
|
|
|
this.wtimer = setTimeout(() => {
|
|
|
|
this.destroy();
|
|
|
|
// create new thing
|
|
|
|
this._recreate();
|
|
|
|
}, 0);
|
|
|
|
}
|
|
|
|
destroy() {
|
|
|
|
// remove old thing
|
|
|
|
if (this.wavelist) {
|
|
|
|
$(this.wavelist.container).remove();
|
|
|
|
this.wavelist = null;
|
|
|
|
}
|
|
|
|
if (this.toolbar) {
|
|
|
|
this.toolbar.destroy();
|
|
|
|
this.toolbar = null;
|
|
|
|
}
|
|
|
|
if (this.clklabel) {
|
|
|
|
this.clklabel.remove();
|
|
|
|
this.clklabel = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_recreate() {
|
|
|
|
this.meta = this.wfp.getSignalMetadata();
|
|
|
|
if (!this.meta)
|
|
|
|
return;
|
|
|
|
var width = $(this.parent).width();
|
|
|
|
this.pageWidth = width - this.scrollbarWidth;
|
|
|
|
var rowHeight = 40; // TODO
|
|
|
|
this.setClocksPerPage();
|
|
|
|
this.clockMax = 0;
|
|
|
|
this.wavelist = new vlist_1.VirtualList({
|
|
|
|
w: width,
|
|
|
|
h: $(this.parent).height(),
|
|
|
|
itemHeight: rowHeight,
|
|
|
|
totalRows: this.meta.length + 1,
|
|
|
|
generatorFn: (row) => {
|
|
|
|
var metarow = this.meta[row]; // TODO: why null?
|
|
|
|
//var s = metarow != null ? metarow.label : "";
|
|
|
|
let linediv = document.createElement("div");
|
|
|
|
let canvas = document.createElement("canvas");
|
|
|
|
canvas.width = width - 12; // room for scrollbar
|
|
|
|
canvas.height = rowHeight;
|
|
|
|
linediv.appendChild(canvas); //document.createTextNode(s));
|
|
|
|
linediv.classList.add('waverow');
|
|
|
|
this.lines[row] = canvas;
|
|
|
|
this.refreshRow(row);
|
|
|
|
// click to change input
|
|
|
|
if (metarow && metarow.input && BUILTIN_INPUT_PORTS.indexOf(metarow.label) < 0) {
|
|
|
|
linediv.onmousedown = (e) => {
|
|
|
|
var meta = this.meta[row];
|
|
|
|
if (meta && meta.input) {
|
|
|
|
this.changeInputValue(row);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
linediv.classList.add('editable');
|
|
|
|
linediv.style.cursor = 'grab';
|
|
|
|
}
|
|
|
|
return linediv;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
var wlc = this.wavelist.container;
|
|
|
|
wlc.tabIndex = -1; // make it focusable
|
|
|
|
//wlc.style = "overflow-x: hidden"; // TODO?
|
|
|
|
this.toolbar = new toolbar_1.Toolbar(this.parent, this.parent);
|
|
|
|
this.toolbar.span.css('display', 'inline-block');
|
|
|
|
this.clklabel = document.createElement('span');
|
|
|
|
this.clklabel.innerText = "-";
|
|
|
|
$(this.parent).append(this.clklabel);
|
|
|
|
$(this.parent).append(wlc);
|
|
|
|
var down = false;
|
|
|
|
var selfn = (e) => {
|
|
|
|
this.setSelTime(e.offsetX / this.zoom + this.t0 - 0.5);
|
|
|
|
};
|
|
|
|
$(wlc).mousedown((e) => {
|
|
|
|
down = true;
|
|
|
|
selfn(e);
|
|
|
|
//if (e['pointerId']) e.target.setPointerCapture(e['pointerId']);
|
|
|
|
});
|
|
|
|
$(wlc).mousemove((e) => {
|
|
|
|
if (down)
|
|
|
|
selfn(e);
|
|
|
|
});
|
|
|
|
$(wlc).mouseup((e) => {
|
|
|
|
down = false;
|
|
|
|
//if (e['pointerId']) e.target.releasePointerCapture(e['pointerId']);
|
|
|
|
});
|
|
|
|
// scroll left/right
|
|
|
|
$(wlc).on('wheel', (event) => {
|
|
|
|
if (Math.abs(event.originalEvent.deltaX) > Math.abs(event.originalEvent.deltaY)) {
|
|
|
|
var xdelta = Math.max(-1000, Math.min(1000, event.originalEvent.deltaX));
|
|
|
|
if (xdelta)
|
|
|
|
this.setOrgTime(this.t0 + xdelta);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.toolbar.add('=', 'Zoom In', 'glyphicon-zoom-in', (e, combo) => {
|
|
|
|
this.setZoom(this.zoom * 2);
|
|
|
|
});
|
|
|
|
this.toolbar.add('+', 'Zoom In', null, (e, combo) => {
|
|
|
|
this.setZoom(this.zoom * 2);
|
|
|
|
});
|
|
|
|
this.toolbar.add('-', 'Zoom Out', 'glyphicon-zoom-out', (e, combo) => {
|
|
|
|
this.setZoom(this.zoom / 2);
|
|
|
|
});
|
|
|
|
this.toolbar.add('ctrl+shift+left', 'Move to beginning', 'glyphicon-backward', (e, combo) => {
|
|
|
|
this.setSelTime(0);
|
|
|
|
this.setOrgTime(0);
|
|
|
|
});
|
|
|
|
this.toolbar.add('shift+left', 'Move left 1/4 page', 'glyphicon-fast-backward', (e, combo) => {
|
|
|
|
this.setSelTime(this.tsel - this.clocksPerPage / 4);
|
|
|
|
});
|
|
|
|
this.toolbar.add('left', 'Move left 1 clock', 'glyphicon-step-backward', (e, combo) => {
|
|
|
|
this.setSelTime(this.tsel - 1);
|
|
|
|
});
|
|
|
|
this.toolbar.add('right', 'Move right 1 clock', 'glyphicon-step-forward', (e, combo) => {
|
|
|
|
this.setSelTime(this.tsel + 1);
|
|
|
|
});
|
|
|
|
this.toolbar.add('shift+right', 'Move right 1/4 page', 'glyphicon-fast-forward', (e, combo) => {
|
|
|
|
this.setSelTime(this.tsel + this.clocksPerPage / 4);
|
|
|
|
});
|
|
|
|
this.toolbar.add('space', 'Go to current time', 'glyphicon-flash', (e, combo) => {
|
|
|
|
this.setOrgTime(this.tnow);
|
|
|
|
this.setSelTime(this.tnow);
|
|
|
|
});
|
|
|
|
this.toolbar.add('h', 'Switch between hex/decimal format', 'glyphicon-barcode', (e, combo) => {
|
|
|
|
this.hexformat = !this.hexformat;
|
|
|
|
this.refresh();
|
|
|
|
});
|
|
|
|
$(window).resize(() => {
|
|
|
|
this.recreate();
|
|
|
|
}); // TODO: remove?
|
|
|
|
}
|
|
|
|
roundT(t) {
|
|
|
|
t = Math.round(t);
|
|
|
|
t = Math.max(0, t); // make sure >= 0
|
|
|
|
t = Math.min(this.clockMax + this.clocksPerPage / 2, t); // make sure <= end
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
setOrgTime(t) {
|
|
|
|
this.t0 = this.roundT(t);
|
|
|
|
this.refresh();
|
|
|
|
}
|
|
|
|
setCurrentTime(t) {
|
|
|
|
this.tnow = this.roundT(t);
|
|
|
|
this.refresh();
|
|
|
|
}
|
|
|
|
setSelTime(t) {
|
|
|
|
t = this.roundT(t);
|
|
|
|
if (t >= this.t0 + this.clocksPerPage - 1)
|
|
|
|
this.t0 += this.clocksPerPage / 4;
|
|
|
|
if (t <= this.t0 + 2)
|
|
|
|
this.t0 -= this.clocksPerPage / 4;
|
|
|
|
this.tsel = t;
|
|
|
|
this.setOrgTime(this.t0);
|
|
|
|
this.clklabel.innerText = " clk " + this.tsel;
|
|
|
|
}
|
|
|
|
setClocksPerPage() {
|
|
|
|
this.clocksPerPage = Math.floor(this.pageWidth / this.zoom) - 1;
|
|
|
|
}
|
|
|
|
setZoom(zoom) {
|
|
|
|
this.zoom = Math.max(1 / 16, Math.min(64, zoom));
|
|
|
|
this.setClocksPerPage();
|
|
|
|
this.t0 = Math.max(0, Math.round(this.tsel - this.clocksPerPage / 2));
|
|
|
|
this.refresh();
|
|
|
|
}
|
|
|
|
refresh() {
|
|
|
|
if (!this.meta)
|
|
|
|
this.recreate();
|
|
|
|
if (!this.meta)
|
|
|
|
return;
|
|
|
|
for (var i = 0; i < this.meta.length; i++) {
|
|
|
|
this.refreshRow(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
value2str(val, meta) {
|
|
|
|
var radix = this.hexformat ? 16 : 10;
|
|
|
|
var txt = val.toString(radix);
|
|
|
|
if (radix == 16 && meta && meta.len > 3)
|
|
|
|
txt = `${meta.len}'h${txt}`;
|
|
|
|
//else if (radix == 10 && meta.len > 3)
|
|
|
|
//txt = `${meta.len}'d${txt}`;
|
|
|
|
return txt;
|
|
|
|
}
|
|
|
|
refreshRow(row) {
|
|
|
|
var canvas = this.lines[row];
|
|
|
|
var meta = this.meta[row];
|
|
|
|
if (!canvas || !meta)
|
|
|
|
return;
|
|
|
|
var isclk = (meta.label == 'clk');
|
|
|
|
var w = canvas.width;
|
|
|
|
var h = canvas.height;
|
|
|
|
var ctx = canvas.getContext("2d");
|
|
|
|
var fontbig = "14px Andale Mono, Lucida Console, monospace";
|
|
|
|
var fontsml = "10px Andale Mono, Lucida Console, monospace";
|
|
|
|
ctx.font = fontbig;
|
|
|
|
// clear to black
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// highlighted?
|
|
|
|
var tags = [];
|
|
|
|
if (meta.input && BUILTIN_INPUT_PORTS.indexOf(meta.label) < 0)
|
|
|
|
tags.push('input');
|
|
|
|
//if (meta.output) tags.push('output');
|
|
|
|
// draw waveform
|
|
|
|
var fh = 12;
|
|
|
|
var b1 = fh + 4;
|
|
|
|
var b2 = 4;
|
|
|
|
var h2 = h - b1 - b2;
|
|
|
|
var yrange = meta.len == 32 ? 4294967296.0 : ((1 << meta.len) - 1) || 0;
|
|
|
|
var data = this.wfp.getSignalData(row, this.t0, Math.ceil(w / this.zoom));
|
|
|
|
this.clockMax = Math.max(this.clockMax, this.t0 + data.length);
|
|
|
|
var printvals = meta.len > 1 && this.zoom >= 32;
|
|
|
|
var ycen = b1 + h2 - 4;
|
|
|
|
ctx.fillStyle = "#336633";
|
|
|
|
ctx.fillRect(0, fh / 2, 3, b1 + h2 - fh / 2); // draw left tag
|
|
|
|
const COLOR_LINE = ctx.strokeStyle = "#33dd33";
|
|
|
|
const COLOR_HILITE = ctx.fillStyle = "#66ffff";
|
|
|
|
const COLOR_NAME = "#dddddd";
|
|
|
|
// draw waveform
|
|
|
|
ctx.beginPath();
|
|
|
|
var x = 0;
|
|
|
|
var y = 0;
|
|
|
|
var lastval = -1;
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
|
|
var val = data[i];
|
|
|
|
if (printvals && val != lastval && x < w - 100) { // close to right edge? omit
|
|
|
|
var ytext = ycen;
|
|
|
|
var txt = this.value2str(val, null);
|
|
|
|
if (txt.length > 4)
|
|
|
|
ctx.font = fontsml;
|
|
|
|
ctx.fillText(txt, x + this.zoom / 4, ytext);
|
|
|
|
}
|
|
|
|
if (i > 0)
|
|
|
|
ctx.lineTo(x, y);
|
|
|
|
y = b1 + (1.0 - val / yrange) * h2;
|
|
|
|
if (!isclk)
|
|
|
|
x += this.zoom * (1 / 8);
|
|
|
|
if (i == 0)
|
|
|
|
ctx.moveTo(x, y);
|
|
|
|
else
|
|
|
|
ctx.lineTo(x, y);
|
|
|
|
if (this.zoom > 0.75 && lastval != val && Math.abs(lastval - val) < yrange * 0.1)
|
|
|
|
ctx.fillRect(x, y, 1 + this.zoom / 4, 1);
|
|
|
|
if (isclk)
|
|
|
|
x += this.zoom;
|
|
|
|
else
|
|
|
|
x += this.zoom * (7 / 8);
|
|
|
|
lastval = val;
|
|
|
|
}
|
|
|
|
ctx.stroke();
|
|
|
|
// draw selection thingie
|
|
|
|
ctx.font = fontbig;
|
|
|
|
if (this.tsel >= this.t0) {
|
|
|
|
ctx.strokeStyle = ctx.fillStyle = "#ff66ff";
|
|
|
|
ctx.beginPath();
|
|
|
|
x = (this.tsel - this.t0) * this.zoom + this.zoom / 2;
|
|
|
|
ctx.moveTo(x, 0);
|
|
|
|
ctx.lineTo(x, h);
|
|
|
|
ctx.stroke();
|
|
|
|
// print value
|
|
|
|
var val = data[this.tsel - this.t0];
|
|
|
|
ctx.textAlign = 'right';
|
|
|
|
if (val !== undefined) {
|
|
|
|
var s = this.value2str(val, meta);
|
|
|
|
var x = w - fh;
|
|
|
|
var dims = ctx.measureText(s);
|
|
|
|
ctx.fillStyle = 'black';
|
|
|
|
ctx.fillRect(x - dims.width - 2, ycen - 13, dims.width + 4, 17);
|
|
|
|
ctx.fillStyle = "#ff66ff";
|
|
|
|
ctx.fillText(s, x, ycen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// draw current line
|
|
|
|
if (this.tnow >= this.t0) {
|
|
|
|
ctx.strokeStyle = ctx.fillStyle = "#6666cc";
|
|
|
|
ctx.beginPath();
|
|
|
|
x = (this.tnow - this.t0) * this.zoom + this.zoom / 2;
|
|
|
|
ctx.moveTo(x, 0);
|
|
|
|
ctx.lineTo(x, h);
|
|
|
|
ctx.stroke();
|
|
|
|
}
|
|
|
|
// draw labels
|
|
|
|
ctx.fillStyle = COLOR_NAME;
|
|
|
|
ctx.textAlign = "left";
|
|
|
|
var lbl = meta.label;
|
|
|
|
if (tags.length > 0) {
|
|
|
|
lbl += " (" + tags.join(', ') + ")";
|
|
|
|
}
|
|
|
|
ctx.fillText(lbl, 5, fh);
|
|
|
|
}
|
|
|
|
changeInputValue(row) {
|
|
|
|
var meta = this.meta[row];
|
|
|
|
if (!meta)
|
|
|
|
return;
|
|
|
|
var data = this.wfp.getSignalData(row, this.t0, 1);
|
|
|
|
var oldValue = data[0] || 0;
|
|
|
|
var min = 0;
|
|
|
|
var max = (1 << meta.len) - 1;
|
|
|
|
//console.log(meta, oldValue, min, max);
|
|
|
|
if (max == 1) {
|
|
|
|
this.wfp.setSignalValue(row, oldValue > 0 ? 0 : 1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var rangestr = `${min} to ${max}`;
|
|
|
|
bootbox.prompt({
|
|
|
|
value: oldValue + "",
|
|
|
|
inputType: "number",
|
|
|
|
//min: 0,
|
|
|
|
//max: meta.len-1,
|
|
|
|
//placeholder: rangestr,
|
2022-09-14 04:33:52 +00:00
|
|
|
title: `Enter new value for "${dompurify_1.default.sanitize(meta.label)}" (${rangestr}):`,
|
2022-02-21 15:35:52 +00:00
|
|
|
callback: (result) => {
|
|
|
|
if (result != null) {
|
|
|
|
var value = parseInt(result);
|
|
|
|
if (value >= min && value <= max) {
|
|
|
|
this.wfp.setSignalValue(row, parseInt(result));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
exports.WaveformView = WaveformView;
|
|
|
|
//# sourceMappingURL=waveform.js.map
|