From f9d917bc4415c6878c32f08b1a721b4c343bd4f6 Mon Sep 17 00:00:00 2001
From: Valentin Perrelle <valentin.perrelle@cea.fr>
Date: Mon, 12 Oct 2020 16:04:41 +0200
Subject: [PATCH] [dive] zoom widget for cytoscape and zoom by double clicking
 a compound node

---
 ivette/package.json                       |  1 +
 ivette/src/frama-c/dive/Dive.tsx          | 30 +++++++++++++++++++++-
 ivette/src/frama-c/dive/cytoscape_libs.js | 31 +++++++++++++----------
 ivette/yarn.lock                          | 12 +++++++++
 4 files changed, 60 insertions(+), 14 deletions(-)

diff --git a/ivette/package.json b/ivette/package.json
index 3dacbd3f46b..f6cd03b5b37 100644
--- a/ivette/package.json
+++ b/ivette/package.json
@@ -62,6 +62,7 @@
     "cytoscape-cxtmenu": "^3.1.2",
     "cytoscape-dagre": "^2.2.2",
     "cytoscape-klay": "^3.1.3",
+    "cytoscape-panzoom": "^2.5.3",
     "cytoscape-popper": "^1.0.7",
     "immutable": "^4.0.0-rc.12",
     "lodash": "^4.17.15",
diff --git a/ivette/src/frama-c/dive/Dive.tsx b/ivette/src/frama-c/dive/Dive.tsx
index c0480f5b4b5..e77030f430c 100644
--- a/ivette/src/frama-c/dive/Dive.tsx
+++ b/ivette/src/frama-c/dive/Dive.tsx
@@ -10,6 +10,7 @@ import * as API from 'api/plugins/dive';
 import Cytoscape from 'cytoscape';
 import CytoscapeComponent from 'react-cytoscapejs';
 import './cytoscape_libs';
+import 'cytoscape-panzoom/cytoscape.js-panzoom.css';
 
 import tippy, * as Tippy from 'tippy.js';
 import 'tippy.js/dist/tippy.css';
@@ -34,6 +35,7 @@ interface Cxtcommand {
 
 interface CytoscapeExtended extends Cytoscape.Core {
   cxtmenu(options: any): void;
+  panzoom(options: any): void;
 }
 
 function callstackToString(callstack: API.callstack): string {
@@ -52,6 +54,19 @@ function buildCxtMenu(
   });
 }
 
+/* double click events for Cytoscape */
+
+function enableDoubleClickEvents(cy: Cytoscape.Core, delay = 350) {
+  let last: Cytoscape.EventObject | undefined;
+  cy.on('click', (e) => {
+    if (last && last.target === e.target &&
+      e.timeStamp - last.timeStamp < delay) {
+      e.target.trigger('double-click', e);
+    }
+    last = e;
+  });
+}
+
 /* The Dive class handles the selection of nodes according to user actions.
    To prevent cytoscape to automatically select (and unselect) nodes wrongly,
    we make some nodes unselectable. We then use the functions below to make
@@ -83,8 +98,17 @@ class Dive {
     this.cy = cy || Cytoscape();
     this.headless = this.cy.container() === null;
     this.cy.elements().remove();
-    this.cy.off('click'); // Remove previous listeners
+
+    // Remove previous listeners
+    this.cy.off('click');
+    this.cy.off('double-click');
+
+    // Add new listeners
+    enableDoubleClickEvents(this.cy);
     this.cy.on('click', 'node', (event) => this.clickNode(event.target));
+    this.cy.on('double-click', '$node > node', // compound nodes
+      (event) => this.doubleClickNode(event.target));
+    (this.cy as CytoscapeExtended).panzoom({});
 
     this.layout = 'cose-bilkent';
 
@@ -436,6 +460,10 @@ class Dive {
     node.unselectify();
   }
 
+  doubleClickNode(node: Cytoscape.NodeSingular) {
+    this.cy.animate({ fit: { eles: node, padding: 10 } });
+  }
+
   selectLocation(location: States.Location | undefined, doExplore: boolean) {
     if (!location) {
       // Reset whole graph if no location is selected.
diff --git a/ivette/src/frama-c/dive/cytoscape_libs.js b/ivette/src/frama-c/dive/cytoscape_libs.js
index 0bd1aced540..35ecb1b1ffd 100644
--- a/ivette/src/frama-c/dive/cytoscape_libs.js
+++ b/ivette/src/frama-c/dive/cytoscape_libs.js
@@ -2,18 +2,23 @@
 This prevents Hot Module Reloading for modules where Cytescope.use is used.
 Grouping all Cytoscape plugins registrations here solves the problem. */
 
-import Cytoscape from 'cytoscape' ;
+import Cytoscape from 'cytoscape';
 
-import CytoscapeMenu from 'cytoscape-cxtmenu';
-import CytoscapePopper from 'cytoscape-popper';
-import CytoscapeLayoutDagre from 'cytoscape-dagre';
-import CytoscapeLayoutCola from 'cytoscape-cola';
-import CytoscapeLayoutCoseBilkent from 'cytoscape-cose-bilkent';
-import CytoscapeLayoutKlay from 'cytoscape-klay';
+import CxtMenu from 'cytoscape-cxtmenu';
+import Popper from 'cytoscape-popper';
+import Panzoom from 'cytoscape-panzoom';
 
-Cytoscape.use(CytoscapePopper);
-Cytoscape.use(CytoscapeMenu);
-Cytoscape.use(CytoscapeLayoutDagre);
-Cytoscape.use(CytoscapeLayoutCola);
-Cytoscape.use(CytoscapeLayoutCoseBilkent);
-Cytoscape.use(CytoscapeLayoutKlay);
+// Layouts
+import Dagre from 'cytoscape-dagre';
+import Cola from 'cytoscape-cola';
+import CoseBilkent from 'cytoscape-cose-bilkent';
+import Klay from 'cytoscape-klay';
+
+Cytoscape.use(Popper);
+Cytoscape.use(CxtMenu);
+Panzoom(Cytoscape); // register extension
+
+Cytoscape.use(Dagre);
+Cytoscape.use(Cola);
+Cytoscape.use(CoseBilkent);
+Cytoscape.use(Klay);
diff --git a/ivette/yarn.lock b/ivette/yarn.lock
index 1402c44ae3b..e708d53b066 100644
--- a/ivette/yarn.lock
+++ b/ivette/yarn.lock
@@ -3008,6 +3008,13 @@ cytoscape-klay@^3.1.3:
   dependencies:
     klayjs "^0.4.1"
 
+cytoscape-panzoom@^2.5.3:
+  version "2.5.3"
+  resolved "https://registry.yarnpkg.com/cytoscape-panzoom/-/cytoscape-panzoom-2.5.3.tgz#edf041b5aa8be1cbe3c001f16a8b2193b46127a7"
+  integrity sha512-//qLOqbbFUCGddarNKHDZArItOJHgnkQ1TvxI9nV2/8aOOl/5wuEOHmra3fL/aWSjB4AYpYTG4LX7w96uWfRTQ==
+  dependencies:
+    jquery "^1.4 || ^2.0 || ^3.0"
+
 cytoscape-popper@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/cytoscape-popper/-/cytoscape-popper-1.0.7.tgz#8154ff507d0cc1a17952f00643e71fc1f8ea9fae"
@@ -5396,6 +5403,11 @@ jest-worker@^25.1.0:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
+"jquery@^1.4 || ^2.0 || ^3.0":
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5"
+  integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==
+
 js-base64@^2.1.9:
   version "2.5.2"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.2.tgz#313b6274dda718f714d00b3330bbae6e38e90209"
-- 
GitLab