linny-r 1.7.4 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/package.json +1 -1
- package/static/index.html +23 -16
- package/static/linny-r.css +11 -1
- package/static/scripts/linny-r-ctrl.js +2 -0
- package/static/scripts/linny-r-gui-controller.js +177 -123
- package/static/scripts/linny-r-gui-documentation-manager.js +1 -1
- package/static/scripts/linny-r-gui-expression-editor.js +10 -10
- package/static/scripts/linny-r-gui-paper.js +13 -6
- package/static/scripts/linny-r-milp.js +55 -17
- package/static/scripts/linny-r-model.js +7 -1
- package/static/scripts/linny-r-vm.js +102 -37
@@ -501,6 +501,9 @@ class GUIController extends Controller {
|
|
501
501
|
|
502
502
|
// Visible draggable dialogs are sorted by their z-index.
|
503
503
|
this.dr_dialog_order = [];
|
504
|
+
|
505
|
+
// Record of message that was overridden by more important message.
|
506
|
+
this.old_info_line = null;
|
504
507
|
}
|
505
508
|
|
506
509
|
get color() {
|
@@ -617,7 +620,8 @@ class GUIController extends Controller {
|
|
617
620
|
UI.updateButtons();
|
618
621
|
}
|
619
622
|
});
|
620
|
-
this.buttons.solve.addEventListener('click',
|
623
|
+
this.buttons.solve.addEventListener('click',
|
624
|
+
(event) => VM.solveModel(event.altKey));
|
621
625
|
this.buttons.stop.addEventListener('click', () => VM.halt());
|
622
626
|
this.buttons.reset.addEventListener('click', () => UI.resetModel());
|
623
627
|
|
@@ -905,7 +909,7 @@ class GUIController extends Controller {
|
|
905
909
|
// Reset the Virtual Machine.
|
906
910
|
VM.reset();
|
907
911
|
this.updateIssuePanel();
|
908
|
-
this.
|
912
|
+
this.clearStatusLine();
|
909
913
|
this.updateButtons();
|
910
914
|
// Undoable operations no longer apply!
|
911
915
|
UNDO_STACK.clear();
|
@@ -1729,8 +1733,8 @@ class GUIController extends Controller {
|
|
1729
1733
|
}
|
1730
1734
|
for(let i = fc.sub_clusters.length-1; i >= 0; i--) {
|
1731
1735
|
const obj = fc.sub_clusters[i];
|
1732
|
-
// NOTE:
|
1733
|
-
// being dragged over will be detected instead
|
1736
|
+
// NOTE: Ignore cluster that is being dragged, so that a cluster
|
1737
|
+
// it is being dragged over will be detected instead.
|
1734
1738
|
if(obj != this.dragged_node &&
|
1735
1739
|
obj.containsPoint(this.mouse_x, this.mouse_y)) {
|
1736
1740
|
this.on_cluster = obj;
|
@@ -1738,12 +1742,13 @@ class GUIController extends Controller {
|
|
1738
1742
|
break;
|
1739
1743
|
}
|
1740
1744
|
}
|
1741
|
-
//
|
1745
|
+
// Unset and redraw target cluster if cursor no longer over it.
|
1742
1746
|
if(!this.on_cluster && this.target_cluster) {
|
1743
1747
|
const c = this.target_cluster;
|
1744
1748
|
this.target_cluster = null;
|
1745
1749
|
UI.paper.drawCluster(c);
|
1746
|
-
// NOTE:
|
1750
|
+
// NOTE: Element is persistent, so semi-transparency must also be
|
1751
|
+
// undone.
|
1747
1752
|
c.shape.element.setAttribute('opacity', 1);
|
1748
1753
|
}
|
1749
1754
|
for(let i = fc.notes.length-1; i >= 0; i--) {
|
@@ -1754,15 +1759,15 @@ class GUIController extends Controller {
|
|
1754
1759
|
}
|
1755
1760
|
}
|
1756
1761
|
if(this.active_button === this.buttons.link && this.linking_node) {
|
1757
|
-
// Draw red dotted line from linking node to cursor
|
1762
|
+
// Draw red dotted line from linking node to cursor.
|
1758
1763
|
this.paper.dragLineToCursor(this.linking_node, this.mouse_x, this.mouse_y);
|
1759
1764
|
} else if(this.start_sel_x >= 0 && this.start_sel_y >= 0) {
|
1760
|
-
// Draw selecting rectangle in red dotted lines
|
1765
|
+
// Draw selecting rectangle in red dotted lines.
|
1761
1766
|
this.paper.dragRectToCursor(this.start_sel_x, this.start_sel_y,
|
1762
1767
|
this.mouse_x, this.mouse_y);
|
1763
1768
|
} else if(this.active_button === this.buttons.constraint &&
|
1764
1769
|
this.constraining_node) {
|
1765
|
-
// Draw red dotted line from constraining node to cursor
|
1770
|
+
// Draw red dotted line from constraining node to cursor.
|
1766
1771
|
this.paper.dragLineToCursor(this.constraining_node,
|
1767
1772
|
this.mouse_x, this.mouse_y);
|
1768
1773
|
} else if(this.dragged_node) {
|
@@ -1770,14 +1775,14 @@ class GUIController extends Controller {
|
|
1770
1775
|
this.mouse_y - this.move_dy - this.dragged_node.y);
|
1771
1776
|
}
|
1772
1777
|
let cr = 'pointer';
|
1773
|
-
// NOTE:
|
1774
|
-
// with nodes
|
1778
|
+
// NOTE: First check ON_CONSTRAINT because constraint thumbnails overlap
|
1779
|
+
// with nodes.
|
1775
1780
|
if(this.on_constraint) {
|
1776
1781
|
DOCUMENTATION_MANAGER.update(this.on_constraint, e.shiftKey);
|
1777
|
-
// NOTE:
|
1782
|
+
// NOTE: Skip the "on node" check if the node is being dragged.
|
1778
1783
|
} else if(this.on_node && this.on_node !== this.dragged_node) {
|
1779
1784
|
if((this.active_button === this.buttons.link) && this.linking_node) {
|
1780
|
-
// Cannot link process to process
|
1785
|
+
// Cannot link process to process.
|
1781
1786
|
cr = (MODEL.canLink(this.linking_node, this.on_node) ?
|
1782
1787
|
'crosshair' : 'not-allowed');
|
1783
1788
|
} else if(this.active_button === this.buttons.constraint) {
|
@@ -1785,21 +1790,21 @@ class GUIController extends Controller {
|
|
1785
1790
|
cr = (this.constraining_node.canConstrain(this.on_node) ?
|
1786
1791
|
'crosshair' : 'not-allowed');
|
1787
1792
|
} else if(!this.on_node.hasBounds) {
|
1788
|
-
// Products can only constrain when they have bounds
|
1793
|
+
// Products can only constrain when they have bounds.
|
1789
1794
|
cr = 'not-allowed';
|
1790
1795
|
}
|
1791
1796
|
}
|
1792
|
-
// NOTE:
|
1797
|
+
// NOTE: Do not overwite status line when cursor is on a block arrow.
|
1793
1798
|
if(!this.on_block_arrow) {
|
1794
1799
|
DOCUMENTATION_MANAGER.update(this.on_node, e.shiftKey);
|
1795
1800
|
}
|
1796
1801
|
} else if(this.on_note) {
|
1797
|
-
// When shift-moving over a note, show the model's documentation
|
1802
|
+
// When shift-moving over a note, show the model's documentation.
|
1798
1803
|
DOCUMENTATION_MANAGER.update(MODEL, e.shiftKey);
|
1799
1804
|
} else {
|
1800
1805
|
if((this.active_button === this.buttons.link && this.linking_node) ||
|
1801
1806
|
(this.active_button === this.buttons.constraint && this.constraining_node)) {
|
1802
|
-
// Cannot link to clusters or notes
|
1807
|
+
// Cannot link to clusters or notes.
|
1803
1808
|
cr = (this.on_cluster || this.on_note ? 'not-allowed' : 'crosshair');
|
1804
1809
|
} else if(!this.on_note && !this.on_constraint && !this.on_link &&
|
1805
1810
|
!this.on_cluster_edge) {
|
@@ -1815,38 +1820,42 @@ class GUIController extends Controller {
|
|
1815
1820
|
}
|
1816
1821
|
}
|
1817
1822
|
// When dragging selection that contains a process, change cursor to
|
1818
|
-
// indicate that selected process(es) will be moved into the cluster
|
1819
|
-
if(this.dragged_node
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1823
|
+
// indicate that selected process(es) will be moved into the cluster.
|
1824
|
+
if(this.dragged_node) {
|
1825
|
+
if(this.on_cluster) {
|
1826
|
+
cr = 'cell';
|
1827
|
+
this.target_cluster = this.on_cluster;
|
1828
|
+
// Redraw the target cluster so it will appear on top (and highlighted).
|
1829
|
+
UI.paper.drawCluster(this.target_cluster);
|
1830
|
+
} else {
|
1831
|
+
cr = 'grab';
|
1832
|
+
}
|
1824
1833
|
}
|
1825
1834
|
}
|
1826
1835
|
this.paper.container.style.cursor = cr;
|
1827
1836
|
}
|
1828
1837
|
|
1829
1838
|
mouseDown(e) {
|
1830
|
-
// Responds to mousedown event in model diagram area
|
1839
|
+
// Responds to mousedown event in model diagram area.
|
1831
1840
|
// In case mouseup event occurred outside drawing area,ignore this
|
1832
|
-
// mousedown event, so that only the mouseup will be processed
|
1841
|
+
// mousedown event, so that only the mouseup will be processed.
|
1833
1842
|
if(this.start_sel_x >= 0 && this.start_sel_y >= 0) return;
|
1834
1843
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
1835
1844
|
this.mouse_down_x = cp[0];
|
1836
1845
|
this.mouse_down_y = cp[1];
|
1837
1846
|
// De-activate "stay active" buttons if dysfunctional, or if SHIFT,
|
1838
|
-
// ALT or CTRL is pressed
|
1847
|
+
// ALT or CTRL is pressed.
|
1839
1848
|
if((e.shiftKey || e.altKey || e.ctrlKey ||
|
1840
1849
|
this.on_note || this.on_cluster || this.on_link || this.on_constraint ||
|
1841
1850
|
(this.on_node && this.active_button !== this.buttons.link &&
|
1842
1851
|
this.active_button !== this.buttons.constraint)) && this.stayActive) {
|
1843
1852
|
resetActiveButton();
|
1844
1853
|
}
|
1845
|
-
// NOTE:
|
1854
|
+
// NOTE: Only left button is detected (browser catches right menu button).
|
1846
1855
|
if(e.ctrlKey) {
|
1847
1856
|
// Remove clicked item from selection
|
1848
1857
|
if(MODEL.selection) {
|
1849
|
-
// NOTE:
|
1858
|
+
// NOTE: First check constraints -- see mouseMove() for motivation.
|
1850
1859
|
if(this.on_constraint) {
|
1851
1860
|
if(MODEL.selection.indexOf(this.on_constraint) >= 0) {
|
1852
1861
|
MODEL.deselect(this.on_constraint);
|
@@ -1885,7 +1894,7 @@ class GUIController extends Controller {
|
|
1885
1894
|
} // END IF Ctrl
|
1886
1895
|
|
1887
1896
|
// Clear selection unless SHIFT pressed or mouseDown while hovering
|
1888
|
-
// over a SELECTED node or link
|
1897
|
+
// over a SELECTED node or link.
|
1889
1898
|
if(!(e.shiftKey ||
|
1890
1899
|
(this.on_node && MODEL.selection.indexOf(this.on_node) >= 0) ||
|
1891
1900
|
(this.on_cluster && MODEL.selection.indexOf(this.on_cluster) >= 0) ||
|
@@ -1897,7 +1906,7 @@ class GUIController extends Controller {
|
|
1897
1906
|
}
|
1898
1907
|
|
1899
1908
|
// If one of the top six sidebar buttons is active, prompt for new node
|
1900
|
-
// (not link or constraint)
|
1909
|
+
// (not link or constraint).
|
1901
1910
|
if(this.active_button && this.active_button !== this.buttons.link &&
|
1902
1911
|
this.active_button !== this.buttons.constraint) {
|
1903
1912
|
this.add_x = this.mouse_x;
|
@@ -1939,16 +1948,16 @@ class GUIController extends Controller {
|
|
1939
1948
|
}
|
1940
1949
|
|
1941
1950
|
// ALT key pressed => open properties dialog if cursor hovers over
|
1942
|
-
// some element
|
1951
|
+
// some element.
|
1943
1952
|
if(e.altKey) {
|
1944
|
-
// NOTE:
|
1953
|
+
// NOTE: First check constraints -- see mouseMove() for motivation.
|
1945
1954
|
if(this.on_constraint) {
|
1946
1955
|
this.showConstraintPropertiesDialog(this.on_constraint);
|
1947
1956
|
} else if(this.on_node) {
|
1948
1957
|
if(this.on_node instanceof Process) {
|
1949
1958
|
this.showProcessPropertiesDialog(this.on_node);
|
1950
1959
|
} else if(e.shiftKey) {
|
1951
|
-
// Shift-Alt on product is like Shift-Double-click
|
1960
|
+
// Shift-Alt on product is like Shift-Double-click.
|
1952
1961
|
this.showReplaceProductDialog(this.on_node);
|
1953
1962
|
} else {
|
1954
1963
|
this.showProductPropertiesDialog(this.on_node);
|
@@ -1960,7 +1969,7 @@ class GUIController extends Controller {
|
|
1960
1969
|
} else if(this.on_link) {
|
1961
1970
|
this.showLinkPropertiesDialog(this.on_link);
|
1962
1971
|
}
|
1963
|
-
// NOTE:
|
1972
|
+
// NOTE: First check constraints -- see mouseMove() for motivation.
|
1964
1973
|
} else if(this.on_constraint) {
|
1965
1974
|
MODEL.select(this.on_constraint);
|
1966
1975
|
} else if(this.on_note) {
|
@@ -1969,17 +1978,17 @@ class GUIController extends Controller {
|
|
1969
1978
|
this.move_dy = this.mouse_y - this.on_note.y;
|
1970
1979
|
MODEL.select(this.on_note);
|
1971
1980
|
UNDO_STACK.push('move', this.dragged_node, true);
|
1972
|
-
// Cursor on node => add link or constraint, or start moving
|
1981
|
+
// Cursor on node => add link or constraint, or start moving.
|
1973
1982
|
} else if(this.on_node) {
|
1974
1983
|
if(this.active_button === this.buttons.link) {
|
1975
1984
|
this.linking_node = this.on_node;
|
1976
1985
|
// NOTE: return without updating buttons
|
1977
1986
|
return;
|
1978
1987
|
} else if(this.active_button === this.buttons.constraint) {
|
1979
|
-
// Allow constraints only on nodes having upper bounds defined
|
1988
|
+
// Allow constraints only on nodes having upper bounds defined.
|
1980
1989
|
if(this.on_node.upper_bound.defined) {
|
1981
1990
|
this.constraining_node = this.on_node;
|
1982
|
-
// NOTE:
|
1991
|
+
// NOTE: Here, too, return without updating buttons.
|
1983
1992
|
return;
|
1984
1993
|
}
|
1985
1994
|
} else {
|
@@ -1987,7 +1996,7 @@ class GUIController extends Controller {
|
|
1987
1996
|
this.move_dx = this.mouse_x - this.on_node.x;
|
1988
1997
|
this.move_dy = this.mouse_y - this.on_node.y;
|
1989
1998
|
if(MODEL.selection.indexOf(this.on_node) < 0) MODEL.select(this.on_node);
|
1990
|
-
// Pass dragged node for UNDO
|
1999
|
+
// Pass dragged node for UNDO.
|
1991
2000
|
UNDO_STACK.push('move', this.dragged_node, true);
|
1992
2001
|
}
|
1993
2002
|
} else if(this.on_cluster) {
|
@@ -2006,23 +2015,23 @@ class GUIController extends Controller {
|
|
2006
2015
|
}
|
2007
2016
|
|
2008
2017
|
mouseUp(e) {
|
2009
|
-
// Responds to mouseup event
|
2018
|
+
// Responds to mouseup event.
|
2010
2019
|
const cp = this.paper.cursorPosition(e.pageX, e.pageY);
|
2011
2020
|
this.mouse_up_x = cp[0];
|
2012
2021
|
this.mouse_up_y = cp[1];
|
2013
|
-
// First check whether user is selecting a rectangle
|
2022
|
+
// First check whether user is selecting a rectangle.
|
2014
2023
|
if(this.start_sel_x >= 0 && this.start_sel_y >= 0) {
|
2015
2024
|
// Clear previous selection unless user is adding to it (by still
|
2016
|
-
// holding SHIFT button down)
|
2025
|
+
// holding SHIFT button down).
|
2017
2026
|
if(!e.shiftKey) MODEL.clearSelection();
|
2018
|
-
// Compute defining points of rectangle (top left and bottom right)
|
2027
|
+
// Compute defining points of rectangle (top left and bottom right).
|
2019
2028
|
const
|
2020
2029
|
tlx = Math.min(this.start_sel_x, this.mouse_up_x),
|
2021
2030
|
tly = Math.min(this.start_sel_y, this.mouse_up_y),
|
2022
2031
|
brx = Math.max(this.start_sel_x, this.mouse_up_x),
|
2023
2032
|
bry = Math.max(this.start_sel_y, this.mouse_up_y);
|
2024
2033
|
// If rectangle has size greater than 2x2 pixels, select all elements
|
2025
|
-
// having their center inside the selection rectangle
|
2034
|
+
// having their center inside the selection rectangle.
|
2026
2035
|
if(brx - tlx > 2 && bry - tly > 2) {
|
2027
2036
|
const ol = [], fc = MODEL.focal_cluster;
|
2028
2037
|
for(let i = 0; i < fc.processes.length; i++) {
|
@@ -2051,19 +2060,19 @@ class GUIController extends Controller {
|
|
2051
2060
|
}
|
2052
2061
|
for(let i in MODEL.links) if(MODEL.links.hasOwnProperty(i)) {
|
2053
2062
|
const obj = MODEL.links[i];
|
2054
|
-
// Only add a link if both its nodes are selected as well
|
2063
|
+
// Only add a link if both its nodes are selected as well.
|
2055
2064
|
if(fc.linkInList(obj, ol)) {
|
2056
2065
|
ol.push(obj);
|
2057
2066
|
}
|
2058
2067
|
}
|
2059
2068
|
for(let i in MODEL.constraints) if(MODEL.constraints.hasOwnProperty(i)) {
|
2060
2069
|
const obj = MODEL.constraints[i];
|
2061
|
-
// Only add a constraint if both its nodes are selected as well
|
2070
|
+
// Only add a constraint if both its nodes are selected as well.
|
2062
2071
|
if(fc.linkInList(obj, ol)) {
|
2063
2072
|
ol.push(obj);
|
2064
2073
|
}
|
2065
2074
|
}
|
2066
|
-
// Having compiled the object list, actually select them
|
2075
|
+
// Having compiled the object list, actually select them.
|
2067
2076
|
MODEL.selectList(ol);
|
2068
2077
|
this.paper.drawSelection(MODEL);
|
2069
2078
|
}
|
@@ -2071,10 +2080,10 @@ class GUIController extends Controller {
|
|
2071
2080
|
this.start_sel_y = -1;
|
2072
2081
|
this.paper.hideDragRect();
|
2073
2082
|
|
2074
|
-
// Then check whether user is drawing a flow link
|
2075
|
-
//
|
2083
|
+
// Then check whether user is drawing a flow link (by dragging its
|
2084
|
+
// endpoint).
|
2076
2085
|
} else if(this.linking_node) {
|
2077
|
-
// If so, check whether the cursor is over a node of the appropriate type
|
2086
|
+
// If so, check whether the cursor is over a node of the appropriate type.
|
2078
2087
|
if(this.on_node && MODEL.canLink(this.linking_node, this.on_node)) {
|
2079
2088
|
const obj = MODEL.addLink(this.linking_node, this.on_node);
|
2080
2089
|
UNDO_STACK.push('add', obj);
|
@@ -2085,8 +2094,8 @@ class GUIController extends Controller {
|
|
2085
2094
|
if(!this.stayActive) this.resetActiveButton();
|
2086
2095
|
this.paper.hideDragLine();
|
2087
2096
|
|
2088
|
-
// Then check whether user is drawing a constraint link
|
2089
|
-
//
|
2097
|
+
// Then check whether user is drawing a constraint link (again: by
|
2098
|
+
// dragging its endpoint).
|
2090
2099
|
} else if(this.constraining_node) {
|
2091
2100
|
if(this.on_node && this.constraining_node.canConstrain(this.on_node)) {
|
2092
2101
|
// display constraint editor
|
@@ -2100,35 +2109,37 @@ class GUIController extends Controller {
|
|
2100
2109
|
UI.drawDiagram(MODEL);
|
2101
2110
|
|
2102
2111
|
// Then check whether the user is moving a node (possibly part of a
|
2103
|
-
// larger selection)
|
2112
|
+
// larger selection).
|
2104
2113
|
} else if(this.dragged_node) {
|
2105
2114
|
// Always perform the move operation (this will do nothing if the
|
2106
|
-
// cursor did not move)
|
2115
|
+
// cursor did not move).
|
2107
2116
|
MODEL.moveSelection(
|
2108
2117
|
this.mouse_up_x - this.mouse_x, this.mouse_up_y - this.mouse_y);
|
2109
|
-
//
|
2110
|
-
|
2118
|
+
// Set cursor to pointer, as it should be on some node while dragging.
|
2119
|
+
this.paper.container.style.cursor = 'pointer';
|
2120
|
+
// @@TO DO: if on top of a cluster, move it there.
|
2121
|
+
// NOTE: Cursor will always be over the selected cluster (while dragging).
|
2111
2122
|
if(this.on_cluster && !this.on_cluster.selected) {
|
2112
2123
|
UNDO_STACK.push('drop', this.on_cluster);
|
2113
2124
|
MODEL.dropSelectionIntoCluster(this.on_cluster);
|
2114
2125
|
this.on_node = null;
|
2115
2126
|
this.on_note = null;
|
2116
2127
|
this.target_cluster = null;
|
2117
|
-
// Redraw cluster to erase its "target corona"
|
2128
|
+
// Redraw cluster to erase its orange "target corona".
|
2118
2129
|
UI.paper.drawCluster(this.on_cluster);
|
2119
2130
|
}
|
2120
2131
|
|
2121
|
-
// Check wether the cursor has been moved
|
2132
|
+
// Check wether the cursor has been moved.
|
2122
2133
|
const
|
2123
2134
|
absdx = Math.abs(this.mouse_down_x - this.mouse_x),
|
2124
2135
|
absdy = Math.abs(this.mouse_down_y - this.mouse_y);
|
2125
|
-
// If no *significant* move made, remove the move undo
|
2136
|
+
// If no *significant* move made, remove the move undo.
|
2126
2137
|
if(absdx + absdy === 0) UNDO_STACK.pop('move');
|
2127
2138
|
if(this.doubleClicked && absdx + absdy < 3) {
|
2128
2139
|
// Double-clicking opens properties dialog, except for clusters;
|
2129
|
-
// then "drill down", i.e., make the double-clicked cluster focal
|
2140
|
+
// then "drill down", i.e., make the double-clicked cluster focal.
|
2130
2141
|
if(this.dragged_node instanceof Cluster) {
|
2131
|
-
// NOTE:
|
2142
|
+
// NOTE: Bottom & right cluster edges remain sensitive!
|
2132
2143
|
if(this.on_cluster_edge) {
|
2133
2144
|
this.showClusterPropertiesDialog(this.dragged_node);
|
2134
2145
|
} else {
|
@@ -2138,7 +2149,7 @@ class GUIController extends Controller {
|
|
2138
2149
|
if(e.shiftKey) {
|
2139
2150
|
// Shift-double-clicking on a *product* prompts for "remapping"
|
2140
2151
|
// the product position to another product (and potentially
|
2141
|
-
// deleting the original one if it has no more occurrences)
|
2152
|
+
// deleting the original one if it has no more occurrences).
|
2142
2153
|
this.showReplaceProductDialog(this.dragged_node);
|
2143
2154
|
} else {
|
2144
2155
|
this.showProductPropertiesDialog(this.dragged_node);
|
@@ -2151,7 +2162,7 @@ class GUIController extends Controller {
|
|
2151
2162
|
}
|
2152
2163
|
this.dragged_node = null;
|
2153
2164
|
|
2154
|
-
// Then check whether the user is clicking on a link
|
2165
|
+
// Then check whether the user is clicking on a link.
|
2155
2166
|
} else if(this.on_link) {
|
2156
2167
|
if(this.doubleClicked) {
|
2157
2168
|
this.showLinkPropertiesDialog(this.on_link);
|
@@ -2167,8 +2178,8 @@ class GUIController extends Controller {
|
|
2167
2178
|
}
|
2168
2179
|
|
2169
2180
|
dragOver(e) {
|
2170
|
-
//
|
2171
|
-
// a placeholder in the focal cluster
|
2181
|
+
// Accept products that are dragged from the Finder and do not have
|
2182
|
+
// a placeholder in the focal cluster.
|
2172
2183
|
this.updateCursorPosition(e);
|
2173
2184
|
const p = MODEL.products[e.dataTransfer.getData('text')];
|
2174
2185
|
if(p && MODEL.focal_cluster.indexOfProduct(p) < 0) e.preventDefault();
|
@@ -2176,7 +2187,7 @@ class GUIController extends Controller {
|
|
2176
2187
|
|
2177
2188
|
drop(e) {
|
2178
2189
|
// Adds a product that is dragged from the Finder to the focal cluster
|
2179
|
-
// at the cursor position if it does not have a placeholder yet
|
2190
|
+
// at the cursor position if it does not have a placeholder yet.
|
2180
2191
|
const p = MODEL.products[e.dataTransfer.getData('text')];
|
2181
2192
|
if(p && MODEL.focal_cluster.indexOfProduct(p) < 0) {
|
2182
2193
|
e.preventDefault();
|
@@ -2185,7 +2196,7 @@ class GUIController extends Controller {
|
|
2185
2196
|
this.selectNode(p);
|
2186
2197
|
this.drawDiagram(MODEL);
|
2187
2198
|
}
|
2188
|
-
// NOTE:
|
2199
|
+
// NOTE: Update afterwards, as the modeler may target a precise (X, Y).
|
2189
2200
|
this.updateCursorPosition(e);
|
2190
2201
|
}
|
2191
2202
|
|
@@ -2194,14 +2205,16 @@ class GUIController extends Controller {
|
|
2194
2205
|
//
|
2195
2206
|
|
2196
2207
|
checkModals(e) {
|
2197
|
-
// Respond to Escape, Enter and shortcut keys
|
2208
|
+
// Respond to Escape, Enter and shortcut keys.
|
2198
2209
|
const
|
2199
2210
|
ttype = e.target.type,
|
2200
2211
|
ttag = e.target.tagName,
|
2201
2212
|
modals = document.getElementsByClassName('modal');
|
2202
|
-
// Modal dialogs: hide on ESC and move to next input on ENTER
|
2213
|
+
// Modal dialogs: hide on ESC and move to next input on ENTER.
|
2203
2214
|
let maxz = 0,
|
2204
|
-
topmod = null
|
2215
|
+
topmod = null,
|
2216
|
+
code = e.code,
|
2217
|
+
alt = e.altKey;
|
2205
2218
|
for(let i = 0; i < modals.length; i++) {
|
2206
2219
|
const
|
2207
2220
|
m = modals[i],
|
@@ -2212,11 +2225,11 @@ class GUIController extends Controller {
|
|
2212
2225
|
maxz = z;
|
2213
2226
|
}
|
2214
2227
|
}
|
2215
|
-
// NOTE:
|
2216
|
-
if(
|
2228
|
+
// NOTE: Consider only the top modal (if any is showing).
|
2229
|
+
if(code === 'Escape') {
|
2217
2230
|
e.stopImmediatePropagation();
|
2218
2231
|
if(topmod) topmod.style.display = 'none';
|
2219
|
-
} else if(
|
2232
|
+
} else if(code === 'Enter' && ttype !== 'textarea') {
|
2220
2233
|
e.preventDefault();
|
2221
2234
|
if(topmod) {
|
2222
2235
|
const inp = Array.from(topmod.getElementsByTagName('input'));
|
@@ -2225,73 +2238,78 @@ class GUIController extends Controller {
|
|
2225
2238
|
if(i < inp.length) {
|
2226
2239
|
inp[i].focus();
|
2227
2240
|
} else if('constraint-modal xp-clusters-modal'.indexOf(topmod.id) >= 0) {
|
2228
|
-
// NOTE:
|
2229
|
-
// when Enter is pressed
|
2241
|
+
// NOTE: Constraint modal and "ignore clusters" modal must NOT close
|
2242
|
+
// when Enter is pressed, but only de-focus the input field.
|
2230
2243
|
e.target.blur();
|
2231
2244
|
} else {
|
2232
2245
|
const btns = topmod.getElementsByClassName('ok-btn');
|
2233
2246
|
if(btns.length > 0) btns[0].dispatchEvent(new Event('click'));
|
2234
2247
|
}
|
2235
2248
|
} else if(this.dr_dialog_order.length > 0) {
|
2236
|
-
// Send ENTER key event to the top draggable dialog
|
2249
|
+
// Send ENTER key event to the top draggable dialog.
|
2237
2250
|
const last = this.dr_dialog_order.length - 1;
|
2238
2251
|
if(last >= 0) {
|
2239
2252
|
const mgr = window[this.dr_dialog_order[last].dataset.manager];
|
2240
2253
|
if(mgr && 'enterKey' in mgr) mgr.enterKey();
|
2241
2254
|
}
|
2242
2255
|
}
|
2243
|
-
} else if(
|
2256
|
+
} else if(code === 'Backspace' &&
|
2244
2257
|
ttype !== 'text' && ttype !== 'password' && ttype !== 'textarea') {
|
2245
|
-
// Prevent backspace to be interpreted (by FireFox) as "go back in browser"
|
2258
|
+
// Prevent backspace to be interpreted (by FireFox) as "go back in browser".
|
2246
2259
|
e.preventDefault();
|
2247
2260
|
} else if(ttag === 'BODY') {
|
2248
|
-
// Constraint Editor accepts arrow keys
|
2261
|
+
// Constraint Editor accepts arrow keys.
|
2249
2262
|
if(topmod && topmod.id === 'constraint-modal') {
|
2250
|
-
if(
|
2263
|
+
if(code.startsWith('Arrow')) {
|
2251
2264
|
e.preventDefault();
|
2252
2265
|
CONSTRAINT_EDITOR.arrowKey(e);
|
2253
2266
|
return;
|
2254
2267
|
}
|
2255
2268
|
}
|
2256
|
-
// Up and down arrow keys
|
2257
|
-
if(
|
2269
|
+
// Up and down arrow keys.
|
2270
|
+
if(code === 'ArrowUp' || code === 'ArrowDown') {
|
2258
2271
|
e.preventDefault();
|
2259
|
-
// Send event to the top draggable dialog
|
2272
|
+
// Send event to the top draggable dialog.
|
2260
2273
|
const last = this.dr_dialog_order.length - 1;
|
2261
2274
|
if(last >= 0) {
|
2262
2275
|
const mgr = window[this.dr_dialog_order[last].dataset.manager];
|
2263
|
-
// NOTE:
|
2276
|
+
// NOTE: Pass key direction as -1 for UP and +1 for DOWN.
|
2264
2277
|
if(mgr && 'upDownKey' in mgr) mgr.upDownKey(e.keyCode - 39);
|
2265
2278
|
}
|
2266
2279
|
}
|
2267
|
-
//
|
2268
|
-
if(
|
2269
|
-
|
2280
|
+
// End, Home, and left and right arrow keys.
|
2281
|
+
if(code === 'End') {
|
2282
|
+
e.preventDefault();
|
2270
2283
|
MODEL.t = MODEL.end_period - MODEL.start_period + 1;
|
2271
2284
|
UI.updateTimeStep();
|
2272
2285
|
UI.drawDiagram(MODEL);
|
2273
|
-
} else if(
|
2286
|
+
} else if(code === 'Home') {
|
2287
|
+
e.preventDefault();
|
2274
2288
|
MODEL.t = 1;
|
2275
2289
|
UI.updateTimeStep();
|
2276
2290
|
UI.drawDiagram(MODEL);
|
2277
|
-
} else if(
|
2291
|
+
} else if(code === 'ArrowLeft') {
|
2292
|
+
e.preventDefault();
|
2278
2293
|
this.stepBack(e);
|
2279
|
-
} else if(
|
2294
|
+
} else if(code === 'ArrowRight') {
|
2295
|
+
e.preventDefault();
|
2280
2296
|
this.stepForward(e);
|
2281
|
-
} else if(
|
2282
|
-
//
|
2297
|
+
} else if(alt && code === 'KeyR') {
|
2298
|
+
// Alt-R means: run to diagnose infeasible/unbounded problem.
|
2299
|
+
VM.solveModel(true);
|
2300
|
+
} else if(alt && ['KeyC', 'KeyM'].indexOf(code) >= 0) {
|
2301
|
+
// Special shortcut keys for "clone selection" and "model settings".
|
2283
2302
|
const be = new Event('click');
|
2284
|
-
|
2285
|
-
if(e.keyCode === 67) {
|
2303
|
+
if(code === 'KeyC') {
|
2286
2304
|
this.buttons.clone.dispatchEvent(be);
|
2287
2305
|
} else {
|
2288
2306
|
this.buttons.settings.dispatchEvent(be);
|
2289
2307
|
}
|
2290
|
-
} else if(!e.shiftKey && !
|
2291
|
-
(!topmod || [
|
2292
|
-
// Interpret special keys as shortcuts unless a modal dialog is open
|
2293
|
-
if(
|
2294
|
-
// DEL button => delete selection
|
2308
|
+
} else if(!e.shiftKey && !alt &&
|
2309
|
+
(!topmod || ['KeyA', 'KeyC', 'KeyV'].indexOf(code) < 0)) {
|
2310
|
+
// Interpret special keys as shortcuts unless a modal dialog is open.
|
2311
|
+
if(code === 'Delete') {
|
2312
|
+
// DEL button => delete selection.
|
2295
2313
|
e.preventDefault();
|
2296
2314
|
if(!this.hidden('constraint-modal')) {
|
2297
2315
|
CONSTRAINT_EDITOR.deleteBoundLine();
|
@@ -2300,18 +2318,18 @@ class GUIController extends Controller {
|
|
2300
2318
|
} else {
|
2301
2319
|
this.buttons['delete'].dispatchEvent(new Event('click'));
|
2302
2320
|
}
|
2303
|
-
} else if (
|
2304
|
-
// Ctrl-. (dot) moves entire diagram to upper-left corner
|
2321
|
+
} else if (code === 'Period' && (e.ctrlKey || e.metaKey)) {
|
2322
|
+
// Ctrl-. (dot) moves entire diagram to upper-left corner.
|
2305
2323
|
e.preventDefault();
|
2306
2324
|
this.paper.fitToSize();
|
2307
2325
|
MODEL.alignToGrid();
|
2308
|
-
} else if (
|
2309
|
-
// ALWAYS prevent browser to do respond to Ctrl-letter commands
|
2310
|
-
// NOTE:
|
2326
|
+
} else if (code >= 'KeyA' && code <= 'KeyZ' && (e.ctrlKey || e.metaKey)) {
|
2327
|
+
// ALWAYS prevent browser to do respond to Ctrl-letter commands.
|
2328
|
+
// NOTE: This cannot prevent a new tab from opening on Ctrl-T.
|
2311
2329
|
e.preventDefault();
|
2312
|
-
let shortcut =
|
2330
|
+
let shortcut = code.substring(3);
|
2313
2331
|
if(shortcut === 'Z' && e.shiftKey) {
|
2314
|
-
// Interpret Shift-Ctrl-Z as Ctrl-Y (redo last undone operation)
|
2332
|
+
// Interpret Shift-Ctrl-Z as Ctrl-Y (redo last undone operation).
|
2315
2333
|
shortcut = 'Y';
|
2316
2334
|
}
|
2317
2335
|
if(this.shortcuts.hasOwnProperty(shortcut)) {
|
@@ -2662,15 +2680,22 @@ class GUIController extends Controller {
|
|
2662
2680
|
//
|
2663
2681
|
// Informing the modeler via the status line
|
2664
2682
|
//
|
2665
|
-
|
2683
|
+
|
2684
|
+
clearStatusLine() {
|
2685
|
+
// Clear message on the status line.
|
2686
|
+
this.info_line.innerHTML = '';
|
2687
|
+
UI.info_line.classList.remove(...UI.info_line.classList);
|
2688
|
+
}
|
2689
|
+
|
2666
2690
|
setMessage(msg, type=null) {
|
2667
|
-
//
|
2668
|
-
// info, warning or error message is already displayed
|
2691
|
+
// Display `msg` on infoline unless no type (= plain text) and some
|
2692
|
+
// info, warning or error message is already displayed.
|
2669
2693
|
super.setMessage(msg, type);
|
2670
2694
|
const types = ['notification', 'warning', 'error'];
|
2671
2695
|
let d = new Date(),
|
2672
2696
|
t = d.getTime(),
|
2673
|
-
dt = t - this.time_last_message,
|
2697
|
+
dt = t - this.time_last_message, // Time since display
|
2698
|
+
rt = this.message_display_time - dt, // Time remaining
|
2674
2699
|
mti = types.indexOf(type),
|
2675
2700
|
lmti = types.indexOf(this.last_message_type);
|
2676
2701
|
if(type) {
|
@@ -2683,18 +2708,36 @@ class GUIController extends Controller {
|
|
2683
2708
|
// When receiver is active, add message to its log.
|
2684
2709
|
if(RECEIVER.active) RECEIVER.log(`[${now}] ${msg}`);
|
2685
2710
|
}
|
2686
|
-
|
2687
|
-
|
2688
|
-
|
2711
|
+
if(mti === 1 && lmti === 2 && rt > 0) {
|
2712
|
+
// Queue warnings if an error message is still being displayed.
|
2713
|
+
setTimeout(() => {
|
2714
|
+
UI.info_line.innerHTML = msg;
|
2715
|
+
UI.info_line.classList.remove(...UI.info_line.classList);
|
2716
|
+
if(type) UI.info_line.classList.add(type);
|
2717
|
+
UI.updateIssuePanel();
|
2718
|
+
}, rt);
|
2719
|
+
} else if(lmti < 0 || mti > lmti || rt <= 0) {
|
2720
|
+
// Display text only if previous message has "timed out" or was less
|
2721
|
+
// urgent than this one.
|
2722
|
+
const override = mti === 2 && lmti === 1 && rt > 0;
|
2689
2723
|
this.time_last_message = t;
|
2690
2724
|
this.last_message_type = type;
|
2691
2725
|
if(type) SOUNDS[type].play().catch(() => {
|
2692
2726
|
console.log('NOTICE: Sounds will only play after first user action');
|
2693
2727
|
});
|
2694
|
-
|
2695
|
-
|
2696
|
-
|
2697
|
-
|
2728
|
+
if(override && !this.old_info_line) {
|
2729
|
+
// Set time-out to restore overridden warning.
|
2730
|
+
this.old_info_line = {msg: this.info_line.innerHTML, status: types[lmti]};
|
2731
|
+
setTimeout(() => {
|
2732
|
+
UI.info_line.innerHTML = UI.old_info_line.msg;
|
2733
|
+
UI.info_line.classList.add(UI.old_info_line.status);
|
2734
|
+
UI.old_info_line = null;
|
2735
|
+
UI.updateIssuePanel();
|
2736
|
+
}, this.message_display_time);
|
2737
|
+
}
|
2738
|
+
UI.info_line.classList.remove(...UI.info_line.classList);
|
2739
|
+
if(type) UI.info_line.classList.add(type);
|
2740
|
+
UI.info_line.innerHTML = msg;
|
2698
2741
|
}
|
2699
2742
|
}
|
2700
2743
|
|
@@ -2846,11 +2889,15 @@ class GUIController extends Controller {
|
|
2846
2889
|
}
|
2847
2890
|
}
|
2848
2891
|
} else if(type === 'process' || type === 'product') {
|
2892
|
+
/* NOT CLEAR WHAT THIS CODE DOES, SO DISABLE IT
|
2849
2893
|
if(this.dbl_clicked_node) {
|
2850
2894
|
n = this.dbl_clicked_node;
|
2851
2895
|
md = this.modals['add-' + type];
|
2852
2896
|
this.dbl_clicked_node = null;
|
2853
2897
|
} else {
|
2898
|
+
*/
|
2899
|
+
if(true) { // added line
|
2900
|
+
this.dbl_clicked_node = null; // added line
|
2854
2901
|
if(type === 'process') {
|
2855
2902
|
md = this.modals['add-process'];
|
2856
2903
|
nn = md.element('name').value;
|
@@ -2904,7 +2951,7 @@ class GUIController extends Controller {
|
|
2904
2951
|
UNDO_STACK.pop();
|
2905
2952
|
return false;
|
2906
2953
|
}
|
2907
|
-
// NOTE:
|
2954
|
+
// NOTE: Pre-check if product exists.
|
2908
2955
|
const pp = MODEL.objectByName(nn);
|
2909
2956
|
n = MODEL.addProduct(nn);
|
2910
2957
|
if(n) {
|
@@ -2925,7 +2972,7 @@ class GUIController extends Controller {
|
|
2925
2972
|
}
|
2926
2973
|
if(n) {
|
2927
2974
|
// If process, and X and Y are set, it exists; then if not in the
|
2928
|
-
// focal cluster, ask whether to move it there
|
2975
|
+
// focal cluster, ask whether to move it there.
|
2929
2976
|
if(n instanceof Process && (n.x !== 0 || n.y !== 0)) {
|
2930
2977
|
if(n.cluster !== MODEL.focal_cluster) {
|
2931
2978
|
this.confirmToMoveNode(n);
|
@@ -2943,9 +2990,9 @@ class GUIController extends Controller {
|
|
2943
2990
|
MODEL.inferIgnoredEntities();
|
2944
2991
|
if(n) {
|
2945
2992
|
md.hide();
|
2946
|
-
// Select the newly added entity
|
2947
|
-
// NOTE: If the focal cluster was selected (via the top tool bar),
|
2948
|
-
// cannot be selected
|
2993
|
+
// Select the newly added entity.
|
2994
|
+
// NOTE: If the focal cluster was selected (via the top tool bar),
|
2995
|
+
// it cannot be selected.
|
2949
2996
|
if(n !== MODEL.focal_cluster) this.selectNode(n);
|
2950
2997
|
}
|
2951
2998
|
}
|
@@ -3503,12 +3550,13 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3503
3550
|
md.element('block-length').value = model.block_length;
|
3504
3551
|
md.element('look-ahead').value = model.look_ahead;
|
3505
3552
|
md.element('time-limit').value = model.timeout_period;
|
3506
|
-
this.setBox('settings-encrypt', model.encrypt);
|
3507
3553
|
this.setBox('settings-decimal-comma', model.decimal_comma);
|
3508
3554
|
this.setBox('settings-align-to-grid', model.align_to_grid);
|
3555
|
+
this.setBox('settings-block-arrows', model.show_block_arrows);
|
3556
|
+
this.setBox('settings-diagnose', MODEL.always_diagnose);
|
3509
3557
|
this.setBox('settings-cost-prices', model.infer_cost_prices);
|
3510
3558
|
this.setBox('settings-report-results', model.report_results);
|
3511
|
-
this.setBox('settings-
|
3559
|
+
this.setBox('settings-encrypt', model.encrypt);
|
3512
3560
|
md.show('name');
|
3513
3561
|
}
|
3514
3562
|
|
@@ -3563,6 +3611,12 @@ console.log('HERE name conflicts', name_conflicts, mapping);
|
|
3563
3611
|
model.report_results = UI.boxChecked('settings-report-results');
|
3564
3612
|
model.encrypt = UI.boxChecked('settings-encrypt');
|
3565
3613
|
model.decimal_comma = UI.boxChecked('settings-decimal-comma');
|
3614
|
+
MODEL.always_diagnose = this.boxChecked('settings-diagnose');
|
3615
|
+
// Notify modeler that diagnosis changes the value of +INF.
|
3616
|
+
if(MODEL.always_diagnose) {
|
3617
|
+
UI.notify('To diagnose unbounded problems, values beyond 1e+10 ' +
|
3618
|
+
'are considered as infinite (\u221E)');
|
3619
|
+
}
|
3566
3620
|
// Some changes may necessitate redrawing the diagram
|
3567
3621
|
let cb = UI.boxChecked('settings-align-to-grid'),
|
3568
3622
|
redraw = !model.align_to_grid && cb;
|