Skip to content

Commit

Permalink
Merge pull request #643 from ICRAR/GR2-shiftSelect
Browse files Browse the repository at this point in the history
Gr2 shift select
  • Loading branch information
M-Wicenec authored Jan 24, 2024
2 parents 4cfe183 + fd60ffc commit 1102a2f
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 18 deletions.
249 changes: 232 additions & 17 deletions src/GraphRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@ export class GraphRenderer {
static dragSelectionHandled : any = false
static dragSelectionDoubleClick :boolean = false;

//drag selection region globals
static isDraggingSelectionRegion :boolean = false;
static selectionRegionStart = {x:0, y:0};
static selectionRegionEnd = {x:0, y:0};

static mousePosX : ko.Observable<number> = ko.observable(-1);
static mousePosY : ko.Observable<number> = ko.observable(-1);
static legacyGraph : boolean = false; //used for marking a graph when its nodes don't have a radius set. in this case we will do some conversion
Expand Down Expand Up @@ -640,8 +645,8 @@ export class GraphRenderer {
}

//select handlers
if(node !== null && event.which != 2){
// check if shift key is down, if so, add or remove selected node to/from current selection
if(node !== null && event.which != 2 && !event.shiftKey){
// check if shift key is down, if so, add or remove selected node to/from current selection | keycode 2 is the middle mouse button
if (node !== null && event.shiftKey && !event.altKey){
GraphRenderer.dragSelectionHandled = true
eagle.editSelection(Eagle.RightWindowMode.Inspector, node, Eagle.FileType.Graph);
Expand Down Expand Up @@ -685,8 +690,27 @@ export class GraphRenderer {
}
}
}else{
//if node is null, the empty canvas has been clicked. clear the selection
eagle.setSelection(Eagle.RightWindowMode.Inspector, null, Eagle.FileType.Graph);
if(event.shiftKey){
//drag selection region handler
GraphRenderer.isDraggingSelectionRegion = true
GraphRenderer.selectionRegionStart = {x:GraphRenderer.SCREEN_TO_GRAPH_POSITION_X(),y:GraphRenderer.SCREEN_TO_GRAPH_POSITION_Y()}
GraphRenderer.selectionRegionEnd = {x:GraphRenderer.SCREEN_TO_GRAPH_POSITION_X(),y:GraphRenderer.SCREEN_TO_GRAPH_POSITION_Y()}
//making the selection box visible
$('#selectionRectangle').show()

//setting start and end region to current mouse co-ordinates
$('#selectionRectangle').css({'left':GraphRenderer.selectionRegionStart.x+'px','top':GraphRenderer.selectionRegionStart.y+'px'})

const containerWidth = $('#logicalGraphD3Div').width()
const containerHeight = $('#logicalGraphD3Div').height()
const selectionBottomOffset = containerHeight - GraphRenderer.selectionRegionEnd.y
const selectionRightOffset = containerWidth - GraphRenderer.selectionRegionEnd.x
$('#selectionRectangle').css({'right':selectionRightOffset+'px','bottom':selectionBottomOffset+'px'})
}else{
//if node is null, the empty canvas has been clicked. clear the selection
eagle.setSelection(Eagle.RightWindowMode.Inspector, null, Eagle.FileType.Graph);

}
}

//this is the timeout for the double click that is used to select the children of constructs
Expand All @@ -700,7 +724,7 @@ export class GraphRenderer {
const mouseEvent: MouseEvent = <MouseEvent>event.originalEvent;
GraphRenderer.dragCurrentPosition = {x:event.pageX,y:event.pageY}
if (eagle.isDragging()){
if (eagle.draggingNode() !== null){
if (eagle.draggingNode() !== null && !GraphRenderer.isDraggingSelectionRegion ){
const node:Node = eagle.draggingNode()
$('.node.transition').removeClass('transition')

Expand Down Expand Up @@ -755,7 +779,24 @@ export class GraphRenderer {
$('#'+parent.getId()).removeClass('transition')
}

} else {
} else if(GraphRenderer.isDraggingSelectionRegion){
GraphRenderer.selectionRegionEnd = {x:GraphRenderer.SCREEN_TO_GRAPH_POSITION_X(), y:this.SCREEN_TO_GRAPH_POSITION_Y()}
const containerWidth = $('#logicalGraphD3Div').width()
const containerHeight = $('#logicalGraphD3Div').height()

if(GraphRenderer.selectionRegionEnd.x>GraphRenderer.selectionRegionStart.x){
$('#selectionRectangle').css({'left':GraphRenderer.selectionRegionStart.x+'px','right':containerWidth - GraphRenderer.selectionRegionEnd.x+'px'})
}else{
$('#selectionRectangle').css({'left':GraphRenderer.selectionRegionEnd.x+'px','right':containerWidth - GraphRenderer.selectionRegionStart.x+'px'})
}

if(GraphRenderer.selectionRegionEnd.y>GraphRenderer.selectionRegionStart.y){
$('#selectionRectangle').css({'top':GraphRenderer.selectionRegionStart.y+'px','bottom':containerHeight - GraphRenderer.selectionRegionEnd.y+'px'})
}else{
$('#selectionRectangle').css({'top':GraphRenderer.selectionRegionEnd.y+'px','bottom':containerHeight - GraphRenderer.selectionRegionStart.y+'px'})
}

}else{
// move background
eagle.globalOffsetX(eagle.globalOffsetX() + mouseEvent.movementX/eagle.globalScale());
eagle.globalOffsetY(eagle.globalOffsetY() + mouseEvent.movementY/eagle.globalScale());
Expand All @@ -770,25 +811,199 @@ export class GraphRenderer {

static endDrag = (node: Node, event: any) : void => {
const eagle = Eagle.getInstance();

// if we dragged a selection region
if (GraphRenderer.isDraggingSelectionRegion){
const nodes: Node[] = GraphRenderer.findNodesInRegion(GraphRenderer.selectionRegionStart.x, GraphRenderer.selectionRegionEnd.x, GraphRenderer.selectionRegionStart.y, GraphRenderer.selectionRegionEnd.y);

//checking if there was no drag distance, if so we are clicking a single object and we will toggle its seletion
if(Math.abs(GraphRenderer.selectionRegionStart.x-GraphRenderer.selectionRegionEnd.x)+Math.abs(GraphRenderer.selectionRegionStart.y - GraphRenderer.selectionRegionEnd.y)<3){
eagle.editSelection(Eagle.RightWindowMode.Inspector, node,Eagle.FileType.Graph);
}else{
const edges: Edge[] = GraphRenderer.findEdgesContainedByNodes(eagle.logicalGraph().getEdges(), nodes);
console.log("Found", nodes.length, "nodes and", edges.length, "edges in region");
const objects: (Node | Edge)[] = [];

// only add those objects which are not already selected
for (const node of nodes){
if (!eagle.objectIsSelected(node)){
objects.push(node);
}
}
for (const edge of edges){
if (!eagle.objectIsSelected(edge)){
objects.push(edge);
}
}

objects.forEach(function(element){
eagle.editSelection(Eagle.RightWindowMode.Hierarchy, element, Eagle.FileType.Graph )
})
}


// if (isDraggingWithAlt){
// for (const node of nodes){
// node.setCollapsed(false);
// }
// }

GraphRenderer.selectionRegionStart.x = 0;
GraphRenderer.selectionRegionStart.y = 0;
GraphRenderer.selectionRegionEnd.x = 0;
GraphRenderer.selectionRegionEnd.y = 0;

// finish selecting a region
GraphRenderer.isDraggingSelectionRegion = false;

//hide the selection rectangle
$('#selectionRectangle').hide()

// necessary to make uncollapsed nodes show up
eagle.logicalGraph.valueHasMutated();
}else{
if(node != null){
if(!GraphRenderer.dragSelectionHandled){
const distanceMovedX = Math.abs(GraphRenderer.dragStartPosition.x-GraphRenderer.dragCurrentPosition.x)
const distanceMovedY = Math.abs(GraphRenderer.dragStartPosition.y-GraphRenderer.dragCurrentPosition.y)

if(distanceMovedX<5 || distanceMovedY<5){
eagle.setSelection(null, node,Eagle.FileType.Graph)
}
}
}

if (node != null && node.getParentKey() != null){
const parentNode = eagle.logicalGraph().findNodeByKeyQuiet(node.getParentKey())
}
}

//console.log("endDrag", node ? node.getName() : node)
eagle.isDragging(false);
eagle.draggingNode(null);

if(node != null){
if(!GraphRenderer.dragSelectionHandled){
const distanceMovedX = Math.abs(GraphRenderer.dragStartPosition.x-GraphRenderer.dragCurrentPosition.x)
const distanceMovedY = Math.abs(GraphRenderer.dragStartPosition.y-GraphRenderer.dragCurrentPosition.y)
eagle.draggingNode(null)

if(distanceMovedX<5 || distanceMovedY<5){
eagle.setSelection(null, node,Eagle.FileType.Graph)
}

static findNodesInRegion(left: number, right: number, top: number, bottom: number): Node[] {
const eagle = Eagle.getInstance();
const result: Node[] = [];
const nodeData : Node[] = GraphRenderer.depthFirstTraversalOfNodes(eagle.logicalGraph(), eagle.showDataNodes());

// re-assign left, right, top, bottom in case selection region was not dragged in the typical NW->SE direction
const realLeft = left <= right ? left : right;
const realRight = left <= right ? right : left;
const realTop = top <= bottom ? top : bottom;
const realBottom = top <= bottom ? bottom : top;

for (let i = nodeData.length - 1; i >= 0 ; i--){
const node : Node = nodeData[i];

// use center of node as position
const centerX : number = node.getPosition().x
const centerY : number = node.getPosition().y
const nodeRadius : number = node.getRadius()

//checking if the node is fully inside the selection box
if (centerX+-nodeRadius >= realLeft && realRight+-nodeRadius >= centerX && centerY+-nodeRadius >= realTop && realBottom+-nodeRadius >= centerY){
result.push(node);
}
}

return result;
}



static getEdges(graph: LogicalGraph, showDataNodes: boolean): Edge[]{
if (showDataNodes){
return graph.getEdges();
} else {
//return [graph.getEdges()[0]];
const edges: Edge[] = [];

for (const edge of graph.getEdges()){
let srcHasConnectedInput: boolean = false;
let destHasConnectedOutput: boolean = false;

for (const e of graph.getEdges()){
if (e.getDestNodeKey() === edge.getSrcNodeKey()){
srcHasConnectedInput = true;
}
if (e.getSrcNodeKey() === edge.getDestNodeKey()){
destHasConnectedOutput = true;
}
}

const srcIsDataNode: boolean = GraphRenderer.findNodeWithKey(edge.getSrcNodeKey(), graph.getNodes()).isData();
const destIsDataNode: boolean = GraphRenderer.findNodeWithKey(edge.getDestNodeKey(), graph.getNodes()).isData();
//console.log("edge", edge.getId(), "srcIsDataNode", srcIsDataNode, "srcHasConnectedInput", srcHasConnectedInput, "destIsDataNode", destIsDataNode, "destHasConnectedOutput", destHasConnectedOutput);

if (destIsDataNode){
if (!destHasConnectedOutput){
// draw edge as normal
edges.push(edge);
}
continue;
}

if (srcIsDataNode){
if (srcHasConnectedInput){
// build a new edge
const newSrc = GraphRenderer.findInputToDataNode(graph.getEdges(), edge.getSrcNodeKey());
edges.push(new Edge(newSrc.nodeKey, newSrc.portId, edge.getDestNodeKey(), edge.getDestPortId(), edge.getDataType(), edge.isLoopAware(), edge.isClosesLoop(), false));
} else {
// draw edge as normal
edges.push(edge);
}
}
}

return edges;
}
}

static findEdgesContainedByNodes(edges: Edge[], nodes: Node[]): Edge[]{
const result: Edge[] = [];

for (const edge of edges){
const srcKey = edge.getSrcNodeKey();
const destKey = edge.getDestNodeKey();
let srcFound = false;
let destFound = false;

for (const node of nodes){
if ((node.getKey() === srcKey) ||
(node.hasInputApplication() && node.getInputApplication().getKey() === srcKey) ||
(node.hasOutputApplication() && node.getOutputApplication().getKey() === srcKey)){
srcFound = true;
}

if ((node.getKey() === destKey) ||
(node.hasInputApplication() && node.getInputApplication().getKey() === destKey) ||
(node.hasOutputApplication() && node.getOutputApplication().getKey() === destKey)){
destFound = true;
}
}

if (srcFound && destFound){
result.push(edge);
}
}

if (node != null && node.getParentKey() != null){
const parentNode = eagle.logicalGraph().findNodeByKeyQuiet(node.getParentKey())
return result;
}


static findInputToDataNode(edges: Edge[], nodeKey: number) : {nodeKey:number, portId: string}{
for (const edge of edges){
if (edge.getDestNodeKey() === nodeKey){
return {
nodeKey: edge.getSrcNodeKey(),
portId: edge.getSrcPortId()
};
}
}

return null;
}

static centerConstructs = (construct:Node, graphNodes:Node[]) :void => {
Expand Down
7 changes: 6 additions & 1 deletion static/graph.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
-webkit-transform-origin: 0 0;
}

#selectionRectangle{
border:1px black solid;
position: absolute;
display: none;
}

#logicalGraphD3Div svg{
position:absolute;
z-index: 2;
Expand Down Expand Up @@ -50,7 +56,6 @@
pointer-events: all;
}


.node .container, .constructOutputApp, .constructInputApp{
z-index: 5;
pointer-events: all;
Expand Down
1 change: 1 addition & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
<!-- /ko -->
</svg>
<!-- /ko -->
<div id="selectionRectangle"></div>
</div>

<!-- ko if: logicalGraph === null || logicalGraph() === null || logicalGraph().getNodes().length === 0 -->
Expand Down

0 comments on commit 1102a2f

Please sign in to comment.