// Copyright (c) 2016-17 Walter Bender
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the The GNU Affero General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// You should have received a copy of the GNU Affero General Public
// License along with this library; if not, write to the Free Software
// Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
// This widget makes displays the status of selected parameters and
// notes as they are being played.
function StatusMatrix() {
const BUTTONDIVWIDTH = 128;
const BUTTONSIZE = 53;
const ICONSIZE = 32;
const OUTERWINDOWWIDTH = 620;
const INNERWINDOWWIDTH = OUTERWINDOWWIDTH - BUTTONSIZE * 1.5;
docById('statusDiv').style.visibility = 'hidden';
this.init = function(logo) {
// Initializes the status matrix. First removes the
// previous matrix and them make another one in DOM (document
// object model)
this._logo = logo;
var w = window.innerWidth;
this._cellScale = w / 1200;
var iconSize = ICONSIZE * this._cellScale;
var canvas = docById('myCanvas');
// Position the widget and make it visible.
var statusDiv = docById('statusDiv');
statusDiv.style.visibility = 'visible';
statusDiv.setAttribute('draggable', 'true');
statusDiv.style.left = '200px';
statusDiv.style.top = '150px';
// The status buttons
var statusButtonsDiv = docById('statusButtonsDiv');
statusButtonsDiv.style.display = 'inline';
statusButtonsDiv.style.visibility = 'visible';
statusButtonsDiv.style.width = BUTTONDIVWIDTH;
statusButtonsDiv.innerHTML = '
';
var buttonTable = docById('statusButtonTable');
var header = buttonTable.createTHead();
var row = header.insertRow(0);
// For the button callbacks
var that = this;
var cell = this._addButton(row, 'close-button.svg', ICONSIZE, _('close'));
cell.onclick=function() {
statusTableDiv.style.visibility = 'hidden';
statusButtonsDiv.style.visibility = 'hidden';
statusDiv.style.visibility = 'hidden';
}
// We use this cell as a handle for dragging.
var dragCell = this._addButton(row, 'grab.svg', ICONSIZE, _('drag'));
dragCell.style.cursor = 'move';
this._dx = dragCell.getBoundingClientRect().left - statusDiv.getBoundingClientRect().left;
this._dy = dragCell.getBoundingClientRect().top - statusDiv.getBoundingClientRect().top;
this._dragging = false;
this._target = false;
this._dragCellHTML = dragCell.innerHTML;
dragCell.onmouseover = function(e) {
// In order to prevent the dragged item from triggering a
// browser reload in Firefox, we empty the cell contents
// before dragging.
dragCell.innerHTML = '';
};
dragCell.onmouseout = function(e) {
if (!that._dragging) {
dragCell.innerHTML = that._dragCellHTML;
}
};
canvas.ondragover = function(e) {
e.preventDefault();
};
canvas.ondrop = function(e) {
if (that._dragging) {
that._dragging = false;
var x = e.clientX - that._dx;
statusDiv.style.left = x + 'px';
var y = e.clientY - that._dy;
statusDiv.style.top = y + 'px';
dragCell.innerHTML = that._dragCellHTML;
}
};
statusDiv.ondragover = function(e) {
e.preventDefault();
};
statusDiv.ondrop = function(e) {
if (that._dragging) {
that._dragging = false;
var x = e.clientX - that._dx;
statusDiv.style.left = x + 'px';
var y = e.clientY - that._dy;
statusDiv.style.top = y + 'px';
dragCell.innerHTML = that._dragCellHTML;
}
};
statusDiv.onmousedown = function(e) {
that._dragging = true;
that._target = e.target;
};
statusDiv.ondragstart = function(e) {
if (dragCell.contains(that._target)) {
e.dataTransfer.setData('text/plain', '');
} else {
e.preventDefault();
}
};
// The status table
var statusTableDiv = docById('statusTableDiv');
statusTableDiv.style.display = 'inline';
statusTableDiv.style.visibility = 'visible';
statusTableDiv.style.border = '0px';
// We use an outer div to scroll vertically and an inner div to
// scroll horizontally.
statusTableDiv.innerHTML = '';
var n = Math.max(Math.floor((window.innerHeight * 0.5) / 100), 8);
var outerDiv = docById('statusOuterDiv');
if (this._logo.turtles.turtleList.length > n) {
outerDiv.style.height = this._cellScale * MATRIXSOLFEHEIGHT * (n + 2) + 'px';
var w = Math.max(Math.min(window.innerWidth, this._cellScale * OUTERWINDOWWIDTH), BUTTONDIVWIDTH);
outerDiv.style.width = w + 'px';
} else {
if (this._logo.statusFields.length > 4) { // Assume we need a horizontal slider
outerDiv.style.height = this._cellScale * (MATRIXBUTTONHEIGHT2 + (2 + MATRIXSOLFEHEIGHT) * this._logo.turtles.turtleList.length) + 30 + 'px';
} else {
outerDiv.style.height = this._cellScale * (MATRIXBUTTONHEIGHT2 + (2 + MATRIXSOLFEHEIGHT) * this._logo.turtles.turtleList.length) + 'px';
}
var w = Math.max(Math.min(window.innerWidth, this._cellScale * OUTERWINDOWWIDTH - 20), BUTTONDIVWIDTH);
outerDiv.style.width = w + 'px';
}
var w = Math.max(Math.min(window.innerWidth, this._cellScale * INNERWINDOWWIDTH), BUTTONDIVWIDTH - BUTTONSIZE);
var innerDiv = docById('statusInnerDiv');
innerDiv.style.width = w + 'px';
innerDiv.style.marginLeft = (BUTTONSIZE * this._cellScale) + 'px';
// Each row in the status table contains a note label in the
// first column and a table of buttons in the second column.
var statusTable = docById('statusTable');
var header = statusTable.createTHead();
var row = header.insertRow();
var iconSize = Math.floor(this._cellScale * 24);
var cell = row.insertCell();
cell.style.backgroundColor = MATRIXBUTTONCOLOR;
cell.className = 'headcol';
cell.style.height = Math.floor(MATRIXBUTTONHEIGHT * this._cellScale) + 'px';
cell.style.width = (BUTTONSIZE * this._cellScale) + 'px';
cell.innerHTML = ' ';
// One column per field
for (var i = 0; i < this._logo.statusFields.length; i++) {
var cell = row.insertCell(i + 1);
cell.style.fontSize = Math.floor(this._cellScale * 100) + '%';
switch (this._logo.statusFields[i][1]) {
case 'plus':
case 'minus':
case 'neg':
case 'divide':
case 'power':
case 'multiply':
case 'sqrt':
case 'int':
case 'mod':
var label = '';
break;
case 'namedbox':
var label = this._logo.blocks.blockList[this._logo.statusFields[i][0]].privateData;
break;
default:
var label = this._logo.blocks.blockList[this._logo.statusFields[i][0]].protoblock.staticLabels[0];
break;
}
cell.innerHTML = ' ' + label + ' '
cell.style.height = Math.floor(MATRIXBUTTONHEIGHT * this._cellScale) + 'px';
cell.style.backgroundColor = MATRIXBUTTONCOLOR;
}
if (_THIS_IS_MUSIC_BLOCKS_) {
var cell = row.insertCell();
cell.style.fontSize = Math.floor(this._cellScale * 100) + '%';
cell.innerHTML = ' ' + _('note') + ' '
cell.style.height = Math.floor(MATRIXBUTTONHEIGHT * this._cellScale) + 'px';
cell.style.backgroundColor = MATRIXBUTTONCOLOR;
}
// One row per voice (turtle)
var activeTurtles = 0;
for (var turtle = 0; turtle < this._logo.turtles.turtleList.length; turtle++) {
if (this._logo.turtles.turtleList[turtle].trash) {
continue;
}
var row = header.insertRow();
var cell = row.insertCell();
cell.style.backgroundColor = MATRIXLABELCOLOR;
if (_THIS_IS_MUSIC_BLOCKS_) {
cell.innerHTML = ' ';
} else {
cell.innerHTML = ' ';
}
cell.style.width = (BUTTONSIZE * this._cellScale) + 'px';
cell.style.height = Math.floor(MATRIXSOLFEHEIGHT * this._cellScale) + 'px';
cell.className = 'headcol';
if (_THIS_IS_MUSIC_BLOCKS_) {
// + 1 is for the note column
for (var i = 0; i < this._logo.statusFields.length + 1; i++) {
var cell = row.insertCell();
cell.style.backgroundColor = MATRIXRHYTHMCELLCOLOR;
cell.style.fontSize = Math.floor(this._cellScale * 100) + '%';
cell.innerHTML = '';
cell.style.height = Math.floor(MATRIXSOLFEHEIGHT * this._cellScale) + 'px';
}
} else {
for (var i = 0; i < this._logo.statusFields.length; i++) {
var cell = row.insertCell();
cell.style.backgroundColor = MATRIXRHYTHMCELLCOLOR;
cell.style.fontSize = Math.floor(this._cellScale * 100) + '%';
cell.innerHTML = '';
cell.style.height = Math.floor(MATRIXSOLFEHEIGHT * this._cellScale) + 'px';
}
}
activeTurtles += 1;
}
};
this.updateAll = function() {
// Update status of all of the voices in the matrix.
var table = docById('statusTable');
this._logo.updatingStatusMatrix = true;
var activeTurtles = 0;
for (var turtle = 0; turtle < this._logo.turtles.turtleList.length; turtle++) {
if (this._logo.turtles.turtleList[turtle].trash) {
continue;
}
for (var i = 0; i < this._logo.statusFields.length; i++) {
var saveStatus = this._logo.inStatusMatrix;
this._logo.inStatusMatrix = false;
this._logo.parseArg(this._logo, turtle, this._logo.statusFields[i][0]);
switch (this._logo.blocks.blockList[this._logo.statusFields[i][0]].name) {
case 'x':
case 'y':
case 'heading':
var value = this._logo.blocks.blockList[this._logo.statusFields[i][0]].value.toFixed(2);
break;
case 'elapsednotes':
var value = mixedNumber(this._logo.blocks.blockList[this._logo.statusFields[i][0]].value);
break;
case 'namedbox':
var name = this._logo.blocks.blockList[this._logo.statusFields[i][0]].privateData;
if (name in this._logo.boxes) {
var value = this._logo.boxes[name];
} else {
var value = '';
}
break;
default:
var value = this._logo.blocks.blockList[this._logo.statusFields[i][0]].value;
break;
}
var innerHTML = value;
this._logo.inStatusMatrix = saveStatus;
var cell = table.rows[activeTurtles + 1].cells[i + 1];
if (cell != null) {
cell.innerHTML = innerHTML;
}
}
if (_THIS_IS_MUSIC_BLOCKS_) {
var note = '';
var value = '';
if (this._logo.noteStatus[turtle] != null) {
var notes = this._logo.noteStatus[turtle][0];
for (var j = 0; j < notes.length; j++) {
note += notes[j];
if (typeof(notes[j]) === 'number') {
note += 'Hz ';
} else {
note += ' ';
}
}
var value = this._logo.noteStatus[turtle][1];
var obj = rationalToFraction(value);
note += obj[1] + '/' + obj[0];
}
var cell = table.rows[activeTurtles + 1].cells[i + 1];
if (cell != null) {
cell.innerHTML = note.replace(/#/g, '♯').replace(/b/, '♭');
}
}
activeTurtles += 1;
}
this._logo.updatingStatusMatrix = false;
};
this._addButton = function(row, icon, iconSize, label) {
var cell = row.insertCell(-1);
cell.innerHTML = ' ';
cell.style.width = BUTTONSIZE + 'px';
cell.style.minWidth = cell.style.width;
cell.style.maxWidth = cell.style.width;
cell.style.height = cell.style.width;
cell.style.minHeight = cell.style.height;
cell.style.maxHeight = cell.style.height;
cell.style.backgroundColor = MATRIXBUTTONCOLOR;
cell.onmouseover=function() {
this.style.backgroundColor = MATRIXBUTTONCOLORHOVER;
}
cell.onmouseout=function() {
this.style.backgroundColor = MATRIXBUTTONCOLOR;
}
return cell;
};
};