From d9a1c8906f6260b8e11ab0ce684f1842f478c4e9 Mon Sep 17 00:00:00 2001 From: ghidravore Date: Mon, 2 Nov 2020 14:15:01 -0500 Subject: [PATCH] GP-310 - Graphing - Updated mouse handling to be consistent with other graph widgets. --- Ghidra/Features/Base/certification.manifest | 2 + .../Base/src/main/help/help/TOC_Source.xml | 4 +- .../help/help/topics/Graph/GraphIntro.html | 24 ++ .../help/topics/Graph/GraphServicesIntro.html | 25 ++ .../src/main/help/help/TOC_Source.xml | 8 +- .../topics/GraphServices/GraphDisplay.htm | 322 +++++++++++++----- .../help/topics/GraphServices/GraphExport.htm | 26 +- .../images/DefaultGraphDisplay.png | Bin 10078 -> 14713 bytes .../GraphServices/images/ExportDialog.png | Bin 3221 -> 8736 bytes .../ExportAttributedGraphDisplayProvider.java | 2 +- .../graph/export/GraphExporterDialog.java | 2 +- .../DefaultDisplayGraphIcons.java | 4 +- .../visualization/DefaultGraphDisplay.java | 200 +++++------ .../mouse/AbstractJgtGraphMousePlugin.java | 311 +++++++++++++++++ .../mouse/JgtCursorRestoringPlugin.java | 55 +++ .../mouse/JgtEdgeNavigationPlugin.java | 113 ++++++ .../mouse/JgtPluggableGraphMouse.java | 70 ++++ .../mouse/JgtTranslatingPlugin.java | 177 ++++++++++ .../JgtUtils.java} | 132 +++---- .../mouse/JgtVertexFocusingPlugin.java | 57 ++++ .../src/main/resources/jungrapht.properties | 7 + .../ProgramGraphPlugin/ProgramGraph.htm | 219 ++++++------ .../BlockModelGraphDisplayListener.java | 2 + .../ghidra/graph/viewer/GraphComponent.java | 9 +- .../VisualGraphAbstractGraphMousePlugin.java | 10 +- ...lGraphAnimatedPickingGraphMousePlugin.java | 7 +- ...lGraphCursorRestoringGraphMousePlugin.java | 7 + ...lGraphEventForwardingGraphMousePlugin.java | 12 +- .../mouse/VisualGraphHoverMousePlugin.java | 5 + ...ualGraphMouseTrackingGraphMousePlugin.java | 6 +- .../VisualGraphPickingGraphMousePlugin.java | 12 +- ...raphSatelliteAbstractGraphMousePlugin.java | 2 +- ...elliteAnimatedPickingGraphMousePlugin.java | 25 -- ...atelliteEdgeSelectionGraphMousePlugin.java | 25 -- ...phSatelliteNavigationGraphMousePlugin.java | 2 +- ...GraphSatelliteScalingGraphMousePlugin.java | 2 +- ...hSatelliteTranslatingGraphMousePlugin.java | 2 +- .../VisualGraphScalingGraphMousePlugin.java | 7 +- .../VisualGraphScreenPositioningPlugin.java | 10 +- .../VisualGraphScrollWheelPanningPlugin.java | 10 +- ...isualGraphTranslatingGraphMousePlugin.java | 14 +- ...alGraphZoomingPickingGraphMousePlugin.java | 6 +- .../screenshot/GraphServicesScreenShots.java | 7 +- 43 files changed, 1468 insertions(+), 474 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphIntro.html create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphServicesIntro.html create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtPluggableGraphMouse.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java rename Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/{GhidraGraphMouse.java => mouse/JgtUtils.java} (50%) create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java delete mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteAnimatedPickingGraphMousePlugin.java delete mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteEdgeSelectionGraphMousePlugin.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 386226b0b2..5ce773a23f 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -443,6 +443,8 @@ src/main/help/help/topics/GhidraServer/GhidraServer.htm||GHIDRA||||END| src/main/help/help/topics/Glossary/glossary.htm||GHIDRA||||END| src/main/help/help/topics/Glossary/images/BigEndian.png||GHIDRA||reviewed||END| src/main/help/help/topics/Glossary/images/LittleEndian.png||GHIDRA||reviewed||END| +src/main/help/help/topics/Graph/GraphIntro.html||GHIDRA||||END| +src/main/help/help/topics/Graph/GraphServicesIntro.html||GHIDRA||||END| src/main/help/help/topics/HeadlessAnalyzer/HeadlessAnalyzer.htm||GHIDRA||||END| src/main/help/help/topics/ImporterPlugin/images/About_pdb.png||GHIDRA||reviewed||END| src/main/help/help/topics/ImporterPlugin/images/BatchImportDialog.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml b/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml index 9a27608027..803c9e2aac 100644 --- a/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Features/Base/src/main/help/help/TOC_Source.xml @@ -150,7 +150,9 @@ - + + + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphIntro.html b/Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphIntro.html new file mode 100644 index 0000000000..21530cd2f8 --- /dev/null +++ b/Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphIntro.html @@ -0,0 +1,24 @@ + + + + + Graphing + + + + + + + +

Graphing

+ +
+

+ This section contains all help related to the creation and display of Graphs. Content will + appear inside of this section as plugins are added. To see the available graph features, + see the Graph menu on the toolbar. +

+
+ + + \ No newline at end of file diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphServicesIntro.html b/Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphServicesIntro.html new file mode 100644 index 0000000000..201f64dc74 --- /dev/null +++ b/Ghidra/Features/Base/src/main/help/help/topics/Graph/GraphServicesIntro.html @@ -0,0 +1,25 @@ + + + + + Graph Services + + + + + + + +

Graph Services

+ +
+

+ This section contains all help related to the graph services that provide capabilities for + generated graphs, such as displaying and exporting. Content will appear inside of this ' + section as plugins are added. To see the available graph features, + see the Graph menu on the toolbar. +

+
+ + + \ No newline at end of file diff --git a/Ghidra/Features/GraphServices/src/main/help/help/TOC_Source.xml b/Ghidra/Features/GraphServices/src/main/help/help/TOC_Source.xml index 372d07792a..4bc4df43ef 100644 --- a/Ghidra/Features/GraphServices/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Features/GraphServices/src/main/help/help/TOC_Source.xml @@ -50,11 +50,9 @@ - - - - - + + + diff --git a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm index 651c7b122c..ea093ef790 100644 --- a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm +++ b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm @@ -5,103 +5,245 @@ - Graphing + Graph Display - + + +

Default Graph Display

-

Visualization of a Graph

-
-

The visualization display will show the graph in a new window or in a new tab of a previously created graph window.

-
-
-

-
-
-
-

Manipulating the Graph:

-
    -
  • MouseButton1+drag will translate the display in the x and y axis
  • -
  • Mouse Wheel will zoom in and out
  • -
  • CTRL+Mouse Wheel will zoom in and out in the X-Axis only
  • -
  • ALT+Mouse Wheel will zoom in and out in the Y-Axis only
  • -
  • Ctrl+MouseButton1 will select a vertex or edge
  • -
      -
    • Shift+Ctrl+MouseButton1 over an unselected vertex will add that vertex to the selection
    • -
    • Shift+Ctrl+MouseButton1 over a previously selected vertex will remove that vertex from the selection
    • -
    -
  • Ctrl+MouseButton1+drag on an empty area will create a rectangular area and select enclosed vertices
  • -
  • Ctrl+MouseButton1+drag over a vertex will reposition all selected vertices
  • -
-

Toolbar Buttons

-

The toggle button, when 'set' will cause a focused vertex (red arrow) to be scrolled to the center of the view

-

The toggle button, when 'set' will allow the user to draw a free-form shape that encloses the vertices they wish to select.

-

The toggle button, when 'set' will open a satellite mini view of the graph in the lower right corner. The mini-view can be manipulated with the mouse to affect the main view

-

The button will reset any visual transformations on the graph and center it at a best-effort size

-

The toggle button, when 'set' will open a rectangular magnification lens in the graph view

-
-
    -
  • MouseButton1 click-drag on the lens center circle to move the magnifier lens
  • -
  • MouseButton1 click-draw on a lens edge diamond to resize the magnifier lens
  • -
  • MouseButton1 click on the upper-right circle-cross to dispose of the magnifier lens
  • -
  • MouseWheel will change the magnification of the lens
  • -
-
- -

The button will open a Filter dialog. Select buttons in the dialog to hide specific vertices or edges in the display. - The Filter dialog buttons are created by examining the graph vertex/edge properties to discover candidates for filtering.

- -

The Arrangement menu is used to select one of several graph layout algorithms.

-
+
+

The visualization display will show the graph in a new window or in a new tab of a + previously created graph window.

-
-
-

Popup Actions

-
-

Standard Popup Actions

-
-

Vertex Popup Actions

-
- -

Edge Popup Actions

-
+
+ + + + +
+
+ +

Manipulating the Graph

+ +
    +
  • Dragging in the graph or on any unselected vertices will pan the graph (translate the + display in the x and y axis)
  • + +
  • Dragging a selected vertex will reposition all selected vertices
  • + +
  • Using the Mouse Wheel will zoom the graph in and out
  • + +
  • Control+Mouse Wheel will zoom the graph in and out on the X-Axis only
  • + +
  • ALT+Mouse Wheel will zoom the graph in and out in the Y-Axis only
  • + +
  • Ctrl+Click will select a vertex +
      +
    • Ctrl+Click over an unselected vertex will add that vertex to the + selection
    • + +
    • Ctrl+Click over a previously selected vertex will remove that vertex + from the selection
    • +
    +
  • + +
  • Ctrl+drag on an empty area will create a rectangular area and select + enclosed vertices
  • + + +
+ +

Toolbar Buttons

+ +

+ The toggle button, when 'set' will cause a focused + vertex (the vertex with the red arrow) to be moved to the center of the view

+ +

+ The toggle button, when 'set' will + allow the user to draw a free-form shape that encloses the vertices they wish to select.

+ +

+ The toggle button, + when 'set' will open a satellite mini view of the graph in the lower right corner. The + mini-view can be manipulated with the mouse to affect the main view

+ +

+ The button will reset any visual transformations on the + graph and center it at a best-effort size

+ +

+ The toggle button, when 'set' will open a rectangular + magnification lens in the graph view

+ +
+
+
    +
  • MouseButton1 click-drag on the lens center circle to move the magnifier lens
  • + +
  • MouseButton1 click-draw on a lens edge diamond to resize the magnifier lens
  • + +
  • MouseButton1 click on the upper-right circle-cross to dispose of the magnifier + lens
  • + +
  • MouseWheel will change the magnification of the lens
  • +
+
+
+ +

+ The button will open a Filter dialog. Select + buttons in the dialog to hide specific vertices or edges in the display. The Filter dialog + buttons are created by examining the graph vertex/edge properties to discover candidates for + filtering.

+ +

+ The Arrangement menu is used to + select one of several graph layout algorithms.

+ +
+
+ +
+
+ +

Popup Actions

+ +
+

Standard Popup Actions

+ +
+ +

Vertex Popup Actions

+ +
+ +

Edge Popup Actions

+ +
+
+ +

Provided By:  GraphDisplayBrokerPlugin

+ +

Related Topics:

+ +

+
- \ No newline at end of file + diff --git a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphExport.htm b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphExport.htm index d6080a0adc..e0e6a5d3b3 100644 --- a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphExport.htm +++ b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphExport.htm @@ -11,19 +11,22 @@ - +

Graph Export Service

Export Dialog

Whenever a graph is generated and the graph output is set to Graph Export, then the following graph export dialog is displayed:


-
-
-

-
-
+
+ + + + +
+

The Export Graph dialog offers a choice of the following graph formats:

@@ -49,5 +52,16 @@

The Ok button will marshal the graph to the selected file in the selected format and close the dialog.

The Cancel button will close the dialog and perform no other action.

+ + +

Provided By:  GraphDisplayBrokerPlugin

+ +

Related Topics:

+
+
+
+ \ No newline at end of file diff --git a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/images/DefaultGraphDisplay.png b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/images/DefaultGraphDisplay.png index e0b1d7c5ea53d3dad718dd15859c29d0eecf0756..c39a2cdf1e0bb919d06d2480d0f5da1c6554139d 100644 GIT binary patch literal 14713 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�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 RTfjab1F)%H`K`a6{xABkj_m*d literal 10078 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 Vi(%G7#k)Dcr3&^z;`CsZFsbc^D diff --git a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/images/ExportDialog.png b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/images/ExportDialog.png index 7bf7f1a8a2d70a0eb7c672406e3f8ca6645bbc19..089efa0baa2231f3f4b49af6b61b745e0ea0f7dc 100644 GIT binary patch literal 8736 zcmb`Nbx>Sev*;m#5Zv9J00RVf_eqe!-CYvg-CY8MThQP(xFisCg1ft0aEHgqIp6p0 ztNLEux>fg|U8`z$tzO-|``2B)SA>#+3@Q>45)2Fss+_DO2nGh0{bl{*?W>n(_f%N| z3=HLtoTQklyZ%uYqUOgv;_p>8kf>S|E%9HowF*Y!R~R9ZTJn^*$&oaSW-&^PG!eFi zAng!&H0oo%ka zn$q3|vIW;48iV(DBVLwBlUQN|2-Xo4(XSzr7`*}7RBty#0hp5gmL-a=S;9Qx8X|h1 z-eFYcpRoIo!R-M=US!{X`Lbg|z0UEgB5PO8IBI@?gIcxVrBy_)Xs)8JFIRa`5q3Ca_C_h29|i!xeh0qoV+?QzdNs)(rKwcA&c! zC*`eZvcLIP@W-76YraS)l?y2V{f8`>5^)T?3~3VEC5Q0kR1w=Rm==IF?qt8L4?6Yr z!-b$Y{27i1E&ooV$=W>(ZO^+maiE+og!MPl2NYyPJ`zs7-n=19xS82)821x5gr#jU zHjQTZO>iky6M2Y5+Zi_|U2lm=+}>ozAx6mw`J5v4bGUXfe~sLIn{T&rfIwP9N$ z67hfyyUMfudQVJHki{n}3)&=CNBLD)4ESk}NMhqMhZryLH@&XvhzzY5Q9Md!tf~p? zQPeFqge@h%jNUm+jku~NzhDv+up`$IM&0*8o5w?a`)i^9)aD#@?}v2_KE({+2@v+h zboD-qq5`Nm)GUI1g?xv|W zJic1wcKyTuF)WC}&(g3pjdZ8+S@%g2u*iu0+EKemF}(Z&Goyj`7S;F)DH*lrpH2(* zrkWjGG6$dT;kXkse{Hv2OZ@FiR7*r=ZF6b3H7ri^MNZ$$*`|Y|M(kpB;w38H#-`7< z^}XQTvx%lXNxr;)3$={J^_|3t0Y-&8iWqPp$!I`;tqTt-nQQb&T!HB zd1g{@SH^Ustvd(3z8+A42m{szVop%T94ao=&zMnd5Z&T_`foxZ5x2c>)4`iYkZnS& zc5Xhe?@sKCFx{siC8mGdeztOkh-CJxiTTCNpUV02_E<~7T%g%*e$g(oRSS7&?t!r_ z0vB*$0fad|$dz~M4IJIYDY+CN#QV|FhrE7G#myqv90SThB0-H@<-ypb1K0NGUEesi zIGs@Lk*qG7cD>F{OU7PI5{E;H&b5*wbL}Zb&3wZXu0r1!gvkjw7|Ib&*RgB+K#Xd&gXPDxLQoD&$0J*YwX9PT_#V>MU>4}%Gl-G6ch(q6LxcTkXMrIA+HL9HP zMi+|+u`e!#=a7N1{N#%@e6IXomm9qqbQZ^llP83iSlMh-b{sqtzZ4x{*<)awtVnW} z&^|c(!$v$b+KOP{bt?pY-P$?Bw*U-0i=Od;F2S)H69w0W{@UDGBUV=Sb*N~=!L(SP zGQSt1E4(D*$5ejBVf~qV8qPOmC!JaGRN-@y*K;J018Nfuh;`9WSFzujdzkW5 z>3@$xV-@ld;Ut^{@#+qge@17jM;(^=i8l*Vjk@cVkC+Pa?Z8fv=deC{9AL&@#mQmr|s)mlnc^vO(kd*yszkd*1&=f=f(ad@iqBm zhwtOnh{&_F_L~vkd!gd$_3K7*qV0jYl+nZQ5m99g2;a?b?ri9+Zy`-#xV8e>@kyCi zPmkoX$Q!4uorT*tBEC;uhZE{N)PdC^< zMr;KT7^Lls8w{>I_R10TiLDnr1C|C+eW@}}L6TSB)BO?y(1e!ZF*1YpjEJN^QR{Z4 zxY%7V2ZM(zTgil;AS`j9*S0&c>gw^f^psVcNb%i_(GDOjAo44BjGG!YxIz39Bacco)BdX(<=+VoW~->Whc5jnp_v4)k>sz zO44kpCM9p`qn_8LTdjH^l#biJsV?$b^7~!VzGyFW%6J9T^n3#w3Rx5>#Okj#C(x!b ziU-h2nbnG&^^cNKMH+IBeLOZsufCvf`c*!LSMVIX36MXTEALQ8Y`b#e7C=?j_SqwA zWgIUbp=h<^Kf2TGRhE(H=&ukc3>)bQE8aZUYBQe7Z81AVQgI9-3_a-i09{X=dMpS{ zeWO3rk>Tz=L^0rNj+smX&;n62zy1=5G&?j~?6`mR!%8X*v6X9!w(!on*d427p%w&Y1I+Lp&w&e)UK^XFs)1b)0>LJac(6C z?_kTnA(WOK3o$einhT46J*bUmw^!@hUp}&MPgYx+WiJ_OBAu(h+M_H}U2xNNYX5Qgv zDI?w^%g^Zx<|9e!j0afZr8IqGM>}NsADul4$6?BD(yPYCmyLAHhVm6jrtAQPZh>B& z6ZQ->q0{HFT~_SjKUiz6b&Tbk+6N^*w#fdvU<};QxsvE4-gAz^-f8>)|tCI#72qh;>GS)P%JgzTF77%sX%bx#DNPj3R`OE!VX4-R5o3e ztp&1bi~IT&J7fVt1ul%`-|m?PnV2}b8lsb*^;xUKCvy2k4a7iD`kV>j0VByW@*}Ld zzP78)Q>!0|5_OtBGg9ZJ=A^z#`kz&SMvI%LNZR^6TWSGL3zzy{&DpWC-HZK<7lNcd zPu46Uv`hdT92`7?^wBc9y|joluuCO4Sq9H}Qx72G!4MBVZD4f7<_`h_$Z`1H4!PViKu?(hBal7*p#Y)J zwme#hN=+jdWE)<8I9KDNX0`UOdgfT}BPPC|oV(VpV#Y9nI#+tw3W7S^An0c&+S_*v z*hpu`izv&yHPlg~>{I5|!P~>XS_>CD0#|-kHqNPneB&fPcDbT9qe-B;hW;$X-&z?` zY^!z&^v-DRP;xJ?VHDx0>=JL}sR$|r;YMyHJNNCb52 z97;g6&Mj`(FrB1%5@LVzPl0@knkw*(h-?S#&*w&6vyDtwj-69h)&x9hSEHe|a}5*r zGPs}j-s>ZG>J2x~fKI++BlvBv;bW2A`@zHIXUVbv+5Ura1)3?!w4~Q zyc^AT_RO}?qEgcKi3unHL4@bv3vz*-D3r-j!u&edfS}GJtA|Yix_}()sMVdH>^Kjq zE~juJYNTS?!v9S(CBC{!$r@avq+kAZeE+8GUGZ?(9U1z?sR$*GAp-$MXvmMfg+Kif z1>0{^W##u>qN|0JouJ&Ix%l5Dv%z6;WSw5mYu)62Nbm$mZ-R}CKIf%IXd?wwSO0c9 zS*AvT(&kASuc{iSG^6MuMG8 ztO@zL<@?EiO{U;G&R@ORPc10J#&OJ3+qUP69t{wn1#xSvKI^ zu0TOexHxpZ#HR=w!S##%jJkMUReHxk$Olhi_K&wKT)Qr+^#ruTlO>1Gkf9WP0s*Log9EZ+GiFIHKkQ-?%7zAoC3li3~m?ry%$^M%yo+MGKHjpo?)zA9n8Q~LM3#Z`UV zX+bRmgRZ2NCmO#I+@_0gBf$%cu4*e)xbN8DeZn~LjBGrnH3PVeWbCt69ei%NIogng zH*k*}`&ejb{t<0UvwI;K;FjZw7242wIKmFi|gG<{!!8dYE> zB=LCul=8tK%l`4Gc2?OnLNR&aWdo#i7@EcSy*??Iz^D(#a1Mj6Tt< zBDYa1TPE2mejfSm%PdblGAiahX2WF391$B*d+m}u_*J8j2hI(<`|KJ5M39@y<{pKeacyB z7^;LVI5dtJ=1&?^*#brgT<(5NomJ-Q2Y}_vx;%^slMLjGh;>53|p`-Dmf3#A6!YzYH#0< zfGc+99Lt#`74~J(Ae{<7nc5+=9{S21nrBWLK-ktVm>-~Tqq6nD24;xCeiYV7ZcYXUYouaSn}@?i?ci=j(aw#4j}S7* zT79ow$KU1uNPIm*|2Z1~f2bhM1^^}=hCYS4rrZNu+)6DB(R+rg4qM9i0Sn0jvt?L@EK1Z3OP1^t!?9A)^quNUE#yNrF+e zQaj=Zg+Xi2;T?OYvRW4YkLr6gv=c6?syZ3XdVeC7_^5Cgh*~z8j9akPfr>46s*0!Pa2II}AmeCB!3Tb)DJc*}G!k6rSV`wTNf6^GCc>M=9*jGkJb z-OTv$BGc@BWK`_AYAaOUb@x4J2CzpcaMfANb43(;up%X01oe-g({IdvlvrA+*Qd&i z@-g6xZ?dCC$|EuBZS?RB&G&MSKj4;yIvjkh6tN%L34SLb6w{DZvM2_+x+mux(v5j@ z4pDtSXT@V?h_7|4Z}L$Q?6Bg1TLCr)b6>F5;IRCSN@dI2WTg)2213#4afk1tq-GBs z3EPJ-Igy0> zzuf|i^}ww7*xBy{e^M_&gU)*$$?vOw8#W1gn+uKx-~f<;rR8=!CyN_eAoa`RZUeuk z$0{ZNJVQ|eynnR3%z0q+?>9Gbf&-N5;m(i;uSqYoY^>^LN8QH~am8Un17VFc2{Jo` z(qXJ4!bznHryA?ds%VS*=ydUs*lQo3`=)Jx6U(bpijbaa(@avvr>j9hw&*8V%w%@veAoAAobDeAz}- zQ6@k6zL(%=-!oiy01c&@EWvnDm6SJz8^Fj(fc)*n@6LDw?el}E8CGAvzRwhLvbb2G z^_`Y*2kTy7mcW7$c&_hr!>td+#s z1#Sf5dgay`>hW!IT$FV-w*ZBmY<33~yT0#Jr?A)QLexse-zpMPDUP*S^mh_{r5{c2e$D5|A1qr^yx7x! z`1`lF=6w{5pe&!ckeB31EY?v$>jg32!uD(okv6eWLbbNsfFPKx_^-p!(1o{x=b4VM zIe;Nz4x_y_0MUp$jA*8$yl>Mk}|ABb5>cVw3UA4h%YP*KZt;}~C2hV~c7&wtRQDF=bHi6!Dx z0yg)Uu&{sX0V}q+YY3&pDF!rDq>bxM28;V$o~@AVq-_Ib>+{%wOMLW}#rAG#uq|Pr zZ$24+c@Hzq&c3zuWX3n3HrK-u3;Svlv~m5(j$|-!yi^aNaP3^rM4sb_O@ixlIl$>AoK2stLmgQKN94!l;XV*z}jH&|+y8b@R)K)OjiM z>g6?6>%J)?Z%+P_6NJJIeZr_{ZFJPM_++<#|yJO8XOuZTwjB z>sP0n%@~j94~H#y%H(T^8Rz^GuD8k)Gn39U)Q`hM3|(5fW#`18arB$XJNiN`xr#gR zu!YH7HFQEr(~`;-d9VK!JQX~hVA?H2*hZ}tIZJ^lR5H!6I4$x_hoK zW#%$zj1L5A)VR1?cJzeZTAawJ+$h*a0BiJ=ALiBHVfi32{@YIuYw1oAeB$4oYDjF# zwM?^eH*A}yv0dTmLjk+pMD0(r599?~Qw{FDV|UztzHs&iG=)Ok>yGcu$wlHg6s(*=>m#AdV`(LrCC(*)*k=RSL zoPW93l)3z9SBz*1v686PQ3Ay^gl>^}usMyy(S75h5WzkygK0o z+iu=vmrE8pOq&#LkhzkUbtVz|HPfA+B_OI1sjUw#!bd9%U z`c_h23Rz~#Ybp@D@0aJI)k54Gew+l)?w0oN=LCa;!Hco)r)>cd8(ouz&^?J(w|MYL zzLDplIVI+=w>238r73~+Nr7hsLMh0NWx&BXo|>Yy3zCw*K=TK|;d@aMiDwz_{`ls@ z(6rk0B)7`HBfe|e9$t@?(B;t?ti3yw4)Yf^2&pF^FC!c-6fO?^yvB#Pe-Q|3^yQMJ zFhzQ$($-aUzPqDf3kkbUMloCD_EN3x^?2w%pn?(tr|)En^qS9e|0x9tHML8b83tz$ zTdn2@1*reZM1@xL5rd6X)tiEU$?a1zE&;(0*4u_ zekO)Sx69N58$rW1SCPv(6okZJRZ1h-j1D~hTllSew7YUhy_T(Io82GQHTRRnmH|*9t}~G&<8xNcPg)wW#CYp`Vcbfys!FNAe=be@=ig}>KsYf1 g_lqM80F(WFE5XqF+q?pNb6nPu3L|$EwkQpXP!Y~VY zC4PB_G~+R*Ml)0Lm?7iRjLGoRb=O^I-F4Qvf8Bfk`L4acYwfka>$5-Ky+8Z@Mf>w| zJC%1rAP_lgD@!K`1gf)TH_J$DojQ!Wju42fyS1gcbA;z&G1dPUhU#k(ZFJK(FnRmH zKuXEZc4fnqL+I5Bi%DDgJqm@VDq0dor2>vCoE$&+NbM{4qJ-1@#h<4%Q?AE3Wr$Qk0*YTW+JY%|(N({^elXv~E6NK!olb?)Z_Ueoksa z>yk@3$1)xJBp6p4C)?tkgs;BRFE^$ms@h*fXzHiqL$908<$dhYQw1s2ucVJ3AGM>&0iY||cM<)2qY9Im=# z8~2v)pnznT-_Pm!#oj)8YMjPWNKp7R{q3 z>{P-L=;M?ti0I$2w;}yziW!n?xm0I^y>nC&;$3?r$P65(?i_ajPF<})pKMroct<*T zBc$_#wo`3;!Uq8%1?5hR_@+xY%gDbM(VP>ys7xSVV)oWQxovUS_DABho((s|elD;W zAJni)Y#Q2%eCE=`^tQo(?1rq?%=;i0R-5IRKIv79xx0YY4GK3e_lsO*5k$4Rw8umS z;)ATzhAF7ZYU`RZK4P3Ji3I8lj7`}ctghrCmMBwQEa^6vDa zG(62jvlZ8b>FN;TNo8T%Wg$@qqIW%yp; z$HtdoM4W(4GCu-EK2$b6*}jV+65~i1>^bbn1ar zJVfd0Qxbgb#PV9ZdYL-%+*aUppJ_C&Ja~!OE-8?FV^xM1yABDuwP#ci$J!y@6^_6O z;Ysz=5byE^d^7Z*w5c$Czf^qI(Q=M0YrNuWuYqviMPF--6=PGAfBS_a2TdT9^i%A4+T- z24g)8Sz+G-9O#-lOZzRfwL*dXPD&^>G(=z&I|%?ZGd+2_qJHh0Azg?ICK576lP)a>~Z3 z%4HwkM|L!LWjZDG=GW>eRnXE|nWJ^rHTJhy1%1sIhP`OVpD#sZy{Nd{J|g@6Dcbkt z&BzkoCEsUuhHVk9PteEI%wi%wcO@?{DC+}LUfD*Zbe(qN|(LBf}a6?8?#0Lt>tAw zo4w8~*y*(lGA(R7w&Y!-Q}abbBa2*o&>4Z7>buvjqAZ$&kHFU_X61tu!VP10!ag5$ zDHcKaJ0(U(gDhFO|D~6&7ujU?Fu9#Jg}FHj)3NYlI=+19caDqe2&l5y_suKb|}t45w2~>lhfXNHxDHGA2}9Z1{u< zUP~3=*Juuf&(}MjBN1L)JT{&UQy1^}ve3`?j9-)%@8}+wD>O%V{Xw}u6PJj0b8{;= zwzvV(!@1JIXTGzR*h?c6I6R#HbK)LAElC2*FTVl}+1^aIUtDSdPF%vllJNoU&?4dzjGiZ-YJam=bK!=^;%IB(UiWl-9;I zo>prLJ*inE!Yi^bvShV%`8H2E^DfsW9*HoY-6OTA2DZ{PiZ-9E16v$+Et37}XXQ%5 zFl8GxM`50fwKpe2KBtQJ?6c3u-WDvRjx9Y!gc&t6L*+B2Pxv#zQ7qeE|hiYsYtpnRvo?>&p1 zV|TWw%SWMvW1V!xNA&PBf(7dUD_ij6^N!0xx45g$XgfA>huyAyfolbBG2oyN(dJ13 zwlPPjFO->dbq=^>jA^<%&^J&P3l4m7^qGXmWnxoFMnwfux{rg+I zACorWayE?FD3N=mYvrBI_Nb)#Rc%sGXXQiJ;+{w6^uD?9;pnJ}$dQhyNtg?v6;o^H zGFcL5qO>q4*T-^LVmbpRQQU%T=z>UpMW8md^;)*mcGpVt&F*!Ye3zUHAi zl3bzHgh=*@$XLT^%WZD5;^JQ_+yRAMP3nQpT)X3~j1AY*8sbbFV9fe^t2*&Fu2I+@ zBuyQ2NQ*cbx(&?t(QXByw{rf)@&7|p|MS24pXP%+GGkK=eZ3+W!Tkhz#3iFYT0$Sg z{;InEB*GMruPq`{RF;iyv>JF%;bsF8@X+2vZ1E=ohZSema)rzpIc^S&@K8r*DmXE4 zG7?Bg#6!8^RY-(D1^JkRGzTC8w>>G+1b=^mbV|zd>oYWYFsct6TG%_poPnji&2?aV z!bPhfmNLG&n0|IMbJ8z*?M*f3^Xji+>jpeW_s_nv@AR-scUD zYDa#IF1iey**ilNdPlnk!X(A?t>jk)V;$SWl$JMF>n32$(GK5u;Wu^fuP_JdvyY^N z(w{A6CqItjRQc-oRL53%&|ngv#0qz=*gIe8@N{orIVUPiRDv0IFm8Cx`n@;p*5sj~ zx6Ij1)H;S*PbQCTcYOuM3aX)?gi9^@GkYeT|LO#4dYoeaGv#3=X12}QA9ayZ$hHJb zXt0A1@aYa_q;{tz*|<6a|2@2(hmE=#u~O-wlUo_!+jltj!{`N-Ej+|MdIg>(vA1~c zk;W%%r$?8b^GDM;EgAPuis{4{7kLjQ@!7{{a7Uj&lbS_*Z&O^Cl4sO?Drzzb&ozqQ0+I&>X*fs%UUEAqL0fW8_~-S%%LM4n z8}%T8U$=nEotDy(O#4o#`>3S9Y!uS;pA|{QJMo@v*?LYx%0dm9scjOV%$cLK2M2rp cYE1puHKvajK0hSbS~nopXYDO(&s viewer; /** - * the {@link PluginTool} + * The {@link PluginTool} */ private final PluginTool pluginTool; - /** - * the "owner name" for action - mainly affects default help location - */ - private final String actionOwnerName = "GraphServices"; - - /** - * provides the component for the {@link GraphDisplay} - */ private final DefaultGraphDisplayComponentProvider componentProvider; /** - * whether to ensure the focused vertex is visible, scrolling if necessary + * Whether to ensure the focused vertex is visible, scrolling if necessary * the visualization in order to center the selected vertex * or the center of the set of selected vertices */ private boolean ensureVertexIsVisible = false; /** - * allows selection of various {@link LayoutAlgorithm} ('arrangements') + * Allows selection of various {@link LayoutAlgorithm} ('arrangements') */ private final LayoutTransitionManager layoutTransitionManager; /** - * provides graph displays for supplied graphs + * Provides graph displays for supplied graphs */ private final DefaultGraphDisplayProvider graphDisplayProvider; /** * the vertex that has been nominated to be 'focused' in the graph display and listing */ private AttributedVertex focusedVertex; + + /** + * Runs animation jobs for updating the display + */ private final GraphJobRunner jobRunner = new GraphJobRunner(); + /** * a satellite view that shows in the lower left corner as a birds-eye view of the graph display */ private final SatelliteVisualizationViewer satelliteViewer; - /** - * generated filters on edges - */ - private AttributeFilters edgeFilters; - /** - * generated filters on vertices - */ - private AttributeFilters vertexFilters; - /** - * a dialog populated with generated vertex/edge filters - */ + private FilterDialog filterDialog; - /** - * holds the vertex icons (instead of recomputing them) - */ + private AttributeFilters edgeFilters; + private AttributeFilters vertexFilters; + private GhidraIconCache iconCache; + /** - * multi-selection is done in a free-form traced shape instead of a rectangle + * Multi-selection is done in a free-form traced shape instead of a rectangle */ private boolean freeFormSelection; /** - * Handles the popup + * Handles all mouse interaction */ - private GhidraGraphMouse graphMouse; + private JgtPluggableGraphMouse graphMouse; - /** - * Will accept a {@link Graph} and use it to create a new graph display in - * a new tab or new window - */ - Consumer> subgraphConsumer = g -> { - - AttributedGraph attributedGraph = new AttributedGraph(); - g.vertexSet().forEach(attributedGraph::addVertex); - g.edgeSet().forEach(e -> { - AttributedVertex source = g.getEdgeSource(e); - AttributedVertex target = g.getEdgeTarget(e); - attributedGraph.addEdge(source, target, e); - }); - displaySubGraph(attributedGraph); - }; private ToggleDockingAction hideSelectedAction; private ToggleDockingAction hideUnselectedAction; private SwitchableSelectionItemListener switchableSelectionListener; @@ -226,7 +199,7 @@ public class DefaultGraphDisplay implements GraphDisplay { .builder(viewer.getRenderContext().getVertexBoundsFunction()) .build()); - graphMouse = new GhidraGraphMouse(componentProvider, viewer); + graphMouse = new JgtPluggableGraphMouse(this); createToolbarActions(); createPopupActions(); @@ -302,7 +275,7 @@ public class DefaultGraphDisplay implements GraphDisplay { private void createToolbarActions() { // create a toggle for 'scroll to selected vertex' - new ToggleActionBuilder("Scroll To Selection", actionOwnerName) + new ToggleActionBuilder("Scroll To Selection", ACTION_OWNER) .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) .description("Ensure that the 'focused' vertex is visible") .selected(true) @@ -314,7 +287,7 @@ public class DefaultGraphDisplay implements GraphDisplay { // create a toggle for enabling 'free-form' selection: selection is // inside of a traced shape instead of a rectangle - new ToggleActionBuilder("Free-Form Selection", actionOwnerName) + new ToggleActionBuilder("Free-Form Selection", ACTION_OWNER) .toolBarIcon(DefaultDisplayGraphIcons.LASSO_ICON) .description("Trace Free-Form Shape to select multiple vertices (CTRL-click-drag)") .selected(false) @@ -323,14 +296,14 @@ public class DefaultGraphDisplay implements GraphDisplay { .buildAndInstallLocal(componentProvider); // create an icon button to display the satellite view - new ToggleActionBuilder("SatelliteView", actionOwnerName).description("Show Satellite View") + new ToggleActionBuilder("SatelliteView", ACTION_OWNER).description("Show Satellite View") .toolBarIcon(DefaultDisplayGraphIcons.SATELLITE_VIEW_ICON) .onAction(this::toggleSatellite) .selected(graphDisplayProvider.getDefaultSatelliteState()) .buildAndInstallLocal(componentProvider); // create an icon button to reset the view transformations to identity (scaled to layout) - new ActionBuilder("Reset View", actionOwnerName) + new ActionBuilder("Reset View", ACTION_OWNER) .description("Reset all view transforms to center graph in display") .toolBarIcon(Icons.REFRESH_ICON) .onAction(context -> viewer.scaleToLayout()) @@ -338,7 +311,7 @@ public class DefaultGraphDisplay implements GraphDisplay { // create a button to show the view magnify lens LensSupport magnifyViewSupport = createMagnifier(); - ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", actionOwnerName) + ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER) .description("Show View Magnifier") .toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON) .onAction(context -> magnifyViewSupport.activate( @@ -349,90 +322,91 @@ public class DefaultGraphDisplay implements GraphDisplay { componentProvider.addLocalAction(lensToggle); // create an action button to show a dialog with generated filters - new ActionBuilder("Show Filters", actionOwnerName).description("Show Graph Filters") + new ActionBuilder("Show Filters", ACTION_OWNER).description("Show Graph Filters") .toolBarIcon(DefaultDisplayGraphIcons.FILTER_ICON) .onAction(context -> showFilterDialog()) .buildAndInstallLocal(componentProvider); // create a menu with graph layout algorithm selections - new MultiStateActionBuilder("Arrangement", actionOwnerName) - .description("Select Layout Arrangement Algorithm") + List> layoutActionStates = getLayoutActionStates(); + new MultiStateActionBuilder("Arrangement", ACTION_OWNER) + .description("Arrangement: " + layoutActionStates.get(0).getName()) .toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON) .fireFirstAction(false) .onActionStateChanged((s, t) -> layoutChanged(s.getName())) - .addStates(getLayoutActionStates()) + .addStates(layoutActionStates) .buildAndInstallLocal(componentProvider); } private void createPopupActions() { - new ActionBuilder("Select Vertex", actionOwnerName) + new ActionBuilder("Select Vertex", ACTION_OWNER) .popupMenuPath("Select Vertex") .popupMenuGroup("selection", "1") .withContext(VertexGraphActionContext.class) - .enabledWhen(c -> !viewer.getSelectedVertexState().isSelected(c.getClickedVertex())) + .enabledWhen(c -> !isSelected(c.getClickedVertex())) .onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex())) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Deselect Vertex", actionOwnerName) + new ActionBuilder("Deselect Vertex", ACTION_OWNER) .popupMenuPath("Deselect Vertex") .popupMenuGroup("selection", "2") .withContext(VertexGraphActionContext.class) - .enabledWhen(c -> viewer.getSelectedVertexState().isSelected(c.getClickedVertex())) + .enabledWhen(c -> isSelected(c.getClickedVertex())) .onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex())) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Select Edge", actionOwnerName) + new ActionBuilder("Select Edge", ACTION_OWNER) .popupMenuPath("Select Edge") .popupMenuGroup("selection", "1") .withContext(EdgeGraphActionContext.class) - .enabledWhen(c -> !viewer.getSelectedEdgeState().isSelected(c.getClickedEdge())) + .enabledWhen(c -> !isSelected(c.getClickedEdge())) .onAction(c -> selectEdge(c.getClickedEdge())) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Deselect Edge", actionOwnerName) + new ActionBuilder("Deselect Edge", ACTION_OWNER) .popupMenuPath("Deselect Edge") .popupMenuGroup("selection", "2") .withContext(EdgeGraphActionContext.class) - .enabledWhen(c -> viewer.getSelectedEdgeState().isSelected(c.getClickedEdge())) + .enabledWhen(c -> isSelected(c.getClickedEdge())) .onAction(c -> deselectEdge(c.getClickedEdge())) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Edge Source", actionOwnerName) + new ActionBuilder("Edge Source", ACTION_OWNER) .popupMenuPath("Go To Edge Source") .popupMenuGroup("Go To") .withContext(EdgeGraphActionContext.class) .onAction(c -> setFocusedVertex(graph.getEdgeSource(c.getClickedEdge()))) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Edge Target", actionOwnerName) + new ActionBuilder("Edge Target", ACTION_OWNER) .popupMenuPath("Go To Edge Target") .popupMenuGroup("Go To") .withContext(EdgeGraphActionContext.class) .onAction(c -> setFocusedVertex(graph.getEdgeTarget(c.getClickedEdge()))) .buildAndInstallLocal(componentProvider); - hideSelectedAction = new ToggleActionBuilder("Hide Selected", actionOwnerName) + hideSelectedAction = new ToggleActionBuilder("Hide Selected", ACTION_OWNER) .popupMenuPath("Hide Selected") .popupMenuGroup("z", "1") .description("Toggles whether or not to show selected vertices and edges") .onAction(c -> manageVertexDisplay()) .buildAndInstallLocal(componentProvider); - hideUnselectedAction = new ToggleActionBuilder("Hide Unselected", actionOwnerName) + hideUnselectedAction = new ToggleActionBuilder("Hide Unselected", ACTION_OWNER) .popupMenuPath("Hide Unselected") .popupMenuGroup("z", "2") .description("Toggles whether or not to show selected vertices and edges") .onAction(c -> manageVertexDisplay()) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Invert Selection", actionOwnerName) + new ActionBuilder("Invert Selection", ACTION_OWNER) .popupMenuPath("Invert Selection") .popupMenuGroup("z", "3") .description("Inverts the current selection") .onAction(c -> invertSelection()) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Grow Selection To Targets", actionOwnerName) + new ActionBuilder("Grow Selection To Targets", ACTION_OWNER) .popupMenuPath("Grow Selection To Targets") .popupMenuGroup("z", "4") .description("Extends the current selection by including the target vertex " + @@ -442,7 +416,7 @@ public class DefaultGraphDisplay implements GraphDisplay { .onAction(c -> growSelection(getTargetVerticesFromSelected())) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Grow Selection From Sources", actionOwnerName) + new ActionBuilder("Grow Selection From Sources", ACTION_OWNER) .popupMenuPath("Grow Selection From Sources") .popupMenuGroup("z", "4") .description("Extends the current selection by including the target vertex " + @@ -452,15 +426,23 @@ public class DefaultGraphDisplay implements GraphDisplay { .onAction(c -> growSelection(getSourceVerticesFromSelected())) .buildAndInstallLocal(componentProvider); - new ActionBuilder("Create Subgraph", actionOwnerName) - .popupMenuPath("Display Selected as New Graph") + new ActionBuilder("Clear Selection", ACTION_OWNER) + .popupMenuPath("Clear Selection") .popupMenuGroup("z", "5") + .keyBinding("escape") + .enabledWhen(c -> hasSelection()) + .onAction(c -> clearSelection()) + .buildAndInstallLocal(componentProvider); + + new ActionBuilder("Create Subgraph", ACTION_OWNER) + .popupMenuPath("Display Selected as New Graph") + .popupMenuGroup("zz", "5") .description("Creates a subgraph from the selected nodes") .enabledWhen(c -> !viewer.getSelectedVertexState().getSelected().isEmpty()) .onAction(c -> createAndDisplaySubGraph()) .buildAndInstallLocal(componentProvider); - togglePopupsAction = new ToggleActionBuilder("Display Popup Windows", actionOwnerName) + togglePopupsAction = new ToggleActionBuilder("Display Popup Windows", ACTION_OWNER) .popupMenuPath("Display Popup Windows") .popupMenuGroup("zz", "1") .description("Toggles whether or not to show popup windows, such as tool tips") @@ -471,6 +453,24 @@ public class DefaultGraphDisplay implements GraphDisplay { } + private void clearSelection() { + viewer.getSelectedVertexState().clear(); + viewer.getSelectedEdgeState().clear(); + } + + private boolean hasSelection() { + return !(viewer.getSelectedVertexState().getSelected().isEmpty() && + viewer.getSelectedEdgeState().getSelected().isEmpty()); + } + + private boolean isSelected(AttributedVertex v) { + return viewer.getSelectedVertexState().isSelected(v); + } + + private boolean isSelected(AttributedEdge e) { + return viewer.getSelectedEdgeState().isSelected(e); + } + private void createAndDisplaySubGraph() { GraphDisplay display = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY); try { @@ -556,7 +556,7 @@ public class DefaultGraphDisplay implements GraphDisplay { for (String layoutName : names) { ActionState state = new ActionState<>(layoutName, DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON, layoutName); - state.setHelpLocation(new HelpLocation(actionOwnerName, layoutName)); + state.setHelpLocation(new HelpLocation(ACTION_OWNER, layoutName)); actionStates.add(state); } return actionStates; @@ -603,24 +603,6 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.repaint(); } - private void displaySubGraph(Graph subGraph) { - - try { - GraphDisplay graphDisplay = - graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY); - graphDisplay.setGraph((AttributedGraph) subGraph, "SubGraph", false, TaskMonitor.DUMMY); - graphDisplay.setGraphDisplayListener(listener); - } - catch (CancelledException e) { - // can't happen while using a dummy monitor - } - } - - /** - * create a SatelliteViewer for the Visualization - * @param parentViewer the main visualization 'parent' of the satellite view - * @return a new SatelliteVisualizationViewer - */ private SatelliteVisualizationViewer createSatelliteViewer( VisualizationViewer parentViewer) { Dimension viewerSize = parentViewer.getSize(); @@ -649,9 +631,6 @@ public class DefaultGraphDisplay implements GraphDisplay { return satellite; } - /** - * close this graph display - */ @Override public void close() { graphDisplayProvider.remove(this); @@ -662,10 +641,6 @@ public class DefaultGraphDisplay implements GraphDisplay { componentProvider.closeComponent(); } - /** - * accept a {@code GraphDisplayListener} - * @param listener the listener to be notified - */ @Override public void setGraphDisplayListener(GraphDisplayListener listener) { if (this.listener != null) { @@ -697,7 +672,7 @@ public class DefaultGraphDisplay implements GraphDisplay { viewer.getSelectedVertexState().addItemListener(switchableSelectionListener); } - protected void setFocusedVertex(AttributedVertex vertex) { + public void setFocusedVertex(AttributedVertex vertex) { setFocusedVertex(vertex, EventTrigger.API_CALL); } @@ -1008,8 +983,7 @@ public class DefaultGraphDisplay implements GraphDisplay { return new Point2D.Double(p.x, p.y); } - // they did not pick a vertex to center, so - // just center the graph + // they did not pick a vertex to center, so just center the graph Point2D center = viewer.getCenter(); Point p = Point.of(center.getX(), center.getY()); return new Point2D.Double(p.x, p.y); @@ -1211,13 +1185,13 @@ public class DefaultGraphDisplay implements GraphDisplay { public ActionContext getActionContext(MouseEvent e) { - AttributedVertex pickedVertex = graphMouse.getPickedVertex(e); + AttributedVertex pickedVertex = JgtUtils.getVertex(e, viewer); if (pickedVertex != null) { return new VertexGraphActionContext(componentProvider, graph, getSelectedVertices(), focusedVertex, pickedVertex); } - AttributedEdge pickedEdge = graphMouse.getPickedEdge(e); + AttributedEdge pickedEdge = JgtUtils.getEdge(e, viewer); if (pickedEdge != null) { return new EdgeGraphActionContext(componentProvider, graph, getSelectedVertices(), focusedVertex, pickedEdge); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java new file mode 100644 index 0000000000..d6bc5ea70d --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/AbstractJgtGraphMousePlugin.java @@ -0,0 +1,311 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization.mouse; + +import java.awt.Cursor; +import java.awt.event.*; + +import org.jungrapht.visualization.SatelliteVisualizationViewer; +import org.jungrapht.visualization.VisualizationViewer; +import org.jungrapht.visualization.control.AbstractGraphMousePlugin; +import org.jungrapht.visualization.selection.MutableSelectedState; + +/** + * Graph mouse plugin base class. + * + * Usage Notes: + *
    + *
  • We clear state on mouseReleased() and mouseExited(), since we will get + * at least one of those calls
  • + *
+ * @param the vertex type + * @param the edge type + */ +//@formatter:off +public abstract class AbstractJgtGraphMousePlugin + extends AbstractGraphMousePlugin + implements MouseListener, MouseMotionListener { +//@formatter:on + + protected boolean isHandlingMouseEvents; + + protected V selectedVertex; + protected E selectedEdge; + + public AbstractJgtGraphMousePlugin() { + this(InputEvent.BUTTON1_DOWN_MASK); + } + + public AbstractJgtGraphMousePlugin(int selectionModifiers) { + super(selectionModifiers); + } + + public VisualizationViewer getViewer(MouseEvent e) { + VisualizationViewer viewer = getGraphViewer(e); + return viewer; + } + + /** + * Returns the primary/master graph viewer. + * + * @param e the mouse event from which to get the viewer + * @return the viewer + */ + @SuppressWarnings("unchecked") + public VisualizationViewer getGraphViewer(MouseEvent e) { + VisualizationViewer viewer = (VisualizationViewer) e.getSource(); + + // is this the satellite viewer? + if (viewer instanceof SatelliteVisualizationViewer) { + return ((SatelliteVisualizationViewer) viewer).getMaster(); + } + + return viewer; + } + + /** + * Returns the satellite graph viewer. This assumes that the mouse event originated from + * the satellite viewer. + * + * @param e the mouse event from which to get the viewer + * @return the viewer + */ + @SuppressWarnings("unchecked") + public SatelliteVisualizationViewer getSatelliteGraphViewer(MouseEvent e) { + + VisualizationViewer viewer = (VisualizationViewer) e.getSource(); + + // is this the satellite viewer? + if (viewer instanceof SatelliteVisualizationViewer) { + return (SatelliteVisualizationViewer) viewer; + } + + throw new IllegalStateException("Do not have a satellite GraphViewer"); + } + + /** + * Signals to perform any cleanup when this plugin is going away + */ + public void dispose() { + // stub + } + + /** + * Checks the given mouse event to see if it is a valid event for selecting a vertex at the + * mouse location. If so, then the vertex is selected in this mouse handler and the event + * is consumed. + * @param e the event + * @return true if a vertex was selected + */ + protected boolean checkForVertex(MouseEvent e) { + if (!checkModifiers(e)) { + selectedVertex = null; + return false; + } + + VisualizationViewer vv = getViewer(e); + selectedVertex = JgtUtils.getVertex(e, vv); + if (selectedVertex == null) { + return false; + } + + e.consume(); + return true; + } + + /** + * Checks the given mouse event to see if it is a valid event for selecting an edge at the + * mouse location. If so, then the edge is selected in this mouse handler and the event + * is consumed. + * @param e the event + * @return true if an edge was selected + */ + protected boolean checkForEdge(MouseEvent e) { + if (!checkModifiers(e) || isOverVertex(e)) { + selectedEdge = null; + return false; + } + + VisualizationViewer vv = getViewer(e); + selectedEdge = JgtUtils.getEdge(e, vv); + if (selectedEdge == null) { + return false; + } + + e.consume(); + isHandlingMouseEvents = true; + return true; + } + + /** + * Selects the given vertex + * @param vertex the vertex + * @param viewer the graph viewer + * @return true if the vertex is selected + */ + protected boolean selectVertex(V vertex, VisualizationViewer viewer) { + MutableSelectedState selectedVertexState = viewer.getSelectedVertexState(); + if (selectedVertexState == null) { + return false; + } + + selectedVertexState.isSelected(vertex); + + if (selectedVertexState.isSelected(vertex) == false) { + selectedVertexState.clear(); + selectedVertexState.select(vertex, true); + } + + return true; + } + + /** + * Selects the given edge + * @param edge the edge + * @param viewer the graph viewer + * @return true if the edge is selected + */ + protected boolean selectEdge(E edge, VisualizationViewer viewer) { + + MutableSelectedState selectedVertexState = viewer.getSelectedEdgeState(); + if (selectedVertexState == null) { + return false; + } + + selectedVertexState.isSelected(edge); + + if (selectedVertexState.isSelected(edge) == false) { + selectedVertexState.clear(); + selectedVertexState.select(edge, true); + } + return true; + } + + /** + * Returns true if the location of the mouse event is over a vertex + * @param e the event + * @return true if the location of the mouse event is over a vertex + */ + protected boolean isOverVertex(MouseEvent e) { + return getVertex(e) != null; + } + + /** + * Returns the vertex if the mouse event is over a vertex + * @param e the event + * @return a vertex or null + */ + protected V getVertex(MouseEvent e) { + VisualizationViewer viewer = getViewer(e); + return JgtUtils.getVertex(e, viewer); + } + + /** + * Returns true if the location of the mouse event is over a edge + * @param e the event + * @return true if the location of the mouse event is over a edge + */ + protected boolean isOverEdge(MouseEvent e) { + VisualizationViewer viewer = getViewer(e); + E edge = JgtUtils.getEdge(e, viewer); + if (edge == null) { + return false; + } + + return !isOverVertex(e); + } + + protected void installCursor(Cursor newCursor, MouseEvent e) { + VisualizationViewer viewer = getViewer(e); + viewer.setCursor(newCursor); + } + + protected boolean shouldShowCursor(MouseEvent e) { + return isOverVertex(e); // to showing cursor over vertices + } + + @Override + public void mousePressed(MouseEvent e) { + if (!checkModifiers(e)) { + return; + } + + // override this method to do stuff + } + + @Override + public void mouseClicked(MouseEvent e) { + if (!isHandlingMouseEvents) { + return; + } + + e.consume(); + resetState(); + } + + protected void resetState() { + isHandlingMouseEvents = false; + selectedVertex = null; + selectedEdge = null; + } + + @Override + public void mouseDragged(MouseEvent e) { + if (!isHandlingMouseEvents) { + return; + } + + e.consume(); + resetState(); + } + + @Override + public void mouseMoved(MouseEvent e) { + if (isHandlingMouseEvents) { + e.consume(); + } + + // only "turn on" the cursor; resetting is handled elsewhere (in the mouse driver) + if (shouldShowCursor(e)) { + installCursor(cursor, e); + e.consume(); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (isHandlingMouseEvents) { + e.consume(); + } + + if (shouldShowCursor(e)) { + installCursor(cursor, e); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + if (shouldShowCursor(e)) { + installCursor(cursor, e); + e.consume(); + } + } + + @Override + public void mouseExited(MouseEvent e) { + installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java new file mode 100644 index 0000000000..cb576b621f --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtCursorRestoringPlugin.java @@ -0,0 +1,55 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization.mouse; + +import java.awt.Cursor; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; + +import org.jungrapht.visualization.VisualizationViewer; +import org.jungrapht.visualization.control.AbstractGraphMousePlugin; + +/** + * Restores the cursor after other graph mouse operations. + * + * Future: this is copied from the Visual Graph counterpart--consolidate these + * + * @param the vertex type + * @param the edge type + */ +public class JgtCursorRestoringPlugin extends AbstractGraphMousePlugin + implements MouseMotionListener { + + public JgtCursorRestoringPlugin() { + super(0); + } + + @Override + public void mouseDragged(MouseEvent e) { + // don't care + } + + @Override + public void mouseMoved(MouseEvent e) { + installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e); + } + + @SuppressWarnings("unchecked") + private void installCursor(Cursor newCursor, MouseEvent e) { + VisualizationViewer viewer = (VisualizationViewer) e.getSource(); + viewer.setCursor(newCursor); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java new file mode 100644 index 0000000000..f1514ce714 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtEdgeNavigationPlugin.java @@ -0,0 +1,113 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization.mouse; + +import java.awt.Cursor; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +import org.jgrapht.Graph; +import org.jungrapht.visualization.VisualizationViewer; +import org.jungrapht.visualization.layout.model.Point; +import org.jungrapht.visualization.selection.MutableSelectedState; + +import ghidra.graph.visualization.CenterAnimationJob; + +/** + * Mouse plugin to allow for edge navigation + * + * @param the vertex type + * @param the edge type + */ +public class JgtEdgeNavigationPlugin extends AbstractJgtGraphMousePlugin { + + public JgtEdgeNavigationPlugin() { + this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); + } + + @Override + public void mousePressed(MouseEvent e) { + if (!checkModifiers(e)) { + return; + } + + if (e.getClickCount() != 2) { + return; + } + + checkForEdge(e); // this will select an edge if we can and store off the edge + } + + @Override + public void mouseClicked(MouseEvent e) { + if (!isHandlingMouseEvents) { + return; + } + + E edge = selectedEdge; // save off before we reset + e.consume(); + resetState(); + + // on double-clicks we go to the vertex in the current edge direction unless that vertex + // is already selected, then we go to the other vertex + VisualizationViewer viewer = getViewer(e); + MutableSelectedState selectedState = viewer.getSelectedVertexState(); + + Graph graph = viewer.getVisualizationModel().getGraph(); + V end = graph.getEdgeTarget(edge); + if (!selectedState.isSelected(end)) { + pickAndShowVertex(end, selectedState, viewer); + return; + } + + // the destination was picked, go the other direction + V source = graph.getEdgeSource(edge); + pickAndShowVertex(source, selectedState, viewer); + } + + private void pickAndShowVertex(V vertex, MutableSelectedState selectedVertexState, + VisualizationViewer viewer) { + + // TODO animate; this requires a single view updater + Point2D existingCenter = viewer.getRenderContext() + .getMultiLayerTransformer() + .inverseTransform(viewer.getCenter()); + Point vp = viewer.getVisualizationModel().getLayoutModel().get(vertex); + Point2D newCenter = new Point2D.Double(vp.x, vp.y); + CenterAnimationJob job = new CenterAnimationJob(viewer, existingCenter, newCenter); + job.finished(); + + selectedVertexState.clear(); + selectedVertexState.select(vertex); + + /* + VisualGraphViewUpdater updater = viewer.getViewUpdater(); + updater.moveVertexToCenterWithAnimation(vertex, isBusy -> { + + // pick the vertex after the animation has run + if (!isBusy) { + GPickedState pickedStateWrapper = (GPickedState) selectedVertexState; + pickedStateWrapper.pickToActivate(vertex); + } + }); + */ + } + + @Override + protected boolean shouldShowCursor(MouseEvent e) { + return isOverEdge(e); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtPluggableGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtPluggableGraphMouse.java new file mode 100644 index 0000000000..be575731c8 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtPluggableGraphMouse.java @@ -0,0 +1,70 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization.mouse; + +import java.awt.event.InputEvent; + +import org.jungrapht.visualization.control.*; + +import docking.DockingUtils; +import ghidra.graph.visualization.DefaultGraphDisplay; +import ghidra.service.graph.AttributedEdge; +import ghidra.service.graph.AttributedVertex; + +/** + * Pluggable graph mouse for jungrapht + */ +public class JgtPluggableGraphMouse extends DefaultGraphMouse { + + private DefaultGraphDisplay graphDisplay; + + // TODO we should not need the graph display for any mouse plugins, but the API is net yet + // robust enough to communicate fully without it + public JgtPluggableGraphMouse(DefaultGraphDisplay graphDisplay) { + super(DefaultGraphMouse. builder()); + this.graphDisplay = graphDisplay; + } + + @Override + public void loadPlugins() { + + // + // Note: the order of these additions matters, as an event will flow to each plugin until + // it is handled. + // + + // edge + add(new JgtEdgeNavigationPlugin()); + + add(new JgtVertexFocusingPlugin(graphDisplay)); + + // scaling + add(new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out)); + + // the grab/pan feature + add(new JgtTranslatingPlugin()); + + add(new SelectingGraphMousePlugin( + InputEvent.BUTTON1_DOWN_MASK, + 0, + DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + + // cursor cleanup + add(new JgtCursorRestoringPlugin()); + + setPluginsLoaded(); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java new file mode 100644 index 0000000000..cf2ff54aff --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtTranslatingPlugin.java @@ -0,0 +1,177 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization.mouse; + +import java.awt.Cursor; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +import org.jungrapht.visualization.*; +import org.jungrapht.visualization.MultiLayerTransformer.Layer; +import org.jungrapht.visualization.control.TranslatingGraphMousePlugin; +import org.jungrapht.visualization.transform.MutableTransformer; + +/** + * Note: this class is based on {@link TranslatingGraphMousePlugin}. + *

+ * TranslatingGraphMousePlugin uses a MouseButtonOne press and drag gesture to translate + * the graph display in the x and y direction. The default MouseButtonOne modifier can be overridden + * to cause a different mouse gesture to translate the display. + * + * @param the vertex type + * @param the edge type + */ +public class JgtTranslatingPlugin + extends AbstractJgtGraphMousePlugin { + + private boolean panning; + private boolean isHandlingEvent; + + public JgtTranslatingPlugin() { + this(InputEvent.BUTTON1_DOWN_MASK); + } + + public JgtTranslatingPlugin(int modifiers) { + super(modifiers); + this.cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); + } + + @Override + public void mousePressed(MouseEvent e) { + boolean accepted = checkModifiers(e) && isInDraggingArea(e); + if (!accepted) { + return; + } + + down = e.getPoint(); + VisualizationViewer viewer = getGraphViewer(e); + viewer.setCursor(cursor); + isHandlingEvent = true; + e.consume(); + } + + @Override + public void mouseReleased(MouseEvent e) { + boolean wasHandlingEvent = isHandlingEvent; + isHandlingEvent = false; + down = null; + installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e); + + // NOTE: we are only consuming the event here if we actually did pan...this allows follow-on + // mouse handlers to process the mouseReleased() event. This is a bit odd and not the + // normal event processing (which is to consume all related events). + if (wasHandlingEvent && panning) { + e.consume(); + } + + panning = false; + } + + @Override + public void mouseDragged(MouseEvent e) { + boolean accepted = checkModifiers(e); + if (!accepted) { + return; + } + + if (!isHandlingEvent) { + return; + } + + panning = true; + + VisualizationViewer viewer = getGraphViewer(e); + RenderContext context = viewer.getRenderContext(); + MultiLayerTransformer multiLayerTransformer = context.getMultiLayerTransformer(); + MutableTransformer layoutTransformer = multiLayerTransformer.getTransformer(Layer.LAYOUT); + viewer.setCursor(cursor); + Point2D downPoint = multiLayerTransformer.inverseTransform(down); + Point2D p = multiLayerTransformer.inverseTransform(e.getPoint()); + float dx = (float) (p.getX() - downPoint.getX()); + float dy = (float) (p.getY() - downPoint.getY()); + + layoutTransformer.translate(dx, dy); + down.x = e.getX(); + down.y = e.getY(); + e.consume(); + viewer.repaint(); + } + + @Override + public void mouseClicked(MouseEvent e) { + // don't care + } + + @Override + public void mouseEntered(MouseEvent e) { + if (isHandlingEvent) { + return; + } + + if (!isInDraggingArea(e)) { + return; + } + + if (!checkModifiersForCursor(e)) { + return; + } + + installCursor(cursor, e); + } + + @Override + public void mouseExited(MouseEvent e) { + installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e); + } + + @Override + public void mouseMoved(MouseEvent e) { + if (!checkModifiersForCursor(e)) { + return; + } + + if (isHandlingEvent) { + e.consume(); + } + + if (isInDraggingArea(e)) { + installCursor(cursor, e); + e.consume(); + } + } + + private boolean checkModifiersForCursor(MouseEvent e) { + if (e.getModifiersEx() == 0) { + return true; + } + return false; + } + +//================================================================================================== +// Private methods +//================================================================================================== + + private boolean isInDraggingArea(MouseEvent e) { + return !(isOverVertex(e) || isOverEdge(e)); + } + + @Override + public void installCursor(Cursor newCursor, MouseEvent e) { + VisualizationViewer viewer = getViewer(e); + viewer.setCursor(newCursor); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtUtils.java similarity index 50% rename from Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouse.java rename to Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtUtils.java index 604fc853ea..33a15ac02a 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.graph.visualization; +package ghidra.graph.visualization.mouse; import static org.jungrapht.visualization.VisualizationServer.*; @@ -22,42 +22,86 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.jungrapht.visualization.VisualizationViewer; -import org.jungrapht.visualization.control.*; +import org.jungrapht.visualization.control.GraphElementAccessor; +import org.jungrapht.visualization.control.TransformSupport; import org.jungrapht.visualization.layout.model.LayoutModel; import org.jungrapht.visualization.selection.ShapePickSupport; -import docking.ComponentProvider; -import ghidra.service.graph.AttributedEdge; -import ghidra.service.graph.AttributedVertex; - /** - * An extension of the jungrapht DefaultGraphMouse. This class has references to - *

    - *
  • a {@link VisualizationViewer} (to access the Graph and LayoutModel) - * + * Keeper of shared logic for jungrapht handling */ -public class GhidraGraphMouse extends DefaultGraphMouse { +public class JgtUtils { private static final String PICK_AREA_SIZE_PROPERTY = PREFIX + "pickAreaSize"; - private VisualizationViewer viewer; - - private int pickSize; - /** - * create an instance with default values - * @param componentProvider the graph component provider - * @param viewer the graph viewer component + * Returns the edge under the given mouse event + * + * @param the vertex type + * @param the edge type + * @param e the event + * @param viewer the graph viewer + * @return the edge */ - GhidraGraphMouse(ComponentProvider componentProvider, - VisualizationViewer viewer) { + public static E getEdge(MouseEvent e, VisualizationViewer viewer) { + if (e == null) { + return null; + } - super(DefaultGraphMouse. builder()); - this.viewer = viewer; - pickSize = Integer.getInteger(GhidraGraphMouse.PICK_AREA_SIZE_PROPERTY, 4); + Rectangle2D footprintRectangle = getFootprint(e); + LayoutModel layoutModel = viewer.getVisualizationModel().getLayoutModel(); + GraphElementAccessor pickSupport = viewer.getPickSupport(); + if (pickSupport == null) { + return null; + } + + if (pickSupport instanceof ShapePickSupport) { + ShapePickSupport shapePickSupport = + (ShapePickSupport) pickSupport; + return shapePickSupport.getEdge(layoutModel, footprintRectangle); + } + + TransformSupport transformSupport = + viewer.getTransformSupport(); + Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint()); + return pickSupport.getEdge(layoutModel, layoutPoint.getX(), layoutPoint.getY()); } - private Rectangle2D getFootprint(MouseEvent e) { + /** + * Returns the vertex under the given mouse event + * + * @param the vertex type + * @param the edge type + * @param e the event + * @param viewer the graph viewer + * @return the vertex + */ + public static V getVertex(MouseEvent e, VisualizationViewer viewer) { + if (e == null) { + return null; + } + + Rectangle2D footprintRectangle = getFootprint(e); + LayoutModel layoutModel = viewer.getVisualizationModel().getLayoutModel(); + GraphElementAccessor pickSupport = viewer.getPickSupport(); + if (pickSupport == null) { + return null; + } + + if (pickSupport instanceof ShapePickSupport) { + ShapePickSupport shapePickSupport = + (ShapePickSupport) pickSupport; + return shapePickSupport.getVertex(layoutModel, footprintRectangle); + } + + TransformSupport transformSupport = + viewer.getTransformSupport(); + Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint()); + return pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY()); + } + + private static Rectangle2D getFootprint(MouseEvent e) { + int pickSize = Integer.getInteger(PICK_AREA_SIZE_PROPERTY, 4); return new Rectangle2D.Float( e.getPoint().x - pickSize / 2f, e.getPoint().y - pickSize / 2f, @@ -65,44 +109,4 @@ public class GhidraGraphMouse extends DefaultGraphMouse layoutModel = viewer.getVisualizationModel().getLayoutModel(); - GraphElementAccessor pickSupport = - viewer.getPickSupport(); - if (pickSupport instanceof ShapePickSupport) { - ShapePickSupport shapePickSupport = - (ShapePickSupport) pickSupport; - return shapePickSupport.getEdge(layoutModel, footprintRectangle); - } - - TransformSupport transformSupport = - viewer.getTransformSupport(); - Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint()); - return pickSupport.getEdge(layoutModel, layoutPoint.getX(), layoutPoint.getY()); - } - - AttributedVertex getPickedVertex(MouseEvent e) { - if (e == null) { - return null; - } - Rectangle2D footprintRectangle = getFootprint(e); - LayoutModel layoutModel = viewer.getVisualizationModel().getLayoutModel(); - GraphElementAccessor pickSupport = - viewer.getPickSupport(); - if (pickSupport instanceof ShapePickSupport) { - ShapePickSupport shapePickSupport = - (ShapePickSupport) pickSupport; - return shapePickSupport.getVertex(layoutModel, footprintRectangle); - } - - TransformSupport transformSupport = - viewer.getTransformSupport(); - Point2D layoutPoint = transformSupport.inverseTransform(viewer, e.getPoint()); - return pickSupport.getVertex(layoutModel, layoutPoint.getX(), layoutPoint.getY()); - } - } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java new file mode 100644 index 0000000000..cfac3f259c --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtVertexFocusingPlugin.java @@ -0,0 +1,57 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.graph.visualization.mouse; + +import java.awt.event.MouseEvent; + +import ghidra.graph.visualization.DefaultGraphDisplay; +import ghidra.service.graph.AttributedVertex; + +/** + * A mouse plugin to focus a vertex when clicked + * + * @param the vertex type + * @param the edge type + */ +public class JgtVertexFocusingPlugin extends AbstractJgtGraphMousePlugin { + + private DefaultGraphDisplay graphDisplay; + + public JgtVertexFocusingPlugin(DefaultGraphDisplay graphDisplay) { + this.graphDisplay = graphDisplay; + } + + @Override + public void mousePressed(MouseEvent e) { + if (!checkModifiers(e)) { + return; + } + + selectedVertex = getVertex(e); + } + + @Override + public void mouseClicked(MouseEvent e) { + if (selectedVertex == null) { + return; + } + + graphDisplay.setFocusedVertex((AttributedVertex) selectedVertex); + + // Note: do not consume the event. We will just focus our vertex, regardless of further + // mouse event processing. + } +} diff --git a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties index 68092f7035..627ce97c18 100644 --- a/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties +++ b/Ghidra/Features/GraphServices/src/main/resources/jungrapht.properties @@ -62,3 +62,10 @@ jungrapht.initialDimensionVertexDensity=0.3f jungrapht.minScale=0.001 jungrapht.maxScale=1.0 + + +11:45 AM + +# not using spatial data structures for edges (fixed in versions after 1.0) +jungrapht.edgeSpatialSupport=NONE +jungrapht.vertexSpatialSupport=NONE diff --git a/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm b/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm index d23c6eac22..0cb44ad2d6 100644 --- a/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm +++ b/Ghidra/Features/ProgramGraph/src/main/help/help/topics/ProgramGraphPlugin/ProgramGraph.htm @@ -13,7 +13,7 @@

    Graphing the Program

    -

    Graph Output

    +

    Graph Output

    To display or export a graph, Ghidra supports multiple graph services. Ghidra has two built-in graph services; one to display a graph and one to export a graph. Before invoking @@ -21,7 +21,7 @@ will direct the output of the graph function to the active graph service. To select a graph service, use the GraphGraph Output

  • menu. -

    Graph types

    +

    Graph types

    Program control flow Graphs can be created and then shown using an appropriate graph service. A control flow graph is a representation of the flow from one portion of the code to another. The nodes of the graph represent blocks of code and the edges represent flow between @@ -49,7 +49,7 @@

    Selection and Location events are synchronized between each graph and the other windows in the tool. -

    Selection

    +

    Selection

    The current selection within the graph display is represented by a red box around selected nodes as shown below on the node labeled "00408133". A node is selected if any addresses it represents are contained within the @@ -74,7 +74,7 @@ from the basic blocks found within the selected subroutine.

-

Location

+

Location

The node containing the current address location is marked with a large red arrow as shown below on the graph node labeled "00408133".

@@ -95,7 +95,7 @@ current address location within Ghidra to change to the minimum address represented by the graph node.

-

Graph Representation

+

Graph Representation

By Default, the graphs use the following icons and colors to represent the nodes and edges.

@@ -220,99 +220,7 @@ -

Block Flow Graph

- -

A Block Flow Graph consists of nodes that represent Basic blocks of contiguous instructions. - Basic blocks are broken up by any instruction that causes a change in execution flow. All Jump, - Call, Branch, and Return instructions can cause the execution flow to change. Arithmetic and - store/load instructions do not break a Basic block because they do not change the execution - flow. A labeled instruction will always start a block regardless of the instruction type.

-

For example:

- -

- -

Would generate the following graph:

- -

-

- -
-

If there is a current selection, the nodes and edges - will be restricted to blocks of code that fall within the selection.

-
- -

To Graph Block Flow Using the default model,

- -
    -
  1. Select Graph Block Flow
  2. - -
  3. A new graph window is created
  4. -
- -

Graph Code Flow

- -

A Code Flow Graph is an extension of a Block Flow - Graph in which each graph node (i.e., vertex) contains the list of instructions contained - within the associated block. The list of instructions are passed to the graph as the vertex - label.

- -


-
-

- -

Graph Calls

- -

A graph of the call instruction flow from one subroutine to another can be created with - Graph Calls. The graph is created using the default - Call Model. Several Subroutine Models are available. Each model provides a slightly - different perspective on what constitutes a subroutine.

- -
-
-

If there is a current selection, the nodes and edges - will be restricted to blocks of code that fall within the selection.

-
- -

To Graph Calls Using the default model,

- -
    -
  1. Select Graph Calls
  2. - -
  3. A new graph window is created
  4. -
- -

To Graph Calls Using a specific model*,

- -
    -
  1. Select Graph Calls Using Model <a Call Model>
  2. - -
  3. - Select one of - -
      -
    • Isolated Entry Model
    • - -
    • Multiple Entry Model
    • - -
    • Overlapped Code Model
    • - -
    • Partitioned Code Model
    • -
    -
  4. - -
  5. A new graph window is created
  6. -
- -
-

*For a more thorough description of each Call Block - Model (i.e., Subroutine Model), see Block - Models. The specific list of models presented to the user may vary depending upon the - set of block models configured into the tool.

-
-
-

Reuse Graph

When Reuse Graph is turned on, creating any new graphs will re-use the active graph @@ -378,6 +286,123 @@

When Show Location is turned off, the graph view will not change as the current address location changes.

+ + +
+
+
+
+
+
+
+
+
+
+
+ +

Block Flow Graph

+ +

A Block Flow Graph consists of nodes that represent Basic blocks of contiguous instructions. + Basic blocks are broken up by any instruction that causes a change in execution flow. All Jump, + Call, Branch, and Return instructions can cause the execution flow to change. Arithmetic and + store/load instructions do not break a Basic block because they do not change the execution + flow. A labeled instruction will always start a block regardless of the instruction type.

+ +

For example:

+ +

+ +

Would generate the following graph:

+ +

+

+ +
+

If there is a current selection, the nodes and edges + will be restricted to blocks of code that fall within the selection.

+
+ +

To Graph Block Flow Using the default model,

+ +
    +
  1. Select Graph Block Flow
  2. + +
  3. A new graph window is created
  4. +
+ +
+ +

Rename Vertex

+
+

Allows the user to rename the symbol represented by the given vertex. +

+
+ +
+ + +

Graph Code Flow

+ +

A Code Flow Graph is an extension of a Block Flow + Graph in which each graph node (i.e., vertex) contains the list of instructions contained + within the associated block. The list of instructions are passed to the graph as the vertex + label.

+ +


+
+

+ +

Graph Calls

+ +

A graph of the call instruction flow from one subroutine to another can be created with + Graph Calls. The graph is created using the default + Call Model. Several Subroutine Models are available. Each model provides a slightly + different perspective on what constitutes a subroutine.

+ +
+
+

If there is a current selection, the nodes and edges + will be restricted to blocks of code that fall within the selection.

+
+ +

To Graph Calls Using the default model,

+ +
    +
  1. Select Graph Calls
  2. + +
  3. A new graph window is created
  4. +
+ +

To Graph Calls Using a specific model*,

+ +
    +
  1. Select Graph Calls Using Model <a Call Model>
  2. + +
  3. + Select one of + +
      +
    • Isolated Entry Model
    • + +
    • Multiple Entry Model
    • + +
    • Overlapped Code Model
    • + +
    • Partitioned Code Model
    • +
    +
  4. + +
  5. A new graph window is created
  6. +
+ +
+

*For a more thorough description of each Call Block + Model (i.e., Subroutine Model), see Block + Models. The specific list of models presented to the user may vary depending upon the + set of block models configured into the tool.

+
+

diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java index 858fc4a804..7a03962fca 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockModelGraphDisplayListener.java @@ -26,6 +26,7 @@ import ghidra.program.model.block.*; import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolTable; import ghidra.service.graph.*; +import ghidra.util.HelpLocation; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -47,6 +48,7 @@ public class BlockModelGraphDisplayListener extends AddressBasedGraphDisplayList display.addAction(new ActionBuilder("Rename Vertex", "Block Graph") .popupMenuPath("Rename Vertex") .withContext(VertexGraphActionContext.class) + .helpLocation(new HelpLocation("ProgramGraphPlugin", "Rename Vertex")) // only enable action when vertex corresponds to an address .enabledWhen(c -> getAddress(c.getClickedVertex().getId()) != null) .onAction(this::updateVertexName) diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphComponent.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphComponent.java index f1141d40ae..f7d4c526e6 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphComponent.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphComponent.java @@ -1128,9 +1128,13 @@ public class GraphComponent, G e private V selectedVertex; - @SuppressWarnings("deprecation") // deprecated until we fix the checkModifiers() code public VertexClickMousePlugin() { - super(InputEvent.BUTTON1_MASK); + super(InputEvent.BUTTON1_DOWN_MASK); + } + + @Override + public boolean checkModifiers(MouseEvent e) { + return e.getModifiersEx() == modifiers; } @Override @@ -1203,6 +1207,5 @@ public class GraphComponent, G e public void mouseExited(MouseEvent e) { // stub } - } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphAbstractGraphMousePlugin.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphAbstractGraphMousePlugin.java index 4ac46b1d3b..2e256c4042 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphAbstractGraphMousePlugin.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphAbstractGraphMousePlugin.java @@ -32,6 +32,9 @@ import ghidra.graph.viewer.*; *
  • We clear state on mouseReleased() and mouseExited(), since we will get * at least one of those calls
  • * + * + * @param the vertex type + * @param the edge type */ //@formatter:off public abstract class VisualGraphAbstractGraphMousePlugin the vertex type + * @param the edge type */ public class VisualGraphAnimatedPickingGraphMousePlugin> extends AnimatedPickingGraphMousePlugin implements VisualGraphMousePlugin { @@ -32,7 +35,7 @@ public class VisualGraphAnimatedPickingGraphMousePlugin extends AbstractGr super(0); } + @Override + public boolean checkModifiers(MouseEvent e) { + return e.getModifiersEx() == modifiers; + } + + @Override public void mouseDragged(MouseEvent e) { // don't care } + @Override public void mouseMoved(MouseEvent e) { installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e); } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphEventForwardingGraphMousePlugin.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphEventForwardingGraphMousePlugin.java index a280b79851..e19320608f 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphEventForwardingGraphMousePlugin.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphEventForwardingGraphMousePlugin.java @@ -38,9 +38,9 @@ public class VisualGraphEventForwardingGraphMousePlugin viewer) { - super(InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK); + super(InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | + InputEvent.BUTTON3_DOWN_MASK); this.viewer = Objects.requireNonNull(viewer); viewer.addPostRenderPaintable(paintable); } @Override public boolean checkModifiers(MouseEvent e) { - int eventModifiers = e.getModifiers(); + int eventModifiers = e.getModifiersEx(); eventModifiers = turnOffControlKey(eventModifiers); return ((eventModifiers & getModifiers()) == eventModifiers); } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPickingGraphMousePlugin.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPickingGraphMousePlugin.java index 51c33b3fa0..5bd2779934 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPickingGraphMousePlugin.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphPickingGraphMousePlugin.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,18 +38,17 @@ public class VisualGraphPickingGraphMousePlugin { public VisualGraphSatelliteAbstractGraphMousePlugin() { - this(InputEvent.BUTTON1_MASK); + this(InputEvent.BUTTON1_DOWN_MASK); } public VisualGraphSatelliteAbstractGraphMousePlugin(int selectionModifiers) { diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteAnimatedPickingGraphMousePlugin.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteAnimatedPickingGraphMousePlugin.java deleted file mode 100644 index 26dec6c33a..0000000000 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteAnimatedPickingGraphMousePlugin.java +++ /dev/null @@ -1,25 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.graph.viewer.event.mouse; - -import ghidra.graph.viewer.VisualEdge; -import ghidra.graph.viewer.VisualVertex; - -public class VisualGraphSatelliteAnimatedPickingGraphMousePlugin> - extends VisualGraphAnimatedPickingGraphMousePlugin { - - // TODO - delete this class--it should not longer be needed -} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteEdgeSelectionGraphMousePlugin.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteEdgeSelectionGraphMousePlugin.java deleted file mode 100644 index 1b9f456a90..0000000000 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteEdgeSelectionGraphMousePlugin.java +++ /dev/null @@ -1,25 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.graph.viewer.event.mouse; - -import ghidra.graph.viewer.VisualEdge; -import ghidra.graph.viewer.VisualVertex; - -public class VisualGraphSatelliteEdgeSelectionGraphMousePlugin> - extends VisualGraphEdgeSelectionGraphMousePlugin { - - // TODO this class probably can be deleted now -} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteNavigationGraphMousePlugin.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteNavigationGraphMousePlugin.java index 22bf89e201..ba90a894ff 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteNavigationGraphMousePlugin.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteNavigationGraphMousePlugin.java @@ -25,7 +25,7 @@ public class VisualGraphSatelliteNavigationGraphMousePlugin { public VisualGraphSatelliteNavigationGraphMousePlugin() { - super(InputEvent.BUTTON1_MASK); + super(InputEvent.BUTTON1_DOWN_MASK); } @Override diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteScalingGraphMousePlugin.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteScalingGraphMousePlugin.java index 71756c346d..f84deae84b 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteScalingGraphMousePlugin.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/event/mouse/VisualGraphSatelliteScalingGraphMousePlugin.java @@ -46,7 +46,7 @@ public class VisualGraphSatelliteScalingGraphMousePlugin the vertex type + * @param the edge type */ public class VisualGraphScalingGraphMousePlugin> extends ScalingGraphMousePlugin implements VisualGraphMousePlugin { @@ -51,8 +54,8 @@ public class VisualGraphScalingGraphMousePlugin the vertex type + * @param the edge type */ public class VisualGraphTranslatingGraphMousePlugin> extends AbstractGraphMousePlugin @@ -43,9 +43,8 @@ public class VisualGraphTranslatingGraphMousePlugin viewer = getGraphViewer(e); @@ -158,7 +162,7 @@ public class VisualGraphTranslatingGraphMousePlugin the vertex type + * @param the edge type */ public class VisualGraphZoomingPickingGraphMousePlugin> extends VisualGraphAbstractGraphMousePlugin { - // TODO for deprecated usage note, see the VisualGraphMousePlugin interface public VisualGraphZoomingPickingGraphMousePlugin() { - super(InputEvent.BUTTON1_MASK); + super(InputEvent.BUTTON1_DOWN_MASK); this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java index c3ca2e97eb..c776dc3f94 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java @@ -72,10 +72,11 @@ public class GraphServicesScreenShots extends GhidraScreenShotGenerator { AttributedEdge e2 = graph.addEdge(v1, v3); e2.setAttribute("EdgeType", "Unconditional-Call"); - display.setGraph(graph, "test", false, TaskMonitor.DUMMY); + display.setGraph(graph, "Program Graph", false, TaskMonitor.DUMMY); waitForSwing(); setGraphWindowSize(700, 500); - ((DefaultGraphDisplay) display).centerAndScale(); + runSwing(() -> ((DefaultGraphDisplay) display).centerAndScale()); + waitForSwing(); captureProvider(DefaultGraphDisplayComponentProvider.class); } @@ -95,7 +96,7 @@ public class GraphServicesScreenShots extends GhidraScreenShotGenerator { provider.getComponent().requestFocus(); paintFix(window); }); - + waitForSwing(); } }