5520 lines
258 KiB
JavaScript
5520 lines
258 KiB
JavaScript
/**********************************************************************************************
|
|
* panel Servlet - Draw JMRI panels on browser screen
|
|
* Retrieves panel xml from JMRI and builds panel client-side from that xml, including
|
|
* click functions. Sends and listens for changes to panel elements using the JSON WebSocket server.
|
|
* If no parm "name" passed, page will list links to available panels.
|
|
* Include parm protect=yes to treat panel as read-only
|
|
* Approach: Read panel's xml and create widget objects in the browser with all needed attributes.
|
|
* There are 5 "widgetFamily"s: text, input, icon, drawn and switch. States are handled by storing member's
|
|
* iconX, textX, cssX where X is the state. The corresponding members are "shown" whenever the state changes.
|
|
* CSS classes are used throughout to attach events to correct widgets, as well as control appearance.
|
|
* The JSON type is used to send changes to JSON server and to listen for changes made elsewhere.
|
|
* Drawn widgets are handled by drawing directly on the javascript "canvas" layer.
|
|
* Switch widgets are handled by drawing directly on an individual javascript "canvas", placed in a flexbox layout.
|
|
*
|
|
* See java/src/jmri/server/json/JsonNamedBeanSocketService.java#onMessage() for GET method that adds a listener.
|
|
* See JMRI Web Server - Panel Servlet in help/en/html/web/PanelServlet.shtml for an example description of
|
|
* the interaction between the Web Servlets, the Web Browser and the JMRI application.
|
|
*
|
|
* TODO: show error dialog while retrying connection
|
|
* TODO: add Cancel button to return to home page on errors (not found, etc.)
|
|
* TODO: handle "&" in usernames (see Indicator Demo 00.xml)
|
|
* TODO: update drawn track on color and width changes (would need to create system objects to reflect these chgs)
|
|
* TODO: research movement of locoicons ("promote" locoicon to system entity in JMRI?, add panel-level listeners?)
|
|
* TODO: deal with mouseleave, mouseout, touchout, etc. Slide off Stop button on rb1 for example.
|
|
* TODO: alignment of text sensorIcons without fixed width is very different. Recommended workaround is to use fixed width.
|
|
* TODO: add support for slipturnouticon (one2beros)
|
|
* TODO: handle (and test) disableWhenOccupied for layoutslip
|
|
* TODO: handle block color and track widths for turntable raytracks
|
|
* TODO: fix JMRI WARN about username on memoryicons
|
|
*
|
|
**********************************************************************************************/
|
|
|
|
var log = new Logger();
|
|
|
|
//persistent (global) variables
|
|
var $gWidgets = {}; //array of all widget objects, key=CSSId
|
|
var $gPanelList = {}; //store list of available panels
|
|
var $gPanel = {}; //store overall panel info
|
|
var whereUsed = {}; //associative array of array of elements indexed by systemName or userName
|
|
var audioIconIDs = {}; //associative array of audio icons
|
|
var occupancyNames = {}; //associative array of array of elements indexed by occupancy sensor name
|
|
var $oblockNames = {}; //associative array of array of elements indexed by occupancy block name (CPE panels)
|
|
var $gPts = {}; //array of all points, key="pointname.pointtype" (used for layoutEditor panels)
|
|
var $gBlks = {}; //array of all blocks, key="blockname" (used for layoutEditor panels)
|
|
var $gCtx; //persistent context of canvas layer
|
|
var $gDashArray = [12, 12]; //on,off of dashed lines
|
|
var $rows = 1; //persistent storage of shared switchboard property number of rows, if 0 use autoRows
|
|
var $total = 1; //persistent storage of shared switchboard property total number of items displayed
|
|
var $autoRows = 0;
|
|
var $activeColor = 'red';
|
|
var $inactiveColor = 'gray';
|
|
var $unknownColor = 'gray';
|
|
var $showUserName = 'no';
|
|
var DOWNEVENT = 'touchstart mousedown'; // check both touch and mouse events
|
|
var UPEVENT = 'touchend mouseup';
|
|
var BLUR = 'blur';
|
|
var KEYUP = 'keyup';
|
|
var CHANGE = 'change';
|
|
|
|
var SIZE = 3; // default factor for circles
|
|
|
|
var UNKNOWN = '0'; // constants to match JSON Server state names
|
|
var ACTIVE = '2';
|
|
var CLOSED = '2';
|
|
var INACTIVE = '4';
|
|
var THROWN = '4';
|
|
var INCONSISTENT = '8';
|
|
|
|
var ALLOCATED = 0x10; // constants to match JSON Server oblock status names
|
|
var RUNNING = 0x20; // Oblock that running train has reached
|
|
var OUT_OF_SERVICE = 0x40; // Oblock that should not be used
|
|
var TRACK_ERROR = 0x80; // Oblock has Error
|
|
|
|
var CLOSEDCLOSED = '5'; // constants for slipturnouticon
|
|
var CLOSEDTHROWN = '7';
|
|
var THROWNCLOSED = '9';
|
|
var THROWNTHROWN = '11';
|
|
|
|
var PT_CEN = ".POS_POINT"; // named constants for point types
|
|
var PT_A = ".TURNOUT_A";
|
|
var PT_B = ".TURNOUT_B";
|
|
var PT_C = ".TURNOUT_C";
|
|
var PT_D = ".TURNOUT_D";
|
|
|
|
var LEVEL_XING_A = ".LEVEL_XING_A";
|
|
var LEVEL_XING_B = ".LEVEL_XING_B";
|
|
var LEVEL_XING_C = ".LEVEL_XING_C";
|
|
var LEVEL_XING_D = ".LEVEL_XING_D";
|
|
|
|
var SLIP_A = ".SLIP_A";
|
|
var SLIP_B = ".SLIP_B";
|
|
var SLIP_C = ".SLIP_C";
|
|
var SLIP_D = ".SLIP_D";
|
|
|
|
var STATE_AC = 0x02;
|
|
var STATE_BD = 0x04;
|
|
var STATE_AD = 0x06;
|
|
var STATE_BC = 0x08;
|
|
|
|
var DARK = 0x00; //named constants for signalhead states
|
|
var RED = 0x01;
|
|
var FLASHRED = 0x02;
|
|
var YELLOW = 0x04;
|
|
var FLASHYELLOW = 0x08;
|
|
var GREEN = 0x10;
|
|
var FLASHGREEN = 0x20;
|
|
var LUNAR = 0x40;
|
|
var FLASHLUNAR = 0x80;
|
|
var HELD = 0x0100; //additional to deal with "Held" pseudo-state
|
|
|
|
var RH_TURNOUT = "RH_TURNOUT"; //named constants for turnout types
|
|
var LH_TURNOUT = "LH_TURNOUT";
|
|
var WYE_TURNOUT = "WYE_TURNOUT";
|
|
var DOUBLE_XOVER = "DOUBLE_XOVER";
|
|
var RH_XOVER = "RH_XOVER";
|
|
var LH_XOVER = "LH_XOVER";
|
|
var SINGLE_SLIP = "SINGLE_SLIP";
|
|
var DOUBLE_SLIP = "DOUBLE_SLIP";
|
|
|
|
var jmri = null;
|
|
|
|
var jmri_logging = false;
|
|
|
|
/******************************************************************
|
|
* ======= Debug functions =======
|
|
*/
|
|
|
|
// log object properties
|
|
function $logProperties(obj) {
|
|
if (jmri_logging) {
|
|
var $propList = "";
|
|
for (var $propName in obj) {
|
|
if (isDefined(obj[$propName])) {
|
|
$propList += ($propName + "='" + obj[$propName] + "', ");
|
|
}
|
|
}
|
|
log.log("$logProperties(obj): " + $propList + ".");
|
|
}
|
|
}
|
|
|
|
function isUndefined(x) {
|
|
return (typeof x === "undefined");
|
|
}
|
|
|
|
function isDefined(x) {
|
|
return (typeof x !== "undefined");
|
|
}
|
|
|
|
/******************************************************************
|
|
* ======= Primary functions =======
|
|
*/
|
|
|
|
// request the panel xml from the server, and set up callback to process the response
|
|
var requestPanelXML = function(panelName) {
|
|
$("#activity-alert").addClass("show").removeClass("hidden");
|
|
$.ajax({
|
|
type: "GET",
|
|
url: "/panel/" + panelName + "?format=xml", // request proper url
|
|
success: function(data, textStatus, jqXHR) {
|
|
processPanelXML(data, textStatus, jqXHR);
|
|
setTitle($gPanel["name"]); // set final title once load completes, helps with testing
|
|
// set new attribute data-panel-name on the panel-area div to the panel name so that a user's css can use it.
|
|
$("#panel-area").attr("data-panel-name", $gPanel["name"]);
|
|
},
|
|
error: function( jqXHR, textStatus, errorThrown) {
|
|
alert("Error retrieving panel xml from server. Please press OK to retry.\n\nDetails: " +
|
|
textStatus + " - " + errorThrown);
|
|
window.location = window.location.pathname;
|
|
},
|
|
async: true,
|
|
timeout: 15000, // very long timeout, since this can be a slow process for complicated panels
|
|
dataType: "xml"
|
|
});
|
|
};
|
|
|
|
// process the response returned for the requestPanelXML command
|
|
function processPanelXML($returnedData, $success, $xhr) {
|
|
|
|
$('div#messageText').text("rendering panel from xml, please wait...");
|
|
$("#activity-alert").addClass("show").removeClass("hidden");
|
|
var $xml = $($returnedData); //jQuery-ize returned data for easier access
|
|
|
|
//remove whitespace
|
|
$xml.xmlClean();
|
|
|
|
//get the panel-level values from the xml
|
|
var $panel = $xml.find('panel');
|
|
$($panel[0].attributes).each(function() {
|
|
$gPanel[this.name] = this.value;
|
|
});
|
|
$("#panel-area").width($gPanel.width);
|
|
$("#panel-area").height($gPanel.height);
|
|
|
|
//override "Allow Layout Control" attribute when protect parm set to "yes"
|
|
var protect = getParameterByName("protect");
|
|
if (protect == "yes") {
|
|
$gPanel["controlling"] = "no";
|
|
}
|
|
|
|
// insert the canvas layer and set up context used by LayoutEditor "drawn" objects, set some defaults
|
|
if ($gPanel.paneltype == "LayoutPanel") {
|
|
createPanelCanvas(); //insure canvas layer is available for drawing
|
|
}
|
|
|
|
// set up context used by SwitchboardEditor "beanswitch" objects, set some defaults
|
|
if ($gPanel.paneltype == "Switchboard") {
|
|
$("#panel-area").width("100%"); // reset to fill the (mobile) screen
|
|
$("#panel-area").height("100%"); // reset to fill the (mobile) screen
|
|
// background color already set for #panel-area, inherited
|
|
$activeColor = $gPanel.activecolor;
|
|
$inactiveColor = $gPanel.inactivecolor;
|
|
$showUserName = $gPanel.showusername;
|
|
$total = Number($gPanel.total);
|
|
$rows = Number($gPanel.rows);
|
|
if ($rows == 0) { // AutoRows set, automatically choose grid showing largest tiles using flexbox
|
|
$("#panel-area").css({'display': "flex", 'flex-flow': "row wrap"})
|
|
$autoRows = 1;
|
|
$rows = autoRows(window.screen.width, window.screen.height - 200); // use (mobile) screen size, leave space for header
|
|
// check browser window (window.innerWidth) size vs whole screen (window.screen.width)
|
|
$swWidth = Math.ceil(0.95*Math.min(window.screen.width, window.innerWidth)*Math.max(0.01, Math.min(1, 1/(Math.ceil($total/$rows)))));
|
|
$swWidth = Math.max(Math.min($swWidth, 200), 70); // catch extreme width result
|
|
$swHeight = Math.ceil(0.9*Math.min(window.screen.height, window.innerHeight)/Math.max(0.01, $rows));
|
|
$swHeight = Math.max($swHeight, 90); // minimum height to display 2 labels
|
|
// 0.9 to leave room for the Switchboard name label at top
|
|
} else {
|
|
$swWidth = Math.ceil(0.95*($gPanel.panelwidth)*Math.max(0.01, Math.min(1, 1/(Math.ceil($total/$rows)))));
|
|
// calculate from jmri rows number, 95pc to fit on screen
|
|
// Math.min(1,... to prevent >100% width calc (when hide unconnected selected)
|
|
// Math.max(0.001,... to prevent 0 width in case 0 items are connected
|
|
// 1/Math.ceil($total/$rows) to account for unused tiles:
|
|
// include RxC unused cells in calc: for 22 switches we need at least 24 tiles (4x6, 3x8, 2x12 etc)
|
|
$swHeight = Math.ceil(0.9*$gPanel.panelheight/Math.max(0.01, $rows));
|
|
// Math.max(0.001,... to prevent 0 division in case 0 items are connected
|
|
}
|
|
var onOffSpans = "";
|
|
if (($gPanel.type == "L") && ($gPanel.controlling == "yes")) {
|
|
// handlers to switch on/off, I18N
|
|
onOffSpans = " <span id='allOff' class='lightswitch'>All Off</span> <span id='allOn' class='lightswitch'>All On</span>";
|
|
// handlers added later
|
|
}
|
|
// add short banner at top of Swb
|
|
$("#panel-area").append("<div id='name' class='show' style='color: " + $gPanel.defaulttextcolor +
|
|
";'> Switchboard "" + $gPanel.name + "" (conn: " +
|
|
$gPanel.connection + ", type: " + $gPanel.type + ")" + onOffSpans + "</div>"); // TODO I18N
|
|
}
|
|
|
|
// process all elements in the panel xml, drawing them on screen, and building persistent array of widgets
|
|
$panel.contents().each(
|
|
function() {
|
|
var $widget = new Array();
|
|
$widget['widgetType'] = this.nodeName;
|
|
$widget['scale'] = "1"; //default to no scale
|
|
$widget['degrees'] = 0; //default to no rotation
|
|
$widget['rotation'] = 0; // default to no rotation
|
|
// convert attributes to an object array
|
|
$(this.attributes).each(function() {
|
|
$widget[this.name] = this.value;
|
|
});
|
|
//default various css attributes to not-set, then set in later code as needed
|
|
var $hoverText = "";
|
|
|
|
//set value as JMRI would if "Allow Layout Control" turned off
|
|
if ($gPanel.controlling != "yes") {
|
|
$widget['forcecontroloff'] = "true";
|
|
}
|
|
|
|
// add and normalize the various type-unique values, from the various spots they are stored
|
|
// icon names based on states returned from JSON server,
|
|
$widget['state'] = UNKNOWN; //initial state is unknown
|
|
$widget.jsonType = ""; //default to no JSON type (avoid undefined)
|
|
|
|
if (isUndefined($widget["systemName"]) && isDefined($widget["id"])) {
|
|
$widget.systemName = $widget["id"]; //set systemName from id if missing
|
|
}
|
|
$widget["id"] = "widget-" + $gUnique(); //set id to a unique value (since same element can be in multiple widgets)
|
|
$widget['widgetFamily'] = $getWidgetFamily($widget, this);
|
|
$widget['extraAttributes'] = ""; //some type-specific attrs
|
|
var $jc = "";
|
|
if (isDefined($widget["class"])) {
|
|
var $ta = $widget["class"].split('.'); //get last part of java class name for a css class
|
|
$jc = $ta[$ta.length - 1];
|
|
}
|
|
if ($widget.widgetFamily == "switch") {
|
|
$widget['classes'] = $widget.widgetType + " " + $jc; // rest of classes are not used on a switch
|
|
} else {
|
|
$widget['classes'] = $widget.widgetType + " " + $widget.widgetFamily + " rotatable " + $jc;
|
|
}
|
|
if ($widget.momentary == "true") {
|
|
$widget.classes += " momentary ";
|
|
}
|
|
if ($widget.hidden == "yes") {
|
|
$widget.classes += " hidden ";
|
|
}
|
|
if (isDefined($widget.showtooltip) && $widget.showtooltip == "true") { //set tooltip for custom tooltip
|
|
var ht = $(this).find('tooltip').text();
|
|
if (ht != "") $widget['hoverText'] = ht;
|
|
}
|
|
|
|
// set additional values in this widget
|
|
switch ($widget.widgetFamily) {
|
|
case "icon" :
|
|
$widget['styles'] = $getTextCSSFromObj($widget);
|
|
switch ($widget.widgetType) {
|
|
case "positionablelabel" :
|
|
$widget['icon' + UNKNOWN] = $(this).find('icon').attr('url');
|
|
$widget['rotation'] = $(this).find('icon').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icon').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('icon').attr('scale');
|
|
break;
|
|
case "audioicon" :
|
|
$widget.jsonType = 'audio'; // JSON object type
|
|
$widget['identity'] = $(this).find('Identity').text();
|
|
audioIconIDs['audioicon:'+$widget['identity']] = $widget; // Ensure the key is a string, not a number
|
|
$widget['icon' + UNKNOWN] = $(this).find('icon').attr('url');
|
|
$widget['sound'] = $(this).attr('sound');
|
|
$widget['onClickOperation'] = $(this).attr('onClickOperation');
|
|
$widget['audio_widget'] = new Audio($widget['sound']);
|
|
$widget['playSoundWhenJmriPlays'] = $(this).attr('playSoundWhenJmriPlays') == "yes";
|
|
$widget['stopSoundWhenJmriStops'] = $(this).attr('stopSoundWhenJmriStops') == "yes";
|
|
$widget['rotation'] = $(this).find('icon').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icon').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('icon').attr('scale');
|
|
$widget.classes += " " + $widget.jsonType + " clickable"; //make it clickable
|
|
if (!$('#' + $widget.id).hasClass('clickable')) {
|
|
$('#' + $widget.id).addClass("clickable");
|
|
$('#' + $widget.id).bind(UPEVENT, $handleClick);
|
|
}
|
|
jmri.getAudio($widget.systemName);
|
|
jmri.getAudioIcon($widget['identity']);
|
|
break;
|
|
case "logixngicon" :
|
|
$widget['identity'] = $(this).find('Identity').text();
|
|
$widget['icon' + UNKNOWN] = $(this).find('icon').attr('url');
|
|
$widget['rotation'] = $(this).find('icon').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icon').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('icon').attr('scale');
|
|
$widget.classes += " " + $widget.jsonType + " clickable"; //make it clickable
|
|
if (!$('#' + $widget.id).hasClass('clickable')) {
|
|
$('#' + $widget.id).addClass("clickable");
|
|
$('#' + $widget.id).bind(UPEVENT, $handleClick);
|
|
}
|
|
break;
|
|
case "linkinglabel" :
|
|
$widget['icon' + UNKNOWN] = $(this).find('icon').attr('url');
|
|
$widget['rotation'] = $(this).find('icon').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icon').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('icon').attr('scale');
|
|
$url = $(this).find('url').text();
|
|
$widget['url'] = $url; //default to using url value as is
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
break;
|
|
case "indicatortrackicon" : // TODO clean up unused icon copies, carefully
|
|
// named after (o)block
|
|
$widget['icon' + UNKNOWN] = $(this).find('iconmap').find('ClearTrack').attr('url'); // clear via oblock
|
|
$widget['icon2'] = $(this).find('iconmap').find('OccupiedTrack').attr('url'); // occupied via sensor
|
|
$widget['icon4'] = $widget['icon' + UNKNOWN]; // clear via sensor
|
|
$widget['icon8'] = $widget['icon' + UNKNOWN]; // status from sensor inconsistent
|
|
$widget['icon16'] = $(this).find('iconmap').find('AllocatedTrack').attr('url'); //
|
|
$widget['icon32'] = $(this).find('iconmap').find('PositionTrack').attr('url'); // Running
|
|
$widget['icon64'] = $(this).find('iconmap').find('DontUseTrack').attr('url'); // Not in use
|
|
$widget['icon128'] = $(this).find('iconmap').find('ErrorTrack').attr('url'); // Power Error
|
|
|
|
$widget['iconOccupied' + UNKNOWN] = $(this).find('iconmap').find('OccupiedTrack').attr('url');
|
|
$widget['iconOccupied2'] = $(this).find('iconmap').find('OccupiedTrack').attr('url');
|
|
$widget['iconOccupied16'] = $(this).find('iconmap').find('OccupiedTrack').attr('url'); // Allocated + Occupied
|
|
$widget['iconOccupied32'] = $(this).find('iconmap').find('PositionTrack').attr('url');
|
|
$widget['iconOccupied128'] = $(this).find('iconmap').find('ErrorTrack').attr('url');
|
|
$widget['rotation'] = $(this).find('iconmap').find('ClearTrack').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('iconmap').find('ClearTrack').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('iconmap').find('ClearTrack').attr('scale');
|
|
// CPE CircuitBuilder Oblocks
|
|
if ($(this).find('occupancysensor').text()) { // store occupancy sensor name
|
|
$widget['occupancysensor'] = $(this).find('occupancysensor').text();
|
|
$widget['name'] = $widget.occupancysensor;
|
|
$widget['occupancyblock'] = "none"; // clear oblockname
|
|
//console.log("ITI SENSOR=" + $widget['occupancysensor']);
|
|
//$widget.jsonType = "sensor"; // JSON object type - not necessary
|
|
jmri.getSensor($widget["occupancysensor"]); // listen for occupancy changes
|
|
} else if ($(this).find('oblocksysname').text() && ($(this).find('oblocksysname').text() != "none")) {
|
|
// extract the occupancyblock name
|
|
$widget['oblocksysname'] = $(this).find('oblocksysname').text();
|
|
$widget['name'] = $(this).find('occupancyblock').text(); // display name of oblock in hovertext, like CPE
|
|
$widget['occupancysensor'] = "none"; // clear occ.sensorname
|
|
//console.log("ITI OBLOCK =" + $widget['oblocksysname']);
|
|
jmri.getOblock($widget["oblocksysname"]); // listen for oblock changes via json, fired by OBlock#setState()
|
|
// store ControlPanelEditor oblocks where-used
|
|
$store_occupancyblock($widget.id, $widget.oblocksysname);
|
|
}
|
|
$widget['occupancystate'] = UNKNOWN;
|
|
break;
|
|
case "indicatorturnouticon" :
|
|
$widget['name'] = $(this).find('turnout').text(); // it could be empty on incomplete indicators
|
|
$widget.jsonType = 'turnout'; // JSON object type
|
|
$widget['icon' + UNKNOWN] = $(this).find('iconmaps').find('ClearTrack').find('BeanStateUnknown').attr('url');
|
|
$widget['icon2'] = $(this).find('iconmaps').find('ClearTrack').find('TurnoutStateClosed').attr('url'); // Clear + Closed
|
|
$widget['icon4'] = $(this).find('iconmaps').find('ClearTrack').find('TurnoutStateThrown').attr('url'); // Clear + Thrown
|
|
$widget['icon8'] = $(this).find('iconmaps').find('ClearTrack').find('BeanStateInconsistent').attr('url');
|
|
$widget['icon16'] = $(this).find('iconmaps').find('AllocatedTrack').find('BeanStateUnknown').attr('url'); // Allocated + ?
|
|
$widget['icon18'] = $(this).find('iconmaps').find('AllocatedTrack').find('TurnoutStateClosed').attr('url'); // Allocated + Closed
|
|
$widget['icon20'] = $(this).find('iconmaps').find('AllocatedTrack').find('TurnoutStateThrown').attr('url'); // Allocated + Thrown
|
|
$widget['icon22'] = $(this).find('iconmaps').find('AllocatedTrack').find('BeanStateInconsistent').attr('url');// Allocated + X
|
|
$widget['icon32'] = $(this).find('iconmaps').find('AllocatedTrack').find('BeanStateUnknown').attr('url'); // Running + ? (should be Occupied, see below)
|
|
$widget['icon34'] = $(this).find('iconmaps').find('PositionTrack').find('TurnoutStateClosed').attr('url'); // Running + Closed
|
|
$widget['icon36'] = $(this).find('iconmaps').find('PositionTrack').find('TurnoutStateThrown').attr('url'); // Running + Thrown
|
|
$widget['icon38'] = $(this).find('iconmaps').find('PositionTrack').find('BeanStateInconsistent').attr('url'); // Running + X
|
|
$widget['icon64'] = $(this).find('iconmaps').find('DontUseTrack').find('BeanStateUnknown').attr('url'); // Not in use + ?
|
|
$widget['icon66'] = $(this).find('iconmaps').find('DontUseTrack').find('TurnoutStateClosed').attr('url'); // Not in use + Closed
|
|
$widget['icon68'] = $(this).find('iconmaps').find('DontUseTrack').find('TurnoutStateThrown').attr('url'); // Not in use + Thrown
|
|
$widget['icon70'] = $(this).find('iconmaps').find('DontUseTrack').find('BeanStateInconsistent').attr('url'); // Not in use + X
|
|
$widget['icon128'] = $(this).find('iconmaps').find('ErrorTrack').find('BeanStateUnknown').attr('url'); // Power Error + ?
|
|
$widget['icon130'] = $(this).find('iconmaps').find('ErrorTrack').find('TurnoutStateClosed').attr('url'); // Power Error + Closed
|
|
$widget['icon132'] = $(this).find('iconmaps').find('ErrorTrack').find('TurnoutStateThrown').attr('url'); // Power Error + Thrown
|
|
$widget['icon134'] = $(this).find('iconmaps').find('ErrorTrack').find('BeanStateInconsistent').attr('url'); // Power Error + X
|
|
|
|
$widget['iconOccupied' + UNKNOWN] = $(this).find('iconmaps').find('OccupiedTrack').find('BeanStateUnknown').attr('url');// 4 icons for
|
|
$widget['iconOccupied2'] = $(this).find('iconmaps').find('OccupiedTrack').find('TurnoutStateClosed').attr('url'); // occ.detect
|
|
$widget['iconOccupied4'] = $(this).find('iconmaps').find('OccupiedTrack').find('TurnoutStateThrown').attr('url'); // by sensor
|
|
$widget['iconOccupied8'] = $(this).find('iconmaps').find('OccupiedTrack').find('BeanStateInconsistent').attr('url'); //
|
|
$widget['iconOccupied16'] = $(this).find('iconmaps').find('OccupiedTrack').find('BeanStateUnknown').attr('url'); // 4 icons for
|
|
$widget['iconOccupied18'] = $(this).find('iconmaps').find('OccupiedTrack').find('TurnoutStateClosed').attr('url'); // occ.detect
|
|
$widget['iconOccupied20'] = $(this).find('iconmaps').find('OccupiedTrack').find('TurnoutStateThrown').attr('url'); // by oblock
|
|
$widget['iconOccupied22'] = $(this).find('iconmaps').find('OccupiedTrack').find('BeanStateInconsistent').attr('url');//
|
|
$widget['iconOccupied32'] = $(this).find('iconmaps').find('AllocatedTrack').find('BeanStateUnknown').attr('url'); // Running + ?
|
|
$widget['iconOccupied34'] = $(this).find('iconmaps').find('PositionTrack').find('TurnoutStateClosed').attr('url'); // Running + Closed
|
|
$widget['iconOccupied36'] = $(this).find('iconmaps').find('PositionTrack').find('TurnoutStateThrown').attr('url'); // Running + Thrown
|
|
$widget['iconOccupied38'] = $(this).find('iconmaps').find('PositionTrack').find('BeanStateInconsistent').attr('url'); // Running + X
|
|
$widget['iconOccupied128'] = $(this).find('iconmaps').find('ErrorTrack').find('BeanStateUnknown').attr('url');
|
|
$widget['iconOccupied130'] = $(this).find('iconmaps').find('ErrorTrack').find('TurnoutStateClosed').attr('url');
|
|
$widget['iconOccupied132'] = $(this).find('iconmaps').find('ErrorTrack').find('TurnoutStateThrown').attr('url');
|
|
$widget['iconOccupied134'] = $(this).find('iconmaps').find('ErrorTrack').find('BeanStateInconsistent').attr('url');
|
|
// no icons for Occupied + DontUseTrack
|
|
$widget['rotation'] = $(this).find('iconmaps').find('ClearTrack').find('BeanStateUnknown').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('iconmaps').find('ClearTrack').find('BeanStateUnknown').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('iconmaps').find('ClearTrack').find('BeanStateUnknown').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
// CPE CircuitBuilder Oblocks
|
|
if ($(this).find('occupancysensor').text()) { // instead, store occupancy sensor name
|
|
$widget['occupancyblock'] = "none"; // clear oblockname
|
|
$widget['occupancysensor'] = $(this).find('occupancysensor').text();
|
|
//console.log("ITOI SENSOR =" + $widget['occupancysensor']);
|
|
jmri.getSensor($widget["occupancysensor"]); // listen for occupancy changes
|
|
$store_occupancysensor($widget.id, $widget.occupancysensor); // only do that now we know no oblock is set
|
|
} else if ($(this).find('oblocksysname').text() && ($(this).find('oblocksysname').text() != "none")) {
|
|
// extract the occupancy block name
|
|
$widget['oblocksysname'] = $(this).find('oblocksysname').text();
|
|
$widget['occupancysensor'] = "none"; // clear oblockname
|
|
//console.log("ITOI OBLOCK =" + $widget['oblocksysname']);
|
|
jmri.getOblock($widget["oblocksysname"]); // listen for oblock changes, fired by Block#setState(), under development
|
|
$store_occupancyblock($widget.id, $widget.oblocksysname);
|
|
}
|
|
$widget['occupancystate'] = UNKNOWN;
|
|
jmri.getTurnout($widget["systemName"]);
|
|
break;
|
|
case "turnouticon" :
|
|
$widget['name'] = $widget.turnout; //normalize name
|
|
$widget.jsonType = "turnout"; // JSON object type
|
|
$widget['icon' + UNKNOWN] = $(this).find('icons').find('unknown').attr('url');
|
|
$widget['icon2'] = $(this).find('icons').find('closed').attr('url');
|
|
$widget['icon4'] = $(this).find('icons').find('thrown').attr('url');
|
|
$widget['icon8'] = $(this).find('icons').find('inconsistent').attr('url');
|
|
$widget['rotation'] = $(this).find('icons').find('unknown').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icons').find('unknown').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('icons').find('unknown').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
jmri.getTurnout($widget["systemName"]);
|
|
break;
|
|
case "outputindicator" :
|
|
$widget['name'] = $widget.turnout; //normalize name
|
|
$widget.jsonType = "turnout"; // JSON object type
|
|
$widget['icon' + UNKNOWN] = $(this).find('icons').find('unknown').attr('url');
|
|
$widget['icon2'] = $(this).find('icons').find('closed').attr('url');
|
|
$widget['icon4'] = $(this).find('icons').find('thrown').attr('url');
|
|
$widget['icon8'] = $(this).find('icons').find('inconsistent').attr('url');
|
|
$widget['rotation'] = $(this).find('icons').find('unknown').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icons').find('unknown').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('icons').find('unknown').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
jmri.getTurnout($widget["systemName"]);
|
|
break;
|
|
case "sensoricon" :
|
|
$widget['name'] = $widget.sensor; //normalize name
|
|
$widget.jsonType = "sensor"; // JSON object type
|
|
$widget['icon' + UNKNOWN] = $(this).find('unknown').attr('url');
|
|
$widget['icon2'] = $(this).find('active').attr('url');
|
|
$widget['icon4'] = $(this).find('inactive').attr('url');
|
|
$widget['icon8'] = $(this).find('inconsistent').attr('url');
|
|
$widget['rotation'] = $(this).find('unknown').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('unknown').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('unknown').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getSensor($widget["systemName"]);
|
|
break;
|
|
case "LightIcon" :
|
|
$widget['name'] = $widget.light; //normalize name
|
|
$widget.jsonType = "light"; // JSON object type
|
|
$widget['icon' + UNKNOWN] = $(this).find('icons').find('unknown').attr('url');
|
|
$widget['icon2'] = $(this).find('icons').find('on').attr('url');
|
|
$widget['icon4'] = $(this).find('icons').find('off').attr('url');
|
|
$widget['icon8'] = $(this).find('icons').find('inconsistent').attr('url');
|
|
$widget['rotation'] = $(this).find('icons').find('unknown').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icons').find('unknown').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('unknown').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getLight($widget["systemName"]);
|
|
break;
|
|
case "signalheadicon" :
|
|
$widget['name'] = $widget.signalhead; //normalize name
|
|
$widget.jsonType = "signalHead"; // JSON object type
|
|
$widget['icon' + HELD] = $(this).find('icons').find('held').attr('url');
|
|
$widget['icon' + DARK] = $(this).find('icons').find('dark').attr('url');
|
|
$widget['icon' + RED] = $(this).find('icons').find('red').attr('url');
|
|
if (isUndefined($widget['icon' + RED])) { //look for held if no red
|
|
$widget['icon' + RED] = $(this).find('icons').find('held').attr('url');
|
|
}
|
|
$widget['icon' + YELLOW] = $(this).find('icons').find('yellow').attr('url');
|
|
$widget['icon' + GREEN] = $(this).find('icons').find('green').attr('url');
|
|
$widget['icon' + FLASHRED] = $(this).find('icons').find('flashred').attr('url');
|
|
$widget['icon' + FLASHYELLOW] = $(this).find('icons').find('flashyellow').attr('url');
|
|
$widget['icon' + FLASHGREEN] = $(this).find('icons').find('flashgreen').attr('url');
|
|
$widget['icon' + LUNAR] = $(this).find('icons').find('lunar').attr('url');
|
|
$widget['icon' + FLASHLUNAR] = $(this).find('icons').find('lunar').attr('url');
|
|
$widget['rotation'] = $(this).find('icons').find('dark').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('icons').find('dark').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('icons').find('dark').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
jmri.getSignalHead($widget["systemName"]);
|
|
break;
|
|
case "signalmasticon" :
|
|
$widget['name'] = $widget.signalmast; //normalize name
|
|
$widget.jsonType = "signalMast"; // JSON object type
|
|
var icons = $(this).find('icons').children(); //get array of icons
|
|
icons.each(function(i, item) { //loop thru icons array and set all iconXX urls for widget
|
|
$widget['icon' + $(item).attr('aspect')] = $(item).attr('url');
|
|
});
|
|
$widget['degrees'] = $(this).attr('degrees') * 1;
|
|
$widget['scale'] = $(this).attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
if (isDefined($widget["iconUnlit"])) {
|
|
$widget['state'] = "Unlit"; //set the initial aspect to Unlit if defined
|
|
} else {
|
|
$widget['state'] = "Unknown"; //else set to Unknown
|
|
}
|
|
jmri.getSignalMast($widget["systemName"]);
|
|
break;
|
|
case "multisensoricon" :
|
|
//create multiple widgets, 1st with all images, stack others with non-active states set to a clear image
|
|
// set up siblings array so each widget can also set state of the others
|
|
$widget.jsonType = "sensor"; // JSON object type
|
|
$widget['icon' + UNKNOWN] = $(this).find('unknown').attr('url');
|
|
$widget['icon4'] = $(this).find('inactive').attr('url');
|
|
$widget['icon8'] = $(this).find('inconsistent').attr('url');
|
|
$widget['rotation'] = $(this).find('unknown').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('unknown').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('unknown').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
$widget['siblings'] = new Array(); //array of related multisensors
|
|
$widget['hoverText'] = ""; //for override of hovertext
|
|
var actives = $(this).find('active'); //get array of actives used by this multisensor
|
|
var $id = $widget.id;
|
|
actives.each(function(i, item) { //loop thru array once to set up siblings array, to be copied to all siblings
|
|
$widget.siblings.push($id);
|
|
$widget.hoverText += $(item).attr('sensor') + " "; //add sibling names to hovertext
|
|
$id = "widget-" + $gUnique(); //set new id to a unique value for each sibling
|
|
});
|
|
actives.each(function(i, item) { //loop thru array again to create each widget
|
|
$widget['id'] = $widget.siblings[i]; // use id already set in sibling array
|
|
$widget.name = $(item).attr('sensor');
|
|
$widget['icon2'] = $(item).attr('url');
|
|
if (i < actives.size() - 1) { //only save widget and make a new one if more than one active found
|
|
$preloadWidgetImages($widget); //start loading all images
|
|
$widget['safeName'] = $safeName($widget.name); //add a html-safe version of name
|
|
$widget["systemName"] = $widget.name;
|
|
$gWidgets[$widget.id] = $widget; //store widget in persistent array
|
|
$drawIcon($widget); //actually place and position the widget on the panel
|
|
jmri.getSensor($widget["systemName"]);
|
|
if (!($widget.systemName in whereUsed)) { //set where-used for this new sensor
|
|
whereUsed[$widget.systemName] = new Array();
|
|
}
|
|
whereUsed[$widget.systemName][whereUsed[$widget.systemName].length] = $widget.id;
|
|
$widget = jQuery.extend(true, {}, $widget); //get a new copy of widget
|
|
$widget['icon' + UNKNOWN] = "/web/images/transparent_1x1.png";
|
|
$widget['icon4'] = "/web/images/transparent_1x1.png"; //set non-actives to transparent image
|
|
$widget['icon8'] = "/web/images/transparent_1x1.png";
|
|
$widget['state'] = ACTIVE; //to avoid sizing based on the transparent image
|
|
}
|
|
});
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getSensor($widget["systemName"]);
|
|
break;
|
|
case "memoryicon" :
|
|
$widget['name'] = $widget.memory; //normalize name
|
|
$widget.jsonType = "memory"; // JSON object type
|
|
$widget['state'] = null; //set initial state to null
|
|
var memorystates = $(this).find('memorystate');
|
|
memorystates.each(function(i, item) { //get any memorystates defined
|
|
//store icon url in "iconXX" where XX is the state to match
|
|
$widget['icon' + item.attributes['value'].value] = item.attributes['icon'].value;
|
|
$widget.state = item.attributes['value'].value; //default state to last defined value to draw icon
|
|
});
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getMemory($widget["systemName"]);
|
|
break;
|
|
case "slipturnouticon" : // added 2022, adapted from indicatorturnouticon
|
|
// no direct link to a JSON/named bean (systemName = id)
|
|
// also used for three way turnouts
|
|
// see java/src/jmri/jmrit/display/SlipTurnoutIcon.java
|
|
// and java/src/jmri/jmrit/display/configurexml/SlipTurnoutIconXml.java
|
|
$widget['turnoutEast'] = $(this).find('turnoutEast').text();
|
|
$widget['turnoutWest'] = $(this).find('turnoutWest').text();
|
|
$widget['name'] = $widget['turnoutEast'] + " " +$widget['turnoutWest'];
|
|
$widget.jsonType = "turnout"; // JSON object type, used to send commands in $handleClick(e)
|
|
$widget['slipicontype'] = $(this).find('turnoutType').text();
|
|
$widget['slipStateEast'] = UNKNOWN;
|
|
$widget['slipStateWest'] = UNKNOWN;
|
|
$widget['slipState'] = UNKNOWN; // combined state
|
|
// set icons
|
|
$widget['icon' + UNKNOWN] = $(this).find('unknown').attr('url');
|
|
$widget['icon' + INCONSISTENT] = $(this).find('inconsistent').attr('url');
|
|
$widget['icon5'] = $(this).find('upperWestToLowerEast').attr('url');
|
|
$widget['icon7'] = $widget['icon' + INCONSISTENT]; // state 7 loaded later where supported
|
|
$widget['icon9'] = $(this).find('lowerWestToLowerEast').attr('url');
|
|
$widget['icon11'] = $(this).find('lowerWestToUpperEast').attr('url');
|
|
|
|
switch ($widget.turnoutType) {
|
|
case "singleSlip" :
|
|
// $widget['singleSlipRoute'] = "lowerWestToLowerEast" or "upperWestToUpperEast"
|
|
if ($widget.singleSlipRoute == "upperWestToUpperEast") {
|
|
$widget['icon7'] = $(this).find('upperWestToUpperEast').attr('url');
|
|
}
|
|
break;
|
|
case "threeWay" :
|
|
// $widget['firstTurnoutExit'] = "upper" or "lower"
|
|
if ($widget.firstTurnoutExit == "lower") { // swap icons7 and 9
|
|
$widget['icon7'] = $widget.icon9;
|
|
$widget['icon9'] = $(this).find('lowerWestToUpperEast').attr('url');
|
|
}
|
|
break;
|
|
case "scissor" :
|
|
$widget['turnoutLowerEast'] = $(this).find('turnoutLowerEast').text();
|
|
$widget['turnoutLowerWest'] = $(this).find('turnoutLowerWest').text();
|
|
if (isDefined($widget.turnoutLowerEast)) {
|
|
$widget['singleCrossOver'] = "false";
|
|
// connect 2 extra turnouts now to prevent extra switch case below, no need to listen
|
|
// jmri.getTurnout($widget['turnoutLowerEast']);
|
|
// jmri.getTurnout($widget['turnoutLowerWest']);
|
|
} else {
|
|
$widget['singleCrossOver'] = "true";
|
|
}
|
|
$widget['icon7'] = $widget.icon5; // UWLE
|
|
$widget['icon5'] = $widget.icon9; // LWLE
|
|
$widget['icon9'] = $widget.icon11; // LWUE
|
|
$widget['icon11'] = $widget['icon' + INCONSISTENT];
|
|
break;
|
|
case "doubleSlip" : // default
|
|
$widget['icon7'] = $(this).find('upperWestToUpperEast').attr('url');
|
|
break;
|
|
}
|
|
|
|
$widget['rotation'] = $(this).find('lowerWestToLowerEast').find('rotation').text() * 1;
|
|
$widget['degrees'] = ($(this).find('lowerWestToLowerEast').attr('degrees') * 1) - ($widget.rotation * 90);
|
|
$widget['scale'] = $(this).find('lowerWestToLowerEast').attr('scale');
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
jmri.getTurnout($widget['turnoutEast']);
|
|
jmri.getTurnout($widget['turnoutWest']);
|
|
|
|
// add turnout to whereUsed array (as $widget.id + 'e')
|
|
if (!($widget.turnoutEast in whereUsed)) { //set where-used for this new turnout
|
|
whereUsed[$widget.turnoutEast] = new Array();
|
|
}
|
|
whereUsed[$widget.turnoutEast][whereUsed[$widget.turnoutEast].length] = $widget.id + "e";
|
|
|
|
// add turnoutB to whereUsed array (as $widget + 'w')
|
|
if (!($widget.turnoutWest in whereUsed)) { //set where-used for this new turnout
|
|
whereUsed[$widget.turnoutWest] = new Array();
|
|
}
|
|
whereUsed[$widget.turnoutWest][whereUsed[$widget.turnoutWest].length] = $widget.id + "w";
|
|
// TODO add the extra 2 turnouts to whereUsed that optionally are part of a scissor?
|
|
break;
|
|
}
|
|
|
|
$preloadWidgetImages($widget); //start loading all images
|
|
$widget['safeName'] = $safeName($widget.name); //add a html-safe version of name
|
|
$gWidgets[$widget.id] = $widget; //store widget in persistent array
|
|
$drawIcon($widget); //actually place and position the widget on the panel
|
|
break;
|
|
|
|
case "text" :
|
|
case "input" :
|
|
$widget['styles'] = $getTextCSSFromObj($widget);
|
|
switch ($widget.widgetType) {
|
|
case "audioicon" :
|
|
$widget.jsonType = 'audio'; // JSON object type
|
|
$widget['identity'] = $(this).find('Identity').text();
|
|
audioIconIDs['audioicon:'+$widget['identity']] = $widget; // Ensure the key is a string, not a number
|
|
$widget['sound'] = $(this).attr('sound');
|
|
$widget['onClickOperation'] = $(this).attr('onClickOperation');
|
|
$widget['audio_widget'] = new Audio($widget['sound']);
|
|
$widget['playSoundWhenJmriPlays'] = $(this).attr('playSoundWhenJmriPlays') == "yes";
|
|
$widget['stopSoundWhenJmriStops'] = $(this).attr('stopSoundWhenJmriStops') == "yes";
|
|
$widget.styles['user-select'] = "none";
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
if (!$('#' + $widget.id).hasClass('clickable')) {
|
|
$('#' + $widget.id).addClass("clickable");
|
|
$('#' + $widget.id).bind(UPEVENT, $handleClick);
|
|
}
|
|
jmri.getAudio($widget.systemName);
|
|
jmri.getAudioIcon($widget['identity']);
|
|
break;
|
|
case "logixngicon" :
|
|
$widget.jsonType = "logixngicon"; // JSON object type
|
|
$widget['identity'] = $(this).find('Identity').text();
|
|
$widget.styles['user-select'] = "none";
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
break;
|
|
case "sensoricon" :
|
|
$widget['name'] = $widget.sensor; //normalize name
|
|
$widget.jsonType = "sensor"; // JSON object type
|
|
//set each state's text
|
|
$widget['text' + UNKNOWN] = $(this).find('unknownText').attr('text');
|
|
$widget['text2'] = $(this).find('activeText').attr('text');
|
|
$widget['text4'] = $(this).find('inactiveText').attr('text');
|
|
$widget['text8'] = $(this).find('inconsistentText').attr('text');
|
|
//set each state's css attribute array (text color, etc.)
|
|
$widget['css' + UNKNOWN] = $getTextCSSFromObj($getObjFromXML($(this).find('unknownText')[0]));
|
|
$widget['css2'] = $getTextCSSFromObj($getObjFromXML($(this).find('activeText')[0]));
|
|
$widget['css4'] = $getTextCSSFromObj($getObjFromXML($(this).find('inactiveText')[0]));
|
|
$widget['css8'] = $getTextCSSFromObj($getObjFromXML($(this).find('inconsistentText')[0]));
|
|
if (isDefined($widget.name) && $widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getSensor($widget["systemName"]);
|
|
break;
|
|
case "locoicon" :
|
|
case "trainicon" :
|
|
//also set the background icon for this one (additional css in .html file)
|
|
$widget['icon' + UNKNOWN] = $(this).find('icon').attr('url');
|
|
$widget.styles['background-image'] = "url('" + $widget['icon' + UNKNOWN] + "')";
|
|
$widget['scale'] = $(this).find('icon').attr('scale');
|
|
if ($widget.scale != 1) {
|
|
$widget.styles['background-size'] = $widget.scale * 100 + "%";
|
|
$widget.styles['line-height'] = $widget.scale * 20 + "px"; //center vertically
|
|
}
|
|
break;
|
|
case "fastclock" :
|
|
jmri.getMemory("IMRATEFACTOR"); //enable updates for fast clock rate
|
|
$widget['name'] = 'IMCURRENTTIME'; // already defined in JMRI
|
|
$widget.jsonType = 'memory';
|
|
$widget.styles['width'] = "166px"; //hard-coded to match original size of clock image
|
|
$widget.styles['height'] = "166px";
|
|
$widget['scale'] = $(this).attr('scale');
|
|
if (isUndefined($widget.level)) {
|
|
$widget['level'] = 10; //if not included in xml
|
|
}
|
|
$widget['text'] = "00:00 AM";
|
|
$widget['state'] = "00:00 AM";
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getMemory($widget["systemName"]);
|
|
break;
|
|
case "reportericon" :
|
|
$widget['name'] = $widget.reporter; //normalize name
|
|
$widget.jsonType = "reporter"; // JSON object type
|
|
$widget['text'] = $widget.reporter; //use name for initial text
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getReporter($widget["systemName"]);
|
|
break;
|
|
case "BlockContentsIcon" :
|
|
$widget['name'] = $widget.systemName; //normalize name (id got stepped on)
|
|
$widget.jsonType = "block"; // JSON object type
|
|
$widget['text'] = $widget.name; //use name for initial text
|
|
$widget['state'] = $widget.name; //use name for initial state as well
|
|
jmri.getBlock($widget["systemName"]);
|
|
break;
|
|
case "blockContentsInputIcon" :
|
|
$widget['name'] = $widget.block; //normalize name
|
|
$widget.jsonType = "block"; // JSON object type
|
|
$widget['text'] = $widget.block; //use name for initial text
|
|
$widget['state'] = $widget.block; //use name for initial state as well
|
|
if (isUndefined($widget.styles.width)) { //set missing width
|
|
if (isDefined($widget.colWidth)) {
|
|
$widget.styles['width'] = $widget.colWidth + "em";
|
|
} else {
|
|
$widget.styles['width'] = "5em";
|
|
}
|
|
}
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getBlock($widget["systemName"]);
|
|
break;
|
|
case "memoryicon" :
|
|
case "memoryInputIcon" :
|
|
case "memoryComboIcon" :
|
|
if ($widget.class.indexOf("MemorySpinnerIcon") >= 0) { //fix for JMRI's bad element naming for this one
|
|
$widget.widgetType = "memorySpinnerIcon";
|
|
$widget.widgetFamily = "input";
|
|
$widget.classes = $widget.classes.replace("memoryicon text", "memorySpinnerIcon input");
|
|
$widget['extraAttributes'] = "type='number' min='0' max='100'";
|
|
}
|
|
$widget['name'] = $widget.memory; //normalize name
|
|
$widget.jsonType = "memory"; // JSON object type
|
|
$widget['text'] = $widget.memory; //use name for initial text
|
|
$widget['state'] = $widget.memory; //use name for initial state as well
|
|
if (isUndefined($widget.styles.width)) { //set missing width
|
|
if (isDefined($widget.colWidth)) {
|
|
$widget.styles['width'] = $widget.colWidth + "em";
|
|
} else {
|
|
$widget.styles['width'] = "5em";
|
|
}
|
|
}
|
|
var items = $(this).find('itemList').children('item');
|
|
$widget['items'] = [];
|
|
items.each(function(i, item) { //get any itemlist defined
|
|
//store item list in items array
|
|
$widget.items[item.attributes['index'].value] = item.textContent;
|
|
});
|
|
if (isUndefined($widget["systemName"]))
|
|
$widget["systemName"] = $widget.name;
|
|
jmri.getMemory($widget["systemName"]);
|
|
break;
|
|
case "linkinglabel" :
|
|
$url = $(this).find('url').text();
|
|
$widget['url'] = $url; //just store url value in widget, for use in click handler
|
|
if ($widget.forcecontroloff != "true") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
break;
|
|
}
|
|
|
|
$widget['safeName'] = $safeName($widget.name);
|
|
switch ($widget['orientation']) { // use orientation instead of degrees if populated
|
|
case "vertical_up" : $widget.degrees = 270;
|
|
case "vertical_down" : $widget.degrees = 90;
|
|
}
|
|
$gWidgets[$widget.id] = $widget; //store widget in persistent array
|
|
|
|
var $hoverText = ""; //add html for hoverText (custom tooltip) if populated
|
|
if (isDefined($widget.hoverText)) {
|
|
$hoverText = " title='" + $widget.hoverText + "' alt='" + $widget.hoverText + "' ";
|
|
}
|
|
|
|
if ($widget.widgetFamily=="input") {
|
|
if ($widget.widgetType=="memoryComboIcon") {
|
|
var s = "<select id=" + $widget.id + " class='" + $widget.classes +
|
|
"' value='" + $widget.text + "' " + $widget.extraAttributes + $hoverText + " >";
|
|
$($widget.items).each(function(i, item) {
|
|
s += " <option value='"+ item + "'>" + item + "</option>";
|
|
});
|
|
s +="</select>";
|
|
$("#panel-area").append(s);
|
|
} else {
|
|
$("#panel-area").append("<input id=" + $widget.id + " class='" + $widget.classes +
|
|
"' value='" + $widget.text + "' " + $widget.extraAttributes + $hoverText + " >");
|
|
}
|
|
} else {
|
|
$("#panel-area").append("<div id=" + $widget.id + " class='" + $widget.classes + $hoverText + "'>" +
|
|
$widget.text + "</div>");
|
|
}
|
|
$("#panel-area>#" + $widget.id).css($widget.styles); // apply style array to widget
|
|
$setWidgetPosition($("#panel-area>#" + $widget.id));
|
|
break;
|
|
|
|
case "drawn" :
|
|
if (jmri_logging) {
|
|
log.log("case drawn " + $widget.widgetType);
|
|
$logProperties($widget);
|
|
}
|
|
switch ($widget.widgetType) {
|
|
case "positionablepoint" :
|
|
//log.log("#### Positionable Point ####");
|
|
//just store these points in persistent variable for use when drawing tracksegments and layoutturnouts
|
|
//End bumpers and Connectors use wrong type, so always store as .POS_POINT
|
|
$gPts[$widget.ident + ".POS_POINT"] = $widget;
|
|
break;
|
|
case "layoutblock" :
|
|
$widget['state'] = UNKNOWN; //add a state member for this block
|
|
$widget["blockcolor"] = $widget.trackcolor; //init blockcolor to trackcolor
|
|
//store these blocks in a persistent var
|
|
$gBlks[$widget.systemName] = $widget;
|
|
//log.log("layoutblock:");
|
|
$logProperties($widget);
|
|
//log.log("block[" + $widget.systemName + "].blockcolor: '" + $widget.trackcolor + "'.")
|
|
jmri.getLayoutBlock($widget.systemName);
|
|
break;
|
|
case "layoutturnout" :
|
|
$widget['id'] = $widget.ident;
|
|
$widget['name'] = $widget.turnoutname; //normalize name
|
|
$widget['safeName'] = $safeName($widget.name); //add a html-safe version of name
|
|
$widget.jsonType = "turnout"; // JSON object type
|
|
$widget['x'] = $widget.xcen; //normalize x,y
|
|
$widget['y'] = $widget.ycen;
|
|
if (isDefined($widget.name) && ($widget.disabled !== "yes")) {
|
|
$widget.classes += " " + $widget.jsonType + " clickable "; //make it clickable (unless no turnout assigned)
|
|
}
|
|
//set widget occupancy sensor from block to speed affected changes later
|
|
if (isDefined($gBlks[$widget.blockname])) {
|
|
$widget['occupancysensorA'] = $gBlks[$widget.blockname].occupancysensor;
|
|
$widget['occupancystateA'] = $gBlks[$widget.blockname].state;
|
|
}
|
|
if (isDefined($gBlks[$widget.blockbname])) {
|
|
$widget['occupancysensorB'] = $gBlks[$widget.blockbname].occupancysensor;
|
|
$widget['occupancystateB'] = $gBlks[$widget.blockbname].state;
|
|
}
|
|
if (isDefined($gBlks[$widget.blockcname])) {
|
|
$widget['occupancysensorC'] = $gBlks[$widget.blockcname].occupancysensor;
|
|
$widget['occupancystateC'] = $gBlks[$widget.blockcname].state;
|
|
}
|
|
if (isDefined($gBlks[$widget.blockdname])) {
|
|
$widget['occupancysensorD'] = $gBlks[$widget.blockdname].occupancysensor;
|
|
$widget['occupancystateD'] = $gBlks[$widget.blockdname].state;
|
|
}
|
|
$gWidgets[$widget.id] = $widget; //store widget in persistent array
|
|
$storeTurnoutPoints($widget); //also store the turnout's 3 end points for other connections
|
|
$drawTurnout($widget); //draw the turnout
|
|
|
|
// add an empty, but clickable, div to the panel and position it over the turnout circle, if control allowed
|
|
if ($gPanel.controlling == "yes") {
|
|
$hoverText = " title='" + $widget.name + "' alt='" + $widget.name + "'";
|
|
$("#panel-area").append("<div id=" + $widget.id + " class='" + $widget.classes + "' " + $hoverText + "></div>");
|
|
var $cr = $gPanel.turnoutcirclesize * SIZE; //turnout circle radius
|
|
var $cd = $cr * 2;
|
|
$("#panel-area>#" + $widget.id).css(
|
|
{
|
|
position: 'absolute',
|
|
left: ($widget.x - $cr) + 'px',
|
|
top: ($widget.y - $cr) + 'px',
|
|
zIndex: 3,
|
|
width: $cd + 'px',
|
|
height: $cd + 'px'
|
|
});
|
|
}
|
|
if (isUndefined($widget["systemName"])) {
|
|
$widget["systemName"] = $widget.name;
|
|
}
|
|
jmri.getTurnout($widget["systemName"]);
|
|
if ($widget["occupancysensorA"])
|
|
jmri.getSensor($widget["occupancysensorA"]); //listen for occupancy changes
|
|
if ($widget["occupancysensorB"])
|
|
jmri.getSensor($widget["occupancysensorB"]); //listen for occupancy changes
|
|
if ($widget["occupancysensorC"])
|
|
jmri.getSensor($widget["occupancysensorC"]); //listen for occupancy changes
|
|
if ($widget["occupancysensorD"])
|
|
jmri.getSensor($widget["occupancysensorD"]); //listen for occupancy changes
|
|
break;
|
|
case 'layoutSlip' :
|
|
$widget['id'] = $widget.ident;
|
|
$widget['name'] = $widget.ident;
|
|
$widget['safeName'] = $safeName($widget.name); //add a html-safe version of name
|
|
$widget.jsonType = "turnout"; // JSON object type
|
|
|
|
//save the slip state to turnout state information
|
|
$widget['turnout'] = $(this).find('turnout:first').text();
|
|
$widget['turnoutB'] = $(this).find('turnoutB:first').text();
|
|
$widget['stateA'] = UNKNOWN;
|
|
$widget['stateB'] = UNKNOWN;
|
|
|
|
//log.log("tA: " + $widget.turnout + ", tB: " + $widget.turnoutB);
|
|
|
|
$widget['turnoutA_AC'] = Number($(this).find('states').find('A-C').find('turnout').text());
|
|
$widget['turnoutA_AD'] = Number($(this).find('states').find('A-D').find('turnout').text());
|
|
$widget['turnoutA_BC'] = Number($(this).find('states').find('B-C').find('turnout').text());
|
|
$widget['turnoutA_BD'] = Number($(this).find('states').find('B-D').find('turnout').text());
|
|
|
|
$widget['turnoutB_AC'] = Number($(this).find('states').find('A-C').find('turnoutB').text());
|
|
$widget['turnoutB_AD'] = Number($(this).find('states').find('A-D').find('turnoutB').text());
|
|
$widget['turnoutB_BC'] = Number($(this).find('states').find('B-C').find('turnoutB').text());
|
|
$widget['turnoutB_BD'] = Number($(this).find('states').find('B-D').find('turnoutB').text());
|
|
|
|
// default to this state
|
|
$widget['state'] = UNKNOWN;
|
|
|
|
$widget['x'] = $widget.xcen; //normalize x,y
|
|
$widget['y'] = $widget.ycen;
|
|
|
|
if ((isDefined($widget.turnout) || isDefined($widget.turnoutB))
|
|
&& ($widget.disabled !== "yes")) {
|
|
$widget.classes += " " + $widget.jsonType + " clickable ";
|
|
}
|
|
|
|
//set widget occupancy sensor from block to speed affected changes later
|
|
if (isDefined($gBlks[$widget.blockname])) {
|
|
$widget['occupancysensorA'] = $gBlks[$widget.blockname].occupancysensor;
|
|
$widget['occupancystateA'] = $gBlks[$widget.blockname].state;
|
|
}
|
|
if (isDefined($gBlks[$widget.blockbname])) {
|
|
$widget['occupancysensorB'] = $gBlks[$widget.blockbname].occupancysensor;
|
|
$widget['occupancystateB'] = $gBlks[$widget.blockbname].state;
|
|
}
|
|
if (isDefined($gBlks[$widget.blockcname])) {
|
|
$widget['occupancysensorC'] = $gBlks[$widget.blockcname].occupancysensor;
|
|
$widget['occupancystateC'] = $gBlks[$widget.blockcname].state;
|
|
}
|
|
if (isDefined($gBlks[$widget.blockdname])) {
|
|
$widget['occupancysensorD'] = $gBlks[$widget.blockdname].occupancysensor;
|
|
$widget['occupancystateD'] = $gBlks[$widget.blockdname].state;
|
|
}
|
|
|
|
$gWidgets[$widget.id] = $widget; //store widget in persistent array
|
|
$storeSlipPoints($widget); //also store the slip's 4 end points for other connections
|
|
$drawSlip($widget); //draw the slip
|
|
|
|
if ($gPanel.controlling == "yes") {
|
|
// convenience variables for points (A, B, C, D)
|
|
var a = $getPoint($widget.ident + SLIP_A);
|
|
var b = $getPoint($widget.ident + SLIP_B);
|
|
var c = $getPoint($widget.ident + SLIP_C);
|
|
var d = $getPoint($widget.ident + SLIP_D);
|
|
|
|
var $cr = $gPanel.turnoutcirclesize * SIZE; //turnout circle radius
|
|
var $cd = $cr * 2; //turnout circle diameter
|
|
|
|
// center
|
|
var cen = [$widget.xcen, $widget.ycen];
|
|
// left center
|
|
var lcen = $point_midpoint(a, b);
|
|
var ldelta = $point_subtract(cen, lcen);
|
|
|
|
// left fraction
|
|
var lf = $cr / Math.hypot(ldelta[0], ldelta[1]);
|
|
// left circle
|
|
var lcc = $point_lerp(cen, lcen, lf);
|
|
|
|
//add an empty, but clickable, div to the panel and position it over the left turnout circle
|
|
$hoverText = " title='" + $widget.turnout + "' alt='" + $widget.turnout + "'";
|
|
$("#panel-area").append("<div id=" + $widget.id + "l class='" + $widget.classes + "' " + $hoverText + "></div>");
|
|
$("#panel-area>#" + $widget.id + "l").css(
|
|
{position: 'absolute', left: (lcc[0] - $cr) + 'px', top: (lcc[1] - $cr) + 'px', zIndex: 3,
|
|
width: $cd + 'px', height: $cd + 'px'});
|
|
// right center
|
|
var rcen = $point_midpoint(c, d);
|
|
var rdelta = $point_subtract(cen, rcen);
|
|
// right fraction
|
|
var rf = $cr / Math.hypot(rdelta[0], rdelta[1]);
|
|
// right circle
|
|
var rcc = $point_lerp(cen, rcen, rf);
|
|
|
|
//add an empty, but clickable, div to the panel and position it over the right turnout circle
|
|
$hoverText = " title='" + $widget.turnoutB + "' alt='" + $widget.turnoutB + "'";
|
|
$("#panel-area").append("<div id=" + $widget.id + "r class='" + $widget.classes + "' " + $hoverText + "></div>");
|
|
$("#panel-area>#" + $widget.id + "r").css(
|
|
{position: 'absolute', left: (rcc[0] - $cr) + 'px', top: (rcc[1] - $cr) + 'px', zIndex: 3,
|
|
width: $cd + 'px', height: $cd + 'px'});
|
|
}
|
|
|
|
// set up notifications (?)
|
|
jmri.getTurnout($widget["turnout"]);
|
|
jmri.getTurnout($widget["turnoutB"]);
|
|
|
|
if ($widget["occupancysensorA"])
|
|
jmri.getSensor($widget["occupancysensorA"]); //listen for occupancy changes
|
|
if ($widget["occupancysensorB"])
|
|
jmri.getSensor($widget["occupancysensorB"]); //listen for occupancy changes
|
|
if ($widget["occupancysensorC"])
|
|
jmri.getSensor($widget["occupancysensorC"]); //listen for occupancy changes
|
|
if ($widget["occupancysensorD"])
|
|
jmri.getSensor($widget["occupancysensorD"]); //listen for occupancy changes
|
|
|
|
// NOTE: turnout & turnoutB may appear to be swapped here however this is intentional
|
|
// (since the left turnout controls the right points and vice-versa) and we want
|
|
// the slip circles to toggle the points (not the turnout) on the corresponding side.
|
|
//
|
|
// note: the <div> areas above have their titles & alts turnouts swapped (left <-> right) also
|
|
|
|
// add turnout to whereUsed array (as $widget.id + 'r')
|
|
if (!($widget.turnout in whereUsed)) { //set where-used for this new turnout
|
|
whereUsed[$widget.turnout] = new Array();
|
|
}
|
|
whereUsed[$widget.turnout][whereUsed[$widget.turnout].length] = $widget.id + "r";
|
|
|
|
// add turnoutB to whereUsed array (as $widget + 'l')
|
|
if (!($widget.turnoutB in whereUsed)) { //set where-used for this new turnout
|
|
whereUsed[$widget.turnoutB] = new Array();
|
|
}
|
|
whereUsed[$widget.turnoutB][whereUsed[$widget.turnoutB].length] = $widget.id + "l";
|
|
break;
|
|
case "tracksegment" :
|
|
//log.log("#### Track Segment ####");
|
|
//set widget occupancy sensor from block to speed affected changes later
|
|
if (isDefined($gBlks[$widget.blockname])) {
|
|
$widget['occupancysensor'] = $gBlks[$widget.blockname].occupancysensor;
|
|
$widget['occupancystate'] = $gBlks[$widget.blockname].state;
|
|
}
|
|
//store this widget in persistent array, with ident as key
|
|
$widget['id'] = $widget.ident;
|
|
$gWidgets[$widget.id] = $widget;
|
|
|
|
if ($widget.bezier == "yes") {
|
|
$widget['controlpoints'] = $(this).find('controlpoint');
|
|
}
|
|
|
|
// find decorations
|
|
var $decorations = $(this).find('decorations');
|
|
|
|
//copy arrow decoration
|
|
//<arrow style="4" end="stop" direction="out" color="#000000" linewidth="4" length="16" gap="1" />
|
|
var $arrow = $decorations.find('arrow');
|
|
var $arrowstyle = $arrow.attr('style');
|
|
if (isDefined($arrowstyle)) {
|
|
if (Number($arrowstyle) > 0) {
|
|
$widget['arrow'] = new ArrowDecoration($widget, $arrow);
|
|
}
|
|
}
|
|
|
|
//copy bridge decoration
|
|
//<bridge side="both" end="both" color="#000000" linewidth="1" approachwidth="8" deckwidth="10" />
|
|
var $bridge = $decorations.find('bridge');
|
|
var $bridgeside = $bridge.attr('side');
|
|
if (isDefined($bridgeside)) {
|
|
$widget['bridge'] = new BridgeDecoration($widget, $bridge);
|
|
}
|
|
|
|
//copy bumper decoration
|
|
//<bumper end="stop" color="#000000" linewidth="2" length="16" />
|
|
var $bumper = $decorations.find('bumper');
|
|
var $bumperend = $bumper.attr('end');
|
|
if (isDefined($bumperend)) {
|
|
$widget['bumper'] = new BumperDecoration($widget, $bumper);
|
|
}
|
|
|
|
//copy tunnel decoration
|
|
//<tunnel side="right" end="both" color="#FF00FF" linewidth="2" entrancewidth="16" floorwidth="12" />
|
|
var $tunnel = $decorations.find('tunnel');
|
|
var $tunnelside = $tunnel.attr('side');
|
|
if (isDefined($tunnelside)) {
|
|
$widget['tunnel'] = new TunnelDecoration($widget, $tunnel);
|
|
}
|
|
|
|
if ($widget["occupancysensor"])
|
|
jmri.getSensor($widget["occupancysensor"]); //listen for occupancy changes
|
|
|
|
//draw the tracksegment
|
|
$drawTrackSegment($widget);
|
|
break;
|
|
case "levelxing" :
|
|
$widget['x'] = $widget.xcen; //normalize x,y
|
|
$widget['y'] = $widget.ycen;
|
|
//set widget occupancy sensor from block to speed affected changes later
|
|
//TODO: handle BD block
|
|
if (isDefined($gBlks[$widget.blocknameac])) {
|
|
$widget['occupancysensorAC'] = $gBlks[$widget.blocknameac].occupancysensor;
|
|
$widget['occupancystateAC'] = $gBlks[$widget.blocknameac].state;
|
|
}
|
|
if (isDefined($gBlks[$widget.blocknamebd])) {
|
|
$widget['occupancysensorBD'] = $gBlks[$widget.blocknamebd].occupancysensor;
|
|
$widget['occupancystateBD'] = $gBlks[$widget.blocknamebd].state;
|
|
}
|
|
//store widget in persistent array
|
|
//$widget['id'] = $widget.ident;
|
|
$gWidgets[$widget.id] = $widget;
|
|
//also store the xing's 4 end points for other connections
|
|
$storeLevelXingPoints($widget);
|
|
//draw the xing
|
|
$drawLevelXing($widget);
|
|
|
|
//listen for occupancy changes
|
|
if ($widget["occupancysensorAC"])
|
|
jmri.getSensor($widget["occupancysensorAC"]);
|
|
if ($widget["occupancysensorBD"])
|
|
jmri.getSensor($widget["occupancysensorBD"]);
|
|
break;
|
|
case "layoutturntable" :
|
|
//log.log("#### Layout Turntable ####");
|
|
$widget['id'] = $widget.ident;
|
|
$widget['name'] = $widget.ident;
|
|
$widget['safeName'] = $safeName($widget.name); //add a html-safe version of name
|
|
$widget.jsonType = "turnout"; // JSON object type
|
|
$gWidgets[$widget.id] = $widget; //store widget in persistent array
|
|
|
|
if ($widget.turnoutControlled == "yes") {
|
|
$widget.classes += " " + $widget.jsonType + " clickable"; //make it clickable
|
|
if (!$('#' + $widget.id).hasClass('clickable')) {
|
|
$('#' + $widget.id).addClass("clickable");
|
|
$('#' + $widget.id).bind(UPEVENT, $handleClick);
|
|
}
|
|
}
|
|
|
|
//get the center
|
|
var $txcen = $widget.xcen * 1;
|
|
var $tycen = $widget.ycen * 1;
|
|
|
|
var $tr = $widget.radius * 1; //turntable circle radius
|
|
var $td = $tr * 2;
|
|
|
|
var $cr = $gPanel.turnoutcirclesize * SIZE; //turnout circle radius
|
|
var $cd = $cr * 2;
|
|
|
|
//loop thru raytracks, calc and store end of ray point for each
|
|
$widget['raytracks'] = $(this).find('raytrack');
|
|
$widget.raytracks.each(function(i, item) {
|
|
$logProperties(item);
|
|
//note:the 50 offset is due to TrackSegment.java TURNTABLE_RAY_OFFSET
|
|
//var rayID = $widget.ident + "." + (50 + item.attributes.index.value * 1);
|
|
var rayID = $widget.ident + ".TURNTABLE_RAY_" + (item.attributes.index.value * 1);
|
|
var $t = {ident:rayID};
|
|
var $angle = $toRadians(item.attributes.angle.value);
|
|
$t['x'] = $txcen + (($tr + $cr) * Math.sin($angle));
|
|
$t['y'] = $tycen - (($tr + $cr) * Math.cos($angle));
|
|
$gPts[$t.ident] = $t; //store the endpoint of this ray
|
|
|
|
if (isDefined(item.attributes.turnout)) {
|
|
var turnout = item.attributes.turnout.value;
|
|
var state = item.attributes.turnoutstate.value;
|
|
//add an empty, but clickable, div to the panel and position it over the turnout circle, if control allowed
|
|
if ($gPanel.controlling == "yes") {
|
|
$("#panel-area").append("<div " +
|
|
"id='" + rayID + "' " +
|
|
"class='" + $widget.classes + "' " +
|
|
"style='position:absolute;" +
|
|
"left:" + ($t.x - $cr) + "px;" +
|
|
"top: " + ($t.y - $cr) + "px;" +
|
|
"z-index: 3;" +
|
|
"width:" + $cd + "px;" +
|
|
"height:" + $cd + "px;' " +
|
|
"title='" + turnout + "(" + state + ")' " +
|
|
"alt='" + turnout + "'" +
|
|
"></div>");
|
|
}
|
|
//set up notifications
|
|
jmri.getTurnout(turnout);
|
|
|
|
// add turnout to whereUsed array (as $widget + 'r')
|
|
if (!(turnout in whereUsed)) { //set where-used for this new turnout
|
|
whereUsed[turnout] = new Array();
|
|
}
|
|
whereUsed[turnout].push(rayID);
|
|
}
|
|
});
|
|
|
|
//draw the turntable
|
|
$drawTurntable($widget);
|
|
break;
|
|
case "backgroundColor": // set background color of the window
|
|
$("body").css({"background-color": "rgb(" + $widget.red + "," + $widget.green + "," + $widget.blue + ")"});
|
|
break;
|
|
case "layoutShape" :
|
|
//log.log("#### Layout Shape ####");
|
|
//store this widget in persistent array, with ident as key
|
|
$widget['id'] = $widget.ident;
|
|
$gWidgets[$widget.id] = $widget;
|
|
|
|
$widget['points'] = $(this).find('point');
|
|
|
|
//draw the LayoutShape
|
|
$drawLayoutShape($widget);
|
|
break;
|
|
case "positionableRectangle" : //just like RoundRect except cornerRadius set to 0;
|
|
case "positionableRoundRect" :
|
|
//log.log("#### positionableRoundRect ####");
|
|
//copy and reformat some attributes from children into object
|
|
$widget['width'] = $(this).find('size').attr('width');
|
|
$widget['height'] = $(this).find('size').attr('height');
|
|
$widget['cornerRadius'] = $(this).find('size').attr('cornerRadius');
|
|
if (isUndefined($widget['cornerRadius'])) {
|
|
$widget['cornerRadius'] = 0; //default to no corner
|
|
}
|
|
lc = $(this).find('lineColor');
|
|
$widget['lineColor'] =
|
|
'rgba('+lc.attr('red')+','+lc.attr('green')+',' +
|
|
lc.attr('blue')+','+lc.attr('alpha')/256+')';
|
|
fc = $(this).find('fillColor');
|
|
$widget['fillColor'] =
|
|
'rgba('+fc.attr('red')+','+fc.attr('green')+',' +
|
|
fc.attr('blue')+','+fc.attr('alpha')/256+')';
|
|
//store this widget in persistent array, with ident as key
|
|
$widget['id'] = $widget.ident;
|
|
$gWidgets[$widget.id] = $widget;
|
|
//draw the positionableRoundRect
|
|
$drawPositionableRoundRect($widget);
|
|
break;
|
|
case "positionableCircle" : //identical except circle has size radius,
|
|
case "positionableEllipse" : //ellipse has size width height
|
|
//copy and reformat some attributes from children into object
|
|
$widget['radius'] = ($(this).find('size').attr('radius'));
|
|
if (isDefined($widget['radius'])) {
|
|
$widget['height'] = $widget.radius; //use radius for height if populated
|
|
$widget['width'] = $widget.radius; //use radius for width if populated
|
|
} else {
|
|
$widget['height'] = ($(this).find('size').attr('height'));
|
|
$widget['width'] = ($(this).find('size').attr('width'));
|
|
}
|
|
lc = $(this).find('lineColor');
|
|
$widget['lineColor'] =
|
|
'rgba('+lc.attr('red')+','+lc.attr('green')+',' +
|
|
lc.attr('blue')+','+lc.attr('alpha')/256+')';
|
|
fc = $(this).find('fillColor');
|
|
$widget['fillColor'] =
|
|
'rgba('+fc.attr('red')+','+fc.attr('green')+',' +
|
|
fc.attr('blue')+','+fc.attr('alpha')/256+')';
|
|
//store this widget in persistent array, with ident as key
|
|
$widget['id'] = $widget.ident;
|
|
$gWidgets[$widget.id] = $widget;
|
|
//draw the positionableEllipse
|
|
$drawPositionableEllipse($widget);
|
|
break;
|
|
default:
|
|
log.warn("unknown $widget.widgetType: " + $widget.widgetType + ".");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case "switch" : // Switchboard BeanSwitches
|
|
// they have no x,y
|
|
$widget['styles'] = {}; // clear built-in styles
|
|
$widget['name'] = $widget.label; // normalize name from label
|
|
$widget['text'] = $widget.label; // use label as initial button text too
|
|
$widget.styles['width'] = $swWidth + "px";
|
|
$widget.styles['height'] = $swHeight + "px";
|
|
// colors, values from Editor via SwitchboardServlet
|
|
$widget['swColor' + UNKNOWN] = 'LightGray'; // unknown
|
|
$widget['swColor2'] = $activeColor; // active = red
|
|
$widget['swColor4'] = $inactiveColor; // inactive = green
|
|
$widget['swColor8'] = 'Gray'; // inconsistent
|
|
if ($widget.connected == "true") {
|
|
switch ($widget['type']) {
|
|
case "T" :
|
|
$widget.jsonType = "turnout"; // JSON object type
|
|
jmri.getTurnout($widget["systemName"]); // switch follows state on layout
|
|
break;
|
|
case "S" :
|
|
$widget.jsonType = "sensor"; // JSON object type
|
|
jmri.getSensor($widget["systemName"]);
|
|
break;
|
|
case "L":
|
|
$widget.jsonType = "light"; // JSON object type
|
|
jmri.getLight($widget["systemName"]);
|
|
break;
|
|
// more types of NamedBeans?
|
|
default :
|
|
break; // skip
|
|
}
|
|
}
|
|
var $canvas = "";
|
|
switch ($widget.shape) { // set each state's text
|
|
case "symbol" :
|
|
case "icon" :
|
|
case "drawing" :
|
|
// settings for symbol/icon
|
|
$widget['text' + UNKNOWN] = $widget.text; // show state changes in color, not in label?
|
|
$widget['text2'] = $(this).find('activeText').attr('text');
|
|
$widget['text4'] = $(this).find('inactiveText').attr('text');
|
|
$widget['text8'] = $(this).find('inconsistentText').attr('text');
|
|
// add a canvas to the text label, reduce canvas HxW to fit inside the div
|
|
$canvas = "<canvas id=" + $widget.id + "c class='bscanvas' width='" + ($swWidth - 12) + "px' height='" +
|
|
($swHeight - 12) + "px' style='border:1px solid white;'></canvas>"; // to insert later
|
|
break;
|
|
case "button" : // mimick java switchboard buttons
|
|
default :
|
|
// add some html to show user name on line 2 when shape is button
|
|
$widget['text' + UNKNOWN] = getSwitchButtonLabel($(this).find('unknownText').attr('text'), $widget.username);
|
|
$widget['text2'] = getSwitchButtonLabel($(this).find('activeText').attr('text'), $widget.username);
|
|
$widget['text4'] = getSwitchButtonLabel($(this).find('inactiveText').attr('text'), $widget.username);
|
|
$widget['text8'] = getSwitchButtonLabel($(this).find('inconsistentText').attr('text'), $widget.username);
|
|
}
|
|
// common settings for all beanswitche shapes
|
|
$widget.classes += " " + $widget.shape + " ";
|
|
$widget['state'] = UNKNOWN; // use UNKNOWN for initial state
|
|
|
|
$widget.styles['color'] = $widget.text['color']; // use jmri color
|
|
// other CSS properties set in css, class .beanswitch
|
|
|
|
if ($widget.connected == "true") {
|
|
$widget['text'] = $widget.text0; // add UNKNOWN state to label of connected switches
|
|
$widget.styles['border-color'] = "black"; //$widget['swColor' + UNKNOWN];
|
|
$widget.classes += " " + $widget.jsonType + " clickable connected";
|
|
}
|
|
|
|
$gWidgets[$widget.id] = $widget; // store widget in persistent array
|
|
|
|
if ($widget.shape == "button") {
|
|
// "button", put only the text (system + user name) element on the page
|
|
$("#panel-area").append("<div id=" + $widget.id + " class='" + $widget.classes +
|
|
"'>" + $widget.text + "</div>");
|
|
} else {
|
|
// add a local canvas
|
|
$("#panel-area").append("<div id=" + $widget.id + " class='" + $widget.classes +
|
|
"' role='img'>" + $canvas + "</div>");
|
|
}
|
|
$("#panel-area>#" + $widget.id).css($widget.styles); // apply style array to widget
|
|
// beanswitch setup ready
|
|
break;
|
|
|
|
default:
|
|
//log any unsupported widgets, listing childnodes as info
|
|
$("div#logArea").append("<br />Unsupported: " + $widget.widgetType + ":");
|
|
$(this.attributes).each(function() {
|
|
$("div#logArea").append(" " + this.name);
|
|
});
|
|
$("div#logArea").append(" | ");
|
|
$(this.childNodes).each(function() {
|
|
$("div#logArea").append(" " + this.nodeName);
|
|
});
|
|
break;
|
|
}
|
|
// add widget.id to whereUsed array to support updates from layout
|
|
if ($widget.systemName) {
|
|
if (!($widget.systemName in whereUsed)) {
|
|
whereUsed[$widget.systemName] = new Array();
|
|
}
|
|
whereUsed[$widget.systemName][whereUsed[$widget.systemName].length] = $widget.id;
|
|
}
|
|
if ($gWidgets[$widget.id]) {
|
|
// store LayoutEditor occupancy sensors where-used
|
|
if ($widget.occupancysensor != "none") {
|
|
$store_occupancysensor($widget.id, $widget.occupancysensor);
|
|
}
|
|
$store_occupancysensor($widget.id, $widget.occupancysensorA);
|
|
$store_occupancysensor($widget.id, $widget.occupancysensorB);
|
|
$store_occupancysensor($widget.id, $widget.occupancysensorC);
|
|
$store_occupancysensor($widget.id, $widget.occupancysensorD);
|
|
$store_occupancysensor($widget.id, $widget.occupancysensorAC);
|
|
$store_occupancysensor($widget.id, $widget.occupancysensorBD);
|
|
}
|
|
} //end of function
|
|
); //end of each
|
|
|
|
//only enable click events if panel is marked to allow control
|
|
if ($gPanel.controlling == "yes") {
|
|
//hook up mouseup state toggle function to non-momentary clickable widgets, except for multisensor and linkinglabel
|
|
$('.clickable:not(.momentary):not(.multisensoricon):not(.linkinglabel)').bind(UPEVENT, $handleClick);
|
|
|
|
//hook up mouseup state change function to multisensor (special handling)
|
|
$('.clickable.multisensoricon').bind('click', $handleMultiClick);
|
|
|
|
//hook up mouseup function to linkinglabel (special handling)
|
|
$('.clickable.linkinglabel').bind(UPEVENT, $handleLinkingLabelClick);
|
|
|
|
//momentary widgets always go active on mousedown, and inactive on mouseup, current state is ignored
|
|
$('.clickable.momentary').bind(DOWNEVENT, function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault(); //prevent double-firing (touch + click)
|
|
sendElementChange($gWidgets[this.id].jsonType, $gWidgets[this.id].systemName, ACTIVE); //send active on down
|
|
}).bind(UPEVENT, function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault(); //prevent double-firing (touch + click)
|
|
sendElementChange($gWidgets[this.id].jsonType, $gWidgets[this.id].systemName, INACTIVE); //send inactive on up
|
|
});
|
|
|
|
//check for update keys and update when needed
|
|
$('input.input').bind(KEYUP, $handleInputKeyUp);
|
|
//update when leaving the input
|
|
$('input.input').bind(BLUR, $handleInputBlur);
|
|
//and update when select input is changed
|
|
$('select.input').bind(CHANGE, $handleInputBlur);
|
|
|
|
// Switchboard All Off/All On buttons
|
|
$(".lightswitch#allOff").bind(UPEVENT, $handleClickAllOff); // all Lights Off
|
|
$(".lightswitch#allOn").bind(UPEVENT, $handleClickAllOn); // all Lights On
|
|
}
|
|
|
|
$drawAllDrawnWidgets(); // draw all the drawn widgets once more, to address some bidirectional dependencies in the xml
|
|
$drawAllSwitchIcons(); // draw icon first time
|
|
|
|
$("#activity-alert").addClass("hidden").removeClass("show");
|
|
} // end of processPanelXML
|
|
|
|
|
|
/******************************************************************
|
|
* ======= Click Handling functions =======
|
|
*/
|
|
|
|
// perform regular click-handling, bound to click event for clickable, non-momentary widgets, except for multisensor and linkinglabel.
|
|
function $handleClick(e) {
|
|
if (jmri_logging) {
|
|
log.log("$handleClick()");
|
|
}
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault(); //prevent double-firing (touch + click)
|
|
|
|
// if (null == $widget) {
|
|
// $logProperties(this);
|
|
// }
|
|
|
|
// special case for LE layoutSlips
|
|
if (this.className.startsWith('layoutSlip ')) {
|
|
if (this.id.startsWith("SL") && (this.id.endsWith("r") || this.id.endsWith("l"))) {
|
|
var $slipID = this.id.slice(0, -1);
|
|
var $widget = $gWidgets[$slipID];
|
|
|
|
if (this.id.endsWith("l")) {
|
|
$widget["side"] = "left";
|
|
} else if (this.id.endsWith("r")) {
|
|
$widget["side"] = "right";
|
|
}
|
|
if (jmri_logging) {
|
|
log.log("\nlayoutSlip-side:" + $widget.side);
|
|
}
|
|
|
|
// convert current slip state to current turnout states
|
|
var $oldStateA, $oldStateB;
|
|
[$oldStateA, $oldStateB] = [$widget.stateA, $widget.stateB];
|
|
|
|
// determine next slip state
|
|
var $newState = getNextSlipState($widget);
|
|
|
|
if (jmri_logging) {
|
|
log.log("$handleClick:layoutSlip: change state from " +
|
|
slipStateToString($widget.state) + " to " + slipStateToString($newState) + ".");
|
|
}
|
|
|
|
// convert new slip state to new turnout states
|
|
var $newStateA, $newStateB;
|
|
[$newStateA, $newStateB] = getTurnoutStatesForSlipState($widget, $newState);
|
|
|
|
if ($oldStateA != $newStateA) {
|
|
sendElementChange($widget.jsonType, $widget.turnout, $newStateA);
|
|
}
|
|
if ($oldStateB != $newStateB) {
|
|
sendElementChange($widget.jsonType, $widget.turnoutB, $newStateB);
|
|
}
|
|
//jmri_logging = false;
|
|
} else {
|
|
log.warn("$handleClick(e): unknown slip widget " + this.id);
|
|
$logProperties(this);
|
|
}
|
|
// special case for LE layoutTurntable
|
|
} else if (this.className.startsWith('layoutturntable ')) {
|
|
var $rayID = this.id;
|
|
var $turntableID = $rayID.split(".")[0];
|
|
var $widget = $gWidgets[$turntableID];
|
|
$widget.raytracks.each(function(i, item) {
|
|
$logProperties(item);
|
|
//note: offset 50 is due to TrackSegment.java TURNTABLE_RAY_OFFSET
|
|
var rayID = $turntableID + ".TURNTABLE_RAY_" + (item.attributes.index.value * 1);
|
|
if (rayID == $rayID) {
|
|
if (isDefined(item.attributes.turnout)) {
|
|
var turnout = item.attributes.turnout.value;
|
|
var state = item.attributes.turnoutstate.value;
|
|
var $newState = (state == 'thrown') ? THROWN : CLOSED;
|
|
sendElementChange($widget.jsonType, turnout, $newState);
|
|
}
|
|
}
|
|
});
|
|
} else if (this.className.startsWith('slipturnouticon')) {
|
|
// special handling of slipturnouticon, which has (at least) 2 turnouts
|
|
var $widget = $gWidgets[this.id];
|
|
var $newState = $getNextState($widget); // determine next state from current state
|
|
var $turnoutWestNewState = 0;
|
|
var $turnoutEastNewState = 0;
|
|
// we may need to send a command to multiple turnouts
|
|
switch ($newState) {
|
|
case 5 :
|
|
$turnoutWestNewState = CLOSED;
|
|
$turnoutEastNewState = CLOSED;
|
|
break;
|
|
case 7 :
|
|
$turnoutWestNewState = THROWN;
|
|
$turnoutEastNewState = CLOSED;
|
|
break;
|
|
case 9 :
|
|
$turnoutWestNewState = CLOSED;
|
|
$turnoutEastNewState = THROWN;
|
|
break;
|
|
case 11 :
|
|
$turnoutWestNewState = THROWN;
|
|
$turnoutEastNewState = THROWN;
|
|
break;
|
|
}
|
|
sendElementChange($widget.jsonType, $widget.turnoutWest, $turnoutWestNewState);
|
|
sendElementChange($widget.jsonType, $widget.turnoutEast, $turnoutEastNewState);
|
|
if (isDefined($widget.turnoutLowerWest)) {
|
|
sendElementChange($widget.jsonType, $widget.turnoutLowerWest, $turnoutEastNewState); // note: same as turnoutWest
|
|
}
|
|
if (isDefined($widget.turnoutLowerEast)) {
|
|
sendElementChange($widget.jsonType, $widget.turnoutLowerEast, $turnoutWestNewState); // note: same as turnoutEast
|
|
}
|
|
return;
|
|
} else if (this.className.startsWith('audioicon ')) {
|
|
// special handling of audioicon
|
|
var $widget = $gWidgets[this.id];
|
|
switch ($widget['onClickOperation']) {
|
|
case "PlaySoundLocally":
|
|
if ($widget['audio_widget'].paused) { // Sound is stopped
|
|
$widget['audio_widget'].loop = false;
|
|
// $widget['audio_widget'].loop = (playNumLoops == -1);
|
|
$widget['audio_widget'].play();
|
|
} else { // Sound is playing
|
|
$widget['audio_widget'].pause();
|
|
$widget['audio_widget'].currentTime = 0;
|
|
}
|
|
break;
|
|
case "PlaySoundGlobally":
|
|
if ($widget['state'] == 16) { // Sound is stopped
|
|
jmri.setAudio($widget.systemName, "Play");
|
|
} else if ($widget['state'] == 17) { // Sound is playing
|
|
jmri.setAudio($widget.systemName, "Stop");
|
|
}
|
|
break;
|
|
}
|
|
} else if (this.className.startsWith('logixngicon ')) {
|
|
// special handling of logixngicon
|
|
var $widget = $gWidgets[this.id];
|
|
jmri.clickLogixNGIcon($widget['identity']);
|
|
} else {
|
|
var $widget = $gWidgets[this.id];
|
|
var $newState = $getNextState($widget); // determine next state from current state
|
|
sendElementChange($widget.jsonType, $widget.systemName, $newState);
|
|
//also send new state to related turnout
|
|
if (isDefined($widget.turnoutB)) {
|
|
sendElementChange($widget.jsonType, $widget.turnoutB, $newState);
|
|
}
|
|
//used for crossover, LE layoutTurnout type 5
|
|
if (isDefined($widget.secondturnoutname)) {
|
|
//invert 2nd turnout if requested
|
|
if ($widget.secondturnoutinverted == "true") {
|
|
$newState = ($newState == CLOSED ? THROWN : CLOSED);
|
|
}
|
|
sendElementChange($widget.jsonType, $widget.secondturnoutname, $newState);
|
|
}
|
|
}
|
|
}
|
|
|
|
// perform multisensor click-handling, bound to click event for clickable multisensor widgets.
|
|
function $handleMultiClick(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault(); //prevent double-firing (touch + click)
|
|
var $widget = $gWidgets[this.id];
|
|
var clickX = (e.offsetX || e.pageX - $(e.target).offset().left); //get click position on the widget
|
|
var clickY = (e.offsetY || e.pageY - $(e.target).offset().top );
|
|
if (jmri_logging) {
|
|
log.log("handleMultiClick X,Y on WxH: " + clickX + "," + clickY + " on " + this.width + "x" + this.height);
|
|
}
|
|
//increment or decrement based on where the click occurred on image
|
|
var missed = true; //flag if click x,y outside image bounds, indicates we didn't get good values
|
|
var dec = false;
|
|
if ($widget.updown == "true") {
|
|
if (clickY >= 0 && clickY <= this.height) missed = false;
|
|
if (clickY > this.height / 2) dec = true;
|
|
} else {
|
|
if (clickX >= 0 && clickX <= this.width) missed = false;
|
|
if (clickX < this.width / 2) dec = true;
|
|
}
|
|
var displaying = 0;
|
|
for (i in $widget.siblings) { //determine which is currently active
|
|
if ($gWidgets[$widget.siblings[i]].state == ACTIVE) {
|
|
displaying = i; //flag the current active sibling
|
|
}
|
|
}
|
|
var next; //determine which is the next one to be set active (loop around only if click outside object)
|
|
if (dec) {
|
|
next = displaying - 1;
|
|
if (next < 0)
|
|
if (missed)
|
|
next = i;
|
|
else
|
|
next = 0;
|
|
} else {
|
|
next = displaying * 1 + 1;
|
|
if (next > i)
|
|
if (missed)
|
|
next = 0;
|
|
else
|
|
next = i;
|
|
}
|
|
for (i in $widget.siblings) { //loop through siblings and send changes as needed
|
|
if (i == next) {
|
|
if ($gWidgets[$widget.siblings[i]].state != ACTIVE) {
|
|
sendElementChange('sensor', $gWidgets[$widget.siblings[i]].name, ACTIVE); //set next sensor to active and send command to JMRI server
|
|
}
|
|
} else {
|
|
if ($gWidgets[$widget.siblings[i]].state != INACTIVE) {
|
|
sendElementChange('sensor', $gWidgets[$widget.siblings[i]].name, INACTIVE); //set all other siblings to inactive if not already
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//perform click-handling of linkinglabel widgets (3 cases: complete url or frame:<name> where name is a panel or a frame)
|
|
function $handleLinkingLabelClick(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault(); //prevent double-firing (touch + click)
|
|
var $widget = $gWidgets[this.id];
|
|
var $url = $widget.url;
|
|
if ($url.toLowerCase().indexOf("frame:") == 0) {
|
|
$frameName = $url.substring(6); //if "frame" found, remove it
|
|
$frameUrl = $gPanelList[$frameName]; //find panel in panel list
|
|
if (isUndefined($frameUrl)) {
|
|
$url = "/frame/" + $frameName + ".html"; //not in list, open using frameserver
|
|
} else {
|
|
$url = "/panel/" + $frameUrl; //format for panel server
|
|
}
|
|
}
|
|
window.location = $url; //navigate to the specified url
|
|
}
|
|
|
|
function $handleClickAllOn(e) { // click button on Switchboards
|
|
//loop thru widgets, setting each connected light to CLOSED/2, when button on top of switchboard was clicked
|
|
jQuery.each($gWidgets, function($id, $widget) {
|
|
if ($widget.connected == "true") {
|
|
sendElementChange($widget.jsonType, $widget.systemName, CLOSED);
|
|
}
|
|
});
|
|
};
|
|
|
|
function $handleClickAllOff(e) { // click button on Switchboards
|
|
//loop thru widgets, setting each connected light to THROWN/4, when button on top of switchboard was clicked
|
|
jQuery.each($gWidgets, function($id, $widget) {
|
|
if ($widget.connected == "true") {
|
|
sendElementChange($widget.jsonType, $widget.systemName, THROWN);
|
|
}
|
|
});
|
|
};
|
|
|
|
//update memory or restore the value when certain keystrokes occur
|
|
function $handleInputKeyUp(e) {
|
|
if (e.keyCode == 13 || e.keyCode == 9) { //on [Enter] or [Tab], send new value to server
|
|
var newVal = $(this).val();
|
|
var $id = $(this).attr('id');
|
|
var $widget = $gWidgets[$id];
|
|
jmri.setObject($widget.jsonType, $widget.systemName, newVal);
|
|
} else if (e.keyCode == 27) { //on [Escape], restore the previous value
|
|
var oldValue = $(this).data("oldValue")
|
|
$(this).val(oldValue);
|
|
}
|
|
};
|
|
|
|
//update memory when the focus is lost
|
|
function $handleInputBlur(e) {
|
|
var newVal = $(this).val();
|
|
var $id = $(this).attr('id');
|
|
var $widget = $gWidgets[$id];
|
|
jmri.setObject($widget.jsonType, $widget.systemName, newVal);
|
|
};
|
|
|
|
// End of Click Handling functions
|
|
|
|
|
|
/******************************************************************
|
|
* ======= (Control) Panel functions =======
|
|
*/
|
|
|
|
//draw an icon-type widget (pass in widget)
|
|
function $drawIcon($widget) {
|
|
var $hoverText = "";
|
|
if (isDefined($widget.hoverText)) {
|
|
$hoverText = " title='" + $widget.hoverText + "' alt='" + $widget.hoverText + "'";
|
|
}
|
|
if ($hoverText == "" && isDefined($widget.name)) { // if name available, use it as hover text if still blank
|
|
$hoverText = " title='" + $widget.name + "' alt='" + $widget.name + "'";
|
|
}
|
|
|
|
// additional naming for indicator*icon widgets to reflect occupancy
|
|
$indicator = "";
|
|
$state = "";
|
|
if ($widget.widgetType == "indicatortrackicon" || $widget.widgetType == "indicatorturnouticon") { // check oblock status
|
|
$indicator = ((($widget.occupancystate & 0x2) == 0x2) ? "Occupied" : ""); // look only at bit 2, compare to $redrawIcon()
|
|
Ostate = ($widget.occupancystate & 0xF0); // binary 11110000, discards (in)active bits in occupancy which we already used above
|
|
$state = Ostate | $widget.state; // adds Turnout state back in to fetch TO state = position icon
|
|
// $hoverText is updated for OUT_OF_SERVICE on redraw only
|
|
} else if ($widget.widgetType == "slipturnouticon") { // check turnout states, compare to $redrawIcon()
|
|
$state = $widget.slipState; // combined Turnouts state
|
|
} else {
|
|
$indicator = ($widget.occupancysensor && $widget.occupancystate == ACTIVE ? "Occupied" : "");
|
|
$state = $widget.state;
|
|
}
|
|
|
|
// add the image to the panel area, with appropriate css classes and id (skip any unsupported)
|
|
if (isDefined($widget['icon' + $indicator + $state])) {
|
|
$imgHtml = "<img id=" + $widget.id + " class='" + $widget.classes +
|
|
"' src='" + $widget["icon" + $indicator + $state].replaceAll("'","'") + "' " + $hoverText + "/>"
|
|
|
|
$("#panel-area").append($imgHtml); // put the html in the panel
|
|
|
|
$("#panel-area>#" + $widget.id).css($widget.styles); // apply style array to widget
|
|
|
|
// add overlay text if specified, one layer above, and copy attributes (except background-color)
|
|
if (isDefined($widget.text)) {
|
|
$("#panel-area").append("<div id=" + $widget.id + "-overlay class='overlay'>" + $widget.text + "</div>");
|
|
ovlCSS = {position:'absolute', left: $widget.x + 'px', top: $widget.y + 'px', zIndex: $widget.level*1 + 1, pointerEvents: 'none'};
|
|
$.extend(ovlCSS, $widget.styles); // append the styles from the widget
|
|
delete ovlCSS['background-color']; // clear the background color
|
|
if (isDefined($widget.fixedHeight)) {
|
|
$.extend(ovlCSS, {lineHeight: $widget.fixedHeight + 'px'}); // add lineheight for vertical centering (if set)
|
|
}
|
|
$("#panel-area>#" + $widget.id + "-overlay").css(ovlCSS);
|
|
}
|
|
} else {
|
|
log.error("ERROR: image not defined for " + $widget.widgetType + " " + $widget.id + ", iconstate=" + $state + " ["+$indicator+"] (icon" + $indicator + $state + ")");
|
|
}
|
|
$setWidgetPosition($("#panel-area #" + $widget.id));
|
|
}
|
|
|
|
//draw the analog clock (pass in widget), called on each update of clock
|
|
function $drawClock($widget) {
|
|
var $fs = $widget.scale * 100; // scale percentage, used for text
|
|
var $fcr = $gWidgets['IMRATEFACTOR'].state * 1; // get the fast clock rate factor from its widget
|
|
var $h = "";
|
|
$h += "<div class='clocktext' style='font-size:" + $fs + "%;' >" + $widget.state + "<br />" + $fcr + ":1</div>"; //add the text
|
|
$h += "<img class='clockface' src='/web/images/clockface.png' />"; //add the clock face
|
|
$h += "<img class='clockhourhand' src='/web/images/clockhourhand.png' />"; //add the hour hand
|
|
$h += "<img class='clockminutehand' src='/web/images/clockminutehand.png' />"; //add the minute hand
|
|
$("#panel-area>#" + $widget.id).html($h); //set the html for the widget
|
|
|
|
var hours = $widget.state.split(':')[0]; //extract hours from format "H:MM AM"
|
|
var mins = $widget.state.split(':')[1].split(' ')[0]; //extract minutes
|
|
var hdegree = hours * 30 + (mins / 2);
|
|
var hrotate = "rotate(" + hdegree + "deg)";
|
|
$("div.fastclock>img.clockhourhand").css({"transform": hrotate}); //set rotation for hour hand
|
|
var mdegree = mins * 6;
|
|
var mrotate = "rotate(" + mdegree + "deg)";
|
|
$("div.fastclock>img.clockminutehand").css({"transform": mrotate}); //set rotation for minute hand
|
|
}
|
|
|
|
// end of Control Panel functions
|
|
|
|
|
|
//build and return CSS array from attributes passed in
|
|
var $getTextCSSFromObj = function($widget) {
|
|
var $retCSS = {};
|
|
$retCSS['color'] = ''; //only clear attributes
|
|
$retCSS['background-color'] = '';
|
|
if (isDefined($widget.red)) {
|
|
$retCSS['color'] = "rgb(" + $widget.red + "," + $widget.green + "," + $widget.blue + ") ";
|
|
}
|
|
//check for new hasBackground element, ignore background colors unless set to yes
|
|
if (isDefined($widget.hasBackground) && $widget.hasBackground == "yes") {
|
|
$retCSS['background-color'] = "rgb(" + $widget.redBack + "," + $widget.greenBack + "," + $widget.blueBack + ") ";
|
|
}
|
|
if (isUndefined($widget.hasBackground) && isDefined($widget.redBack)) {
|
|
$retCSS['background-color'] = "rgb(" + $widget.redBack + "," + $widget.greenBack + "," + $widget.blueBack + ") ";
|
|
}
|
|
if (isDefined($widget.size)) {
|
|
$retCSS['font-size'] = $widget.size + "px ";
|
|
}
|
|
if (isDefined($widget.fontFamily)) {
|
|
$retCSS['font-family'] = $widget.fontFamily;
|
|
}
|
|
if (isDefined($widget.margin)) {
|
|
$retCSS['padding'] = $widget.margin + "px ";
|
|
}
|
|
if (isDefined($widget.borderSize)) {
|
|
$retCSS['border-width'] = $widget.borderSize + "px ";
|
|
}
|
|
if (isDefined($widget.redBorder)) {
|
|
$retCSS['border-color'] = "rgb(" + $widget.redBorder + "," + $widget.greenBorder + "," + $widget.blueBorder + ") ";
|
|
$retCSS['border-style'] = 'solid';
|
|
}
|
|
if (isDefined($widget.fixedWidth)) {
|
|
$retCSS['width'] = $widget.fixedWidth + "px ";
|
|
}
|
|
if (isDefined($widget.fixedHeight)) {
|
|
$retCSS['height'] = $widget.fixedHeight + "px ";
|
|
}
|
|
if (isDefined($widget.justification)) {
|
|
if ($widget.justification == "centre") {
|
|
$retCSS['text-align'] = "center";
|
|
} else {
|
|
$retCSS['text-align'] = $widget.justification;
|
|
}
|
|
}
|
|
if (isDefined($widget.style)) {
|
|
switch ($widget.style) { //set font based on style attrib from xml
|
|
case "1":
|
|
$retCSS['font-weight'] = 'bold';
|
|
break;
|
|
case "2":
|
|
$retCSS['font-style'] = 'italic';
|
|
break;
|
|
case "3":
|
|
$retCSS['font-weight'] = 'bold';
|
|
$retCSS['font-style'] = 'italic';
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $retCSS;
|
|
};
|
|
|
|
//get width of an html element by wrapping a copy in a div, then getting width of div
|
|
function $getElementWidth($e) {
|
|
o = $e.clone();
|
|
o.wrap('<div></div>').css({'position': 'absolute', 'float': 'left', 'white-space': 'nowrap', 'visibility': 'hidden'}).appendTo($('body'));
|
|
w = o.width();
|
|
o.remove();
|
|
return w;
|
|
}
|
|
|
|
//place widget in correct position, rotation, z-index and scale. (pass in dom element, to simplify calling from e.load())
|
|
var $setWidgetPosition = function(e) {
|
|
|
|
var $id = e.attr('id');
|
|
var $widget = $gWidgets[$id]; // look up the widget and get its panel properties
|
|
|
|
if (isDefined($widget) && isDefined(e[0])) {
|
|
//don't bother if widget not found (never called for beanswitch)
|
|
var $height = 0;
|
|
var $width = 0;
|
|
// use html5 original sizes if available
|
|
if (isDefined(e[0].naturalHeight)) {
|
|
$height = e[0].naturalHeight * $widget.scale;
|
|
} else {
|
|
$height = e.height() * $widget.scale;
|
|
}
|
|
if (isDefined(e[0].naturalWidth)) {
|
|
$width = e[0].naturalWidth * $widget.scale;
|
|
} else {
|
|
$width = e.width() * $widget.scale;
|
|
}
|
|
if ($widget.widgetFamily == "text") { //special handling to get width of free-floating text
|
|
$width = $getElementWidth(e) * $widget.scale;
|
|
}
|
|
|
|
// calculate x and y adjustment needed to keep upper left of bounding box in the same spot
|
|
// adapted to match JMRI's NamedIcon.rotate(). Note: transform-origin set in .css file
|
|
var tx = 0;
|
|
var ty = 0;
|
|
|
|
if ($height > 0 && ($widget.degrees !== 0 || $widget.scale != 1)) { // only calc offset if needed
|
|
|
|
var $rad = $toRadians($widget.degrees);
|
|
|
|
if (0 <= $widget.degrees && $widget.degrees < 90
|
|
|| -360 < $widget.degrees && $widget.degrees <= -270) {
|
|
tx = $height * Math.sin($rad);
|
|
ty = 0.0;
|
|
} else if (90 <= $widget.degrees && $widget.degrees < 180
|
|
|| -270 < $widget.degrees && $widget.degrees <= -180) {
|
|
tx = $height * Math.sin($rad) - $width * Math.cos($rad);
|
|
ty = -$height * Math.cos($rad);
|
|
} else if (180 <= $widget.degrees && $widget.degrees < 270
|
|
|| -180 < $widget.degrees && $widget.degrees <= -90) {
|
|
tx = -$width * Math.cos($rad);
|
|
ty = -$width * Math.sin($rad) - $height * Math.cos($rad);
|
|
} else /* if (270<=$widget.degrees && $widget.degrees<360) */{
|
|
tx = 0.0;
|
|
ty = -$width * Math.sin($rad);
|
|
}
|
|
}
|
|
// position widget to adjusted position, set z-index, then set rotation
|
|
e.css({
|
|
position : 'absolute',
|
|
left : (parseInt($widget.x) + tx) + 'px',
|
|
top : (parseInt($widget.y) + ty) + 'px',
|
|
zIndex : $widget.level
|
|
});
|
|
if ($widget.degrees !== 0) {
|
|
var $rot = "rotate(" + $widget.degrees + "deg)";
|
|
e.css({
|
|
"transform" : $rot
|
|
});
|
|
}
|
|
// set new height and width if scale specified
|
|
if ($widget.scale != 1 && $height > 0) {
|
|
e.css({
|
|
height : $height + 'px',
|
|
width : $width + 'px'
|
|
});
|
|
}
|
|
// if this is an image that's rotated or scaled, set callback to
|
|
// reposition on every icon load, as the icons can be different sizes.
|
|
if (e.is("img") && ($widget.degrees !== 0 || $widget.scale != 1.0)) {
|
|
e.unbind('load');
|
|
e.load(function() {
|
|
$setWidgetPosition($(this));
|
|
});
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
// reDraw an icon-based widget to reflect changes to state or occupancy
|
|
var $reDrawIcon = function($widget) {
|
|
// additional naming for indicator*icon widgets to reflect occupancy, error presendence status was already filtered in updateOblocks()
|
|
$indicator = "";
|
|
if ($widget.widgetType == "indicatortrackicon" || $widget.widgetType == "indicatorturnouticon") { // check oblock status
|
|
$indicator = ((($widget.occupancystate & 0x2) == 0x2) ? "Occupied" : ""); // look only at bit 2, compare to $drawIcon()
|
|
Ostate = ($widget.occupancystate & 0xF0); // binary + 11110000, discards (in)active occupancy info in bits 1-4
|
|
$state = (Ostate | $widget.state); // adds Turnout state back in to insert TO state = position icon
|
|
if (isDefined($widget.name)) { // intended for indicatorturnouts to show they are not clickable
|
|
$('img#' + $widget.id).attr('title', $widget.name + ((Ostate & 0x40) == OUT_OF_SERVICE ? " (off)" : ""));
|
|
// explain why not clickable TODO I18N tooltip for OOS + ERROR
|
|
}
|
|
} else if ($widget.widgetType == "slipturnouticon") {
|
|
$state = $widget.slipState; // widget is not a bean, fetch combined state as stored in widget, calculated from 2 turnout states
|
|
//log.log("STI $redrawIcon state: " + $state);
|
|
// adjust some states, copied from Display/SlipTurnoutIcon#displayState(int state), not required?
|
|
// if ($widget.turnoutType == "scissor") {
|
|
// switch ($state) {
|
|
// case 5 :
|
|
// log.log("########### STI $redrawIcon state: " + $state + " set to 0 for Scissor");
|
|
// $state = 0;
|
|
// break;
|
|
// }
|
|
// }
|
|
} else { // default handling
|
|
$indicator = ($widget.occupancysensor && $widget.occupancystate == ACTIVE ? "Occupied" : "");
|
|
$state = $widget.state;
|
|
}
|
|
// set image src to requested state's image, if defined
|
|
if ($widget['icon' + $indicator + $state]) {
|
|
$('img#' + $widget.id).attr('src', $widget['icon' + $indicator + ($state + "")]);
|
|
} else if ($widget['defaulticon']) { // if state icon not found, use default icon if provided
|
|
$('img#' + $widget.id).attr('src', $widget['defaulticon']);
|
|
} else {
|
|
log.error("ERROR: image not defined for " + $widget.widgetType + " " + $widget.id + ", state=" + $widget.state + ", status=" + $widget.occupancystate + ", iconstate=" + $state + " ["+$indicator+"] (icon" + $indicator + $state + ")");
|
|
}
|
|
};
|
|
|
|
// set new value for widget, showing proper icon, return widgets changed
|
|
var $setWidgetState = function($id, $newState, data) {
|
|
var $widget = $gWidgets[$id];
|
|
|
|
// if undefined widget this must be a LE slip or a PE slipTurnoutIcon
|
|
if (isUndefined($widget)) {
|
|
// does it have "e" or "w" suffix? it's a slipTurnoutIcon
|
|
if ($id.endsWith("e") || $id.endsWith("w")) {
|
|
if (jmri_logging) {
|
|
log.log("$setWidgetState STI " + $id + " to state " + $newState);
|
|
}
|
|
// remove suffix
|
|
var $slipID = $id.slice(0, -1);
|
|
// get the slip widget
|
|
$widget = $gWidgets[$slipID];
|
|
// determine combined slipState for icon0/5/7/9/11
|
|
$turnoutName = data.name; // systemName
|
|
//log.log("change from turnout: " + $turnoutName + " to state: " + $newState);
|
|
if (($turnoutName == $widget.turnoutEast) || (data.userName == $widget.turnoutEast)) {
|
|
// east turnout // also compare source by userName
|
|
$widget.slipStateEast = $newState; // store turnout state e
|
|
} else if (($turnoutName == $widget.turnoutWest) || (data.userName == $widget.turnoutWest)) {
|
|
// west turnout // also compare source by userName
|
|
$widget.slipStateWest = $newState; // store turnout state w
|
|
}
|
|
// handle changes from the 2 extra turnouts, if defined (they mirror the basic e and w turnouts)
|
|
// only CCCC, TCCT and CTTC are valid turnout state combinations (for slipState 5, 7 and 9 respectively)
|
|
if (($turnoutName == $widget.turnoutLowerWest) || (data.userName == $widget.turnoutLowerWest)) {
|
|
// scissor additional west turnout, handle like turnoutEast
|
|
if (($newState == CLOSED) && ((($widget.slipStateWest == CLOSED) && ($widget.slipStateEast == CLOSED)) ||
|
|
(($widget.slipStateWest == THROWN) && ($widget.slipStateEast == CLOSED)))) {
|
|
$widget.slipStateEast = $newState;
|
|
} else if (($newState == THROWN) && ($widget.slipStateWest == CLOSED) && ($widget.slipStateEast == THROWN)) {
|
|
$widget.slipStateEast = $newState;
|
|
} else {
|
|
$newState = INCONSISTENT;
|
|
}
|
|
}
|
|
if (($turnoutName == $widget.turnoutLowerEast) || (data.userName == $widget.turnoutLowerEast)) {
|
|
// scissor additional east turnout, handle like turnoutWest
|
|
if (($newState == CLOSED) && ((($widget.slipStateWest == CLOSED) && ($widget.slipStateEast == CLOSED)) ||
|
|
(($widget.slipStateWest == CLOSED) && ($widget.slipStateEast == THROWN)))) {
|
|
$widget.slipStateWest = $newState;
|
|
} else if (($newState == THROWN) && ($widget.slipStateWest == THROWN) && ($widget.slipStateEast == CLOSED)) {
|
|
$widget.slipStateWest = $newState;
|
|
} else {
|
|
$newState = INCONSISTENT;
|
|
}
|
|
}
|
|
|
|
if ($widget.slipStateWest == UNKNOWN || $widget.slipStateEast == UNKNOWN) {
|
|
$widget.slipState = UNKNOWN; // incomplete inputs, set state UNKNOWN
|
|
} else if ($newState == INCONSISTENT) {
|
|
$widget.slipState = INCONSISTENT;
|
|
} else {
|
|
// fix some special sequences, as in java/src/jmri/jmrit/display/SlipTurnoutIcon.java#displayState(state)
|
|
if ($widget.turnoutType == "threeWay" && $widget.slipStateWest == THROWN && $widget.slipStateEast == CLOSED) {
|
|
// ignore slipStateEast CLOSED (slipstate 7), use slipstate 11 instead, like Panel SlipTurnoutIcon.java
|
|
$widget.slipState = (THROWN << 1) | ($widget.slipStateWest >> 1) | 0x01;
|
|
} else {
|
|
$widget.slipState = ($widget.slipStateEast << 1) | ($widget.slipStateWest >> 1) | 0x01;
|
|
}
|
|
}
|
|
log.log("#### $setWidgetState(slipturnouticon " + $slipID + ", " + $widget.slipState +
|
|
"); (was " + $widget.slipState + ")");
|
|
$newState = $widget.slipState;
|
|
// is overwritten by $newState at end of method, so temp only to pass next if-statement and redraw correctly
|
|
$id = $slipID;
|
|
|
|
// does it have "l" or "r" suffix? it's an LE slip
|
|
} else if ($id.endsWith("l") || $id.endsWith("r")) {
|
|
if (jmri_logging) {
|
|
log.log("\n#### INFO: clicked slip " + $id + " to state " + $newState);
|
|
}
|
|
|
|
// remove suffix
|
|
var $slipID = $id.slice(0, -1);
|
|
// get the slip widget
|
|
$widget = $gWidgets[$slipID];
|
|
|
|
// convert current slip state to current turnout states
|
|
var $stateA, $stateB;
|
|
//[$stateA, $stateB] = getTurnoutStatesForSlip($widget);
|
|
//[$stateA, $stateB] = [$widget.turnout.state, $widget.turnoutB.state];
|
|
[$stateA, $stateB] = [$widget.stateA, $widget.stateB];
|
|
$widget.state = getSlipStateForTurnoutStates($widget, $stateA, $stateB);
|
|
if (jmri_logging) {
|
|
log.log("#### Slip " + $widget.name +
|
|
" before: " + slipStateToString($widget.state) +
|
|
", stateA: " + turnoutStateToString($stateA) +
|
|
", stateB: " + turnoutStateToString($stateB));
|
|
}
|
|
|
|
// change appropriate turnout state
|
|
if ($id.endsWith("r")) {
|
|
if ($stateA != $newState) {
|
|
if (jmri_logging) {
|
|
log.log("#### Changed r slip " + $widget.name +
|
|
" $stateA from " + turnoutStateToString($stateA) +
|
|
" to " + turnoutStateToString($newState));
|
|
}
|
|
$stateA = $newState;
|
|
$widget.stateA = $stateA;
|
|
}
|
|
} else if ($id.endsWith("l")) {
|
|
if ($stateB != $newState) {
|
|
if (jmri_logging) {
|
|
log.log("#### Changed l slip " + $widget.name +
|
|
" $stateB from " + turnoutStateToString($stateB) +
|
|
" to " + turnoutStateToString($newState));
|
|
}
|
|
$stateB = $newState;
|
|
$widget.stateB = $stateB;
|
|
}
|
|
}
|
|
|
|
// turn turnout states back into slip state
|
|
$newState = getSlipStateForTurnoutStates($widget, $stateA, $stateB);
|
|
if (jmri_logging) {
|
|
log.log("#### Slip " + $widget.name +
|
|
" after: " + slipStateToString($newState) +
|
|
", stateA: " + turnoutStateToString($stateA) +
|
|
", stateB: " + turnoutStateToString($stateB));
|
|
}
|
|
|
|
if ($widget.state != $newState) {
|
|
if (jmri_logging) {
|
|
log.log("#### Changing slip " + $widget.name + " from " + slipStateToString($widget.state) +
|
|
" to " + slipStateToString($newState));
|
|
}
|
|
}
|
|
//jmri_logging = false;
|
|
|
|
// set $id to slip id
|
|
$id = $slipID;
|
|
} else if ($id.startsWith("TUR")) {
|
|
//log.log("$setWidgetState(" + $id + ", " + $newState + ", " + data + ")");
|
|
$logProperties(data);
|
|
|
|
var turntableID = $id.split(".")[0];
|
|
$widget = $gWidgets[turntableID];
|
|
$widget['activeRayID'] = $id;
|
|
$widget['activeRayTurnout'] = data.name;
|
|
$widget['activeRayState'] = turnoutStateToString($newState);
|
|
$drawTurntable($widget);
|
|
return;
|
|
} else {
|
|
if (jmri_logging) {
|
|
log.log("$setWidgetState unknown $id: '" + $id + "'.");
|
|
}
|
|
return;
|
|
}
|
|
} else if ($widget.widgetType == 'layoutSlip') {
|
|
// JMRI doesn't send slip states, it sends slip turnout states
|
|
// so ignore this (incorrect) slip state change
|
|
if (jmri_logging) {
|
|
log.log("#### $setWidgetState(slip " + $id + ", " + slipStateToString($newState) +
|
|
"); (was " + slipStateToString($widget.state) + ")");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ($widget.state !== $newState) { // don't bother if already this value
|
|
if (jmri_logging) {
|
|
log.log("JMRI changed " + $id + " (" + $widget.jsonType + " " + $widget.name + ") from state '" + $widget.state + "' to '" + $newState + "'.");
|
|
}
|
|
if (data.type == "sensor" && ($widget.widgetType == "indicatortrackicon" || $widget.widgetType == "indicatortrackicon")) {
|
|
$widget.occupancystate = $newState;
|
|
} else { // standard handling of icon widgets
|
|
$widget.state = $newState;
|
|
}
|
|
// override the state with idTag's "name" in a very specific circumstance
|
|
if (($widget.jsonType == "memory" || $widget.jsonType == "block" || $widget.jsonType == "reporter" ) &&
|
|
$widget.widgetFamily == "icon" && data.value !== null && data.value.type == "idTag") {
|
|
$widget.state = data.value.data.name;
|
|
}
|
|
|
|
switch ($widget.widgetFamily) {
|
|
case "icon" :
|
|
if ($widget.widgetType == "indicatortrackicon" || $widget.widgetType == "indicatortrackicon") {
|
|
if ($widget.occupancysensor != "none") {
|
|
$widget.occupancystate = $newState;
|
|
//console.log("SET widget " + $widget.id + " to state=" + $newState);
|
|
} else if ($widget.occupancyblock != "none") { // expected for turnout
|
|
// if defined, follow the occupancyblock and ignore any sensors, don't set widget.state (used for turnout state)
|
|
// only pick up the turnout state change, bits 0-4
|
|
$widget.state = ($newState & 0xF) ;
|
|
//console.log("WARNING UNEXPECTED ITOI widget=" + $widget.id + " to state=" + $newState); // TODO clean up
|
|
}
|
|
}
|
|
$reDrawIcon($widget);
|
|
break;
|
|
case "input" :
|
|
$('#' + $id).val($newState); //update the input
|
|
$('#' + $id).data("oldValue", $newState); //save the current value if needed for [Escape]
|
|
break;
|
|
case "text" :
|
|
if ($widget.jsonType == "memory" || $widget.jsonType == "block" || $widget.jsonType == "reporter" ) {
|
|
if ($widget.widgetType == "fastclock") {
|
|
$drawClock($widget);
|
|
} else { // set memory/block/reporter text or html to new value from server, clearing "null"
|
|
if ($newState == null) {
|
|
$('div#' + $id).text("");
|
|
} else if ($newState.startsWith("<html>")) {
|
|
$('div#' + $id).html($newState);
|
|
} else {
|
|
$('div#' + $id).text($newState);
|
|
}
|
|
}
|
|
} else {
|
|
if (isDefined($widget['text' + $newState])) {
|
|
$('div#' + $id).text($widget['text' + $newState]); // set text to new state's text
|
|
}
|
|
if (isDefined($widget['css' + $newState])) {
|
|
$('div#' + $id).css($widget['css' + $newState]); // set css to new state's css
|
|
}
|
|
}
|
|
break;
|
|
case "drawn" :
|
|
if ($widget.widgetType == "layoutturnout") {
|
|
$drawTurnout($widget);
|
|
} else if ($widget.widgetType == 'layoutSlip') {
|
|
$drawSlip($widget);
|
|
}
|
|
break;
|
|
case "switch" : // Switchboard
|
|
if ($widget.widgetType == "beanswitch" && isDefined($widget['shape'])) {
|
|
if ($widget.shape == "button") { // update div css
|
|
$('div#' + $id).text($widget['text' + $newState]); // set text to new state's text
|
|
$('div#' + $id).css({"background-color": $widget['swColor' + $newState]});
|
|
} else { // icon, symbol, slider (drawing) are directly drawn on canvas
|
|
$widget.text = $widget['text' + $newState]; // set text in Widget to new state's text
|
|
$drawWidgetSymbol($id, $newState);
|
|
} // for newly created items, reload web page to activate json binding
|
|
}
|
|
break;
|
|
}
|
|
$gWidgets[$id].state = $newState; // update the persistent widget to the new state
|
|
}
|
|
};
|
|
|
|
//return a unique ID # when called
|
|
var $gUnique = function() {
|
|
if (isUndefined($gUnique.id)) {
|
|
$gUnique.id = 0;
|
|
}
|
|
$gUnique.id++;
|
|
return $gUnique.id;
|
|
};
|
|
|
|
//clean up a name, for example to use as an id
|
|
var $safeName = function($name) {
|
|
if (isUndefined($name)) {
|
|
return "unique-" + $gUnique();
|
|
} else {
|
|
return $name.replace(/:/g, "_").replace(/ /g, "_").replace(/%20/g, "_");
|
|
}
|
|
};
|
|
|
|
//send request for state change
|
|
var sendElementChange = function(type, name, state) {
|
|
//log.log("Sending JMRI " + type + " '" + name + "' state '" + state + "'.");
|
|
jmri.setObject(type, name, state);
|
|
};
|
|
|
|
//show unexpected ajax errors
|
|
$(document).ajaxError(function(event, xhr, opt, exception) {
|
|
if (xhr.statusText != "abort" && xhr.status != 0) {
|
|
var $msg = "AJAX Error requesting " + opt.url + ", status= " + xhr.status + " " + xhr.statusText;
|
|
$('div#messageText').text($msg);
|
|
$("#activity-alert").addClass("show").removeClass("hidden");
|
|
$('dvi#workingMessage').position({within: "window"});
|
|
log.log($msg);
|
|
return;
|
|
}
|
|
if (xhr.statusText == "timeout") {
|
|
var $msg = "AJAX timeout " + opt.url + ", status= " + xhr.status + " " + xhr.statusText + " resending list....";
|
|
log.log($msg);
|
|
// TODO: need to recover somehow
|
|
}
|
|
});
|
|
|
|
//clear out whitespace from xml, function adapted from
|
|
//http://stackoverflow.com/questions/1539367/remove-whitespace-and-line-breaks-between-html-elements-using-jquery/3103269#3103269
|
|
jQuery.fn.xmlClean = function() {
|
|
this.contents().filter(function() {
|
|
if (this.nodeType != 3) {
|
|
$(this).xmlClean();
|
|
return false;
|
|
}
|
|
else {
|
|
return !/\S/.test(this.nodeValue);
|
|
}
|
|
}).remove();
|
|
}
|
|
|
|
// handle the toggling (or whatever) of the "next" state for the passed-in widget
|
|
var $getNextState = function($widget) {
|
|
var $nextState = undefined;
|
|
$logProperties($widget);
|
|
|
|
if ($widget.widgetType == 'signalheadicon') { //special case for signalheadicons
|
|
switch ($widget.clickmode * 1) { // logic based on SignalHeadIcon.java
|
|
case 0 :
|
|
switch ($widget.state * 1) { // (* 1 is to insure numeric comparisons)
|
|
case RED:
|
|
case FLASHRED:
|
|
$nextState = YELLOW;
|
|
break;
|
|
case YELLOW:
|
|
case FLASHYELLOW:
|
|
$nextState = GREEN;
|
|
break;
|
|
default: //also catches GREEN and FLASHGREEN
|
|
$nextState = RED;
|
|
break;
|
|
}
|
|
case 1 :
|
|
// TODO: handle lit/unlit toggle
|
|
// getSignalHead().setLit(!getSignalHead().getLit());
|
|
break;
|
|
case 2 :
|
|
// getSignalHead().setHeld(!getSignalHead().getHeld());
|
|
$nextState = ($widget.state * 1 == HELD ? RED : HELD); //toggle between red and held states
|
|
break;
|
|
case 3: // loop through all elements, finding iconX and get "next one", skipping special ones
|
|
var $firstState = undefined;
|
|
var $currentState = undefined;
|
|
for (k in $widget) {
|
|
var s = k.substr(4) * 1; //extract the state from current icon var, insure it is treated as numeric
|
|
//get valid value, name starts with 'icon', but not the HELD or DARK ones
|
|
if (k.indexOf('icon') == 0 && isDefined($widget[k]) && k != 'icon' + HELD && k != 'icon' + DARK) {
|
|
if (isUndefined($firstState))
|
|
$firstState = s; //remember the first state (for last one)
|
|
if (isDefined($currentState) && isUndefined($nextState))
|
|
$nextState = s; //last one was the current, so this one must be next
|
|
if (s == $widget.state)
|
|
$currentState = s;
|
|
// log.log('key: '+k+" first="+$firstState+" current="+$currentState+" next="+$nextState);
|
|
}
|
|
}
|
|
if (isUndefined($nextState))
|
|
$nextState = $firstState; // if still not set, start over
|
|
}
|
|
|
|
} else if ($widget.widgetType == 'signalmasticon') { // special case for signalmasticons
|
|
// loop through all elements, finding iconXXX and get next iconXXX, skipping special ones
|
|
switch ($widget.clickmode * 1) { // logic based on SignalMastIcon.java
|
|
case 0 :
|
|
var $firstState = undefined;
|
|
var $currentState = undefined;
|
|
for (k in $widget) {
|
|
var s = k.substr(4); //extract the state from current icon var
|
|
//look for next icon value, skipping Held, Dark and Unknown
|
|
if (k.indexOf('icon') == 0 && isDefined($widget[k]) && s != 'Held' && s != 'Dark'
|
|
&& s !='Unlit' && s != 'Unknown') {
|
|
if (isUndefined($firstState))
|
|
$firstState = s; // remember the first state (for last one)
|
|
if (isDefined($currentState) && isUndefined($nextState))
|
|
$nextState = s; // last one was the current, so this one must be next
|
|
if (s == $widget.state)
|
|
$currentState = s;
|
|
}
|
|
};
|
|
if (isUndefined($nextState))
|
|
$nextState = $firstState; // if still not set, start over
|
|
break;
|
|
|
|
case 1 :
|
|
//TODO: handle lit/unlit states
|
|
break;
|
|
|
|
case 2 :
|
|
//toggle between stop and held state
|
|
$nextState = ($widget.state == "Held" ? "Stop" : "Held");
|
|
break;
|
|
|
|
};
|
|
|
|
} else if ($widget.widgetType == 'slipturnouticon') {
|
|
// slipturnouticons store the current state in .slipState, not .state
|
|
switch ($widget.turnoutType) { // logic based on java/src/jmri/jmrit/display/SlipTurnoutIcon.java
|
|
case "doubleSlip" :
|
|
$nextState = ($widget.slipState == 11 ? 5 : $widget.slipState + 2);
|
|
break;
|
|
case "singleSlip" :
|
|
if ($widget.singleSlipRoute == "lowerWestToLowerEast") {
|
|
switch ($widget.slipState) {
|
|
case 5 :
|
|
$nextState = 9;
|
|
break;
|
|
case 9 :
|
|
$nextState = 11;
|
|
break;
|
|
case 11 :
|
|
$nextState = 5;
|
|
break;
|
|
}
|
|
} else if ($widget.singleSlipRoute == "upperWestToUpperEast") {
|
|
switch ($widget.slipState) {
|
|
case 5 :
|
|
$nextState = 11;
|
|
break;
|
|
case 7 :
|
|
$nextState = 5;
|
|
break;
|
|
case 11 :
|
|
$nextState = 7;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "threeWay" :
|
|
if ($widget.firstTurnoutExit == "lower") {
|
|
$nextState = ($widget.slipState == 9 ? 5 : $widget.slipState + 2);
|
|
} else { // $widget.firstTurnoutExit == "upper"
|
|
switch ($widget.slipState) {
|
|
case 5 :
|
|
$nextState = 9;
|
|
break;
|
|
case 9 :
|
|
$nextState = 11;
|
|
break;
|
|
case 11 :
|
|
$nextState = 5;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "scissor" :
|
|
$nextState = ($widget.slipState == 9 ? 5 : $widget.slipState + 2);
|
|
// State 11 not allowed for a scissor
|
|
// does not provide 5 after 7 as it would require extra logic
|
|
$nextState = ($widget.slipState == 9 ? 5 : $widget.slipState + 2);
|
|
break;
|
|
};
|
|
|
|
} else { // default: start with INACTIVE, then toggle to ACTIVE and back (same for turnout states: 2 <> 4)
|
|
$nextState = ($widget.state == ACTIVE ? INACTIVE : ACTIVE);
|
|
}
|
|
|
|
if (isUndefined($nextState))
|
|
$nextState = $widget.state; //default to no change
|
|
return $nextState;
|
|
};
|
|
|
|
// preload all images referred to by the widget
|
|
var $preloadWidgetImages = function($widget) {
|
|
for (k in $widget) {
|
|
if (k.indexOf('icon') == 0 && isDefined($widget[k]) && $widget[k] !== "yes") {
|
|
//if attribute names starts with 'icon', it's an image, so preload it
|
|
$("<img src='" + $widget[k] + "'/>");
|
|
}
|
|
}
|
|
};
|
|
|
|
// determine widget "family" for broadly grouping behaviors
|
|
// note: not-yet-supported widgets are commented out here so as to return undefined
|
|
var $getWidgetFamily = function($widget, $element) {
|
|
|
|
if (($widget.widgetType == "positionablelabel" || $widget.widgetType == "linkinglabel"
|
|
|| $widget.widgetType == "audioicon" || $widget.widgetType == "logixngicon")
|
|
&& isDefined($widget.text)) {
|
|
return "text"; //special case to distinguish text vs. icon labels
|
|
}
|
|
if ($widget.widgetType == "sensoricon" && $widget.icon == "no") {
|
|
return "text"; //special case to distinguish text vs. icon labels
|
|
}
|
|
if ($widget.widgetType == "memoryicon" && $($element).find('memorystate').length == 0) {
|
|
return "text"; //if no memorystate icons, treat as text
|
|
}
|
|
switch ($widget.widgetType) {
|
|
case "locoicon" :
|
|
case "trainicon" :
|
|
case "fastclock" :
|
|
case "BlockContentsIcon" :
|
|
case "reportericon" :
|
|
return "text";
|
|
break;
|
|
case "memorySpinnerIcon" :
|
|
case "memoryComboIcon" :
|
|
case "memoryInputIcon" :
|
|
case "blockContentsInputIcon" :
|
|
return "input";
|
|
break;
|
|
case "positionablelabel" :
|
|
case "audioicon" :
|
|
case "logixngicon" :
|
|
case "linkinglabel" :
|
|
case "turnouticon" :
|
|
case "outputindicator" :
|
|
case "sensoricon" :
|
|
case "LightIcon" :
|
|
case "multisensoricon" :
|
|
case "signalheadicon" :
|
|
case "signalmasticon" :
|
|
case "indicatortrackicon" :
|
|
case "indicatorturnouticon" :
|
|
case "memoryicon" :
|
|
case "slipturnouticon" :
|
|
return "icon";
|
|
break;
|
|
case 'layoutSlip' :
|
|
case "layoutturnout" :
|
|
case "tracksegment" :
|
|
case "positionablepoint" :
|
|
case "backgroundColor" :
|
|
case "layoutblock" :
|
|
case "levelxing" :
|
|
case "layoutturntable" :
|
|
case "layoutShape" :
|
|
case "positionableRectangle" :
|
|
case "positionableRoundRect" :
|
|
case "positionableCircle" :
|
|
case "positionableEllipse" :
|
|
return "drawn";
|
|
break;
|
|
case "beanswitch" :
|
|
return "switch";
|
|
break;
|
|
}
|
|
log.log("unhandled widget type of '" + $widget.widgetType +"' id = "+$widget.id);
|
|
return; //unrecognized widget returns undefined
|
|
};
|
|
|
|
function listPanels(name) {
|
|
$.ajax({
|
|
url: "/panel/?format=json",
|
|
data: {},
|
|
success: function(data, textStatus, jqXHR) {
|
|
if (data.length !== 0) {
|
|
$.each(data, function(index, value) {
|
|
$gPanelList[value.data.userName] = value.data.name;
|
|
});
|
|
}
|
|
if (name === null || typeof (panelName) === undefined) {
|
|
if (data.length !== 0) {
|
|
$("#panel-list").empty();
|
|
$("#activity-alert").addClass("hidden").removeClass("show");
|
|
$("#panel-list").addClass("show").removeClass("hidden");
|
|
$.each(data, function(index, value) {
|
|
$("#panel-list").append("<div class=\"col-sm-6 col-md-4 col-lg-3\"><div class=\"thumbnail\"><a href=\"/panel/" + value.data.name + "\"><div class=\"thumbnail-image\"><img src=\"/panel/" + value.data.name + "?format=png\" style=\"width: 100%;\"></div><div class=\"caption\">" + value.data.userName + "</div></a></div></div>");
|
|
// (12 / col-lg-#) % index + 1
|
|
if (4 % (index + 1)) {
|
|
$("#panel-list").append("<div class=\"clearfix visible-lg\"></div>");
|
|
}
|
|
// (12 / col-md-#) % index + 1
|
|
if (3 % (index + 1)) {
|
|
$("#panel-list").append("<div class=\"clearfix visible-md\"></div>");
|
|
}
|
|
// (12 / col-sm-#) % index + 1
|
|
if (2 % (index + 1)) {
|
|
$("#panel-list").append("<div class=\"clearfix visible-sm\"></div>");
|
|
}
|
|
});
|
|
// resizeThumbnails(); // sometimes gets .thumbnail sizes too small under image. TODO Fix it
|
|
} else {
|
|
$("#activity-alert").addClass("hidden").removeClass("show");
|
|
$("#warning-no-panels").addClass("show").removeClass("hidden");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function resizeThumbnails() {
|
|
tallest = 0;
|
|
$(".thumbnail-image").each(function() {
|
|
thisHeight = $("img", this).height();
|
|
if (thisHeight > tallest) {
|
|
tallest = thisHeight;
|
|
}
|
|
});
|
|
$(".thumbnail-image").each(function() {
|
|
$(this).height(tallest);
|
|
});
|
|
}
|
|
|
|
$(window).resize(function() {
|
|
resizeThumbnails();
|
|
});
|
|
|
|
//-----------------------------------------javascript processing starts here (main) ---------------------------------------------
|
|
$(document).ready(function() {
|
|
// get panel name if passed as a parameter
|
|
var panelName = getParameterByName("name");
|
|
// get panel name if part of the path
|
|
if (panelName === null || typeof (panelName) === undefined) {
|
|
var path = $(location).attr('pathname');
|
|
path = path.split("/");
|
|
if (path.length > 3) {
|
|
panelName = path[path.length - 2] + "/" + path[path.length - 1];
|
|
}
|
|
}
|
|
// setup the functional menu items
|
|
$("#navbar-panel-reload > a").attr("href", location.href);
|
|
$("#navbar-panel-xml > a").attr("href", location.pathname + "?format=xml");
|
|
// show panel thumbnails if no panel name
|
|
listPanels(panelName);
|
|
if (panelName === null || typeof (panelName) === undefined) {
|
|
$("#panel-list").addClass("show").removeClass("hidden");
|
|
$("#panel-area").addClass("hidden").removeClass("show");
|
|
// hide the Show XML menu when listing panels
|
|
$("#navbar-panel-xml").addClass("hidden").removeClass("show");
|
|
} else {
|
|
// note: the functions and parameter names must match exactly those in web/js/jquery.jmri.js
|
|
// see for example jmri/server/json/turnout/turnout-server.json
|
|
jmri = $.JMRI({
|
|
didReconnect: function() {
|
|
// if a reconnect is triggered, reload the page - it is the
|
|
// simplest method to refresh every object in the panel
|
|
log.log("Reloading at reconnect");
|
|
location.reload(false);
|
|
},
|
|
audio: function(name, state, data) {
|
|
$.each(whereUsed[name], function(index, widgetId) {
|
|
$widget = $gWidgets[widgetId];
|
|
$widget['state'] = state;
|
|
|
|
if (state == 16 && $widget['stopSoundWhenJmriStops']) { // Sound is stopped
|
|
$widget['audio_widget'].pause();
|
|
$widget['audio_widget'].currentTime = 0;
|
|
} else if (state == 17 && $widget['playSoundWhenJmriPlays']) { // Sound is playing
|
|
$widget['audio_widget'].currentTime = 0;
|
|
$widget['audio_widget'].loop = (data.playNumLoops == -1);
|
|
$widget['audio_widget'].play();
|
|
}
|
|
});
|
|
},
|
|
audioicon: function(identity, command, playNumLoops) {
|
|
$widget = audioIconIDs['audioicon:'+identity];
|
|
if (command == "Play") {
|
|
$widget['audio_widget'].loop = (playNumLoops == -1);
|
|
$widget['audio_widget'].play();
|
|
} else if (command == "Stop") {
|
|
$widget['audio_widget'].pause();
|
|
$widget['audio_widget'].currentTime = 0;
|
|
}
|
|
},
|
|
light: function(name, state, data) {
|
|
updateWidgets(name, state, data);
|
|
},
|
|
block: function(name, value, data) {
|
|
//console.log("HEARD BLOCK " + name + " value=" + value);
|
|
if (value !== null) {
|
|
if (value.type == "idTag") {
|
|
value = value.data.userName; // for idTags, use the value in userName instead
|
|
} else if (value.type == "reporter") {
|
|
value = value.data.value; // for reporters, use the value in data instead
|
|
} else if (value.type == "rosterEntry") {
|
|
if (value.data.icon !== null) {
|
|
value = "<html><img src='" + value.data.icon + "'></html>"; // for rosterEntries, create an image tag instead
|
|
} else {
|
|
value = value.data.name; // if roster icon not set, just show the name
|
|
}
|
|
}
|
|
}
|
|
updateWidgets(name, value, data);
|
|
},
|
|
oblock: function(name, status, data) { // data contains data.status (Allocated, Occupied,... not state)
|
|
//console.log("HEARD JSON OBLOCK " + name + " status=" + status + " (" + data.status + ")");
|
|
if (data.status !== null) {
|
|
updateOblocks(name, data.status); // only for indicator(turnout)trackicon widgets
|
|
}
|
|
},
|
|
layoutBlock: function(name, value, data) {
|
|
setBlockColor(name, data.blockColor);
|
|
},
|
|
memory: function(name, value, data) {
|
|
if (value !== null) {
|
|
//console.log("MEMORY " + name + " value=" + value + " data=" + data);
|
|
if (value.type == "idTag") {
|
|
value = value.data.userName; // for idTags, use the value in userName instead
|
|
} else if (value.type == "reporter"){
|
|
value = value.data.value; // for reporters, use the value in data instead
|
|
} else if (value.type == "rosterEntry") {
|
|
if (value.data.icon !== null) {
|
|
value = "<html><img src='" + value.data.icon + "'></html>"; // for rosterEntries, create an image tag instead
|
|
} else {
|
|
value = value.data.name; // if roster icon not set, just show the name
|
|
}
|
|
}
|
|
}
|
|
updateWidgets(name, value, data);
|
|
},
|
|
reporter: function(name, value, data) {
|
|
//console.log("REPORTER " + name + " value=" + value + " data=" + data);
|
|
updateWidgets(name, value, data);
|
|
},
|
|
route: function(name, state, data) {
|
|
updateWidgets(name, state, data);
|
|
},
|
|
sensor: function(name, state, data) {
|
|
updateOccupancy(name, state, data);
|
|
//console.log("Sensor " + name + " state=" + state);
|
|
updateWidgets(name, state, data);
|
|
},
|
|
signalHead: function(name, state, data) {
|
|
updateWidgets(name, state, data);
|
|
},
|
|
signalMast: function(name, state, data) {
|
|
updateWidgets(name, state, data);
|
|
},
|
|
turnout: function(name, state, data) {
|
|
//console.log("Turnout " + name + " state=" + state);
|
|
updateWidgets(name, state, data);
|
|
}
|
|
});
|
|
$("#panel-list").addClass("hidden").removeClass("show");
|
|
$("#panel-area").addClass("show").removeClass("hidden");
|
|
|
|
// include name of panel in page title. Will be updated to userName later
|
|
setTitle("Loading " + panelName + "...");
|
|
|
|
//get updates to fast clock rate
|
|
getRateFactor();
|
|
|
|
// request actual xml of panel, and process it on return
|
|
// uses setTimeout simply to not block other JavaScript since
|
|
// requestPanelXML has a long timeout
|
|
setTimeout(function() {
|
|
requestPanelXML(panelName);
|
|
}, 500);
|
|
}
|
|
});
|
|
|
|
//------------------------------------------- end of main -------------------------------------------
|
|
|
|
// Add Widget to store fastclock rate
|
|
function getRateFactor() {
|
|
$widget = new Array();
|
|
$widget.jsonType = "memory";
|
|
$widget['name'] = "IMRATEFACTOR"; // already defined in JMRI
|
|
$widget['id'] = $widget['name'];
|
|
$widget['safeName'] = $widget['name'];
|
|
$widget['systemName'] = $widget['name'];
|
|
$widget['state'] = "1.0";
|
|
$gWidgets[$widget.id] = $widget;
|
|
if (!($widget.systemName in whereUsed)) { //set where-used for this new memory
|
|
whereUsed[$widget.systemName] = new Array();
|
|
}
|
|
whereUsed[$widget.systemName][whereUsed[$widget.systemName].length] = $widget.id;
|
|
}
|
|
|
|
/******************************************************************
|
|
* ======= Switchboard functions =======
|
|
*/
|
|
|
|
// used to find largest tiles on Switchboard screen
|
|
function autoRows(screenwidth, screenheight) {
|
|
// calculations repeated from SwitchboardEditor for web display
|
|
// find cell matrix that allows largest size icons
|
|
var $cellProp = 1; // assume square tiles prop 1:1 to keep it simple for now
|
|
var $paneEffectiveWidth = Math.ceil(screenwidth / $cellProp);
|
|
var $columnsNum = 1;
|
|
var $rowsNum = 1;
|
|
var $tileSize = 0.1; // start value
|
|
var $tileSizeOld = 0;
|
|
var $totalDisplayed = Math.max($total, 1); // if all items unconnected and set to be hidden, use 1
|
|
while ($tileSize > $tileSizeOld) {
|
|
$rowsNum = ($totalDisplayed + $columnsNum - 1) / $columnsNum; // roundup int
|
|
$tileSizeOld = $tileSize; // store for comparison
|
|
$tileSize = Math.min(($paneEffectiveWidth / $columnsNum), ((screenheight - 90) / $rowsNum));
|
|
// screenheight-90px to leave room for menubar
|
|
if ($tileSize <= $tileSizeOld) {
|
|
break;
|
|
}
|
|
$columnsNum++;
|
|
}
|
|
return $rowsNum;
|
|
}
|
|
|
|
function getSwitchButtonLabel(label, subLabel) {
|
|
if (($showUserName == "no") || (subLabel == "") || isUndefined(subLabel)) {
|
|
return label;
|
|
} else {
|
|
subLabel = subLabel.substring(0, (Math.min(subLabel.length, 25)));
|
|
return label + " (" + subLabel + ")"; // will wrap but TODO show on 2 lines of text
|
|
}
|
|
}
|
|
|
|
// Draw symbol on the beanswitch widget canvas
|
|
var $drawWidgetSymbol = function(id, state) {
|
|
// draw on $widget canvas
|
|
var $canvas = document.getElementById(id + "c");
|
|
var shape = $gWidgets[id].shape;
|
|
if (shape == "button" || typeof $canvas === null) {
|
|
return; // no canvas created (shape = "buttons")
|
|
}
|
|
var ctx = $canvas.getContext("2d");
|
|
ctx.save();
|
|
// backgroundcolor shows through by inherit
|
|
ctx.clearRect(0, 0, $canvas.width, $canvas.height); // for alternating text and 'moving' items
|
|
|
|
ctx.fillStyle = (state == "2" ? $activeColor : $inactiveColor); // simple change in color
|
|
ctx.strokeStyle = "black";
|
|
ctx.translate($canvas.width/2, $canvas.height/2); // origin in center of canvas, easy!
|
|
var radius = Math.min($canvas.width * 0.3, $canvas.height * 0.3);
|
|
|
|
switch (shape) {
|
|
// draw methods
|
|
case "icon" : // slider, 1 shape for all switchtypes (S, T, L)
|
|
ctx.beginPath(); // the sliderspace
|
|
if (state == "2") {
|
|
ctx.strokeStyle = $activeColor;
|
|
} else if (state == "4") {
|
|
ctx.strokeStyle = $inactiveColor;
|
|
} else {
|
|
ctx.strokeStyle = "darkgray";
|
|
}
|
|
ctx.lineCap = "round";
|
|
ctx.lineWidth = radius;
|
|
ctx.moveTo(-radius/2, 0);
|
|
ctx.lineTo(radius/2, 0);
|
|
ctx.stroke();
|
|
ctx.beginPath(); // the knob
|
|
var knobX = (state == "2" ? radius/2 : -radius/2);
|
|
ctx.arc(knobX, 0, radius/2, 0, 2 * Math.PI);
|
|
ctx.fillStyle = "white";
|
|
ctx.fill();
|
|
ctx.strokeStyle = "black";
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
break;
|
|
case "drawing" : // Maerklin Keyboard, 1 shape for all switchtypes (S, T, L)
|
|
// red = upper rounded rect
|
|
ctx.fillStyle = (state == "2" ? $activeColor : "pink");
|
|
ctx.fillRect(-0.5*radius, -1.1*radius, radius, radius/3);
|
|
// + rounded outline
|
|
ctx.lineJoin = "round";
|
|
ctx.lineWidth = radius/5;
|
|
ctx.strokeStyle = (state == "2" ? $activeColor : "pink");
|
|
ctx.strokeRect(-0.5*radius, -1.1*radius, radius, radius/3);
|
|
// green = lower rounded rect
|
|
ctx.fillStyle = (state == "4" ? $inactiveColor : "lightgreen");
|
|
ctx.fillRect(-0.5*radius, 1.1*radius, radius, radius/-3);
|
|
// + rounded outline
|
|
ctx.lineJoin = "round";
|
|
ctx.lineWidth = 10;
|
|
ctx.strokeStyle = (state == "4" ? $inactiveColor : "lightgreen");
|
|
ctx.strokeRect(-0.5*radius, 1.1*radius, radius, radius/-3);
|
|
// add round LED at top
|
|
var grd = ctx.createRadialGradient(-0.1*radius, -1.4*radius, 0.5*radius, 0.1*radius, -1.6*radius, 0);
|
|
grd.addColorStop(0, (state == "2" ? $activeColor : "lightgray"));
|
|
grd.addColorStop(1, "white");
|
|
ctx.fillStyle = grd;
|
|
ctx.arc(0, -1.55*radius, radius/6, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
ctx.lineWidth = 0.2;
|
|
ctx.strokeStyle = "black";
|
|
ctx.stroke();
|
|
break;
|
|
case "symbol" : // Mimic classic icons as vector drawing, specific shape per switchtype (S, T, L)
|
|
switch ($gWidgets[id].type) {
|
|
case "L" : // light
|
|
// line (wire) at back
|
|
ctx.beginPath();
|
|
ctx.lineWidth = (state == "2" ? "3" : "1"); // thinner outline if Off
|
|
ctx.moveTo(-0.4 * $canvas.width, 0);
|
|
ctx.lineTo(0.4 * $canvas.width, 0);
|
|
ctx.stroke();
|
|
// filled circle
|
|
var grd = ctx.createRadialGradient(0, 0, 1.5 * radius, 8, -8, 4);
|
|
grd.addColorStop(0, (state == "2" ? "yellow" : "lightgray"));
|
|
grd.addColorStop(1, "white");
|
|
ctx.fillStyle = grd;
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
ctx.lineWidth = (state == "2" ? "3" : "1"); // thinner outline if Off
|
|
ctx.stroke();
|
|
// cross
|
|
ctx.lineWidth = 1;
|
|
ctx.moveTo(radius * -0.74, radius * -0.74);
|
|
ctx.lineTo(radius * 0.74, radius * 0.74);
|
|
ctx.stroke();
|
|
ctx.lineWidth = 1;
|
|
ctx.moveTo(radius * -0.74, radius * 0.74);
|
|
ctx.lineTo(radius * 0.74, radius * -0.74);
|
|
ctx.stroke();
|
|
break;
|
|
case "S" : // sensor
|
|
var grd = ctx.createRadialGradient(0, 0, 1.5 * radius, 8, -8, 4);
|
|
grd.addColorStop(0, (state == "2" ? $activeColor : "lightgray"));
|
|
grd.addColorStop(1, "white");
|
|
ctx.fillStyle = grd;
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
ctx.lineWidth = (state == "2" ? "3" : "1"); // thinner outline if Off
|
|
ctx.stroke();
|
|
break;
|
|
case "T" : // turnout, orientation on screen same as JMRI
|
|
default :
|
|
ctx.lineWidth = radius/2.9;
|
|
// points, at the back
|
|
ctx.strokeStyle = "lightgray";
|
|
// --angled turnout shape
|
|
//ctx.moveTo(-0.4 * $canvas.width, -20);
|
|
//ctx.lineTo(0.1 * $canvas.width, 10);
|
|
//ctx.lineTo(-0.4 * $canvas.width, 10);
|
|
// --curved turnout shape
|
|
ctx.moveTo(0.4 * $canvas.width, 10);
|
|
ctx.lineTo(-0.4 * $canvas.width, 10);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.arc(0.4 * $canvas.width, 10 - 1.5 * $canvas.width, 1.5 * $canvas.width, 0.5 * Math.PI, 0.675 * Math.PI);
|
|
// --up to here
|
|
ctx.stroke();
|
|
// active line, start with new color
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = $activeColor;
|
|
// --angled turnout shape
|
|
//var endY = (state == "2" ? "10" : "-20");
|
|
//ctx.moveTo(0.4 * $canvas.width, 10);
|
|
//ctx.lineTo(0.1 * $canvas.width, 10);
|
|
//ctx.lineTo(-0.4 * $canvas.width, endY);
|
|
// --curved turnout shape
|
|
if (state == "2") {
|
|
ctx.moveTo(0.4 * $canvas.width, 10);
|
|
ctx.lineTo(-0.4 * $canvas.width, 10);
|
|
} else {
|
|
ctx.arc(0.4 * $canvas.width, 10 - 1.5 * $canvas.width, 1.5 * $canvas.width, 0.5 * Math.PI, 0.675 * Math.PI);
|
|
}
|
|
// --up to here
|
|
ctx.stroke();
|
|
break;
|
|
}
|
|
default :
|
|
// only render label
|
|
}
|
|
|
|
// draw label (system name + state) text
|
|
//ctx.restore(); // resets origin and stroke&fill
|
|
ctx.fillStyle = (state == "0" ? $unknownColor : $gPanel.defaulttextcolor); // simple change in color
|
|
ctx.font = "16px Arial";
|
|
ctx.textAlign = 'center';
|
|
if (shape == "drawing") { // text centered vertically between Maerklin buttons
|
|
ctx.fillText($gWidgets[id].text, 0, 0);
|
|
} else {
|
|
ctx.fillText($gWidgets[id].text, 0, -0.5 * $canvas.height + 16); // +16 for text size below top
|
|
}
|
|
// draw sublabel (user name) text
|
|
ctx.font = "italic 10px Arial";
|
|
if (shape == "drawing") { // text centered between Maerklin buttons
|
|
ctx.fillText($gWidgets[id].username, 0, 0.4 * $canvas.height);
|
|
} else {
|
|
ctx.fillText($gWidgets[id].username, 0, 0.4 * $canvas.height);
|
|
}
|
|
ctx.restore(); // restore color and width back to default
|
|
};
|
|
|
|
// End of Swichboard functions
|
|
|
|
|
|
/******************************************************************
|
|
* ======= Layout Editor functions =======
|
|
*/
|
|
|
|
//draw a Tracksegment (pass in widget)
|
|
function $drawTrackSegment($widget) {
|
|
//if set to hidden, don't draw anything
|
|
if ($widget.hidden == "yes") {
|
|
return;
|
|
}
|
|
|
|
// if positional points have not been loaded...
|
|
if (Object.keys($gPts).length == 0) {
|
|
return; // ... don't try to draw anything yet
|
|
}
|
|
|
|
//get the endpoints by name
|
|
var $ep1, $ep2;
|
|
[$ep1, $ep2] = $getEndPoints$($widget);
|
|
if (isUndefined($ep1)) {
|
|
log.warn("can't draw tracksegment " + $widget.ident + ": connect1: " + $widget.connect1name + "." + $widget.type1 + " undefined.");
|
|
return;
|
|
}
|
|
if (isUndefined($ep2)) {
|
|
log.warn("can't draw tracksegment " + $widget.ident + ": connect2: " + $widget.connect2name + "." + $widget.type2 + " undefined.");
|
|
return;
|
|
}
|
|
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
//get width (assume no block assigned)
|
|
var $width = $gPanel.sidelinetrackwidth;
|
|
if ($widget.mainline == "yes") {
|
|
$width = $gPanel.mainlinetrackwidth;
|
|
}
|
|
|
|
var $color = $getTrackColor($widget);
|
|
|
|
var $blk = $gBlks[$widget.blockname];
|
|
if (isDefined($blk)) {
|
|
$color = $blk.blockcolor;
|
|
|
|
//block assigned; use block width
|
|
$width = $gPanel.sidelineblockwidth;
|
|
if ($widget.mainline == "yes") {
|
|
$width = $gPanel.mainlineblockwidth;
|
|
}
|
|
}
|
|
|
|
// set color and width
|
|
if (isDefined($color)) {
|
|
$gCtx.strokeStyle = $color;
|
|
}
|
|
if (isDefined($width)) {
|
|
$gCtx.lineWidth = $width;
|
|
}
|
|
|
|
if ($widget.dashed == "yes") {
|
|
$gCtx.setLineDash([6, 4]);
|
|
}
|
|
|
|
if ($widget.bezier == "yes") {
|
|
$drawTrackSegmentBezier($widget);
|
|
} else if ($widget.circle == "yes") {
|
|
$drawTrackSegmentCircle($widget);
|
|
} else if ($widget.arc == "yes") { //draw arc of ellipse
|
|
$drawTrackSegmentArc($widget);
|
|
} else {
|
|
$drawLine($ep1.x, $ep1.y, $ep2.x, $ep2.y, $color, $width);
|
|
}
|
|
|
|
if ($widget.dashed == "yes") {
|
|
$gCtx.setLineDash([]);
|
|
}
|
|
|
|
//draw its decorations
|
|
$drawDecorations($widget);
|
|
|
|
$gCtx.restore(); // restore color and width back to default
|
|
} // $drawTrackSegment
|
|
|
|
function $drawTrackSegmentBezier($widget) {
|
|
//get the endpoints by name
|
|
var ep1, ep2;
|
|
[ep1, ep2] = $getEndPoints($widget);
|
|
var $cps = $widget.controlpoints; // get the control points
|
|
var points = [[ep1[0], ep1[1]]]; // first point
|
|
$cps.each(function( idx, elem ) { // control points
|
|
points.push($getLayoutPoint(elem));
|
|
});
|
|
points.push([ep2[0], ep2[1]]); // last point
|
|
|
|
//$point_log("points[0]", points[0]);
|
|
|
|
$drawBezier(points, $gCtx.strokeStyle, $gCtx.lineWidth, 0);
|
|
}
|
|
|
|
function $drawTrackSegmentCircle($widget) {
|
|
//get the endpoints by name
|
|
var $ep1, $ep2;
|
|
[$ep1, $ep2] = $getEndPoints$($widget);
|
|
if (isUndefined($widget.angle) || ($widget.angle == 0)) {
|
|
$widget['angle'] = "90";
|
|
}
|
|
//draw curved line
|
|
if ($widget.flip == "yes") {
|
|
$drawArc($ep2.x, $ep2.y, $ep1.x, $ep1.y, $widget.angle);
|
|
} else {
|
|
$drawArc($ep1.x, $ep1.y, $ep2.x, $ep2.y, $widget.angle);
|
|
}
|
|
}
|
|
|
|
function $drawTrackSegmentArc($widget) {
|
|
//get the endpoints by name
|
|
var $ep1, $ep2;
|
|
[$ep1, $ep2] = $getEndPoints$($widget);
|
|
var ep1x = Number($ep1.x), ep1y = Number($ep1.y), ep2x = Number($ep2.x), ep2y = Number($ep2.y);
|
|
if ($widget.flip == "yes") {
|
|
[ep1x, ep1y, ep2x, ep2y] = [ep2x, ep2y, ep1x, ep1y];
|
|
}
|
|
|
|
var x, y;
|
|
var rw = ep2x - ep1x, rh = ep2y - ep1y;
|
|
|
|
var startAngleRAD, stopAngleRAD;
|
|
if (rw < 0) {
|
|
rw = -rw;
|
|
if (rh < 0) { //log.log("**** QUAD ONE ****");
|
|
x = ep1x; y = ep2y;
|
|
rh = -rh;
|
|
startAngleRAD = Math.PI / 2;
|
|
stopAngleRAD = Math.PI;
|
|
} else { //log.log("**** QUAD TWO ****");
|
|
x = ep2x; y = ep1y;
|
|
startAngleRAD = 0;
|
|
stopAngleRAD = Math.PI / 2;
|
|
}
|
|
} else {
|
|
if (rh < 0) { //log.log("**** QUAD THREE ****");
|
|
x = ep2x; y = ep1y;
|
|
rh = -rh;
|
|
startAngleRAD = Math.PI;
|
|
stopAngleRAD = -Math.PI / 2;
|
|
} else { //log.log("**** QUAD FOUR ****");
|
|
x = ep1x; y = ep2y;
|
|
startAngleRAD = -Math.PI / 2;
|
|
stopAngleRAD = 0;
|
|
}
|
|
}
|
|
|
|
$drawEllipse(x, y, rw, rh, startAngleRAD, stopAngleRAD);
|
|
}
|
|
|
|
function $getEndPoints$($widget) {
|
|
var $ep1 = $gPts[$widget.connect1name + "." + $widget.type1];
|
|
var $ep2 = $gPts[$widget.connect2name + "." + $widget.type2];
|
|
return [$ep1, $ep2];
|
|
}
|
|
|
|
function $getEndPoints($widget) {
|
|
var $ep1, $ep2;
|
|
[$ep1, $ep2] = $getEndPoints$($widget);
|
|
var ep1 = [Number($ep1.x), Number($ep1.y)];
|
|
var ep2 = [Number($ep2.x), Number($ep2.y)];
|
|
return [ep1, ep2];
|
|
}
|
|
|
|
//
|
|
//draw decorations
|
|
//
|
|
function $drawDecorations($widget) {
|
|
if (isDefined($widget.arrow)) {
|
|
$widget.arrow.draw();
|
|
}
|
|
if (isDefined($widget.bridge)) {
|
|
$widget.bridge.draw();
|
|
}
|
|
if (isDefined($widget.bumper)) {
|
|
$widget.bumper.draw();
|
|
}
|
|
if (isDefined($widget.tunnel)) {
|
|
$widget.tunnel.draw();
|
|
}
|
|
} // $drawDecorations
|
|
|
|
// draw a turntable (pass in widget)
|
|
// from jmri.jmrit.display.layoutEditor.layoutTurntable
|
|
function $drawTurntable($widget) {
|
|
$logProperties($widget);
|
|
|
|
//get the center
|
|
var $txcen = $widget.xcen * 1;
|
|
var $tycen = $widget.ycen * 1;
|
|
|
|
var $tr = $widget.radius * 1; //turntable circle radius
|
|
var $cr = $gPanel.turnoutcirclesize * SIZE; //turnout circle radius
|
|
//var $cd = $cr * 2;
|
|
|
|
//the fraction that $cr is of ($tr + $cr)
|
|
//(used to draw ray tracks from circle to ray end point (control circle))
|
|
var f = $cr / ($tr + $cr);
|
|
|
|
//loop thru raytracks drawing each one (and control circles if it has a turnout)
|
|
$widget.raytracks.each(function(i, item) {
|
|
$logProperties(item);
|
|
//var rayID = $widget.ident + "." + (50 + item.attributes.index.value * 1);
|
|
var rayID = $widget.ident + ".TURNTABLE_RAY_" + (item.attributes.index.value * 1);
|
|
var $t = $gPts[rayID];
|
|
//draw the line from ray endpoint to turntable edge
|
|
var $t1 = [];
|
|
$t1['x'] = $t.x - (($t.x - $txcen) * f);
|
|
$t1['y'] = $t.y - (($t.y - $tycen) * f);
|
|
$drawLine($t1.x, $t1.y, $t.x, $t.y, $getTrackColor($widget), $gPanel.sidelinetrackwidth);
|
|
|
|
if (isDefined(item.attributes.turnout) && ($gPanel.controlling == "yes")) {
|
|
// var turnout = item.attributes.turnout.value;
|
|
// var state = item.attributes.turnoutstate.value;
|
|
// log.log("$drawTurntable ray # " + i + " turnout: '" + turnout + "', state: " + state);
|
|
//draw the turnout control circle
|
|
$drawCircle($t.x, $t.y, $cr, $gPanel.turnoutcirclecolor, 1);
|
|
}
|
|
if (isDefined($widget.activeRayID)) {
|
|
var drawFlag = false;
|
|
if (isDefined(item.attributes.turnout)) {
|
|
var turnout = item.attributes.turnout.value;
|
|
if (turnout == $widget.activeRayTurnout) {
|
|
var state = item.attributes.turnoutstate.value;
|
|
if (state.toUpperCase() == $widget.activeRayState) {
|
|
drawFlag = true;
|
|
}
|
|
}
|
|
}
|
|
var $angle = $toRadians(item.attributes.angle.value);
|
|
var $t1 = [];
|
|
$t1['x'] = $txcen + ($tr * Math.sin($angle));
|
|
$t1['y'] = $tycen - ($tr * Math.cos($angle));
|
|
var $t2 = [];
|
|
$t2['x'] = $txcen - ($tr * Math.sin($angle));
|
|
$t2['y'] = $tycen + ($tr * Math.cos($angle));
|
|
if (drawFlag) {
|
|
$drawLine($t1.x, $t1.y, $t2.x, $t2.y, $getTrackColor($widget), $gPanel.sidelinetrackwidth);
|
|
} else {
|
|
$drawLine($t1.x, $t1.y, $t2.x, $t2.y, $gPanel.backgroundcolor, $gPanel.sidelinetrackwidth);
|
|
}
|
|
}
|
|
});
|
|
|
|
var $turntablecirclelinewidth = 2; //matches LayoutTurntableView.java
|
|
$drawCircle($txcen, $tycen, $tr, $getTrackColor($widget), $turntablecirclelinewidth);
|
|
$drawCircle($txcen, $tycen, $tr / 4, $getTrackColor($widget), $turntablecirclelinewidth);
|
|
} //$drawTurntable
|
|
|
|
//draw a LevelXing (pass in widget)
|
|
function $drawLevelXing($widget) {
|
|
//if set to hidden, don't draw anything
|
|
if ($widget.hidden == "yes") {
|
|
return;
|
|
}
|
|
//set colors and widths based on connected segments and blocks
|
|
var $colorAC = $getLegColor($gWidgets[$widget.connectaname], $widget.blocknameac);
|
|
var $colorBD = $getLegColor($gWidgets[$widget.connectbname], $widget.blocknamebd);
|
|
var $widthAC = $getLegWidth($gWidgets[$widget.connectaname], $widget.blocknameac);
|
|
var $widthBD = $getLegWidth($gWidgets[$widget.connectbname], $widget.blocknamebd);
|
|
|
|
//retrieve the points
|
|
var cen = [$widget.xcen, $widget.ycen];
|
|
var a = $getPoint($widget.ident + LEVEL_XING_A);
|
|
var b = $getPoint($widget.ident + LEVEL_XING_B);
|
|
var c = $getPoint($widget.ident + LEVEL_XING_C);
|
|
var d = $getPoint($widget.ident + LEVEL_XING_D);
|
|
|
|
//levelxing A
|
|
// D-+-B
|
|
// C
|
|
$drawLineP(a, c, $colorAC, $widthAC); //A to C
|
|
$drawLineP(b, d, $colorBD, $widthBD); //B to D
|
|
}
|
|
|
|
//draw a Turnout (pass in widget)
|
|
// see LayoutTurnout.draw()
|
|
// colors and widths based on side vs main then block color, turnout can be all one block, or several blocks
|
|
function $drawTurnout($widget) {
|
|
//if set to hidden, don't draw anything
|
|
if ($widget.hidden == "yes") {
|
|
return;
|
|
}
|
|
|
|
//set erase color and width
|
|
var $eraseColor = $gPanel.backgroundcolor;
|
|
var $eraseWidth = $gPanel.mainlinetrackwidth;
|
|
|
|
//set colors and widths based on connected segments and blocks
|
|
var $colorA = $getLegColor($gWidgets[$widget.connectaname], $widget.blockname);
|
|
var $colorB = $getLegColor($gWidgets[$widget.connectbname],
|
|
($widget.blockbname ? $widget.blockbname : $widget.blockname)); //use bname if set
|
|
var $colorC = $getLegColor($gWidgets[$widget.connectcname],
|
|
($widget.blockcname ? $widget.blockcname : $widget.blockname)); //use cname if set
|
|
var $colorD = $getLegColor($gWidgets[$widget.connectdname],
|
|
($widget.blockdname ? $widget.blockdname : $widget.blockname)); //use dname if set
|
|
|
|
var $widthA = $getLegWidth($gWidgets[$widget.connectaname], $widget.blockname);
|
|
var $widthB = $getLegWidth($gWidgets[$widget.connectbname],
|
|
($widget.blockbname ? $widget.blockbname : $widget.blockname)); //use bname if set
|
|
var $widthC = $getLegWidth($gWidgets[$widget.connectcname],
|
|
($widget.blockcname ? $widget.blockcname : $widget.blockname)); //use cname if set
|
|
var $widthD = $getLegWidth($gWidgets[$widget.connectdname],
|
|
($widget.blockdname ? $widget.blockdname : $widget.blockname)); //use dname if set
|
|
|
|
var cen = [$widget.xcen * 1, $widget.ycen * 1]
|
|
var a = $getPoint($widget.ident + ".TURNOUT_A");
|
|
var b = $getPoint($widget.ident + ".TURNOUT_B");
|
|
var c = $getPoint($widget.ident + ".TURNOUT_C");
|
|
|
|
var ab = $point_midpoint(a, b);
|
|
|
|
//turnout A--+--B
|
|
// \-C
|
|
if ($widget.type == LH_TURNOUT || $widget.type == RH_TURNOUT || $widget.type == WYE_TURNOUT) {
|
|
//always draw from a to cen
|
|
$drawLineP(a, cen, $colorA, $widthA); //a to cen
|
|
|
|
//if closed or thrown, draw the selected leg and erase the other one
|
|
if ($widget.state == CLOSED || $widget.state == THROWN) {
|
|
if ($widget.state == $widget.continuing) {
|
|
$drawLineP(cen, c, $eraseColor, $widthC+1); //erase center to C (diverging leg)
|
|
if ($gPanel.turnoutdrawunselectedleg == 'yes') {
|
|
$drawLineP(c, $point_midpoint(cen, c), $colorC, $widthC); //C to midC (diverging leg)
|
|
}
|
|
$drawLineP(cen, b, $colorB, $widthB); //center to B (straight leg)
|
|
} else {
|
|
$drawLineP(cen, b, $eraseColor, $widthB+1); //erase center to B (straight leg)
|
|
if ($gPanel.turnoutdrawunselectedleg == 'yes') {
|
|
$drawLineP(b, $point_midpoint(cen, b), $colorB, $widthB); //B to midB (straight leg)
|
|
}
|
|
$drawLineP(cen, c, $colorC, $widthC); //center to C (diverging leg)
|
|
}
|
|
} else { //if state is undefined, draw both legs
|
|
$drawLineP(cen, b, $colorB, $widthB); //center to B (straight leg)
|
|
$drawLineP(cen, c, $colorC, $widthC); //center to C (diverging leg)
|
|
}
|
|
// xover A--B
|
|
// D--C
|
|
} else if ($widget.type == LH_XOVER || $widget.type == RH_XOVER || $widget.type == DOUBLE_XOVER) {
|
|
var d = $getPoint($widget.ident + ".TURNOUT_D");
|
|
|
|
var ab = $point_midpoint(a, b);
|
|
var cd = $point_midpoint(c, d);
|
|
|
|
if ($widget.state == CLOSED || $widget.state == THROWN) {
|
|
$drawLineP(a, b, $eraseColor, $eraseWidth); //erase A to B
|
|
$drawLineP(c, d, $eraseColor, $eraseWidth); //erase C to D
|
|
$drawLineP(ab, cd, $eraseColor, $eraseWidth); //erase midAB to midDC
|
|
$drawLineP(a, c, $eraseColor, $eraseWidth); //erase A to C
|
|
$drawLineP(b, d, $eraseColor, $eraseWidth); //erase B to D
|
|
if ($widget.state == $widget.continuing) {
|
|
//draw closed legs
|
|
$drawLineP(a, ab, $colorA, $widthA); //A to mid ab
|
|
$drawLineP(b, ab, $colorB, $widthB); //B to mid ab
|
|
$drawLineP(c, cd, $colorC, $widthC); //C to mid cd
|
|
$drawLineP(d, cd, $colorD, $widthD); //D to mid cd
|
|
//draw open legs
|
|
if ($widget.type == DOUBLE_XOVER) {
|
|
var acen = $point_midpoint(a, cen);
|
|
var bcen = $point_midpoint(b, cen);
|
|
var ccen = $point_midpoint(c, cen);
|
|
var dcen = $point_midpoint(d, cen);
|
|
$drawLineP(acen, cen, $colorA, $widthA); //mid a cen to cen
|
|
$drawLineP(bcen, cen, $colorB, $widthB); //mid b cen to cen
|
|
$drawLineP(ccen, cen, $colorC, $widthC); //mid c cen to cen
|
|
$drawLineP(dcen, cen, $colorD, $widthD); //mid d cen to cen
|
|
} else if ($widget.type == RH_XOVER) {
|
|
$drawLineP($point_midpoint(ab, cen), cen, $colorA, $widthA);
|
|
$drawLineP($point_midpoint(cd, cen), cen, $colorC, $widthC);
|
|
} else if ($widget.type == LH_XOVER) {
|
|
$drawLineP($point_midpoint(ab, cen), cen, $colorB, $widthB);
|
|
$drawLineP($point_midpoint(cd, cen), cen, $colorD, $widthD);
|
|
}
|
|
} else {
|
|
var aab = $point_midpoint(a, ab);
|
|
var abb = $point_midpoint(ab, b);
|
|
var ccd = $point_midpoint(c, cd);
|
|
var cdd = $point_midpoint(cd, d);
|
|
if ($widget.type == DOUBLE_XOVER) {
|
|
//draw open legs
|
|
$drawLineP(ab, aab, $colorA, $widthA);
|
|
$drawLineP(ab, abb, $colorB, $widthB);
|
|
$drawLineP(cd, ccd, $colorC, $widthC);
|
|
$drawLineP(cd, cdd, $colorD, $widthD);
|
|
|
|
//draw closed legs
|
|
$drawLineP(a, cen, $colorA, $widthA);
|
|
$drawLineP(b, cen, $colorB, $widthB);
|
|
$drawLineP(c, cen, $colorC, $widthC); //C to cen
|
|
$drawLineP(d, cen, $colorD, $widthD); //D to cen
|
|
} else if ($widget.type == RH_XOVER) {
|
|
//draw open legs
|
|
$drawLineP(b, abb, $colorB, $widthB);
|
|
$drawLineP(d, cdd, $colorD, $widthD);
|
|
|
|
//draw closed legs
|
|
$drawLineP(a, ab, $colorA, $widthA); //A to mid ab
|
|
$drawLineP(ab, cen, $colorA, $widthA); //midAB to cen
|
|
$drawLineP(cen, cd, $colorC, $widthC); //cen to midDC
|
|
$drawLineP(c, cd, $colorC, $widthC); //C to mid cd
|
|
} else { //LH_XOVER
|
|
//draw open legs
|
|
$drawLineP(a, aab, $colorA, $widthA);
|
|
$drawLineP(c, ccd, $colorC, $widthC);
|
|
|
|
//draw closed legs
|
|
$drawLineP(b, ab, $colorB, $widthB); //B to mid ab
|
|
$drawLineP(ab, cen, $colorB, $widthB); //midAB to cen
|
|
$drawLineP(cen, cd, $colorD, $widthD); //cen to midDC
|
|
$drawLineP(d, cd, $colorD, $widthD); //D to mid cd
|
|
}
|
|
}
|
|
} else { //if state is undefined, draw all legs
|
|
$drawLineP(a, ab, $colorA, $widthA); //A to mid ab
|
|
$drawLineP(b, ab, $colorB, $widthB); //B to mid ab
|
|
$drawLineP(c, cd, $colorC, $widthC); //C to mid cd
|
|
$drawLineP(d, cd, $colorD, $widthD); //D to mid cd
|
|
if ($widget.type == DOUBLE_XOVER) {
|
|
$drawLineP(a, cen, $colorA, $widthA); //A to cen
|
|
$drawLineP(b, cen, $colorB, $widthB); //B to cen
|
|
$drawLineP(c, cen, $colorC, $widthC); //C to cen
|
|
$drawLineP(d, cen, $colorD, $widthD); //D to cen
|
|
} else if ($widget.type == RH_XOVER) {
|
|
$drawLineP(ab, cen, $colorA, $widthA); //midAB to cen
|
|
$drawLineP(cen, cd, $colorC, $widthC); //cen to midDC
|
|
} else { //LH_XOVER
|
|
$drawLineP(ab, cen, $colorB, $widthB); //midAB to cen
|
|
$drawLineP(cen, cd, $colorD, $widthD); //cen to midDC
|
|
}
|
|
}
|
|
}
|
|
|
|
// erase and draw turnout circles if enabled, including occupancy check
|
|
if (($gPanel.turnoutcircles == "yes") && ($gPanel.controlling == "yes") && ($widget.disabled !== "yes")) {
|
|
$drawCircle($widget.xcen, $widget.ycen, $gPanel.turnoutcirclesize * SIZE, $eraseColor, 1);
|
|
if (($widget.disableWhenOccupied !== "yes") || ($widget.occupancystate != ACTIVE)) {
|
|
var $color = $gPanel.turnoutcirclecolor;
|
|
|
|
if ($widget.state != CLOSED) {
|
|
$color = $gPanel.turnoutcirclethrowncolor;
|
|
}
|
|
if ($gPanel.turnoutfillcontrolcircles == "yes") {
|
|
$fillCircle($widget.xcen, $widget.ycen, $gPanel.turnoutcirclesize * SIZE, $color, 1);
|
|
} else {
|
|
$drawCircle($widget.xcen, $widget.ycen, $gPanel.turnoutcirclesize * SIZE, $color, 1);
|
|
}
|
|
}
|
|
// if disableWhenOccupied requested, disable click if enabled and active
|
|
if ($widget.disableWhenOccupied == "yes") {
|
|
if ($widget.occupancystate == ACTIVE) {
|
|
$('#'+$widget.id).removeClass("clickable");
|
|
$('#'+$widget.id).unbind(UPEVENT, $handleClick);
|
|
} else {
|
|
$('#'+$widget.id).addClass("clickable");
|
|
$('#'+$widget.id).bind(UPEVENT, $handleClick);
|
|
}
|
|
}
|
|
}
|
|
} // function $drawTurnout($widget)
|
|
|
|
// compute width of turnout leg based on connected segment, then block type
|
|
function $getLegWidth(cs, bn) {
|
|
var width = $gPanel.sidelinetrackwidth;
|
|
if (isDefined(cs)) {
|
|
if (cs.mainline == "yes") {
|
|
width = $gPanel.mainlinetrackwidth;
|
|
}
|
|
var blk = $gBlks[bn];
|
|
if (isDefined(blk)) {
|
|
if (cs.mainline=="yes") {
|
|
width = $gPanel.mainlineblockwidth;
|
|
} else {
|
|
width = $gPanel.sidelineblockwidth;
|
|
}
|
|
}
|
|
}
|
|
return width*1.0; //insure numeric
|
|
}
|
|
|
|
// compute color of turnout leg based on connected segment, then its block color
|
|
function $getLegColor(cs, bn) {
|
|
var color = $gPanel.defaulttrackcolor;
|
|
if (isDefined(cs)) {
|
|
if (isDefined($gPanel.mainRailColor) && (cs.mainline == "yes")) {
|
|
color = $gPanel.mainRailColor;
|
|
} else {
|
|
if (isDefined($gPanel.sideRailColor)) {
|
|
color = $gPanel.sideRailColor;
|
|
}
|
|
}
|
|
var blk = $gBlks[bn];
|
|
if (isDefined(blk)) {
|
|
color = blk.blockcolor;
|
|
}
|
|
}
|
|
return color;
|
|
|
|
} // function $getLegColor()
|
|
|
|
//set trackcolor by default, then main/side
|
|
var $getTrackColor = function(e) {
|
|
var color = $gPanel.defaulttrackcolor;
|
|
if (isDefined($gPanel.mainRailColor) && (e.mainline == "yes")) {
|
|
color = $gPanel.mainRailColor;
|
|
}
|
|
if (isDefined($gPanel.sideRailColor) && (e.mainline != "yes")) {
|
|
color = $gPanel.sideRailColor;
|
|
}
|
|
return color;
|
|
}
|
|
|
|
//draw a Slip (pass in widget)
|
|
// see LayoutSlip.draw()
|
|
function $drawSlip($widget) {
|
|
//if set to hidden, don't draw anything
|
|
if ($widget.hidden == "yes") {
|
|
return;
|
|
}
|
|
if (jmri_logging) {
|
|
log.log("$drawSlip(" + $widget.id + "): state: " + $widget.state);
|
|
}
|
|
|
|
var $mainWidth = $gPanel.mainlinetrackwidth;
|
|
var $sideWidth = $gPanel.sidelinetrackwidth;
|
|
|
|
var $widthA = $sideWidth;
|
|
if (isDefined($gWidgets[$widget.connectaname])) {
|
|
if ($gWidgets[$widget.connectaname].mainline == "yes") {
|
|
$widthA = $mainWidth;
|
|
}
|
|
}
|
|
|
|
var $widthB = $sideWidth;
|
|
if (isDefined($gWidgets[$widget.connectbname])) {
|
|
if ($gWidgets[$widget.connectbname].mainline == "yes") {
|
|
$widthB = $mainWidth;
|
|
}
|
|
}
|
|
|
|
var $widthC = $sideWidth;
|
|
if (isDefined($gWidgets[$widget.connectcname])) {
|
|
if ($gWidgets[$widget.connectcname].mainline == "yes") {
|
|
$widthC = $mainWidth;
|
|
}
|
|
}
|
|
|
|
var $widthD = $sideWidth;
|
|
if (isDefined($gWidgets[$widget.connectdname])) {
|
|
if ($gWidgets[$widget.connectdname].mainline == "yes") {
|
|
$widthD = $mainWidth;
|
|
}
|
|
}
|
|
|
|
var cen = [$widget.xcen * 1, $widget.ycen * 1]
|
|
var a = $getPoint($widget.ident + SLIP_A);
|
|
var b = $getPoint($widget.ident + SLIP_B);
|
|
var c = $getPoint($widget.ident + SLIP_C);
|
|
var d = $getPoint($widget.ident + SLIP_D);
|
|
|
|
var $eraseColor = $gPanel.backgroundcolor;
|
|
var $trackColor = $getTrackColor($widget);
|
|
|
|
var $blkA = $gBlks[$widget.blockname];
|
|
var $colorA = isDefined($blkA) ? $blkA.blockcolor : $trackColor;
|
|
var $colorAt = isDefined($blkA) ? $blkA.trackcolor : $trackColor;
|
|
var $blkB = $gBlks[$widget.blockbname];
|
|
var $colorB = isDefined($blkB) ? $blkB.blockcolor : $colorA;
|
|
var $colorBt = isDefined($blkB) ? $blkB.trackcolor : $colorAt;
|
|
var $blkC = $gBlks[$widget.blockcname];
|
|
var $colorC = isDefined($blkC) ? $blkC.blockcolor : $colorA;
|
|
var $colorCt = isDefined($blkC) ? $blkC.trackcolor : $colorAt;
|
|
var $blkD = $gBlks[$widget.blockdname];
|
|
var $colorD = isDefined($blkD) ? $blkD.blockcolor : $colorA;
|
|
var $colorDt = isDefined($blkD) ? $blkD.trackcolor : $colorAt;
|
|
|
|
//slip A==-==D
|
|
// \\ //
|
|
// X
|
|
// // \\
|
|
// B==-==C
|
|
// var STATE_AC = 0x02;
|
|
// var STATE_BD = 0x04;
|
|
// var STATE_AD = 0x06;
|
|
// var STATE_BC = 0x08;
|
|
|
|
// ERASE EVERYTHING FIRST
|
|
var acen3rd = $point_third(a, cen);
|
|
var bcen3rd = $point_third(b, cen);
|
|
var ccen3rd = $point_third(c, cen);
|
|
var dcen3rd = $point_third(d, cen);
|
|
var ad3rd = $point_midpoint(acen3rd, dcen3rd);
|
|
var bc3rd = $point_midpoint(bcen3rd, ccen3rd);
|
|
|
|
if ($widget.state != STATE_AC) {
|
|
$drawLineP(a, acen3rd, $eraseColor, $mainWidth);
|
|
$drawLineP(acen3rd, ccen3rd, $eraseColor, $mainWidth); //erase AC
|
|
$drawLineP(ccen3rd, c, $eraseColor, $mainWidth);
|
|
}
|
|
if ($widget.state != STATE_BD) {
|
|
$drawLineP(b, bcen3rd, $eraseColor, $mainWidth);
|
|
$drawLineP(bcen3rd, dcen3rd, $eraseColor, $mainWidth); //erase BD
|
|
$drawLineP(dcen3rd, d, $eraseColor, $mainWidth);
|
|
}
|
|
if ($widget.state != STATE_AD) {
|
|
$drawLineP(a, acen3rd, $eraseColor, $mainWidth);
|
|
$drawLineP(acen3rd, dcen3rd, $eraseColor, $mainWidth); //erase AD
|
|
$drawLineP(dcen3rd, d, $eraseColor, $mainWidth);
|
|
}
|
|
if ($widget.slipType == DOUBLE_SLIP) {
|
|
if ($widget.state != STATE_BC) {
|
|
$drawLineP(b, bcen3rd, $eraseColor, $mainWidth);
|
|
$drawLineP(bcen3rd, ccen3rd, $eraseColor, $mainWidth); //erase BC
|
|
$drawLineP(ccen3rd, c, $eraseColor, $mainWidth);
|
|
}
|
|
}
|
|
|
|
// THEN DRAW ROUTE
|
|
var forceUnselected = false;
|
|
if ($widget.state == STATE_AD) {
|
|
// draw A<===>D
|
|
$drawLineP(a, acen3rd, $colorA, $widthA);
|
|
$drawLineP(acen3rd, ad3rd, $colorA, $widthA);
|
|
$drawLineP(d, dcen3rd, $colorD, $widthD);
|
|
$drawLineP(dcen3rd, ad3rd, $colorD, $widthD);
|
|
} else if ($widget.state == STATE_AC) {
|
|
// draw A<===>C
|
|
$drawLineP(a, acen3rd, $colorA, $widthA);
|
|
$drawLineP(acen3rd, cen, $colorA, $widthA);
|
|
$drawLineP(c, ccen3rd, $colorC, $widthC);
|
|
$drawLineP(ccen3rd, cen, $colorC, $widthC);
|
|
} else if ($widget.state == STATE_BD) {
|
|
// draw B<===>D
|
|
$drawLineP(b, bcen3rd, $colorB, $widthB);
|
|
$drawLineP(bcen3rd, cen, $colorB, $widthB);
|
|
$drawLineP(d, dcen3rd, $colorD, $widthD);
|
|
$drawLineP(dcen3rd, cen, $colorD, $widthD);
|
|
} else if ($widget.state == STATE_BC) {
|
|
if ($widget.slipType == DOUBLE_SLIP) {
|
|
// draw B<===>C
|
|
$drawLineP(b, bcen3rd, $colorB, $widthB);
|
|
$drawLineP(bcen3rd, bc3rd, $colorB, $widthB);
|
|
$drawLineP(c, ccen3rd, $colorC, $widthC);
|
|
$drawLineP(ccen3rd, bc3rd, $colorC, $widthC);
|
|
} // DOUBLE_SLIP
|
|
} else {
|
|
forceUnselected = true; // if not valid state force drawing unselected
|
|
}
|
|
|
|
if (forceUnselected || ($gPanel.turnoutdrawunselectedleg == 'yes')) {
|
|
if ($widget.state == STATE_AC) {
|
|
$drawLineP(b, bcen3rd, $colorBt, $widthB);
|
|
$drawLineP(d, dcen3rd, $colorDt, $widthD);
|
|
} else if ($widget.state == STATE_BD) {
|
|
$drawLineP(a, acen3rd, $colorAt, $widthA);
|
|
$drawLineP(c, ccen3rd, $colorCt, $widthC);
|
|
} else if ($widget.state == STATE_AD) {
|
|
$drawLineP(b, bcen3rd, $colorBt, $widthB);
|
|
$drawLineP(c, ccen3rd, $colorCt, $widthC);
|
|
} else if ($widget.state == STATE_BC) {
|
|
$drawLineP(a, acen3rd, $colorAt, $widthA);
|
|
$drawLineP(d, dcen3rd, $colorDt, $widthD);
|
|
} else {
|
|
$drawLineP(a, acen3rd, $colorAt, $widthA);
|
|
$drawLineP(b, bcen3rd, $colorBt, $widthB);
|
|
$drawLineP(c, ccen3rd, $colorCt, $widthC);
|
|
$drawLineP(d, dcen3rd, $colorDt, $widthD);
|
|
}
|
|
}
|
|
|
|
if (($gPanel.turnoutcircles == "yes") && ($gPanel.controlling == "yes") && ($widget.disabled !== "yes")) {
|
|
//draw the two control circles
|
|
var $cr = $gPanel.turnoutcirclesize * SIZE; //turnout circle radius
|
|
|
|
// center
|
|
var cen = [$widget.xcen, $widget.ycen];
|
|
// left center
|
|
var lcen = $point_midpoint(a, b);
|
|
var ldelta = $point_subtract(cen, lcen);
|
|
|
|
// left fraction
|
|
var lf = $cr / Math.hypot(ldelta[0], ldelta[1]);
|
|
// left circle
|
|
var lcc = $point_lerp(cen, lcen, lf);
|
|
|
|
$drawCircleP(lcc, $cr, $gPanel.turnoutcirclecolor, 1);
|
|
|
|
// right center
|
|
var rcen = $point_midpoint(c, d);
|
|
var rdelta = $point_subtract(cen, rcen);
|
|
// right fraction
|
|
var rf = $cr / Math.hypot(rdelta[0], rdelta[1]);
|
|
// right circle
|
|
var rcc = $point_lerp(cen, rcen, rf);
|
|
|
|
$drawCircleP(rcc, $cr, $gPanel.turnoutcirclecolor, 1);
|
|
}
|
|
} // function $drawSlip($widget)
|
|
|
|
function $drawPositionableRoundRect($widget) {
|
|
//log.log("drawing PositionableRoundRect")
|
|
createPanelCanvas(); //insure canvas layer is available for drawing
|
|
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
if (isDefined($widget.lineColor)) {
|
|
$gCtx.strokeStyle = $widget.lineColor;
|
|
}
|
|
if (isDefined($widget.fillColor)) {
|
|
$gCtx.fillStyle = $widget.fillColor;
|
|
}
|
|
if (isDefined($widget.lineWidth)) {
|
|
$gCtx.lineWidth = $widget.lineWidth;
|
|
}
|
|
|
|
$gCtx.beginPath();
|
|
// $gCtx.rotate($toRadians($widget.degrees));
|
|
$gCtx.roundRect($widget.x, $widget.y, $widget.width, $widget.height, $widget.cornerRadius);
|
|
$gCtx.stroke()
|
|
$gCtx.fill()
|
|
$gCtx.restore(); // restore color and width back to default
|
|
|
|
} // function $drawPositionableRoundRect($widget)
|
|
|
|
function $drawPositionableEllipse($widget) {
|
|
//log.log("drawing PositionableEllipse");
|
|
createPanelCanvas(); //insure canvas layer is available for drawing
|
|
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
if (isDefined($widget.lineColor)) {
|
|
$gCtx.strokeStyle = $widget.lineColor;
|
|
}
|
|
if (isDefined($widget.fillColor)) {
|
|
$gCtx.fillStyle = $widget.fillColor;
|
|
}
|
|
if (isDefined($widget.lineWidth)) {
|
|
$gCtx.lineWidth = $widget.lineWidth;
|
|
}
|
|
rw = $widget.width/2;
|
|
rh = $widget.height/2;
|
|
x = $widget.x * 1.0;
|
|
y = $widget.y * 1.0;
|
|
$gCtx.beginPath();
|
|
$gCtx.ellipse(x + rw, y + rh, rw, rh, $toRadians($widget.degrees), 0, 2 * Math.PI);
|
|
$gCtx.stroke()
|
|
$gCtx.fill()
|
|
$gCtx.restore(); // restore color and width back to default
|
|
|
|
} // function $drawPositionableEllipse($widget)
|
|
|
|
function $drawLayoutShape($widget) {
|
|
var $pts = $widget.points; // get the points
|
|
var len = $pts.length;
|
|
if (len > 0) {
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
if (isDefined($widget.lineColor)) {
|
|
$gCtx.strokeStyle = $widget.lineColor;
|
|
}
|
|
if (isDefined($widget.fillColor)) {
|
|
$gCtx.fillStyle = $widget.fillColor;
|
|
}
|
|
if (isDefined($widget.linewidth)) {
|
|
$gCtx.lineWidth = $widget.linewidth; //TODO: check case on this
|
|
}
|
|
|
|
$gCtx.beginPath();
|
|
|
|
var shapeType = $widget.type;
|
|
|
|
$pts.each(function(idx, $lsp) { //loop thru points
|
|
// this point
|
|
var p = $getLayoutPoint($lsp);
|
|
|
|
// left point
|
|
var idxL = $wrapValue(idx - 1, 0, len);
|
|
var $lspL = $pts[idxL];
|
|
var pL = $getLayoutPoint($lspL);
|
|
var midL = $point_midpoint(pL, p);
|
|
|
|
// right point
|
|
var idxR = $wrapValue(idx + 1, 0, len);
|
|
var $lspR = $pts[idxR];
|
|
var pR = $getLayoutPoint($lspR);
|
|
var midR = $point_midpoint(p, pR);
|
|
|
|
var lspt = $lsp.attributes.type.value; // Straight or Curve
|
|
|
|
// if this is an open shape...
|
|
if (shapeType == "eOpen") {
|
|
// and this is first or last point...
|
|
if ((idx == 0) || (idxR == 0)) {
|
|
// then force straight shape point type
|
|
lspt = "Straight";
|
|
}
|
|
}
|
|
if (lspt == "Straight") {
|
|
if (idx == 0) { // if this is the first point...
|
|
// ...and our shape is open...
|
|
if (shapeType == "Open") {
|
|
$gCtx.moveTo(p[0], p[1]); // then start here
|
|
} else { // otherwise
|
|
$gCtx.moveTo(midL[0], midL[1]); //start here
|
|
$gCtx.lineTo(p[0], p[1]); //draw to here
|
|
}
|
|
} else {
|
|
$gCtx.lineTo(midL[0], midL[1]); //start here
|
|
$gCtx.lineTo(p[0], p[1]); //draw to here
|
|
}
|
|
// if this is not the last point...
|
|
// ...or our shape isn't open
|
|
if ((idxR != 0) || (shapeType == "Open")) {
|
|
$gCtx.lineTo(midR[0], midR[1]); // draw to here
|
|
}
|
|
} else if (lspt == "Curve") {
|
|
if (idx == 0) { // if this is the first point
|
|
$gCtx.moveTo(midL[0], midL[1]); // then start here
|
|
}
|
|
$gCtx.quadraticCurveTo(p[0], p[1], midR[0], midR[1]);
|
|
} else {
|
|
log.error("ERROR: unexpected LayoutShape point type '" + lspt + "' for " + $widget.ide);
|
|
}
|
|
}); // $pts.each(function(idx, $lsp)
|
|
|
|
if (shapeType == "Filled") {
|
|
$gCtx.fill();
|
|
}
|
|
$gCtx.stroke();
|
|
|
|
$gCtx.restore(); // restore color and width back to default
|
|
} // if (len > 0)
|
|
}
|
|
|
|
function $getLayoutPoint($p) {
|
|
return [Number($p.attributes.x.value), Number($p.attributes.y.value)];
|
|
}
|
|
|
|
// wrap inValue around between minVal and maxVal
|
|
function $wrapValue(inValue, minVal, maxVal) {
|
|
var range = maxVal - minVal;
|
|
return ((inValue % range) + range) % range;
|
|
}
|
|
|
|
function $lerp(value1, value2, amount) {
|
|
return ((1 - amount) * value1) + (amount * value2);
|
|
}
|
|
|
|
function $half(value1, value2) {
|
|
return $lerp(value1, value2, 1 / 2);
|
|
}
|
|
|
|
function $third(value1, value2) {
|
|
return $lerp(value1, value2, 1 / 3);
|
|
}
|
|
|
|
function $store_occupancysensor(id, sensor) {
|
|
if (id && sensor) {
|
|
if (!(sensor in occupancyNames)) {
|
|
occupancyNames[sensor] = new Array();
|
|
}
|
|
occupancyNames[sensor][occupancyNames[sensor].length] = id;
|
|
//console.log("sensor " + sensor + " stored with widget " + id);
|
|
}
|
|
}
|
|
|
|
function $store_occupancyblock(id, oblock) {
|
|
if (id && oblock) {
|
|
if (!(oblock in $oblockNames)) {
|
|
$oblockNames[oblock] = new Array();
|
|
}
|
|
$oblockNames[oblock][$oblockNames[oblock].length] = id; // id = widgetId
|
|
//console.log("oblock " + oblock + " stored with widget " + id);
|
|
}
|
|
}
|
|
|
|
//store the various points defined with a Turnout (pass in widget)
|
|
//see jmri.jmrit.display.layoutEditor.LayoutTurnout.java for background
|
|
function $storeTurnoutPoints($widget) {
|
|
var $t = [];
|
|
$t['ident'] = $widget.ident + ".TURNOUT_B"; //store B endpoint
|
|
$t['x'] = $widget.xb * 1;
|
|
$t['y'] = $widget.yb * 1;
|
|
$gPts[$t.ident] = $t;
|
|
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + ".TURNOUT_C"; //store C endpoint
|
|
$t['x'] = $widget.xc * 1;
|
|
$t['y'] = $widget.yc * 1;
|
|
$gPts[$t.ident] = $t;
|
|
|
|
if ($widget.type == LH_TURNOUT || $widget.type == RH_TURNOUT) {
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + ".TURNOUT_A"; //calculate and store A endpoint (mirror of B for these)
|
|
$t['x'] = $widget.xcen - ($widget.xb - $widget.xcen);
|
|
$t['y'] = $widget.ycen - ($widget.yb - $widget.ycen);
|
|
$gPts[$t.ident] = $t;
|
|
} else if ($widget.type == WYE_TURNOUT) {
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + ".TURNOUT_A"; //store A endpoint
|
|
$t['x'] = $widget.xa * 1;
|
|
$t['y'] = $widget.ya * 1;
|
|
$gPts[$t.ident] = $t;
|
|
} else if ($widget.type == LH_XOVER || $widget.type == RH_XOVER || $widget.type == DOUBLE_XOVER) {
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + ".TURNOUT_A"; //calculate and store A endpoint (mirror of C for these)
|
|
$t['x'] = $widget.xcen - ($widget.xc - $widget.xcen);
|
|
$t['y'] = $widget.ycen - ($widget.yc - $widget.ycen);
|
|
$gPts[$t.ident] = $t;
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + ".TURNOUT_D"; //calculate and store D endpoint (mirror of B for these)
|
|
$t['x'] = $widget.xcen - ($widget.xb - $widget.xcen);
|
|
$t['y'] = $widget.ycen - ($widget.yb - $widget.ycen);
|
|
$gPts[$t.ident] = $t;
|
|
}
|
|
}
|
|
|
|
//store the various points defined with a Slip (pass in widget)
|
|
//see jmri.jmrit.display.layoutEditor.LayoutSlip.java for background
|
|
function $storeSlipPoints($widget) {
|
|
var $t = [];
|
|
$t['ident'] = $widget.ident + SLIP_A; //store A endpoint
|
|
$t['x'] = $widget.xa * 1;
|
|
$t['y'] = $widget.ya * 1;
|
|
$gPts[$t.ident] = $t;
|
|
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + SLIP_B; //store B endpoint
|
|
$t['x'] = $widget.xb * 1;
|
|
$t['y'] = $widget.yb * 1;
|
|
$gPts[$t.ident] = $t;
|
|
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + SLIP_C; //calculate and store C endpoint (mirror of A for these)
|
|
$t['x'] = $widget.xcen - ($widget.xa - $widget.xcen);
|
|
$t['y'] = $widget.ycen - ($widget.ya - $widget.ycen);
|
|
$gPts[$t.ident] = $t;
|
|
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + SLIP_D; //calculate and store D endpoint (mirror of B for these)
|
|
$t['x'] = $widget.xcen - ($widget.xb - $widget.xcen);
|
|
$t['y'] = $widget.ycen - ($widget.yb - $widget.ycen);
|
|
$gPts[$t.ident] = $t;
|
|
}
|
|
|
|
//store the various points defined with a LevelXing (pass in widget)
|
|
//see jmri.jmrit.display.layoutEditor.LevelXing.java for background
|
|
function $storeLevelXingPoints($widget) {
|
|
var $t = [];
|
|
$t['ident'] = $widget.ident + LEVEL_XING_A; //store A endpoint
|
|
$t['x'] = $widget.xa * 1;
|
|
$t['y'] = $widget.ya * 1;
|
|
$gPts[$t.ident] = $t;
|
|
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + LEVEL_XING_B; //store B endpoint
|
|
$t['x'] = $widget.xb * 1;
|
|
$t['y'] = $widget.yb * 1;
|
|
$gPts[$t.ident] = $t;
|
|
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + LEVEL_XING_C; //calculate and store A endpoint (mirror of A for these)
|
|
$t['x'] = $widget.xcen - ($widget.xa - $widget.xcen);
|
|
$t['y'] = $widget.ycen - ($widget.ya - $widget.ycen);
|
|
$gPts[$t.ident] = $t;
|
|
|
|
$t = [];
|
|
$t['ident'] = $widget.ident + LEVEL_XING_D; //calculate and store D endpoint (mirror of B for these)
|
|
$t['x'] = $widget.xcen - ($widget.xb - $widget.xcen);
|
|
$t['y'] = $widget.ycen - ($widget.yb - $widget.ycen);
|
|
$gPts[$t.ident] = $t;
|
|
}
|
|
|
|
//drawLine, passing in values from xml
|
|
function $drawLine($p1x, $p1y, $p2x, $p2y, $color, $width, dashArray) {
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
if (isDefined($color)) {
|
|
$gCtx.strokeStyle = $color;
|
|
}
|
|
if (isDefined($width)) {
|
|
$gCtx.lineWidth = $width;
|
|
}
|
|
|
|
$gCtx.beginPath();
|
|
|
|
if (isDefined(dashArray)) {
|
|
$gCtx.setLineDash(dashArray);
|
|
}
|
|
|
|
$gCtx.moveTo($p1x, $p1y);
|
|
$gCtx.lineTo($p2x, $p2y);
|
|
|
|
$gCtx.stroke();
|
|
|
|
if (isDefined(dashArray)) {
|
|
$gCtx.setLineDash([]);
|
|
}
|
|
|
|
$gCtx.restore(); // restore color and width back to default
|
|
}
|
|
|
|
function $drawLineP($p1, $p2, $color, $width) {
|
|
$drawLine($p1[0], $p1[1], $p2[0], $p2[1], $color, $width);
|
|
}
|
|
|
|
//drawLine, passing in values from xml
|
|
function $drawDashedLine($p1x, $p1y, $p2x, $p2y, $color, $width, dashArray) {
|
|
$drawLine($p1x, $p1y, $p2x, $p2y, $color, $width, dashArray);
|
|
}
|
|
|
|
// function $drawDashedLineP($p1, $p2, $color, $width, dashArray) {
|
|
// $drawDashedLine($p1[0], $p1[1], $p2[0], $p2[1], $color, $width, dashArray);
|
|
// }
|
|
|
|
//draw a Circle (color and width are optional)
|
|
function $drawCircleP($p, $radius, $color, $width) {
|
|
$drawCircle($p[0], $p[1], $radius, $color, $width);
|
|
}
|
|
|
|
function $drawCircle($px, $py, $radius, $color, $width) {
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
// set color and width
|
|
if (isDefined($color)) {
|
|
$gCtx.strokeStyle = $color;
|
|
}
|
|
if (isDefined($width)) {
|
|
$gCtx.lineWidth = $width;
|
|
}
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.arc($px, $py, $radius, 0, 2 * Math.PI, false);
|
|
$gCtx.stroke();
|
|
|
|
$gCtx.restore(); // restore color and width back to default
|
|
}
|
|
|
|
//draw a Circle (color and width are optional)
|
|
function $fillCircleP($p, $radius, $color, $width) {
|
|
$fillCircle($p[0], $p[1], $radius, $color, $width);
|
|
}
|
|
function $fillCircle($px, $py, $radius, $color, $width) {
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
// set color and width
|
|
if (isDefined($color)) {
|
|
$gCtx.fillStyle = $color;
|
|
}
|
|
if (isDefined($width)) {
|
|
$gCtx.lineWidth = $width;
|
|
}
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.arc($px, $py, $radius, 0, 2 * Math.PI, false);
|
|
$gCtx.fill();
|
|
|
|
$gCtx.restore(); // restore color and width back to default
|
|
}
|
|
|
|
//drawArc, passing in values from xml
|
|
function $drawArc(pt1x, pt1y, pt2x, pt2y, degrees, $color, $width) {
|
|
// Compute arc's chord
|
|
var a = pt2x - pt1x;
|
|
var o = pt2y - pt1y;
|
|
var chord = Math.hypot(a, o); //in pixels
|
|
if (chord > 0) { //don't bother if no length
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
// set color and width
|
|
if (isDefined($color)) {
|
|
$gCtx.strokeStyle = $color;
|
|
}
|
|
if (isDefined($width)) {
|
|
$gCtx.lineWidth = $width;
|
|
}
|
|
|
|
var halfAngleRAD = $toRadians(degrees / 2);
|
|
var radius = (chord / 2) / (Math.sin(halfAngleRAD)); //in pixels
|
|
var startRAD = Math.atan2(a, o) - halfAngleRAD; //in radians
|
|
|
|
// calculate center of circle
|
|
var cx = (pt2x * 1.0) - Math.cos(startRAD) * radius;
|
|
var cy = (pt2y * 1.0) + Math.sin(startRAD) * radius;
|
|
|
|
//calculate start and end angle
|
|
var startAngleRAD = Math.atan2(pt1y - cy, pt1x - cx); //in radians
|
|
var endAngleRAD = Math.atan2(pt2y - cy, pt2x - cx); //in radians
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.arc(cx, cy, radius, startAngleRAD, endAngleRAD, false);
|
|
$gCtx.stroke();
|
|
|
|
$gCtx.restore(); // restore color and width back to default
|
|
}
|
|
}
|
|
|
|
function $drawArcP(pt1, pt2, degrees) {
|
|
$drawArc(pt1[0], pt1[1], pt2[0], pt2[1], degrees);
|
|
}
|
|
|
|
function $drawEllipse(x, y, rw, rh, startAngleRAD, stopAngleRAD)
|
|
{
|
|
$gCtx.beginPath();
|
|
$gCtx.ellipse(x, y, rw, rh, 0, startAngleRAD, stopAngleRAD);
|
|
$gCtx.stroke();
|
|
}
|
|
|
|
// $drawBezier
|
|
var bezier1st = true;
|
|
function $drawBezier(points, $color, $width, displacement) {
|
|
$gCtx.save(); // save current line width and color
|
|
|
|
$gCtx.strokeStyle = $color;
|
|
$gCtx.lineWidth = $width;
|
|
|
|
try {
|
|
bezier1st = true;
|
|
$gCtx.beginPath();
|
|
$plotBezier(points, 0, displacement);
|
|
$gCtx.stroke();
|
|
} catch (e) {
|
|
if (jmri_logging) {
|
|
log.log("$plotBezier exception: " + e);
|
|
var vDebug = "";
|
|
for (var prop in e) {
|
|
vDebug += " ["+ prop+ "]: '"+ e[prop]+ "'\n";
|
|
}
|
|
vDebug += "toString(): " + " value: [" + e.toString() + "]";
|
|
log.log(vDebug);
|
|
}
|
|
}
|
|
|
|
$gCtx.restore(); // restore color and width back to default
|
|
}
|
|
|
|
//
|
|
//plotBezier - recursive function to draw bezier curve
|
|
//
|
|
function $plotBezier(points, depth, displacement) {
|
|
var len = points.length, idx, jdx;
|
|
|
|
// calculate flatness to determine if we need to recurse...
|
|
var outer_distance = 0;
|
|
for (var idx = 1; idx < len; idx++) {
|
|
outer_distance += $point_distance(points[idx - 1], points[idx]);
|
|
}
|
|
var inner_distance = $point_distance(points[0], points[len - 1]);
|
|
var flatness = outer_distance / inner_distance;
|
|
|
|
// depth prevents stack overflow
|
|
// (I picked 12 because 2^12 = 2048 is larger than most monitors ;-)
|
|
// the flatness comparison value is somewhat arbitrary.
|
|
// (I just kept moving it closer to 1 until I got good results. ;-)
|
|
if ((depth > 12) || (flatness <= 1.001)) {
|
|
var p0 = points[0], pN = points[len - 1];
|
|
|
|
var vO = $point_normalizeTo($point_orthogonal($point_subtract(pN, p0)), displacement);
|
|
//$point_log("vO", vO);
|
|
|
|
if (bezier1st) {
|
|
var p0P = $point_add(p0, vO);
|
|
//$point_log("p0P", p0P);
|
|
$gCtx.moveTo(p0P[0], p0P[1]);
|
|
bezier1st = false;
|
|
}
|
|
var pNP = $point_add(pN, vO);
|
|
$gCtx.lineTo(pNP[0], pNP[1]);
|
|
} else {
|
|
// calculate (len - 1) order of points
|
|
// (zero'th order are the input points)
|
|
var orderPoints = [];
|
|
for (idx = 0; idx < len - 1; idx++) {
|
|
var nthOrderPoints = [];
|
|
for (jdx = 0; jdx < len - 1 - idx; jdx++) {
|
|
if (idx == 0) {
|
|
nthOrderPoints.push($point_midpoint(points[jdx], points[jdx + 1]));
|
|
} else {
|
|
nthOrderPoints.push($point_midpoint(orderPoints[idx - 1][jdx], orderPoints[idx - 1][jdx + 1]));
|
|
}
|
|
}
|
|
orderPoints.push(nthOrderPoints);
|
|
}
|
|
|
|
// collect left points
|
|
var leftPoints = [];
|
|
leftPoints.push(points[0]);
|
|
for (idx = 0; idx < len - 1; idx++) {
|
|
leftPoints.push(orderPoints[idx][0]);
|
|
}
|
|
// draw left side Bezier
|
|
$plotBezier(leftPoints, depth + 1, displacement);
|
|
|
|
// collect right points
|
|
var rightPoints = [];
|
|
for (idx = 0; idx < len - 1; idx++) {
|
|
rightPoints.push(orderPoints[len - 2 - idx][idx]);
|
|
}
|
|
rightPoints.push(points[len - 1]);
|
|
// draw right side Bezier
|
|
$plotBezier(rightPoints, depth + 1, displacement);
|
|
}
|
|
}
|
|
|
|
function $point_log(prefix, p) {
|
|
log.log(prefix + ": {" + p[0] + ", " + p[1] + "}");
|
|
}
|
|
|
|
function $getPoint(name) {
|
|
var point$ = $gPts[name];
|
|
return [Number(point$.x), Number(point$.y)];
|
|
}
|
|
|
|
function $point_length(p) {
|
|
var dx = p[0];
|
|
var dy = p[1];
|
|
return Math.hypot(dx, dy);
|
|
}
|
|
|
|
function $point_add(p1, p2) {
|
|
return [p1[0] + p2[0], p1[1] + p2[1]];
|
|
}
|
|
|
|
function $point_subtract(p1, p2) {
|
|
return [p1[0] - p2[0], p1[1] - p2[1]];
|
|
}
|
|
|
|
function $point_distance(p1, p2) {
|
|
var delta = $point_subtract(p1, p2);
|
|
return Math.hypot(delta[0], delta[1]);
|
|
}
|
|
|
|
function $point_midpoint(p1, p2) {
|
|
return [$half(p1[0], p2[0]), $half(p1[1], p2[1])];
|
|
}
|
|
|
|
function $point_normalizeTo(p, new_length) {
|
|
var m = new_length / $point_length(p);
|
|
return [p[0] * m, p[1] * m];
|
|
}
|
|
|
|
function $point_orthogonal(p) {
|
|
return [-p[1],p[0]];
|
|
}
|
|
|
|
function $computeAngleRAD(v) {
|
|
return Math.atan2(v[0], v[1]);
|
|
}
|
|
|
|
function $computeAngleRAD2(p1, p2) {
|
|
return $computeAngleRAD($point_subtract(p1, p2));
|
|
}
|
|
|
|
// Converts from degrees to radians.
|
|
function $toRadians(degrees) {
|
|
return degrees * Math.PI / 180;
|
|
};
|
|
|
|
// Converts from radians to degrees.
|
|
function $toDegrees(radians) {
|
|
return radians * 180 / Math.PI;
|
|
};
|
|
|
|
// rotate a point vector
|
|
function $point_rotate(point, angleRAD) {
|
|
var sinA = Math.sin(angleRAD), cosA = Math.cos(angleRAD);
|
|
var x = point[0], y = point[1];
|
|
return [cosA * x - sinA * y, sinA * x + cosA * y];
|
|
}
|
|
|
|
function $point_lerp(p1, p2, amount) {
|
|
return [$lerp(p1[0], p2[0], amount), $lerp(p1[1], p2[1], amount)]
|
|
}
|
|
|
|
function $point_third(p1, p2) {
|
|
return $point_lerp(p1, p2, 1.0/3.0);
|
|
}
|
|
|
|
//set object attributes from xml attributes, returning object
|
|
var $getObjFromXML = function(e) {
|
|
var $widget = {};
|
|
$(e.attributes).each(function() {
|
|
$widget[this.name] = this.value;
|
|
});
|
|
return $widget;
|
|
};
|
|
|
|
//redraw all "drawn" elements for given block (called after color change)
|
|
function $redrawBlock(blockName) {
|
|
//log.log("redrawing all tracks for block " + blockName);
|
|
//loop thru widgets, if block matches, redraw widget by proper method
|
|
jQuery.each($gWidgets, function($id, $widget) {
|
|
$logProperties($widget);
|
|
if (($widget.blockname == blockName)
|
|
|| ($widget.blocknameac == blockName)
|
|
|| ($widget.blocknamebd == blockName)
|
|
|| ($widget.blockbname == blockName)
|
|
|| ($widget.blockcname == blockName)
|
|
|| ($widget.blockdname == blockName)) {
|
|
switch ($widget.widgetType) {
|
|
case 'layoutturnout' :
|
|
$drawTurnout($widget);
|
|
break;
|
|
case 'layoutSlip' :
|
|
$drawSlip($widget);
|
|
break;
|
|
case 'tracksegment' :
|
|
$drawTrackSegment($widget);
|
|
break;
|
|
case 'levelxing' :
|
|
$drawLevelXing($widget);
|
|
break;
|
|
}
|
|
}
|
|
if ($widget.widgetType == 'layoutSlip') {
|
|
if ((isDefined($widget.connectaname) && ($gWidgets[$widget.connectaname].blockname == blockName))
|
|
|| isDefined($widget.connectbname) && ($gWidgets[$widget.connectbname].blockname == blockName)
|
|
|| isDefined($widget.connectcname) && ($gWidgets[$widget.connectcname].blockname == blockName)
|
|
|| isDefined($widget.connectdname) && ($gWidgets[$widget.connectdname].blockname == blockName)){
|
|
$drawSlip($widget);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
//redraw all "drawn" elements to overcome some bidirectional dependencies in the xml
|
|
var $drawAllDrawnWidgets = function() {
|
|
//loop thru widgets, redrawing each visible widget by proper method
|
|
jQuery.each($gWidgets, function($id, $widget) {
|
|
switch ($widget.widgetType) {
|
|
case 'layoutturnout' :
|
|
$drawTurnout($widget);
|
|
break;
|
|
case 'layoutSlip' :
|
|
$drawSlip($widget);
|
|
break;
|
|
case 'tracksegment' :
|
|
$drawTrackSegment($widget);
|
|
break;
|
|
case 'levelxing' :
|
|
$drawLevelXing($widget);
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
|
|
// redraw all "icon" Control Panel elements. Called after a delay to allow loading of images.
|
|
var $drawAllIconWidgets = function() {
|
|
//loop thru widgets, repositioning each icon widget
|
|
jQuery.each($gWidgets, function($id, $widget) {
|
|
switch ($widget.widgetFamily) {
|
|
case 'icon' :
|
|
$setWidgetPosition($("#panel-area > #" + $widget.id));
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
|
|
// draw all beanswitch icons first time
|
|
var $drawAllSwitchIcons = function() {
|
|
jQuery.each($gWidgets, function($id, $widget) {
|
|
switch ($widget.widgetFamily) {
|
|
case 'switch' :
|
|
if (isDefined($widget['shape']) && ($widget.shape != "button")) {
|
|
$drawWidgetSymbol($id, UNKNOWN); // draw first time UNKNOWN = 0
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
|
|
function createPanelCanvas() {
|
|
if ($gCtx == undefined) { //create canvas if not already created
|
|
$("#panel-area").prepend("<canvas id='panelCanvas' width=" + $gPanel.panelwidth + "px height=" +
|
|
$gPanel.panelheight + "px style='position:absolute;z-index:2;'>");
|
|
var canvas = document.getElementById("panelCanvas");
|
|
$gCtx = canvas.getContext("2d");
|
|
$gCtx.strokeStyle = $gPanel.defaulttrackcolor;
|
|
$gCtx.lineWidth = $gPanel.sidelinetrackwidth;
|
|
//set background color from panel attribute (single hex value)
|
|
$("#panel-area").css({'background-color': $gPanel.backgroundcolor});
|
|
}
|
|
};
|
|
|
|
function updateWidgets(name, state, data) {
|
|
// update all widgets based on the element that changed, using systemname
|
|
if (whereUsed[name]) {
|
|
//log.log("updateWidgets(" + name + ", " + state + ")");
|
|
$.each(whereUsed[name], function(index, widgetId) {
|
|
$setWidgetState(widgetId, state, data);
|
|
});
|
|
}
|
|
//update all widgets based on the element that changed, using username
|
|
if (isDefined(data.userName) && whereUsed[data.userName]) {
|
|
//log.log("updateWidgets by username (" + data.userName + "), " + state);
|
|
$.each(whereUsed[data.userName], function(index, widgetId) {
|
|
$setWidgetState(widgetId, state, data);
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateOccupancy(sensorName, state, data) {
|
|
// handle occupancy sensors by systemname
|
|
if (occupancyNames[sensorName]) {
|
|
updateOccupancySub(sensorName, state);
|
|
}
|
|
// handle occupancy sensors by username
|
|
if (occupancyNames[data.userName]) {
|
|
updateOccupancySub(data.userName, state);
|
|
}
|
|
}
|
|
|
|
function updateOccupancySub(sensorName, state) {
|
|
if (occupancyNames[sensorName]) {
|
|
$.each(occupancyNames[sensorName], function(index, widgetId) {
|
|
$widget = $gWidgets[widgetId];
|
|
|
|
updateBlockSensorState($widget.blockname, sensorName, state);
|
|
updateBlockSensorState($widget.blocknameac, sensorName, state);
|
|
updateBlockSensorState($widget.blocknamebd, sensorName, state);
|
|
updateBlockSensorState($widget.blockbname, sensorName, state);
|
|
updateBlockSensorState($widget.blockcname, sensorName, state);
|
|
updateBlockSensorState($widget.blockdname, sensorName, state);
|
|
|
|
$widget.occupancystate = state; // set occupancy for the widget to the newstate
|
|
|
|
switch ($widget.widgetType) {
|
|
case 'layoutturnout' :
|
|
$drawTurnout($widget);
|
|
break;
|
|
case 'layoutSlip' :
|
|
$drawSlip($widget);
|
|
break;
|
|
case 'indicatortrackicon' :
|
|
case 'indicatorturnouticon' :
|
|
$reDrawIcon($widget)
|
|
//console.log("IT(O)I sensor change");
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateBlockSensorState(blockName, sensorName, sensorState) {
|
|
if (isDefined(blockName)) {
|
|
var $blk = $gBlks[blockName];
|
|
if (isDefined($blk)) {
|
|
if (isDefined($blk.occupancysensor)
|
|
&& ($blk.occupancysensor == sensorName)) {
|
|
$blk.state = sensorState;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function setBlockColor(blockName, newColor) {
|
|
//log.log("setBlockColor(" + blockName + ", " + newColor + ");");
|
|
var $blk = $gBlks[blockName];
|
|
if (isDefined($blk)) {
|
|
$gBlks[blockName].blockcolor = newColor;
|
|
} else {
|
|
log.error("ERROR: block " + blockName + " not found for color " + newColor);
|
|
}
|
|
$redrawBlock(blockName);
|
|
}
|
|
|
|
function updateOblocks(oblockName, status) { // based on updateOccupancy()
|
|
// all oblocks are handled by their systemname
|
|
if ($oblockNames[oblockName]) {
|
|
$.each($oblockNames[oblockName], function(index, widgetId) {
|
|
$widget = $gWidgets[widgetId];
|
|
switch ($widget.widgetType) {
|
|
case 'indicatortrackicon' :
|
|
case 'indicatorturnouticon' : // does not receive turnout state via oblock
|
|
//console.log("updateOblocks UNFILTERED " + oblockName + " on widget " + $widget.id + " status=" + status);
|
|
if (status < 0x16) { // ignore (un)occupied
|
|
// pass on as is
|
|
} else if ((status & TRACK_ERROR) == TRACK_ERROR) { // ErrorTrack, swallow DontUse, Allocated 0x80
|
|
status = (status & 0x86);
|
|
} else if ((status & OUT_OF_SERVICE) == OUT_OF_SERVICE) { // DontUseTrack, swallow Allocated, ignore Occupied 0x40
|
|
status = (status & 0x40);
|
|
} else if ((status & RUNNING) == RUNNING) { // Running = occupied by train (via oblock) 0x20
|
|
status = (status & 0x22); // keep Occupied bit
|
|
} else if ((status & 0x12) == 0x2) { // Occupied, swallow Allocated
|
|
status = (status & 0x2);
|
|
} else if ((status & ALLOCATED) == ALLOCATED) { // Allocated 0x10
|
|
status = (status & 0x12); // only keep Occupied bit, it should overrule ALLOCATED
|
|
}
|
|
$widget.occupancystate = status; // set occupancy for the widget to the new occ.status
|
|
//console.log("updateOblocks FILTERED FOR " + oblockName + " on widget " + $widget.id + " status=" + $widget.occupancystate);
|
|
|
|
// enable/disable turnout click handling
|
|
if (status == OUT_OF_SERVICE) {
|
|
$('#'+$widget.id).removeClass("clickable");
|
|
$('#'+$widget.id).unbind(UPEVENT, $handleClick);
|
|
} else {
|
|
$('#'+$widget.id).addClass("clickable");
|
|
$('#'+$widget.id).bind(UPEVENT, $handleClick);
|
|
}
|
|
|
|
$reDrawIcon($widget);
|
|
break;
|
|
default:
|
|
break; // shouldn't get here
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// convert turnout state to string
|
|
function turnoutStateToString(state) {
|
|
result = "UKNOWN"
|
|
switch (state) {
|
|
case 2:
|
|
result = "CLOSED";
|
|
break;
|
|
case 4:
|
|
result = "THROWN";
|
|
break;
|
|
case 8:
|
|
result = "INCONSISTENT";
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// convert slip state to string
|
|
function slipStateToString(state) {
|
|
result = "UNKNOWN";
|
|
switch (state) {
|
|
case STATE_AC:
|
|
result = "STATE_AC";
|
|
break;
|
|
case STATE_AD:
|
|
result = "STATE_AD";
|
|
break;
|
|
case STATE_BC:
|
|
result = "STATE_BC";
|
|
break;
|
|
case STATE_BD:
|
|
result = "STATE_BD";
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getTurnoutStatesForSlipState(slipWidget, slipState) {
|
|
var results = [UNKNOWN, UNKNOWN];
|
|
if (isDefined(slipWidget)) {
|
|
if (slipWidget.widgetType == 'layoutSlip') {
|
|
switch (slipState) {
|
|
case STATE_AC:
|
|
results = [slipWidget.turnoutA_AC, slipWidget.turnoutB_AC];
|
|
break;
|
|
case STATE_AD:
|
|
results = [slipWidget.turnoutA_AD, slipWidget.turnoutB_AD];
|
|
break;
|
|
case STATE_BC:
|
|
results = [slipWidget.turnoutA_BC, slipWidget.turnoutB_BC];
|
|
break;
|
|
case STATE_BD:
|
|
results = [slipWidget.turnoutA_BD, slipWidget.turnoutB_BD];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function getTurnoutStatesForSlip(slipWidget) {
|
|
return getTurnoutStatesForSlipState(slipWidget, slipWidget.state);
|
|
}
|
|
|
|
function getSlipStateForTurnoutStatesClosest(slipWidget, stateA, stateB, useClosest) {
|
|
var result = UNKNOWN;
|
|
if ((stateA == slipWidget.turnoutA_AC) && (stateB == slipWidget.turnoutB_AC)) {
|
|
result = STATE_AC;
|
|
} else if ((stateA == slipWidget.turnoutA_AD) && (stateB == slipWidget.turnoutB_AD)) {
|
|
result = STATE_AD;
|
|
} else if ((slipWidget.slipType == DOUBLE_SLIP)
|
|
&& (stateA == slipWidget.turnoutA_BC) && (stateB == slipWidget.turnoutB_BC)) {
|
|
result = STATE_BC;
|
|
} else if ((stateA == slipWidget.turnoutA_BD) && (stateB == slipWidget.turnoutB_BD)) {
|
|
result = STATE_BD;
|
|
} else if (useClosest) {
|
|
if ((stateA == slipWidget.turnoutA_AC) || (stateB == slipWidget.turnoutB_AC)) {
|
|
result = STATE_AC;
|
|
} else if ((stateA == slipWidget.turnoutA_AD) || (stateB == slipWidget.turnoutB_AD)) {
|
|
result = STATE_AD;
|
|
} else if ((slipWidget.slipType == DOUBLE_SLIP)
|
|
&& (stateA == slipWidget.turnoutA_BC) || (stateB == slipWidget.turnoutB_BC)) {
|
|
result = STATE_BC;
|
|
} else if ((stateA == slipWidget.turnoutA_BD) || (stateB == slipWidget.turnoutB_BD)) {
|
|
result = STATE_BD;
|
|
} else {
|
|
result = STATE_AD;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getSlipStateForTurnoutStates(slipWidget, stateA, stateB) {
|
|
return getSlipStateForTurnoutStatesClosest(slipWidget, stateA, stateB, false)
|
|
}
|
|
|
|
//slip A==-==D
|
|
// \\ //
|
|
// X
|
|
// // \\
|
|
// B==-==C
|
|
// var STATE_AC = 0x02;
|
|
// var STATE_BD = 0x04;
|
|
// var STATE_AD = 0x06;
|
|
// var STATE_BC = 0x08;
|
|
// var CLOSED = '2';
|
|
// var THROWN = '4';
|
|
|
|
function getNextSlipState(slipWidget) {
|
|
var result = UNKNOWN;
|
|
|
|
// log.log("****************************");
|
|
// log.log("slipWidget.side:" + slipWidget.side);
|
|
// log.log(" slipWidget.state:" + slipWidget.state);
|
|
switch (slipWidget.side) {
|
|
case 'left': {
|
|
switch (slipWidget.state) {
|
|
case STATE_AC:
|
|
if (slipWidget.slipType == SINGLE_SLIP) {
|
|
result = STATE_BD;
|
|
} else {
|
|
result = STATE_BC;
|
|
}
|
|
break;
|
|
case STATE_AD:
|
|
result = STATE_BD;
|
|
break;
|
|
case STATE_BC:
|
|
default:
|
|
result = STATE_AC;
|
|
break;
|
|
case STATE_BD:
|
|
result = STATE_AD;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 'right': {
|
|
switch (slipWidget.state) {
|
|
case STATE_AC:
|
|
result = STATE_AD;
|
|
break;
|
|
case STATE_AD:
|
|
result = STATE_AC;
|
|
break;
|
|
case STATE_BC:
|
|
default:
|
|
result = STATE_BD;
|
|
break;
|
|
case STATE_BD:
|
|
if (slipWidget.slipType == SINGLE_SLIP) {
|
|
result = STATE_AC;
|
|
} else {
|
|
result = STATE_BC;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
log.log("getNextSlipState($widget): unknown $widget.side: " + slipWidget.side);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
// ======= End of Layout Editor functions =======
|
|
|
|
/******************************************************************
|
|
* ======= Layout Editor Decoration classes =======
|
|
*/
|
|
|
|
class Decoration {
|
|
constructor($widget) {
|
|
//log.log("Decoration.constructor(...)");
|
|
$logProperties(this.$widget);
|
|
this.$widget = $widget;
|
|
}
|
|
getEndPoints() {
|
|
[this.ep1, this.ep2] = $getEndPoints(this.$widget);
|
|
//log.log("ep1 = {" + this.ep1[0] + "," + this.ep1[1] + "}, ep2 = {" + this.ep2[0] + "," + this.ep2[1] + "}");
|
|
}
|
|
getAngles() {
|
|
var $widget = this.$widget;
|
|
if ($widget.bezier == "yes") {
|
|
this.getBezierAngles();
|
|
} else if ($widget.circle == "yes") {
|
|
this.getCircleAngles();
|
|
} else if ($widget.arc == "yes") {
|
|
this.getArcAngles();
|
|
} else {
|
|
this.startAngleRAD = (Math.PI / 2) - $computeAngleRAD2(this.ep2, this.ep1);
|
|
this.stopAngleRAD = this.startAngleRAD;
|
|
}
|
|
//log.log("startAngleDEG: " + $toDegrees(this.startAngleRAD) + ", stopAngleDEG: " + $toDegrees(this.stopAngleRAD) + ".");
|
|
}
|
|
getBezierAngles() {
|
|
var $widget = this.$widget;
|
|
var $cps = $widget.controlpoints; // get the control points
|
|
var $cp0 = $cps[0];
|
|
var $cpN = $cps[$cps.length - 1];
|
|
var cp0 = $getLayoutPoint($cp0);
|
|
var cpN = $getLayoutPoint($cpN);
|
|
this.startAngleRAD = (Math.PI / 2) - $computeAngleRAD2(cp0, this.ep1);
|
|
this.stopAngleRAD = (Math.PI / 2) - $computeAngleRAD2(this.ep2, cpN);
|
|
}
|
|
getCircleAngles() {
|
|
var $widget = this.$widget;
|
|
var extentAngleDEG = $widget.angle;
|
|
if (extentAngleDEG == 0) {
|
|
extentAngleDEG = 90;
|
|
}
|
|
var startAngleRAD, stopAngleRAD;
|
|
// Convert angle to radiants in order to speed up math
|
|
var halfAngleRAD = $toRadians(extentAngleDEG) / 2;
|
|
// Compute arc's chord
|
|
var a = this.ep2[0] - this.ep1[0];
|
|
var o = this.ep2[1] - this.ep1[1];
|
|
var chord = Math.hypot(a, o);
|
|
// Make sure chord is not null
|
|
// In such a case (ep1 == ep2), there is no arc to draw
|
|
if (chord > 0) {
|
|
var midAngleRAD = Math.atan2(a, o);
|
|
startAngleRAD = (Math.PI / 2) - (midAngleRAD + halfAngleRAD);
|
|
stopAngleRAD = (Math.PI / 2) - (midAngleRAD - halfAngleRAD);
|
|
}
|
|
this.startAngleRAD = startAngleRAD; this.stopAngleRAD = stopAngleRAD;
|
|
}
|
|
getArcAngles() {
|
|
var startAngleRAD, stopAngleRAD;
|
|
if (this.ep1[0] < this.ep2[0]) {
|
|
if (this.ep1[1] < this.ep2[1]) { //log.log("#### QUAD ONE ####");
|
|
startAngleRAD = 0; stopAngleRAD = Math.PI / 2;
|
|
} else { //log.log("#### QUAD TWO ####");
|
|
startAngleRAD = -Math.PI / 2; stopAngleRAD = 0;
|
|
}
|
|
} else {
|
|
if (this.ep1[1] < this.ep2[1]) { //log.log("#### QUAD THREE ####");
|
|
startAngleRAD = Math.PI / 2; stopAngleRAD = Math.PI;
|
|
} else { //log.log("#### QUAD FOUR ####");
|
|
startAngleRAD = Math.PI; stopAngleRAD = -Math.PI / 2;
|
|
}
|
|
}
|
|
this.startAngleRAD = startAngleRAD; this.stopAngleRAD = stopAngleRAD;
|
|
}
|
|
|
|
draw() {
|
|
this.getEndPoints();
|
|
this.getAngles();
|
|
}
|
|
|
|
getArcParams(rw, rh, tp1, tp2) {
|
|
var x, y;
|
|
if (rw < 0) {
|
|
rw = -rw;
|
|
if (rh < 0) { //log.log("**** QUAD ONE ****");
|
|
x = tp1[0]; y = tp2[1];
|
|
rh = -rh;
|
|
} else { //log.log("**** QUAD TWO ****");
|
|
x = tp2[0]; y = tp1[1];
|
|
}
|
|
} else {
|
|
if (rh < 0) { //log.log("**** QUAD THREE ****");
|
|
x = tp2[0]; y = tp1[1];
|
|
rh = -rh;
|
|
} else { //log.log("**** QUAD FOUR ****");
|
|
x = tp1[0]; y = tp2[1];
|
|
}
|
|
}
|
|
return [x, y, rw, rh];
|
|
}
|
|
} // class Decoration
|
|
|
|
class ArrowDecoration extends Decoration {
|
|
constructor($widget, $arrow) {
|
|
super($widget);
|
|
//<arrow style="4" end="stop" direction="out" color="#000000" linewidth="4" length="16" gap="1" />
|
|
this.style = Number($arrow.attr('style'));
|
|
this.end = $arrow.attr('end');
|
|
this.direction = $arrow.attr('direction');
|
|
this.color = $arrow.attr('color');
|
|
this.linewidth = Number($arrow.attr('linewidth'));
|
|
this.length = Number($arrow.attr('length'));
|
|
this.gap = Number($arrow.attr('gap'));
|
|
//log.log("arrow: {end:" + this.end + ", dir: " + this.direction + "}");
|
|
}
|
|
draw() {
|
|
super.draw();
|
|
$gCtx.save(); // save current line width and color
|
|
// set color and width
|
|
$gCtx.strokeStyle = this.color;
|
|
$gCtx.fillStyle = this.color;
|
|
$gCtx.lineWidth = this.linewidth;
|
|
this.drawArrowStart();
|
|
this.drawArrowStop();
|
|
$gCtx.restore(); // restore color and width back to default
|
|
}
|
|
drawArrowStart() {
|
|
var angleRAD = this.startAngleRAD;
|
|
if (this.$widget.flip == "yes") {
|
|
angleRAD = this.stopAngleRAD;
|
|
}
|
|
this.offset = 1; // draw the start arrows
|
|
if ((this.end == "start") || (this.end == "both")) {
|
|
if ((this.direction == "in") || (this.direction == "both")) {
|
|
this.drawArrowIn(this.ep1, Math.PI + angleRAD);
|
|
}
|
|
if ((this.direction == "out") || (this.direction == "both")) {
|
|
this.drawArrowOut(this.ep1, Math.PI + angleRAD);
|
|
}
|
|
}
|
|
}
|
|
drawArrowStop() {
|
|
var angleRAD = this.stopAngleRAD;
|
|
if (this.$widget.flip == "yes") {
|
|
angleRAD = this.startAngleRAD;
|
|
}
|
|
this.offset = 1; // draw the stop arrows
|
|
if ((this.end == "stop") || (this.end == "both")) {
|
|
if ((this.direction == "in") || (this.direction == "both")) {
|
|
this.drawArrowIn(this.ep2, angleRAD);
|
|
}
|
|
if ((this.direction == "out") || (this.direction == "both")) {
|
|
this.drawArrowOut(this.ep2, angleRAD);
|
|
}
|
|
}
|
|
}
|
|
drawArrowIn(ep, angleRAD) {
|
|
$gCtx.save();
|
|
$gCtx.translate(ep[0], ep[1]);
|
|
$gCtx.rotate(angleRAD);
|
|
|
|
switch (this.style) {
|
|
default:
|
|
this.style = 0;
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
this.drawArrow1In();
|
|
break;
|
|
case 2:
|
|
this.drawArrow2In();
|
|
break;
|
|
case 3:
|
|
this.drawArrow3In();
|
|
break;
|
|
case 4:
|
|
this.drawArrow4In();
|
|
break;
|
|
case 5:
|
|
this.drawArrow5In();
|
|
}
|
|
$gCtx.restore();
|
|
} // drawArrowIn
|
|
|
|
drawArrowOut(ep, angleRAD) {
|
|
$gCtx.save();
|
|
$gCtx.translate(ep[0], ep[1]);
|
|
$gCtx.rotate(angleRAD);
|
|
|
|
switch (this.style) {
|
|
default:
|
|
this.style = 0;
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
this.drawArrow1Out();
|
|
break;
|
|
case 2:
|
|
this.drawArrow2Out();
|
|
break;
|
|
case 3:
|
|
this.drawArrow3Out();
|
|
break;
|
|
case 4:
|
|
this.drawArrow4Out();
|
|
break;
|
|
case 5:
|
|
this.drawArrow5Out();
|
|
}
|
|
$gCtx.restore();
|
|
} // drawArrowIn
|
|
|
|
drawArrow1In() {
|
|
var p1 = [this.offset + this.length, -this.length];
|
|
var p2 = [this.offset, 0];
|
|
var p3 = [this.offset + this.length, +this.length];
|
|
|
|
$drawLineP(p1, p2);
|
|
$drawLineP(p2, p3);
|
|
this.offset += this.length + this.gap;
|
|
}
|
|
|
|
drawArrow1Out() {
|
|
var p1 = [this.offset, -this.length];
|
|
var p2 = [this.offset + this.length, 0];
|
|
var p3 = [this.offset, +this.length];
|
|
|
|
$drawLineP(p1, p2);
|
|
$drawLineP(p2, p3);
|
|
this.offset += this.length + this.gap;
|
|
}
|
|
|
|
drawArrow2In() {
|
|
var p1 = [this.offset + this.length, -this.length];
|
|
var p2 = [this.offset, 0];
|
|
var p3 = [this.offset + this.length, +this.length];
|
|
var p4 = [this.offset + this.linewidth + this.gap + this.length, -this.length];
|
|
var p5 = [this.offset + this.linewidth + this.gap, 0];
|
|
var p6 = [this.offset + this.linewidth + this.gap + this.length, +this.length];
|
|
|
|
$drawLineP(p1, p2);
|
|
$drawLineP(p2, p3);
|
|
$drawLineP(p4, p5);
|
|
$drawLineP(p5, p6);
|
|
this.offset += this.length + (2 * (this.linewidth + this.gap));
|
|
}
|
|
|
|
drawArrow2Out() {
|
|
var p1 = [this.offset, -this.length];
|
|
var p2 = [this.offset + this.length, 0];
|
|
var p3 = [this.offset, +this.length];
|
|
var p4 = [this.offset + this.linewidth + this.gap, -this.length];
|
|
var p5 = [this.offset + this.linewidth + this.gap + this.length, 0];
|
|
var p6 = [this.offset + this.linewidth + this.gap, +this.length];
|
|
|
|
$drawLineP(p1, p2);
|
|
$drawLineP(p2, p3);
|
|
$drawLineP(p4, p5);
|
|
$drawLineP(p5, p6);
|
|
this.offset += this.length + (2 * (this.linewidth + this.gap));
|
|
}
|
|
|
|
drawArrow3In() {
|
|
var p1 = [this.offset + this.length, -this.length];
|
|
var p2 = [this.offset, 0];
|
|
var p3 = [this.offset + this.length, +this.length];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p1[0], p1[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.lineTo(p3[0], p3[1]);
|
|
$gCtx.closePath();
|
|
if (this.linewidth > 1) {
|
|
$gCtx.fill();
|
|
} else {
|
|
$gCtx.stroke();
|
|
}
|
|
this.offset += this.length + this.gap;
|
|
}
|
|
|
|
drawArrow3Out() {
|
|
var p1 = [this.offset, -this.length];
|
|
var p2 = [this.offset + this.length, 0];
|
|
var p3 = [this.offset, +this.length];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p1[0], p1[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.lineTo(p3[0], p3[1]);
|
|
$gCtx.closePath();
|
|
if (this.linewidth > 1) {
|
|
$gCtx.fill();
|
|
} else {
|
|
$gCtx.stroke();
|
|
}
|
|
this.offset += this.length + this.gap;
|
|
}
|
|
|
|
drawArrow4In() {
|
|
var p1 = [this.offset, 0];
|
|
var p2 = [this.offset + (4 * this.length), -this.length];
|
|
var p3 = [this.offset + (3 * this.length), 0];
|
|
var p4 = [this.offset + (4 * this.length), +this.length];
|
|
|
|
$drawLineP(p1, p3);
|
|
$drawLineP(p2, p3);
|
|
$drawLineP(p3, p4);
|
|
this.offset += (3 * this.length) + this.gap;
|
|
}
|
|
|
|
drawArrow4Out() {
|
|
var p1 = [this.offset, 0];
|
|
var p2 = [this.offset + (2 * this.length), -this.length];
|
|
var p3 = [this.offset + (3 * this.length), 0];
|
|
var p4 = [this.offset + (2 * this.length), +this.length];
|
|
|
|
$drawLineP(p1, p3);
|
|
$drawLineP(p2, p3);
|
|
$drawLineP(p3, p4);
|
|
this.offset += (3 * this.length) + this.gap;
|
|
}
|
|
|
|
drawArrow5In() {
|
|
var p1 = [this.offset, 0];
|
|
var p2 = [this.offset + (4 * this.length), -this.length];
|
|
var p3 = [this.offset + (3 * this.length), 0];
|
|
var p4 = [this.offset + (4 * this.length), +this.length];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p4[0], p4[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.lineTo(p3[0], p3[1]);
|
|
$gCtx.closePath();
|
|
if (this.linewidth > 1) {
|
|
$gCtx.fill();
|
|
} else {
|
|
$gCtx.stroke();
|
|
}
|
|
$drawLineP(p1, p3);
|
|
this.offset += (3 * this.length) + this.gap;
|
|
}
|
|
|
|
drawArrow5Out() {
|
|
var p1 = [this.offset, 0];
|
|
var p2 = [this.offset + (2 * this.length), -this.length];
|
|
var p3 = [this.offset + (3 * this.length), 0];
|
|
var p4 = [this.offset + (2 * this.length), +this.length];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p4[0], p4[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.lineTo(p3[0], p3[1]);
|
|
$gCtx.closePath();
|
|
if (this.linewidth > 1) {
|
|
$gCtx.fill();
|
|
} else {
|
|
$gCtx.stroke();
|
|
}
|
|
$drawLineP(p1, p3);
|
|
this.offset += (3 * this.length) + this.gap;
|
|
}
|
|
} // class ArrowDecoration
|
|
|
|
class BridgeDecoration extends Decoration {
|
|
constructor($widget, $bridge) {
|
|
super($widget);
|
|
//<bridge side="both" end="both" color="#000000" linewidth="1" approachwidth="8" deckwidth="10" />
|
|
this.side = $bridge.attr('side');
|
|
this.end = $bridge.attr('end');
|
|
this.color = $bridge.attr('color');
|
|
this.linewidth = Number($bridge.attr('linewidth'));
|
|
this.approachwidth = Number($bridge.attr('approachwidth'));
|
|
this.deckwidth = Number($bridge.attr('deckwidth'));
|
|
}
|
|
draw() {
|
|
super.draw();
|
|
var $widget = this.$widget;
|
|
$gCtx.save(); // save current line width and color
|
|
// set color and width
|
|
$gCtx.strokeStyle = this.color;
|
|
$gCtx.fillStyle = this.color;
|
|
$gCtx.lineWidth = this.linewidth;
|
|
if ($widget.circle == "yes") {
|
|
this.drawBridgeCircle();
|
|
} else if ($widget.arc == "yes") {
|
|
this.drawBridgeArc();
|
|
} else if ($widget.bezier == "yes") {
|
|
this.drawBridgeBezier();
|
|
} else {
|
|
this.drawBridgeStrait();
|
|
}
|
|
this.drawBridgeEnds();
|
|
$gCtx.restore(); // restore color and width back to default
|
|
} // draw()
|
|
|
|
drawBridgeCircle() {
|
|
var $widget = this.$widget;
|
|
var halfWidth = this.deckwidth / 2;
|
|
var ep1 = this.ep1, ep2 = this.ep2;
|
|
var startAngleRAD = this.startAngleRAD, stopAngleRAD = this.stopAngleRAD;
|
|
var v = [0, +halfWidth];
|
|
if ($widget.flip == "yes") {
|
|
v = [0, -halfWidth];
|
|
[startAngleRAD, stopAngleRAD] = [stopAngleRAD, startAngleRAD];
|
|
}
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
var tp1 = $point_add(ep1, $point_rotate(v, startAngleRAD));
|
|
var tp2 = $point_add(ep2, $point_rotate(v, stopAngleRAD));
|
|
if ($widget.flip == "yes") {
|
|
$drawArcP(tp2, tp1, $widget.angle);
|
|
} else {
|
|
$drawArcP(tp1, tp2, $widget.angle);
|
|
}
|
|
}
|
|
if ((this.side == "left") || (this.side == "both")) {
|
|
var tp1 = $point_subtract(ep1, $point_rotate(v, startAngleRAD));
|
|
var tp2 = $point_subtract(ep2, $point_rotate(v, stopAngleRAD));
|
|
if ($widget.flip == "yes") {
|
|
$drawArcP(tp2, tp1, $widget.angle);
|
|
} else {
|
|
$drawArcP(tp1, tp2, $widget.angle);
|
|
}
|
|
}
|
|
}
|
|
drawBridgeArc() { //draw arc of ellipse
|
|
var $widget = this.$widget;
|
|
var tp1 = this.ep1, tp2 = this.ep2;
|
|
var startAngleRAD = this.startAngleRAD, stopAngleRAD = this.stopAngleRAD;
|
|
if ($widget.flip == "yes") {
|
|
[tp1, tp2] = [tp2, tp1];
|
|
startAngleRAD += Math.PI;
|
|
stopAngleRAD += Math.PI;
|
|
}
|
|
var halfWidth = this.deckwidth / 2;
|
|
var x, y;
|
|
var rw = tp2[0] - tp1[0], rh = tp2[1] - tp1[1];
|
|
[x, y, rw, rh] = this.getArcParams(rw, rh, tp1, tp2);
|
|
|
|
rw -= halfWidth; rh -= halfWidth;
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
$drawEllipse(x, y, rw, rh, Math.PI + stopAngleRAD, startAngleRAD);
|
|
}
|
|
rw += this.deckwidth; rh += this.deckwidth;
|
|
if ((this.side == "left") || (this.side == "both")) {
|
|
$drawEllipse(x, y, rw, rh, Math.PI + stopAngleRAD, startAngleRAD);
|
|
}
|
|
} // drawBridgeArc()
|
|
|
|
drawBridgeBezier() {
|
|
var $widget = this.$widget;
|
|
var ep1 = this.ep1, ep2 = this.ep2;
|
|
var points = [[ep1[0], ep1[1]]]; // first point
|
|
var $cps = $widget.controlpoints; // get the control points
|
|
$cps.each(function( idx, elem ) { // control points
|
|
points.push($getLayoutPoint(elem));
|
|
});
|
|
points.push([ep2[0], ep2[1]]); // last point
|
|
var halfWidth = this.deckwidth / 2;
|
|
if (((this.side == "left") || (this.side == "both"))) {
|
|
$drawBezier(points, this.color, this.linewidth, -halfWidth);
|
|
}
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
$drawBezier(points, this.color, this.linewidth, +halfWidth);
|
|
}
|
|
}
|
|
drawBridgeStrait() {
|
|
var $widget = this.$widget;
|
|
var ep1 = this.ep1, ep2 = this.ep2;
|
|
var halfWidth = this.deckwidth / 2;
|
|
var vector = $point_orthogonal($point_normalizeTo($point_subtract(ep2, ep1), halfWidth));
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
$drawLineP($point_add(ep1, vector), $point_add(ep2, vector));
|
|
}
|
|
if (((this.side == "left") || (this.side == "both"))) {
|
|
$drawLineP($point_subtract(ep1, vector), $point_subtract(ep2, vector));
|
|
}
|
|
}
|
|
drawBridgeEnds() {
|
|
if ((this.end == "entry") || (this.end == "both")) {
|
|
this.drawBridgeEntry();
|
|
}
|
|
if ((this.end == "exit") || (this.end == "both")) {
|
|
this.drawBridgeExit();
|
|
}
|
|
}
|
|
drawBridgeEntry() {
|
|
var $widget = this.$widget;
|
|
var ep1 = this.ep1;
|
|
var startAngleRAD = this.startAngleRAD, stopAngleRAD = this.stopAngleRAD;
|
|
var halfWidth = this.deckwidth / 2;
|
|
var isRight = ((this.side == "right") || (this.side == "both"));
|
|
var isLeft = ((this.side == "left") || (this.side == "both"));
|
|
if ($widget.flip == "yes") {
|
|
[isRight, isLeft] = [isLeft, isRight];
|
|
[startAngleRAD, stopAngleRAD] = [stopAngleRAD, startAngleRAD];
|
|
}
|
|
var p1, p2;
|
|
if (isRight) {
|
|
p1 = [-this.approachwidth, +this.approachwidth + halfWidth];
|
|
p2 = [0, +halfWidth];
|
|
p1 = $point_add($point_rotate(p1, startAngleRAD), ep1);
|
|
p2 = $point_add($point_rotate(p2, startAngleRAD), ep1);
|
|
$drawLineP(p1, p2);
|
|
}
|
|
if (isLeft) {
|
|
p1 = [-this.approachwidth, -this.approachwidth - halfWidth];
|
|
p2 = [0, -halfWidth];
|
|
p1 = $point_add($point_rotate(p1, startAngleRAD), ep1);
|
|
p2 = $point_add($point_rotate(p2, startAngleRAD), ep1);
|
|
$drawLineP(p1, p2);
|
|
}
|
|
}
|
|
drawBridgeExit() {
|
|
var $widget = this.$widget;
|
|
var ep2 = this.ep2;
|
|
var startAngleRAD = this.startAngleRAD, stopAngleRAD = this.stopAngleRAD;
|
|
var halfWidth = this.deckwidth / 2;
|
|
var isRight = ((this.side == "right") || (this.side == "both"));
|
|
var isLeft = ((this.side == "left") || (this.side == "both"));
|
|
if ($widget.flip == "yes") {
|
|
[isRight, isLeft] = [isLeft, isRight];
|
|
[startAngleRAD, stopAngleRAD] = [stopAngleRAD, startAngleRAD];
|
|
}
|
|
var p1, p2;
|
|
if (isRight) {
|
|
p1 = [+this.approachwidth, +this.approachwidth + halfWidth];
|
|
p2 = [0, +halfWidth];
|
|
p1 = $point_add($point_rotate(p1, stopAngleRAD), ep2);
|
|
p2 = $point_add($point_rotate(p2, stopAngleRAD), ep2);
|
|
$drawLineP(p1, p2);
|
|
}
|
|
if (isLeft) {
|
|
p1 = [+this.approachwidth, -this.approachwidth - halfWidth];
|
|
p2 = [0, -halfWidth];
|
|
p1 = $point_add($point_rotate(p1, stopAngleRAD), ep2);
|
|
p2 = $point_add($point_rotate(p2, stopAngleRAD), ep2);
|
|
$drawLineP(p1, p2);
|
|
}
|
|
}
|
|
} // BridgeDecoration
|
|
|
|
class BumperDecoration extends Decoration {
|
|
constructor($widget, $bumper) {
|
|
super($widget);
|
|
//<bumper end="stop" color="#000000" linewidth="2" length="16" />
|
|
this.end = $bumper.attr('end');
|
|
this.color = $bumper.attr('color');
|
|
this.linewidth = Number($bumper.attr('linewidth'));
|
|
this.length = Number($bumper.attr('length'));
|
|
}
|
|
draw() {
|
|
super.draw();
|
|
$gCtx.save(); // save current line width and color
|
|
// set color and width
|
|
$gCtx.strokeStyle = this.color;
|
|
$gCtx.fillStyle = this.color;
|
|
$gCtx.lineWidth = this.linewidth;
|
|
var $widget = this.$widget;
|
|
var startAngleRAD = this.startAngleRAD, stopAngleRAD = this.stopAngleRAD;
|
|
if ($widget.flip == "yes") {
|
|
[startAngleRAD, stopAngleRAD] = [stopAngleRAD, startAngleRAD];
|
|
}
|
|
var bumperLength = this.length;
|
|
var halfLength = bumperLength / 2;
|
|
// common points
|
|
if ((this.end == "start") || (this.end == "both")) {
|
|
var p1 = [0, -halfLength], p2 = [0, +halfLength];
|
|
var p1 = $point_add($point_rotate(p1, startAngleRAD), this.ep1);
|
|
var p2 = $point_add($point_rotate(p2, startAngleRAD), this.ep1);
|
|
$drawLineP(p1, p2); // draw cross tie
|
|
}
|
|
if ((this.end == "stop") || (this.end == "both")) {
|
|
var p1 = [0, -halfLength], p2 = [0, +halfLength];
|
|
var p1 = $point_add($point_rotate(p1, stopAngleRAD), this.ep2);
|
|
var p2 = $point_add($point_rotate(p2, stopAngleRAD), this.ep2);
|
|
$drawLineP(p1, p2); // draw cross tie
|
|
}
|
|
$gCtx.restore(); // restore color and width back to default
|
|
}
|
|
} // class BumperDecoration
|
|
|
|
class TunnelDecoration extends Decoration {
|
|
constructor($widget, $tunnel) {
|
|
super($widget);
|
|
//<tunnel side="right" end="both" color="#FF00FF" linewidth="2" entrancewidth="16" floorwidth="12" />
|
|
this.side = $tunnel.attr('side');
|
|
|
|
this.end = $tunnel.attr('end');
|
|
this.color = $tunnel.attr('color');
|
|
this.linewidth = Number($tunnel.attr('linewidth'));
|
|
this.entrancewidth = Number($tunnel.attr('entrancewidth'));
|
|
this.floorwidth = Number($tunnel.attr('floorwidth'));
|
|
}
|
|
draw() {
|
|
super.draw();
|
|
var $widget = this.$widget;
|
|
$gCtx.save(); // save current line width and color
|
|
// set color and width
|
|
$gCtx.strokeStyle = this.color;
|
|
$gCtx.fillStyle = this.color;
|
|
$gCtx.lineWidth = this.linewidth;
|
|
$gCtx.setLineDash([6, 4]);
|
|
if ($widget.circle == "yes") {
|
|
this.drawTunnelCircle();
|
|
} else if ($widget.arc == "yes") {
|
|
this.drawTunnelArc();
|
|
} else if ($widget.bezier == "yes") {
|
|
this.drawTunnelBezier();
|
|
} else {
|
|
this.drawTunnelStrait();
|
|
}
|
|
$gCtx.setLineDash([]);
|
|
this.drawTunnelEnds();
|
|
$gCtx.restore(); // restore color and width back to default
|
|
} // draw()
|
|
|
|
drawTunnelCircle() {
|
|
var $widget = this.$widget;
|
|
var halfWidth = this.floorwidth / 2;
|
|
var ep1 = this.ep1, ep2 = this.ep2;
|
|
var startAngleRAD = this.startAngleRAD, stopAngleRAD = this.stopAngleRAD;
|
|
var v = [0, +halfWidth];
|
|
if ($widget.flip == "yes") {
|
|
v = [0, -halfWidth];
|
|
[startAngleRAD, stopAngleRAD] = [stopAngleRAD, startAngleRAD];
|
|
}
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
var tp1 = $point_add(ep1, $point_rotate(v, startAngleRAD));
|
|
var tp2 = $point_add(ep2, $point_rotate(v, stopAngleRAD));
|
|
if ($widget.flip == "yes") {
|
|
$drawArcP(tp2, tp1, $widget.angle);
|
|
} else {
|
|
$drawArcP(tp1, tp2, $widget.angle);
|
|
}
|
|
}
|
|
if ((this.side == "left") || (this.side == "both")) {
|
|
var tp1 = $point_subtract(ep1, $point_rotate(v, startAngleRAD));
|
|
var tp2 = $point_subtract(ep2, $point_rotate(v, stopAngleRAD));
|
|
if ($widget.flip == "yes") {
|
|
$drawArcP(tp2, tp1, $widget.angle);
|
|
} else {
|
|
$drawArcP(tp1, tp2, $widget.angle);
|
|
}
|
|
}
|
|
}
|
|
drawTunnelArc() { //draw arc of ellipse
|
|
var $widget = this.$widget;
|
|
var tp1 = this.ep1, tp2 = this.ep2;
|
|
var startAngleRAD = this.startAngleRAD, stopAngleRAD = this.stopAngleRAD;
|
|
if ($widget.flip == "yes") {
|
|
[tp1, tp2] = [tp2, tp1];
|
|
startAngleRAD += Math.PI;
|
|
stopAngleRAD += Math.PI;
|
|
}
|
|
var halfWidth = this.floorwidth / 2;
|
|
var x, y;
|
|
var rw = tp2[0] - tp1[0], rh = tp2[1] - tp1[1];
|
|
[x, y, rw, rh] = this.getArcParams(rw, rh, tp1, tp2);
|
|
|
|
rw -= halfWidth; rh -= halfWidth;
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
$drawEllipse(x, y, rw, rh, Math.PI + stopAngleRAD, startAngleRAD);
|
|
}
|
|
rw += this.floorwidth; rh += this.floorwidth;
|
|
if ((this.side == "left") || (this.side == "both")) {
|
|
$drawEllipse(x, y, rw, rh, Math.PI + stopAngleRAD, startAngleRAD);
|
|
}
|
|
} // drawTunnelArc()
|
|
|
|
drawTunnelBezier() {
|
|
var $widget = this.$widget;
|
|
var ep1 = this.ep1, ep2 = this.ep2;
|
|
var points = [[ep1[0], ep1[1]]]; // first point
|
|
var $cps = $widget.controlpoints; // get the control points
|
|
$cps.each(function( idx, elem ) { // control points
|
|
points.push($getLayoutPoint(elem));
|
|
});
|
|
points.push([ep2[0], ep2[1]]); // last point
|
|
var halfWidth = this.floorwidth / 2;
|
|
if (((this.side == "left") || (this.side == "both"))) {
|
|
$drawBezier(points, this.color, this.linewidth, -halfWidth);
|
|
}
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
$drawBezier(points, this.color, this.linewidth, +halfWidth);
|
|
}
|
|
}
|
|
drawTunnelStrait() {
|
|
var $widget = this.$widget;
|
|
var ep1 = this.ep1, ep2 = this.ep2;
|
|
var halfWidth = this.floorwidth / 2;
|
|
var vector = $point_orthogonal($point_normalizeTo($point_subtract(ep2, ep1), halfWidth));
|
|
if ((this.side == "right") || (this.side == "both")) {
|
|
$drawLineP($point_add(ep1, vector), $point_add(ep2, vector));
|
|
}
|
|
if (((this.side == "left") || (this.side == "both"))) {
|
|
$drawLineP($point_subtract(ep1, vector), $point_subtract(ep2, vector));
|
|
}
|
|
}
|
|
drawTunnelEnds() {
|
|
if ((this.end == "entry") || (this.end == "both")) {
|
|
this.drawTunnelEntry();
|
|
}
|
|
if ((this.end == "exit") || (this.end == "both")) {
|
|
this.drawTunnelExit();
|
|
}
|
|
}
|
|
drawTunnelEntry() {
|
|
var $widget = this.$widget;
|
|
var ep1 = this.ep1;
|
|
var angleRAD = this.startAngleRAD;
|
|
var isRight = ((this.side == "right") || (this.side == "both"));
|
|
var isLeft = ((this.side == "left") || (this.side == "both"));
|
|
if ($widget.flip == "yes") {
|
|
[isRight, isLeft] = [isLeft, isRight]; // swap left and right
|
|
angleRAD = this.stopAngleRAD;
|
|
}
|
|
|
|
$gCtx.save();
|
|
$gCtx.translate(ep1[0], ep1[1]);
|
|
$gCtx.rotate(angleRAD);
|
|
|
|
if (isRight) {
|
|
this.drawTunnelEntryRight();
|
|
}
|
|
if (isLeft) {
|
|
this.drawTunnelEntryLeft();
|
|
}
|
|
$gCtx.restore();
|
|
}
|
|
drawTunnelEntryRight() {
|
|
var halfWidth = this.floorwidth / 2;
|
|
var halfEntranceWidth = this.entrancewidth / 2;
|
|
var halfFloorWidth = this.floorwidth / 2;
|
|
var halfDiffWidth = halfEntranceWidth - halfFloorWidth;
|
|
var p1, p2, p3, p4, p5, p6, p7;
|
|
p1 = [0, 0];
|
|
p2 = [0, +halfFloorWidth];
|
|
p3 = [0, +halfEntranceWidth];
|
|
p4 = [-halfEntranceWidth - halfFloorWidth, +halfEntranceWidth];
|
|
p5 = [-halfEntranceWidth - halfFloorWidth, +halfEntranceWidth - halfDiffWidth];
|
|
p6 = [-halfFloorWidth, +halfEntranceWidth - halfDiffWidth];
|
|
p7 = [-halfDiffWidth, 0];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p1[0], p1[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.quadraticCurveTo(p3[0], p3[1], p4[0], p4[1]);
|
|
$gCtx.lineTo(p5[0], p5[1]);
|
|
$gCtx.quadraticCurveTo(p6[0], p6[1], p7[0], p7[1]);
|
|
$gCtx.closePath();
|
|
$gCtx.stroke();
|
|
}
|
|
drawTunnelEntryLeft() {
|
|
var halfWidth = this.floorwidth / 2;
|
|
var halfEntranceWidth = this.entrancewidth / 2;
|
|
var halfFloorWidth = this.floorwidth / 2;
|
|
var halfDiffWidth = halfEntranceWidth - halfFloorWidth;
|
|
var p1, p2, p3, p4, p5, p6, p7;
|
|
p1 = [0, 0];
|
|
p2 = [0, -halfFloorWidth];
|
|
p3 = [0, -halfEntranceWidth];
|
|
p4 = [-halfEntranceWidth - halfFloorWidth, -halfEntranceWidth];
|
|
p5 = [-halfEntranceWidth - halfFloorWidth, -halfEntranceWidth + halfDiffWidth];
|
|
p6 = [-halfFloorWidth, -halfEntranceWidth + halfDiffWidth];
|
|
p7 = [-halfDiffWidth, 0];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p1[0], p1[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.quadraticCurveTo(p3[0], p3[1], p4[0], p4[1]);
|
|
$gCtx.lineTo(p5[0], p5[1]);
|
|
$gCtx.quadraticCurveTo(p6[0], p6[1], p7[0], p7[1]);
|
|
$gCtx.closePath();
|
|
$gCtx.stroke();
|
|
}
|
|
drawTunnelExit() {
|
|
var $widget = this.$widget;
|
|
var ep2 = this.ep2;
|
|
var angleRAD = this.stopAngleRAD;
|
|
var isRight = ((this.side == "right") || (this.side == "both"));
|
|
var isLeft = ((this.side == "left") || (this.side == "both"));
|
|
if ($widget.flip == "yes") {
|
|
[isRight, isLeft] = [isLeft, isRight];
|
|
angleRAD = this.startAngleRAD;
|
|
}
|
|
|
|
var halfWidth = this.floorwidth / 2;
|
|
var halfEntranceWidth = this.entrancewidth / 2;
|
|
var halfFloorWidth = this.floorwidth / 2;
|
|
var halfDiffWidth = halfEntranceWidth - halfFloorWidth;
|
|
|
|
var p1, p2, p3, p4, p5, p6, p7;
|
|
|
|
$gCtx.save();
|
|
$gCtx.translate(ep2[0], ep2[1]);
|
|
$gCtx.rotate(angleRAD);
|
|
|
|
if (isRight) {
|
|
this.drawTunnelExitRight();
|
|
}
|
|
if (isLeft) {
|
|
this.drawTunnelExitLeft();
|
|
}
|
|
$gCtx.restore();
|
|
}
|
|
drawTunnelExitRight() {
|
|
var halfWidth = this.floorwidth / 2;
|
|
var halfEntranceWidth = this.entrancewidth / 2;
|
|
var halfFloorWidth = this.floorwidth / 2;
|
|
var halfDiffWidth = halfEntranceWidth - halfFloorWidth;
|
|
var p1, p2, p3, p4, p5, p6, p7;
|
|
p1 = [0, 0];
|
|
p2 = [0, +halfFloorWidth];
|
|
p3 = [0, +halfEntranceWidth];
|
|
p4 = [halfEntranceWidth + halfFloorWidth, +halfEntranceWidth];
|
|
p5 = [halfEntranceWidth + halfFloorWidth, +halfEntranceWidth - halfDiffWidth];
|
|
p6 = [halfFloorWidth, +halfEntranceWidth - halfDiffWidth];
|
|
p7 = [halfDiffWidth, 0];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p1[0], p1[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.quadraticCurveTo(p3[0], p3[1], p4[0], p4[1]);
|
|
$gCtx.lineTo(p5[0], p5[1]);
|
|
$gCtx.quadraticCurveTo(p6[0], p6[1], p7[0], p7[1]);
|
|
$gCtx.closePath();
|
|
$gCtx.stroke();
|
|
}
|
|
drawTunnelExitLeft() {
|
|
var halfWidth = this.floorwidth / 2;
|
|
var halfEntranceWidth = this.entrancewidth / 2;
|
|
var halfFloorWidth = this.floorwidth / 2;
|
|
var halfDiffWidth = halfEntranceWidth - halfFloorWidth;
|
|
var p1, p2, p3, p4, p5, p6, p7;
|
|
p1 = [0, 0];
|
|
p2 = [0, -halfFloorWidth];
|
|
p3 = [0, -halfEntranceWidth];
|
|
p4 = [halfEntranceWidth + halfFloorWidth, -halfEntranceWidth];
|
|
p5 = [halfEntranceWidth + halfFloorWidth, -halfEntranceWidth + halfDiffWidth];
|
|
p6 = [halfFloorWidth, -halfEntranceWidth + halfDiffWidth];
|
|
p7 = [halfDiffWidth, 0];
|
|
|
|
$gCtx.beginPath();
|
|
$gCtx.moveTo(p1[0], p1[1]);
|
|
$gCtx.lineTo(p2[0], p2[1]);
|
|
$gCtx.quadraticCurveTo(p3[0], p3[1], p4[0], p4[1]);
|
|
$gCtx.lineTo(p5[0], p5[1]);
|
|
$gCtx.quadraticCurveTo(p6[0], p6[1], p7[0], p7[1]);
|
|
$gCtx.closePath();
|
|
$gCtx.stroke();
|
|
}
|
|
}
|
|
|
|
// End of Layout Editor Decoration classes =======
|