/**
* @file Class GlobalController
* @version March 26, 2017
*
* @author Olivier Pirson --- http://www.opimedia.be/
* @license GPLv3 --- Copyright (C) 2017 Olivier Pirson
*/
/**
* Controller for global controls of all matchings and views.
*/
class GlobalController {
/**
* Construct a controller for the global view
* and link to two controllers of matching.
*
* @param {GlobalView} globalView
* @param {MatchingController} leftMatchingController
* @param {MatchingController} rightMatchingController
*/
constructor(globalView, leftMatchingController, rightMatchingController) {
assert(globalView instanceof GlobalView, globalView);
assert(leftMatchingController instanceof MatchingController, leftMatchingController);
assert(rightMatchingController instanceof MatchingController, rightMatchingController);
this._globalView = globalView;
this._leftController = leftMatchingController;
this._rightController = rightMatchingController;
// Set values from HTML elements
this._changeAction();
this._changeVerticalHorizontal();
this._setEventListeners();
}
/**
* Returns the global view.
*/
get globalView() { return this._globalView; }
/**
* Returns the left controller.
*/
get leftController() { return this._leftController; }
/**
* Returns the right controller.
*/
get rightController() { return this._rightController; }
/**
* Fill the list of matchings with all possible perfect matchings
* with the set of points of the left matching.
*
* If disjointMatching
* then keep only perfect matching disjoint with this matching.
*
* If compatibleMatching
* then keep only perfect matching disjoint with this matching.
*
* @param {Boolean} disjointMatching
* @param {Boolean} compatibleMatching
*/
_buildPerfectMatchings(disjointMatching, compatibleMatching) {
assert(typeof disjointMatching === "boolean", disjointMatching);
assert(typeof compatibleMatching === "boolean", compatibleMatching);
const thisGlobalController = this;
this._runWithWaitMessage(function () {
thisGlobalController._leftController.matching.setPerfectMatchings(disjointMatching, compatibleMatching);
thisGlobalController._globalView.update();
});
}
/**
* Fill the list of matchings with a shortest transformation
* between left and right matchings.
*
* @param {Boolean} disjointTransformation
*/
_buildTransformation(disjointTransformation) {
assert(typeof disjointTransformation === "boolean", disjointTransformation);
const thisGlobalController = this;
this._runWithWaitMessage(function () {
const found = thisGlobalController._leftController.matching.setTransformation(disjointTransformation);
thisGlobalController._globalView.update();
if (!found) {
thisGlobalController._warningMessage("Not found!");
}
});
}
/**
* Set action property for two controllers of matching.
*/
_changeAction() {
this._leftController.changeAction();
this._rightController.changeAction();
}
/**
* Set drawSegment property for two controllers of matching.
*/
_changeDrawSegments() {
this._leftController.changeDrawSegments();
this._rightController.changeDrawSegments();
this._globalView.update(null, false);
}
/**
* Set vertical-horizontal property for two controllers of matching.
*/
_changeVerticalHorizontal() {
this._leftController.changeVerticalHorizontal();
this._rightController.changeVerticalHorizontal();
}
/**
* Reset the two controllers.
*/
_clear() {
this._leftController.matching.clearIntermediaryLinkedMatchings();
this._leftController.clear();
this._rightController.clear();
}
/**
* Reset the list of matchings.
*/
_clearTransformation() {
this._leftController.matching.clearIntermediaryLinkedMatchings();
this._globalView.update();
}
/**
* Load matchings from a text format
* (in the format such that returns by Matching.matchingsToString())
* and update the global view.
*
* @param {String} text
*/
_load(text) {
assert(typeof text === "string", text);
this._clear();
const lines = text.split(/\D+/); // split for each number (ignore other stuffs)
// Read points
const numberPoints = parseInt(lines.shift(), 10);
for (let i = 0; i < numberPoints; ++i) {
const x = parseInt(lines.shift(), 10);
const y = parseInt(lines.shift(), 10);
this._leftController.matching.pointAdd(new Point(x, y));
}
// Read matchings
const numberMatchings = parseInt(lines.shift(), 10);
const linkedMatchings = [this._leftController.matching];
for (let i = 1; i < numberMatchings - 1; ++i) {
linkedMatchings.push(new Matching());
}
linkedMatchings.push(this._rightController.matching);
for (let i = 0; i < numberMatchings; ++i) {
// Read segments
const numberSegments = parseInt(lines.shift(), 10);
for (let j = 0; j < numberSegments; ++j) {
const indexA = parseInt(lines.shift(), 10);
const indexB = parseInt(lines.shift(), 10);
linkedMatchings[i].segmentAdd(new Segment(this._leftController.matching.points[indexA], this._leftController.matching.points[indexB]));
}
}
for (let linkedMatching of linkedMatchings) {
linkedMatching._linkedMatchings = linkedMatchings;
}
this._globalView.update();
}
/**
* Load matchings from a local text file
* (in the format such that returns by Matching.matchingsToString())
* and update the global view.
*
* @param {Event} event
*/
_loadFile(event) {
assert(event instanceof Event, event);
const file = document.getElementById("button-load").files[0];
const fileReader = new FileReader();
const controller = this;
fileReader.onload = function(event) {
controller._load(event.target.result.trim());
};
fileReader.readAsText(file);
}
/**
* Load matchings from a text file
* (in the format such that returns by Matching.matchingsToString())
* and update the global view.
*
* @param {String} url
*/
_loadUrl(url) {
assert(typeof url === "string", url);
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.setRequestHeader("Content-Type", "plain/text");
const controller = this;
xhr.onload = function() {
if ((xhr.readyState !== 4) || (xhr.status !== 200)) {
return;
}
controller._load(xhr.responseText);
location.hash = "3-Experiment-by-yourself-1-Interactive-application";
};
xhr.send(null);
}
/**
* Display a waiting message,
* run the function
* and then remove the waiting message.
*
* @param {function} func
*/
_runWithWaitMessage(func) {
assert(typeof func === "function", func);
this._waitShow();
const thisGlobalController = this;
window.setTimeout(function () {
func();
window.setTimeout(thisGlobalController._waitHide, 100);
}, 100);
}
/**
* Save all matchings in a text file named "matchings.txt"
* (in the format returns by Matching.matchingsToString()).
*
* Warning! Only integers are valid.
*/
_saveToFile() {
// Create content
const text = this._leftController.matching.matchingsToString();
const blob = new Blob([text], {type:"text/plain"});
// Create a link to content
const itemA = document.createElement("a");
itemA.download = "matchings.txt";
itemA.href = window.URL.createObjectURL(blob);
itemA.onclick = function (event) { document.body.removeChild(event.target); };
itemA.style.display = "none";
document.body.appendChild(itemA);
// Click to the link
itemA.click();
}
/**
* Swap segments between the left and right matchings.
*/
_swap() {
this._leftController.matching.clearIntermediaryLinkedMatchings();
const tmp = this._leftController.matching._segments;
this._leftController.matching._segments = this._rightController.matching._segments;
this._rightController.matching._segments = tmp;
this._globalView.update();
}
/**
* Set listeners on the view.
*/
_setEventListeners() {
const controller = this;
// Set canonical to left matching
const canonicalLeft = function (event) {
controller._leftController.matching.clearIntermediaryLinkedMatchings();
controller._leftController.matching.setCanonical();
controller._globalView.update();
};
document.getElementById("button-canonical-left").addEventListener("click", canonicalLeft, false);
// Set canonical to right matching
const canonicalRight = function (event) {
controller._leftController.matching.clearIntermediaryLinkedMatchings();
controller._rightController.matching.setCanonical();
controller._globalView.update();
};
document.getElementById("button-canonical-right").addEventListener("click", canonicalRight, false);
// Shuffle segments in left matching
const shuffleLeft = function (event) {
controller._leftController.matching.clearIntermediaryLinkedMatchings();
controller._leftController.matching.shuffleSegments();
controller._globalView.update();
};
document.getElementById("button-shuffle-left").addEventListener("click", shuffleLeft, false);
// Shuffle segments in right matching
const shuffleRight = function (event) {
controller._leftController.matching.clearIntermediaryLinkedMatchings();
controller._rightController.matching.shuffleSegments();
controller._globalView.update();
};
document.getElementById("button-shuffle-right").addEventListener("click", shuffleRight, false);
// Build list perfect matchings
document.getElementById("button-build-list-perfect-matchings").addEventListener("click",
function () { controller._buildPerfectMatchings(false, false); },
false);
// Build list disjoint perfect matchings
document.getElementById("button-build-list-disjoint-perfect-matchings").addEventListener("click",
function () { controller._buildPerfectMatchings(true, false); },
false);
// Build list compatible perfect matchings
document.getElementById("button-build-list-compatible-perfect-matchings").addEventListener("click",
function () { controller._buildPerfectMatchings(false, true); },
false);
// Build list disjoint compatible perfect matchings
document.getElementById("button-build-list-disjoint-compatible-perfect-matchings").addEventListener("click",
function () { controller._buildPerfectMatchings(true, true); },
false);
// Build list transformation
document.getElementById("button-build-list-transformation").addEventListener("click",
function () { controller._buildTransformation(false); },
false);
// Build list disjoint transformation
document.getElementById("button-build-list-disjoint-transformation").addEventListener("click",
function () { controller._buildTransformation(true); },
false);
// Clear list matchings
document.getElementById("button-clear-list-matchings").addEventListener("click",
function () { controller._clearTransformation(); },
false);
// Clear
document.getElementById("button-clear").addEventListener("click",
function () { controller._clear(); },
false);
// Load from file
document.getElementById("button-load").addEventListener("change",
function (event) { controller._loadFile(event); },
false);
this._setEventListenersLoadExample();
// Save to file
document.getElementById("button-save").addEventListener("click",
function () { controller._saveToFile(); },
false);
// Swap
document.getElementById("button-swap").addEventListener("click",
function () { controller._swap(); },
false);
// Change action mode
document.getElementById("radio-action-point").addEventListener("change",
function () { controller._changeAction(); },
false);
document.getElementById("radio-action-segment").addEventListener("change",
function () { controller._changeAction(); },
false);
// Change draw segments mode
document.getElementById("radio-draw-segments-only").addEventListener("change",
function () { controller._changeDrawSegments(); },
false);
document.getElementById("radio-draw-segments-consecutive").addEventListener("change",
function () { controller._changeDrawSegments(); }, false);
document.getElementById("radio-draw-segments-all").addEventListener("change",
function () { controller._changeDrawSegments(); },
false);
// Change vertical-horizontal mode
document.getElementById("checkbox-vertical-horizontal").addEventListener("change",
function () { controller._changeVerticalHorizontal(); },
false);
// Resize the container of list matchings
window.addEventListener("resize",
function () { controller.globalView.resizeListMatchings(); },
false);
}
/**
* Set listeners on the view to load examples.
*/
_setEventListenersLoadExample() {
const controller = this;
const imgs = document.getElementsByTagName('img');
for (let img of imgs) {
const id = img.id;
if (id) {
const matches = /^(.+)-example$/.exec(id);
const name = matches[1];
if (name) {
img.title = "Click to this picture to load it to the two interactive zones.";
img.addEventListener("click",
function (event) { controller._loadUrl("public/examples/" + name + ".txt"); },
false);
}
}
}
}
/**
* Hide the waiting message.
*/
_waitHide() {
document.getElementById("wait-container").style.display = "none";
}
/**
* Show the waiting message.
*/
_waitShow() {
document.getElementById("wait-container").style.display = "flex";
}
/**
* Show temporary the warning message.
*/
_warningMessage(message) {
assert(typeof message === "string", message);
const container = document.getElementById("warning-container");
container.innerHTML = message;
container.style.display = "flex";
window.setTimeout(function () {
container.style.display = "none";
}, 2000);
}
}