Merge remote-tracking branch

'origin/GP-7075-dragonmacher-structure-editor-go-to-next-defined--SQUASHED'
(Closes #9376)
This commit is contained in:
Ryan Kurtz
2026-07-21 15:35:16 -04:00
6 changed files with 141 additions and 8 deletions

View File

@@ -1202,6 +1202,21 @@
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2><A name="Structure_Editor_Go_To_Next_Defined"></A> Go To Next / Previous Defined Type</H2>
<BLOCKQUOTE>
<P>
<B>Go to Next Defined Type</B> and <B>Go to Previous Defined Type</B> are
available from the popup menu. They allow you to jump to the next row that has a
defined data type. This is useful when dealing with structures that have long runs of
undfined types used for padding. If you invoke the action on a row that already has a
defined data type, then the action will first find the next row with an undefined type and
start the search from there.
</P>
</BLOCKQUOTE>
<H2><A name="Structure_Editor_Show_Component_Path"></A> Showing a Component's Data Type <H2><A name="Structure_Editor_Show_Component_Path"></A> Showing a Component's Data Type
Category</H2> Category</H2>

View File

@@ -942,25 +942,83 @@ public abstract class CompositeEditorPanel<T extends Composite, M extends Compos
@Override @Override
public void statusChanged(String message, boolean beep) { public void statusChanged(String message, boolean beep) {
if ((message == null) || (message.length() == 0)) { if (StringUtils.isBlank(message)) {
message = " "; message = " ";
} }
setStatus(message); setStatus(message);
if (beep) { if (beep) {
getToolkit().beep(); getToolkit().beep();
} }
} }
void goToNextDefinedRow(boolean forward) {
Integer nextRow = findNextDefinedRow(forward);
if (nextRow == null) {
getToolkit().beep();
}
else {
goToRow(nextRow);
}
}
private Integer findNextDefinedRow(boolean forward) {
int currentRow = Math.max(0, model.getRow());
DtcMatcher isUndefined = dtc -> isUndefined(dtc);
int undefinedRow = findNextMatchingDtc(currentRow, forward, isUndefined);
int startRow = undefinedRow + (forward ? 1 : -1);
int n = model.getRowCount();
if (startRow >= n) {
return null;
}
DtcMatcher isDefined = dtc -> !isUndefined(dtc);
return findNextMatchingDtc(startRow, forward, isDefined);
}
private int findNextMatchingDtc(int row, boolean forward, DtcMatcher matcher) {
int start = row;
int end = forward ? model.getRowCount() : -1;
int direction = forward ? 1 : -1;
for (int i = start; i != end; i += direction) {
DataTypeComponent dtc = model.getComponent(i);
if (matcher.matches(dtc)) {
return i;
}
}
return -1;
}
// just a nicer predicate
private interface DtcMatcher {
public boolean matches(DataTypeComponent dtc);
}
private boolean isUndefined(DataTypeComponent dtc) {
if (dtc == null) {
return true;
}
DataType dt = dtc.getDataType();
return Undefined.isUndefined(dt);
}
private void goToRow(int row) {
table.getSelectionModel().setSelectionInterval(row, row);
Rectangle cellRect = table.getCellRect(row, 0, true);
table.scrollRectToVisible(cellRect);
}
void search(String searchText, boolean forward) { void search(String searchText, boolean forward) {
searchText = searchText.toLowerCase(); searchText = searchText.toLowerCase();
Integer row = forward ? findForward(searchText) : findBackward(searchText); Integer row = forward ? findForward(searchText) : findBackward(searchText);
if (row != null) { if (row != null) {
table.getSelectionModel().setSelectionInterval(row, row); goToRow(row);
Rectangle cellRect = table.getCellRect(row, 0, true);
table.scrollRectToVisible(cellRect);
} }
} }
private Integer findForward(String text) { private Integer findForward(String text) {
@@ -1085,6 +1143,10 @@ public abstract class CompositeEditorPanel<T extends Composite, M extends Compos
clsm.setSelectionInterval(viewColumn, viewColumn); clsm.setSelectionInterval(viewColumn, viewColumn);
} }
//=================================================================================================
// Inner Classes
//=================================================================================================
private class ComponentStringCellEditor extends ComponentCellEditor { private class ComponentStringCellEditor extends ComponentCellEditor {
public ComponentStringCellEditor(JTextField textField) { public ComponentStringCellEditor(JTextField textField) {
super(textField); super(textField);

View File

@@ -225,6 +225,10 @@ public abstract class CompositeEditorProvider<T extends Composite, M extends Com
} }
} }
public void goToNextDefinedRow(boolean forward) {
editorPanel.goToNextDefinedRow(forward);
}
@Override @Override
public HelpLocation getHelpLocation() { public HelpLocation getHelpLocation() {
return new HelpLocation(getHelpTopic(), getHelpName()); return new HelpLocation(getHelpTopic(), getHelpName());

View File

@@ -0,0 +1,53 @@
/* ###
* 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.compositeeditor;
import docking.ActionContext;
import docking.action.KeyBindingData;
import docking.action.MenuData;
import ghidra.util.HelpLocation;
/**
* An action that lets the user jump to the next row that has a defined type.
*/
public class NextPrevDefinedComponentAction extends CompositeEditorTableAction {
private boolean forward;
public NextPrevDefinedComponentAction(CompositeEditorProvider<?, ?> provider, boolean forward) {
super(provider, getName(forward));
this.forward = forward;
MenuData data = new MenuData(new String[] { getName(forward) });
data.setMenuGroup(BASIC_ACTION_GROUP + "_2"); // put below the basic action group
setPopupMenuData(data);
setKeyBindingData(new KeyBindingData(forward ? "Control Down" : "Control Up"));
setHelpLocation(
new HelpLocation(provider.getHelpTopic(), "Structure_Editor_Go_To_Next_Defined"));
}
@Override
public void actionPerformed(ActionContext context) {
provider.goToNextDefinedRow(forward);
}
private static String getName(boolean forward) {
return "Go to " + (forward ? "Next" : "Previous") + " Defined Type";
}
}

View File

@@ -18,7 +18,6 @@ package ghidra.app.plugin.core.compositeeditor;
import javax.swing.Icon; import javax.swing.Icon;
import docking.ActionContext; import docking.ActionContext;
import docking.action.ToolBarData;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.services.DataTypeManagerService; import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
@@ -37,8 +36,6 @@ public class ShowDataTypeInTreeAction extends CompositeEditorTableAction {
public ShowDataTypeInTreeAction(CompositeEditorProvider<?, ?> provider) { public ShowDataTypeInTreeAction(CompositeEditorProvider<?, ?> provider) {
super(provider, ACTION_NAME, TOOLBAR_GROUP, null /*popupPath*/, null /*menuPath*/, ICON); super(provider, ACTION_NAME, TOOLBAR_GROUP, null /*popupPath*/, null /*menuPath*/, ICON);
setToolBarData(new ToolBarData(ICON, TOOLBAR_GROUP));
} }
@Override @Override

View File

@@ -85,6 +85,8 @@ public class StructureEditorProvider
new AddBitFieldAction(this), new AddBitFieldAction(this),
new EditBitFieldAction(this), new EditBitFieldAction(this),
new ShowDataTypeInTreeAction(this), new ShowDataTypeInTreeAction(this),
new NextPrevDefinedComponentAction(this, true),
new NextPrevDefinedComponentAction(this, false),
// new ViewBitFieldAction(this) // new ViewBitFieldAction(this)
}; };