From 0b0b330bac6786c2d26a75155aaea0ff38f03ff8 Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:13:24 -0400 Subject: [PATCH] GP-5326 created edit structure field action and dialog. --- Ghidra/Features/Base/certification.manifest | 1 + .../main/help/help/topics/DataPlugin/Data.htm | 40 ++- .../DataPlugin/images/EditFieldDialog.png | Bin 0 -> 7673 bytes .../app/plugin/core/data/DataPlugin.java | 44 ++- .../plugin/core/data/EditDataFieldDialog.java | 305 ++++++++++++++++++ .../core/data/RenameDataFieldAction.java | 89 ----- .../core/datamgr/util/DataTypeUtils.java | 32 +- .../core/searchtext/SearchTextPlugin.java | 2 +- .../plugin/core/data/EditFieldDialogTest.java | 232 +++++++++++++ .../database/data/DataTypeComponentDB.java | 7 +- .../model/data/AlignedStructureInspector.java | 9 +- .../program/model/data/DataTypeComponent.java | 10 +- .../model/data/DataTypeComponentImpl.java | 11 +- .../model/data/ReadOnlyDataTypeComponent.java | 13 +- .../screenshot/DataPluginScreenShots.java | 14 +- 15 files changed, 672 insertions(+), 137 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 98b1db44e8..7b6b928e40 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -308,6 +308,7 @@ src/main/help/help/topics/DataPlugin/images/CreateStructureDialog.png||GHIDRA||| src/main/help/help/topics/DataPlugin/images/CreateStructureDialogWithTableSelection.png||GHIDRA||||END| src/main/help/help/topics/DataPlugin/images/DataSelectionSettings.png||GHIDRA||||END| src/main/help/help/topics/DataPlugin/images/DefaultSettings.png||GHIDRA||||END| +src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png||GHIDRA||||END| src/main/help/help/topics/DataPlugin/images/InstanceSettings.png||GHIDRA||||END| src/main/help/help/topics/DataTypeEditors/DataTypeSelectionDialog.htm||GHIDRA||||END| src/main/help/help/topics/DataTypeEditors/EnumEditor.htm||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm index 43e448a829..5d9100e00c 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm @@ -970,21 +970,14 @@ quickly changing the name of a single member:
---
The "Field Name" field must be added to the - "Open Data" tab in the Code Browser header in order for the - data structure field names to show up in the Code Browser.
The second way is more useful for changing the names of multiple members:
-@@ -1373,6 +1362,27 @@ according to the information in the program. + +-
You cannot set the field name of undefined - member
++As a convenience, a structure or union field can be edited directly from the listing without + bringing up the entire structure or union editor. To edit a field, click anywhere on the line + displaying that field in the listing and then right click and select Data
+Edit Field from the popup context menu.
Edit Field Dialog
++
![]()
+
- Field Name: The name of the structure or union field can be changed here.
+- Comment: The comment for the field can be entered or changed here.
+- DataType: The data can be changed here. The text field is read only so you must + press the ... button to bring up the datatype chooser to change the datatype.
++ +
If a default field (a field with an undefined + datatype (??)) is named or given a comment, the datatype will be set to undefined1 if + no specific datatype is set. This is because undefined fields are not stored and therefore + can't have an associated name or comment.
Provided By: Data Plugin
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png new file mode 100644 index 0000000000000000000000000000000000000000..47f90f76f20d5b283a2ae536e9aa53ce2324af6b GIT binary patch literal 7673 zcmb_>XH=72(=HYefk^L7Kzc`|H$#^qNHc`ql!VZWH1kL=BE1(W(n1wT=!7QKfCvN( zO;EZ>lTP47-}n30_vf5-)_cyMd+n8bXZFnO>$);Ci7_lWqaR67`(w*@WYOs#ufUG4>Ap>@sy#(6f8jiP^gPh zx!DoV6gKIB@?_ksf!+P*+nEv7Y;GC{v!L@nAN1<{tA&n12bSz!AL@#^vfQ9_@AFc; z-1pA0R8;-cb|@N2zqZ*^SrK<{C4PV1Qluj8XPGp^l9wr?FWk-2`rgOs?|$C|jijE= zzDx`2{jfuX5*`_kZszm!kuTdGZ_7heNniv*ACAd4w@b#m-DEtKX`1d@%^tukZ`rR^ zRlAYnhXN48m9tNCOt%JpD?-c%O+SA)J8w;_RlI2g=%-MOEXS61vmLhME9eCh6!S*q z4dwd3D9Teg43wGI8Q3$o2tJbq%y3;F4_!l6(je>KNgz9FGZqw%jP5Yp7_Hb)(pPvm z?;bFNxqN{uERp2E^9nZs4mbv9fI-uv zrR=2{YlYHI&Lov6=jk_vka>vdZ@t$i3&a3c!V tLN zz(3U8nK8Efz7^N#?}x9aHX~aqwqcE1bb9UbH=gZ>kJ}7$Ko?C!ip5-7=b`9;^A4BJ zPl5i$YsD09wib+_bM7tcAF*0lU))+-b5v$(5jh}G&)9s!9ky_ f3xuV&K&XFbBC d8W{EL4N;gvJsUykoD{C zPKVuz^YUt=k0ae8)6v9WLohM*T8*R64Y+Tz)%=}r_rJo$MvJ9)uC#+(88zSS9^Kc{ zR!0Du%v+3{{X53T=b_tq8BWoizO}zzT<{halkytUf;4TZ16gM*gULf APdHrJiR%^J9N`)nXxWwe(;|=m2W= z^psd7vb tl@iKI4&^npUriSfdFM?8_&Sgv5#_7e0@u4=-OM8eEFR*QJ&LZF(7Sh zB;{4E*F0h mHClXq(u@l%tG?8Mc|6;!Vol4~xNyee z?b8#g`t3@*v2JYU!(}LcnX`G3TvluVYUa3;bom!cM3i8!cE!IyJ`=_B3c1utGSO@@ znSLYQrV?Y`T`Rrk3VwyU@t(e=k}-gLnze%q_A<(L4D4xu)Cr4b6}T$e!VJm0hn9eY z)L78MKZMXHktWQ|C&4reZ$KjnXH|gjJ(t~Yd*T~phZ~fkDFs2m&KfT}zPQ}wyBmB+ zFS?>m*XMx*P{8WeDE!HL^X68-#SJjirv1@b*TWel*$Vcb(<9)LFZ{E3h=ol<{`8&u zSfhS JaM@K49Y%i z+v+f4hi!FTY25dD;wK296M79r$_A3hK*3gYGr9cPb?4ozM&N?Ay*6OY@_e%r%_6jI zp|TKwnBS$m5IVf`)gfid?=X_?VbW%iVBv^rUgpG5!uoB5o#hH |vn41|B701g$khg}7#p-CxmfYBk-1 z&(PGLsp>2Q=>)2a11GTx(Bp*1VaI0f=t%{3xK5eEu;Fl!#(9HF$X@5*>U`p>`*C6I z{xX5d4IE_<1>PhZs${xEEgoGm#|iZtJyY28Nut+?mF;BY_mDiyM$VB|FAhVrrdxA@ z5y@K}&Z$_o=w#TNjG0s3U(hPTwt(E=apCIJMW~v(+4ZbFrX8DN*VZjRI|cGu5aZV# zmr{>p!C<{vM!EArAM8PV$CvI`)rWiXBRt`WPSiChqU|Tu!)6~}IWeH5Mk|idoHL8{ z*oY*Ru^D{sp)&YorW$b|p`zg?R5V!61`ieYjt4)t8}VG<@s#gX$VR*9APvWFdcsV8 z?YP4DPq+z1U3!lRgkaq95t17CAe^v#5nYD*vi0R#kd0RzT$Pk-XnBAC%Z}s=QFT}b z1xZ*^ZssFcFFY!oDw(ug8W 91P<{0x? zbu|ssXdxR=(ImEoi9y9Df?8dN!ic_TID>eqKl6U_d(`Aup&2PJ!uf?989606q|oBv zDCX&`$IGms3A1gg*k>9V={c<1VGYVSHVSblXAlHKbVyq()fL`4nb0un0%w0IdKDDl zfg6at(8XZ<;1V20>4d}CZyV=W$R?E|fg?sQRFIYKm9)?WWg`eJyzCyLUL?vV`+3-f zGwXw~TPt`^Wv0icb<^9F %H=ufhE>or+ecUQ=Uy~~jS!+tEJ`l|XJUU*h+yF6z-*SBhb!9x2g zzh8Z)h+nv%dv3ACM?M~1oluQyV?l+24r*eMj>d&bzumbb4UwljTlY=eI^uIH6YjaJ zmuO2>x7F r7)V z5~Z+C(t5A$5mIuMB4pC+D}s)Lu650-`F57MXEjr~;VWa!h=pnEV_g}z3g?8yL9@%H z12Ez>gV)uF9d+vD&as`&x{g^FOhh_fBQb-&Q!739BT5}v&T%F~tyYe!SK+t!YW14z zo##vz)V>LNhM#yQR)4)OitMyndfoD6Q)rQ5fi(Ible$S0p E}riyLTd~1b*LW zW9~oXv(=LX ztOcG4Ay>v)`WUwOLoU*aQfCzMF3}lYTff|z&P8WB=m@wIrzu5c0-1h14*YH?JQ$?P zRgj5Itmo3|tu8^=;Va_lD4J4?a-!eGZ!V>NdXHL4Q2pq6df1oXAsi#}MDb@W$MLSY zbPY{5@^&J95oXh9cO$1^D*zK9bHuWK80BT=$D={G0rHT^$XrtYY)^XUA)GpUW6aM_ zi@=bkx3fCYlyIkjf{OC5|BC3K9#w7I`lI%n6U^KtPX&5lEcf$~H>Bm>ZxGmT^6o`M zn?a@#UjmVTp1>pQs{4<%YXM0m1TaP^Ln__O$tlTpsimaen`3!|4GSBsX~u9we9Pbz znEV(UDD#wOVg_1({}nJD=!!w0$~h2~;ZiBMz6R+*a%N@|ZX2y(GGs!?X%&EwLb0$n ziEWS6@ZC@BpWY5pixnMKr|48`(WV^dML$tX`pbYwWeDrrx!0ctgTzl0Vnj)VR0V#; z4Y_OI;C*$QgN>sAKj|*`{mG5lY*Sco^uJH@+JcqeY+?PS*S6U1Zgo>wy`77l83QKo zB!6#SIH^L6p7!&=YK7HbZ}s%p6g_MCZk;*26`_lW_f$)|hr|!WJk3crguRgqgXmJ2 zu!c#77{X%gVj$$MCgB#_B!z@jkW8gBrI({-qyFd)L7+%YS;M+X0SHk^lhaw!a6JH= zl}pkj&1Q=$ysy|=YeMZm5mi6kMiu03uKR_54b0QX8W5!0VY2|2d~K@#uP{pe5yq@g zf@c14)7TI;^bf>A!kG*q%B)@?>Pz2EtTK=~#Y6$))`4vOQ3o@=6rHR5(Zvx?HtVa$ zdt}Mk%TXz1{F<=WAh@j-ar)VFCm*!WHa}|a1LnJPU4Jk&a$dF1KfgOukfvDrC1Gq( zT5sCzJF|1_c}mYgHwmd!gD^u5v<_j&M(h2IaI;?_+jav*URPin*Ye#us;UPz;cha@ zlcV>U?rYdjI;$W@Se=k;6rGf@wXx00*Z3rahdnix$cFHFx@8k5vu87ij!LH;M@i_y z!=>RY#5JF;lL=Kng7RH}D|WCSH?8r;wVmRp%?!ElKr?5v*#upRYwxaI)_)!zy?=|7 zsrxCliWs5C>6K__Z!_zwidI$dVxT3ovmb;L@xEhiMTU{Bk6(uS{yQK`D?;Vw^-qGo zqsQQ?>gB7>3ujkVOBHR^I)(=~g_lSM0v(4ylerZvHE?&~H7iG%T@f+g7J0Xn4=%Nr zIZ|RSAddAi3n`@r1OVl0)BL*U^YF}vjyUT&A37%GBZ_`<3BOOV`+t3C{-6QvU8<`G zb=Q!ZBo?Nh@H8G!^y?)tAxmEA4yg+gDKBZ%+?06zbx){*?iZ@>0T%FyyCJi};;6Rj z(>ISp?htaT`NgWtW@=|s) eaE|RO7v+QY^4_av nO_!j9=u<~>vPGm}@emsNmmb7|o^)-Otq&y2`Y z8m#;A2LR_6PsAJ)#t0mUF`D&AO Fq&zV4Rx$u zGCTU~Dtsg*X1ilOJ?mxgjqP%PmpexUTvESUWVvhyIgh@yX8mDm*%o-%ZkC$GA|?A1 zz6WgCE0i>X?eei4i^p&3=&doo$s+LRT>O3q&fdOsm_oCNAP^*y5L#->U2i>5eD`O* za+D)gi8M(VHH!)O|3J>h|7T?=zoNa(byk>vdM%K#W7I{ eYvtA|}!&szNJRf8Bg8tbPsc{0{qD`9yllazF0prS+8DOKw)zEK=1usGr=4hD%-avmt;f-1?aPX~-Hn50QFZsF zSb~u;G2O@4 YLMVQxn{OXrA-F(YpQ}&c4+f z`+) 4br3gMa@{;c-~W8^%n;^TEii3*eleoX&hMl4n=FV2 zt|E?#E_|ffJ1!R5C;3dT_~uIQxK`-&lDO4}ST<5Dk^{6K_qI@!wqCZ#zzZHx&{gDM z58V?nhG`2wMH7=>t8skKVFcAT##{{0#jw^ZPErQ|HU~|#omGiA)E`R5KcL#kL09B( zZ_1w1KG2JPZ5uWFq&=A7X(T==&W?pwz}!6PMtAEa>&55v+aNY0{uXrTXEO-h-UKZ; z?s*o3wkg)0rB~JYbsN8=G)S%vn>t09>yeUgsxz3)AAR98(pqS< ?04z Ulpd$q+q7rTi#{F{!CY zR^hd;CvRy4H3^jp7 t^K%z0H2YsS&}akoc{yiTQxmH@C=3n5liQ z@bzzR*~dUGXli)P@))kvB`cAb+Bf-Jm=vZ@`^Wj)5OP8}F)L=-@oZ9Q$09)##JsY9 zmfOkF1rSWfE(A1rg_;mblJ~xuA2*8}uskE^STrUeTHau}JL_lK9Jias2e|rh3_6qH z3B`)g{$B|zXfh&P#ohmT6>|uj`ZrA-ymijYqjzVb5sKvI`-k+MraD)@<|Rf=0C!g& z;g;cmY?cl#dUs%l%==E6!WU$iB8S0*z5|h#*xdY-^6%kp&EmiX2b4;aq~3?PUB(H4 z7VDJ~;f2JrTQuu169NpQ|0fKa%1~gEp^PixyJ{=LXs1<0gYYWlKRkq4LllsNf?t(u zN^=nSm#yB{B9>~5=fw%#8@bc6jsenTFn1s(af-n2|G8dRLf2Pv{ulE{7d1a8qV^k< z!q=k$S8sh2x1a_N*8WEY%>S?U@xOZ7|D++EAoQ=m D=8)l&Fc=KxJkzZDxewX z|5aa$Dx$b#$3D3=y3iH4IDO*{F%OFX5B+bkz`O~Ab7L^21j8F`cD|N} ktgDJuAX^n{G^@iVR^aH>XhQgtHMQpJmiUI{}F0Ol~Ms(bqbP-|QM4JN| zT&&cz#w7pBs}nV(H%*Y1iaJvtFWqVRfo`HM2i=3|*RKHJDJbqe_$;L-S}G|!2(?sZ z K-y!Dnlac=0`=SRq;W{t2uwaF|^A>kS{%JudAu7kk%5cVe9hFIsbf4Mj= zw|#f1M?ldy0hoHL>fZAu*{NiK)XISeQj+B;Uf|zkf=?Py&sCJ5P>zR7#21I=Z54#? zepG~xEDT92Kk(p*ak+@EPtFBkaE43>1BB=!LzwU|_oVbT>z606836O#VYUpNOo{Yw zP_T?fCl>(=_SC~2L7SY`K#0l*euOBY&x4hjy?nO*YJ @;CcA59?z=cG6t3COtcIYr$Iz*Ss zWHz?`E>cv6fUg*Wp%{WoWK*RC&ve5fV54$rKqv&vP1poPx)ApM$q?P8a9a!|;h{@F zv(d>c>Cl}^>7M$(Z;DKdmc%VY+5fj3m3l;&S0(%Wok+czTE(aA4od+^b-1Z;&tQl1 w^4kREAu6W;9!yGK_(xw+0i&f=p;x!|dnOr?VOmjyzjTRoGz`>hfVQFk3s favoriteActions = new ArrayList<>(); @@ -113,7 +113,7 @@ public class DataPlugin extends Plugin implements DataService { public DataPlugin(PluginTool tool) { super(tool); - addActions(); + createActions(); favoritesUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateFavoriteActions()); } @@ -135,7 +135,7 @@ public class DataPlugin extends Plugin implements DataService { /** * Add actions */ - private void addActions() { + private void createActions() { recentlyUsedAction = new RecentlyUsedAction(this); recentlyUsedAction.setEnabled(false); @@ -147,12 +147,17 @@ public class DataPlugin extends Plugin implements DataService { createArrayAction = new CreateArrayAction(this); tool.addAction(createArrayAction); - renameDataFieldAction = new RenameDataFieldAction(this); - tool.addAction(renameDataFieldAction); - pointerAction = new PointerDataAction(this); tool.addAction(pointerAction); + new ActionBuilder("Edit Field", getName()) + .popupMenuPath("Data", "Edit Field") + .keyBinding("ctrl shift E") + .withContext(ListingActionContext.class) + .enabledWhen(this::canEditField) + .onAction(this::editField) + .buildAndInstall(tool); + // Data instance settings action based upon data selection in listing new ActionBuilder("Data Settings", getName()).sharedKeyBinding() .popupMenuPath(DATA_SETTINGS_POPUP_PATH) @@ -832,4 +837,27 @@ public class DataPlugin extends Plugin implements DataService { return true; } + public DataType pickDataType() { + return dtmService.getDataType(""); + } + + private boolean canEditField(ListingActionContext context) { + ProgramLocation location = context.getLocation(); + int[] componentPath = location.getComponentPath(); + return componentPath != null && componentPath.length > 0; + } + + private void editField(ListingActionContext context) { + Program program = context.getProgram(); + ProgramLocation location = context.getLocation(); + Address address = location.getAddress(); + int[] path = location.getComponentPath(); + DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path); + if (component != null) { + EditDataFieldDialog dialog = + new EditDataFieldDialog(tool, dtmService, location, component); + tool.showDialog(dialog); + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java new file mode 100644 index 0000000000..0a937a984c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java @@ -0,0 +1,305 @@ +/* ### + * 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.app.plugin.core.data; + +import java.awt.BorderLayout; + +import javax.swing.*; + +import org.apache.commons.lang3.StringUtils; + +import docking.DialogComponentProvider; +import docking.widgets.button.BrowseButton; +import ghidra.app.cmd.data.CreateDataInStructureCmd; +import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; +import ghidra.app.services.DataTypeManagerService; +import ghidra.framework.cmd.Command; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.util.HelpLocation; +import ghidra.util.MessageType; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.layout.PairLayout; + +/** + * Dialog for editing the name, comment, and datatype for a structure or union field. + */ +public class EditDataFieldDialog extends DialogComponentProvider { + + private JTextField nameField; + private JTextField commentField; + private JTextField dataTypeTextField; + + private DataTypeComponent component; + private PluginTool tool; + private DataType newDataType; + private ProgramLocation programLocation; + private DataTypeManagerService dtmService; + + /** + * Constructor + * @param tool The tool hosting this dialog + * @param dtmService the DataTypeManagerService used for choosing datatypes + * @param location the location of the field being edited + * @param dataTypeComponent the component of the field being edited + */ + public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService, + ProgramLocation location, DataTypeComponent dataTypeComponent) { + super("Edit Field Dialog", true, true, true, false); + this.tool = tool; + this.dtmService = dtmService; + this.programLocation = location; + this.component = dataTypeComponent; + setTitle(generateTitle()); + + addWorkPanel(buildMainPanel()); + initializeFields(); + setFocusComponent(nameField); + setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog")); + + addOKButton(); + addCancelButton(); + } + + @Override + public void dispose() { + super.dispose(); + programLocation = null; + component = null; + tool = null; + } + + /** + * Returns the pending new datatype to change to. + * @return the pending new datatype to change to + */ + public DataType getNewDataType() { + return newDataType != null ? newDataType : new Undefined1DataType(); + } + + /** + * Returns the text currently in the text field for the field name. + * @return the text currently in the text field for the field name + */ + public String getNameText() { + return nameField.getText(); + } + + /** + * Sets the dialog's name text field to the given text. + * @param newName the text to put into the name text field + */ + public void setNameText(String newName) { + nameField.setText(newName); + } + + /** + * Returns the text currently in the text field for the field comment. + * @return the text currently in the text field for the field commment + */ + public String getCommentText() { + return commentField.getText(); + } + + /** + * Sets the dialog's comment text field to the given text. + * @param newComment the text to put into the comment text field + */ + public void setCommentText(String newComment) { + commentField.setText(newComment); + } + + /** + * Sets the pending new datatype and updates the datatype text field to the name of that + * datatype. + * @param dataType the new pending datatype + */ + public void setDataType(DataType dataType) { + newDataType = dataType; + updateDataTypeTextField(); + } + + private void initializeFields() { + String name = component.getFieldName(); + if (StringUtils.isBlank(name)) { + name = component.getDefaultFieldName(); + } + nameField.setText(name); + commentField.setText(component.getComment()); + dataTypeTextField.setText(component.getDataType().getDisplayName()); + } + + @Override + protected void okCallback() { + if (updateComponent()) { + close(); + programLocation = null; + } + } + + private boolean updateComponent() { + if (!hasChanges()) { + return true; + } + Command cmd = new UpdateDataComponentCommand(); + if (!tool.execute(cmd, programLocation.getProgram())) { + setStatusText(cmd.getStatusMsg(), MessageType.ERROR); + return false; + } + return true; + } + + private boolean hasChanges() { + return hasNameChange() || hasCommentChange() || hasDataTypeChange(); + } + + private boolean hasCommentChange() { + String newComment = getNewFieldComment(); + if (StringUtils.isBlank(newComment) && StringUtils.isBlank(component.getComment())) { + return false; + } + return !newComment.equals(component.getComment()); + } + + boolean hasDataTypeChange() { + return newDataType != null && !newDataType.equals(component.getDataType()); + } + + boolean hasNameChange() { + String newName = getNewFieldName(); + if (newName.equals(component.getFieldName())) { + return false; + } + if (newName.equals(component.getDefaultFieldName())) { + return false; + } + return true; + } + + private String getNewFieldName() { + return nameField.getText().trim(); + } + + private String getNewFieldComment() { + return commentField.getText().trim(); + } + + private JPanel buildMainPanel() { + JPanel panel = new JPanel(new PairLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + nameField = new JTextField(20); + nameField.setEditable(true); + nameField.addActionListener(e -> okCallback()); + commentField = new JTextField(20); + commentField.setEditable(true); + commentField.addActionListener(e -> okCallback()); + + panel.add(new JLabel("Field Name:", SwingConstants.LEFT)); + panel.add(nameField); + panel.add(new JLabel("Comment:", SwingConstants.LEFT)); + panel.add(commentField); + panel.add(new JLabel("Datatype:", SwingConstants.LEFT)); + panel.add(buildDataTypeChooserPanel()); + + return panel; + } + + private JPanel buildDataTypeChooserPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 0)); + + dataTypeTextField = new JTextField(); + dataTypeTextField.setEditable(false); + BrowseButton browseButton = new BrowseButton(); + browseButton.setToolTipText("Browse the Data Manager"); + browseButton.addActionListener(e -> showDataTypeBrowser()); + + panel.add(dataTypeTextField, BorderLayout.CENTER); + panel.add(browseButton, BorderLayout.EAST); + return panel; + } + + private void showDataTypeBrowser() { + newDataType = dtmService.getDataType(""); + updateDataTypeTextField(); + } + + private void updateDataTypeTextField() { + if (newDataType != null) { + dataTypeTextField.setText(newDataType.getDisplayName()); + } + else { + dataTypeTextField.setText(component.getDataType().getDisplayName()); + } + } + + private String generateTitle() { + DataType parent = component.getParent(); + String compositeName = parent.getName(); + return "Edit " + compositeName + ", Field " + component.getOrdinal(); + } + + public String getDataTypeText() { + return dataTypeTextField.getText(); + } + + private class UpdateDataComponentCommand implements Command { + private String statusMessage = null; + + @Override + public boolean applyTo(Program program) { + if (component.isUndefined() || hasDataTypeChange()) { + DataType dt = getNewDataType(); + Address address = programLocation.getAddress(); + int[] path = programLocation.getComponentPath(); + Command cmd = new CreateDataInStructureCmd(address, path, dt, false); + if (!cmd.applyTo(program)) { + statusMessage = cmd.getStatusMsg(); + return false; + } + component = DataTypeUtils.getDataTypeComponent(program, address, path); + } + if (hasNameChange()) { + try { + component.setFieldName(getNewFieldName()); + } + catch (DuplicateNameException e) { + statusMessage = "Duplicate field name"; + return false; + } + } + if (hasCommentChange()) { + component.setComment(getNewFieldComment()); + } + return true; + } + + @Override + public String getStatusMsg() { + return statusMessage; + } + + @Override + public String getName() { + return "Update Structure Field"; + } + + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java deleted file mode 100644 index 88c6b85301..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java +++ /dev/null @@ -1,89 +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.app.plugin.core.data; - -import java.awt.event.KeyEvent; - -import docking.action.KeyBindingData; -import docking.action.MenuData; -import ghidra.app.context.ListingActionContext; -import ghidra.app.context.ListingContextAction; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.data.*; -import ghidra.program.model.listing.Data; -import ghidra.program.model.listing.Program; -import ghidra.program.util.FieldNameFieldLocation; -import ghidra.program.util.ProgramLocation; - -/** - * Base class for comment actions to edit and delete comments. - */ -class RenameDataFieldAction extends ListingContextAction { - - private DataPlugin plugin; - - public RenameDataFieldAction(DataPlugin plugin) { - super("Rename Data Field", plugin.getName()); - - setPopupMenuData( - new MenuData( - new String[] { "Data", "Rename Field" }, null, "BasicData")); - - setKeyBindingData(new KeyBindingData( - KeyEvent.VK_N, 0)); - - this.plugin = plugin; - setEnabled(true); - } - - @Override - protected void actionPerformed(ListingActionContext context) { - ListingActionContext programActionContext = - (ListingActionContext) context.getContextObject(); - PluginTool tool = plugin.getTool(); - Program program = programActionContext.getProgram(); - ProgramLocation loc = programActionContext.getLocation(); - Data data = program.getListing().getDataContaining(loc.getAddress()); - DataType type = data.getDataType(); - - if (type instanceof Composite) { - Composite comp = (Composite) type; - int[] compPath = loc.getComponentPath(); - for (int i = 0; i < compPath.length - 1; i++) { - DataTypeComponent subComp = comp.getComponent(compPath[i]); - type = subComp.getDataType(); - if (type instanceof Composite) { - comp = (Composite) type; - } - else { - return; - } - } - - Data instance = data.getComponent(compPath); - DataTypeComponent subComp = comp.getComponent(compPath[compPath.length - 1]); - RenameDataFieldDialog dialog = new RenameDataFieldDialog(plugin); - dialog.setDataComponent(program, subComp, instance.getFieldName()); - tool.showDialog(dialog); - } - } - - @Override - protected boolean isEnabledForContext(ListingActionContext context) { - return (context.getLocation() instanceof FieldNameFieldLocation); - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java index dd4a6b9e3f..3f5af5ec9f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java @@ -4,9 +4,9 @@ * 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. @@ -24,8 +24,12 @@ import javax.swing.Icon; import generic.theme.GColor; import generic.theme.GIcon; import ghidra.app.services.DataTypeQueryService; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; +import ghidra.program.model.data.Composite; import ghidra.program.model.data.Enum; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; import ghidra.util.Msg; import ghidra.util.exception.AssertException; import resources.MultiIcon; @@ -469,6 +473,30 @@ public class DataTypeUtils { return index; } + /** + * Finds the DataTypeComponent at an address and component path in a program. + * @param program the program to look for a datatype component + * @param address the address to look for a datatype component + * @param componentPath the component path (an array of indexes into hierarchy of nested + * datatypes) + * @return The datatype component at that address and component path or null if there is + * none at that location. + */ + public static DataTypeComponent getDataTypeComponent(Program program, Address address, + int[] componentPath) { + Data data = program.getListing().getDataContaining(address); + DataType dt = data.getDataType(); + DataTypeComponent comp = null; + for (int i = 0; i < componentPath.length; i++) { + if (!(dt instanceof Composite composite)) { + return null; + } + comp = composite.getComponent(componentPath[i]); + dt = comp.getDataType(); + } + return comp; + } + // finds the index of the first element in the given list--this is used in conjunction with // the binary search, which doesn't produce the desired results when searching lists with // duplicates diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java index 6acf86e9f1..688844313e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java @@ -383,7 +383,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList new ActionBuilder("Search Text", getName()) .menuPath("&Search", "Program &Text...") .menuGroup("search", subGroup) - .keyBinding("ctrl shift E") + .keyBinding("ctrl F") .description(DESCRIPTION) .helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search Text")) .withContext(NavigatableActionContext.class, true) diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java new file mode 100644 index 0000000000..8fab4338eb --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java @@ -0,0 +1,232 @@ +/* ### + * 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.app.plugin.core.data; + +import static org.junit.Assert.*; + +import org.junit.*; + +import docking.action.DockingActionIf; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; +import ghidra.test.*; + +public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest { + private TestEnv env; + private PluginTool tool; + private Program program; + private DockingActionIf editFieldAction; + private EditDataFieldDialog dialog; + private CodeBrowserPlugin codeBrowser; + private Structure structure; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + tool = env.getTool(); + tool.addPlugin(DataPlugin.class.getName()); + tool.addPlugin(CodeBrowserPlugin.class.getName()); + + DataPlugin plugin = getPlugin(tool, DataPlugin.class); + codeBrowser = env.getPlugin(CodeBrowserPlugin.class); + + program = buildProgram(); + env.open(program); + env.showTool(); + editFieldAction = getAction(plugin, "Edit Field"); + Data dataAt = program.getListing().getDataAt(addr(0x100)); + structure = (Structure) dataAt.getDataType(); + codeBrowser.toggleOpen(dataAt); + waitForSwing(); + } + + private Program buildProgram() throws Exception { + ToyProgramBuilder builder = new ToyProgramBuilder("Test", true); + builder.createMemory("Test", "0", 1000); + StructureDataType struct = new StructureDataType("TestStruct", 4); + struct.add(new WordDataType(), "count", "This is the count field"); + struct.add(new WordDataType(), "color", "This is the color field"); + builder.applyDataType("0x100", struct); + return builder.getProgram(); + + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + @Test + public void testEditDefinedFieldName() { + goTo(0x104); + showFieldEditDialog(); + assertEquals("count", structure.getComponent(4).getFieldName()); + assertEquals("count", getNameText()); + + setNameText("weight"); + + pressOk(); + waitForTasks(); + assertEquals("weight", structure.getComponent(4).getFieldName()); + } + + @Test + public void testEditDefinedFieldComment() { + goTo(0x104); + showFieldEditDialog(); + assertEquals("This is the count field", structure.getComponent(4).getComment()); + assertEquals("This is the count field", getCommentText()); + + setCommentText("This is the weight field"); + + pressOk(); + waitForTasks(); + assertEquals("This is the weight field", structure.getComponent(4).getComment()); + } + + @Test + public void testEditDefinedFieldDataType() { + goTo(0x104); + showFieldEditDialog(); + assertEquals("word", structure.getComponent(4).getDataType().getDisplayName()); + assertEquals("word", getDataTypeText()); + + setDataType(new CharDataType()); + + pressOk(); + + waitForTasks(); + assertFalse(isDialogVisible()); + + assertEquals("char", structure.getComponent(4).getDataType().getDisplayName()); + } + + @Test + public void testEditUndefinedFieldName() { + goTo(0x101); + showFieldEditDialog(); + assertNull(structure.getComponent(1).getFieldName()); + assertEquals("field1_0x1", getNameText()); + + setNameText("abc"); + + pressOk(); + waitForTasks(); + assertEquals("abc", structure.getComponent(1).getFieldName()); + assertEquals("undefined1", structure.getComponent(1).getDataType().getDisplayName()); + } + + @Test + public void testEditUndefinedComment() { + goTo(0x101); + showFieldEditDialog(); + assertNull(structure.getComponent(1).getComment()); + assertEquals("", getCommentText()); + + setCommentText("comment"); + + pressOk(); + waitForTasks(); + assertEquals("comment", structure.getComponent(1).getComment()); + assertEquals("undefined1", structure.getComponent(1).getDataType().getDisplayName()); + } + + @Test + public void testEditUndefinedDataType() { + goTo(0x101); + showFieldEditDialog(); + assertNull(structure.getComponent(1).getComment()); + assertEquals("undefined", getDataTypeText()); + + setDataType(new ByteDataType()); + + pressOk(); + waitForTasks(); + assertEquals("byte", structure.getComponent(1).getDataType().getDisplayName()); + } + + @Test + public void testRenameToDuplicateNameError() { + goTo(0x104); + showFieldEditDialog(); + assertEquals("count", structure.getComponent(4).getFieldName()); + assertEquals("count", getNameText()); + + setNameText("color"); + + pressOk(); + waitForTasks(); + assertTrue(isDialogVisible()); + assertEquals("Duplicate field name", getDialogStatusText()); + + assertEquals("count", structure.getComponent(4).getFieldName()); + } + + private boolean isDialogVisible() { + return runSwing(() -> dialog.isVisible()); + } + + private void showFieldEditDialog() { + performAction(editFieldAction, false); + dialog = waitForDialogComponent(EditDataFieldDialog.class); + } + + private void goTo(long addressOffset) { + Address address = addr(addressOffset); + codeBrowser.goToField(address, "Address", 0, 0); + waitForSwing(); + } + + private Address addr(long offset) { + return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + private void pressOk() { + runSwing(() -> dialog.okCallback()); + } + + private String getNameText() { + return runSwing(() -> dialog.getNameText()); + } + + private void setNameText(String newName) { + runSwing(() -> dialog.setNameText(newName)); + } + + private String getCommentText() { + return runSwing(() -> dialog.getCommentText()); + } + + private void setCommentText(String newComment) { + runSwing(() -> dialog.setCommentText(newComment)); + } + + private String getDataTypeText() { + return runSwing(() -> dialog.getDataTypeText()); + } + + private void setDataType(DataType dataType) { + runSwing(() -> dialog.setDataType(dataType)); + } + + private String getDialogStatusText() { + return runSwing(() -> dialog.getStatusText()); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java index eaba8ff8ec..d25c7425c1 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java @@ -455,11 +455,8 @@ class DataTypeComponentDB implements InternalDataTypeComponent { return InternalDataTypeComponent.toString(this); } - /** - * Determine if component is an undefined filler component - * @return true if undefined filler component, else false - */ - boolean isUndefined() { + @Override + public boolean isUndefined() { return record == null && cachedDataType == null; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java index 662b654a47..c648fbd0a6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java @@ -4,9 +4,9 @@ * 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. @@ -146,6 +146,11 @@ public class AlignedStructureInspector extends AlignedStructurePacker { this.dataType = dataType; } + @Override + public boolean isUndefined() { + return component.isUndefined(); + } + } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java index 8d710df8dc..796726c932 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java @@ -4,9 +4,9 @@ * 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. @@ -173,4 +173,10 @@ public interface DataTypeComponent { return false; } + /** + * Returns true if this this component is not defined. It is just a placeholder. + * @return true if this this component is not defined. It is just a placeholder. + */ + public boolean isUndefined(); + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java index cf358520a2..48cb1cd47c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java @@ -4,9 +4,9 @@ * 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. @@ -334,11 +334,8 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali dataType = dt; } - /** - * Determine if component is an undefined filler component - * @return true if undefined filler component, else false - */ - boolean isUndefined() { + @Override + public boolean isUndefined() { return dataType == DataType.DEFAULT; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java index eba4b3e118..6e11ab4524 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java @@ -4,9 +4,9 @@ * 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. @@ -33,10 +33,10 @@ public class ReadOnlyDataTypeComponent implements DataTypeComponent, Serializabl private final int ordinal; // position in parent private final String comment; // comment about this component. private final int length; // my length - + private String fieldName; // name of this prototype in the component private Settings defaultSettings; - + /** * Create a new DataTypeComponent * @param dataType the dataType for this component @@ -195,4 +195,9 @@ public class ReadOnlyDataTypeComponent implements DataTypeComponent, Serializabl return s1.equals(s2); } + @Override + public boolean isUndefined() { + return dataType == DataType.DEFAULT; + } + } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java index 84d9c9fe88..65d0427666 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java @@ -4,9 +4,9 @@ * 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. @@ -39,6 +39,16 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator { captureDialog(500, 400); } + @Test + public void testEditFieldDialog() { + positionListingTop(0x400080); + positionCursor(0x400080, "+"); + leftClickCursor(); + positionListingTop(0x4000a4); + performAction("Edit Field", "DataPlugin", false); + captureDialog(); + } + @Test public void testCreateStructureDialogWithTableSelection() { positionListingTop(0x40d3a4);