mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-10-23 05:31:02 +00:00
GP-792 - Updated the TableChooserDialog to allow for clients to change
the sort behavior; deprecated duplicate 'TableChooeserDialog' and created 'TableSelectionDialog' as a replacement
This commit is contained in:
parent
9a27907bf9
commit
4e34193bb4
|
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
|
@ -38,7 +39,7 @@ import ghidra.program.model.listing.Program;
|
|||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.table.*;
|
||||
|
@ -47,13 +48,13 @@ import ghidra.util.task.TaskMonitor;
|
|||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Dialog to show a table of items. If the dialog is constructed with a non-null
|
||||
* Dialog to show a table of items. If the dialog is constructed with a non-null
|
||||
* {@link TableChooserExecutor}, then a button will be placed in the dialog, allowing the user
|
||||
* to perform the action defined by the executor.
|
||||
*
|
||||
* <p>Each button press will use the selected items as the items to be processed. While the
|
||||
* items are scheduled to be processed, they will still be in the table, painted light gray.
|
||||
* Attempting to reschedule any of these pending items will have no effect. Each time the
|
||||
*
|
||||
* <p>Each button press will use the selected items as the items to be processed. While the
|
||||
* items are scheduled to be processed, they will still be in the table, painted light gray.
|
||||
* Attempting to reschedule any of these pending items will have no effect. Each time the
|
||||
* button is pressed, a new {@link SwingWorker} is created, which will put the processing into
|
||||
* a background thread. Further, by using multiple workers, the work will be performed in
|
||||
* parallel.
|
||||
|
@ -113,8 +114,7 @@ public class TableChooserDialog extends DialogComponentProvider
|
|||
table.installNavigation(goToService, navigatable);
|
||||
}
|
||||
table.getSelectionModel()
|
||||
.addListSelectionListener(
|
||||
e -> setOkEnabled(table.getSelectedRowCount() > 0));
|
||||
.addListSelectionListener(e -> setOkEnabled(table.getSelectedRowCount() > 0));
|
||||
|
||||
GhidraTableFilterPanel<AddressableRowObject> filterPanel =
|
||||
new GhidraTableFilterPanel<>(table, model);
|
||||
|
@ -128,12 +128,12 @@ public class TableChooserDialog extends DialogComponentProvider
|
|||
* @param callback the callback to notify
|
||||
*/
|
||||
public void setClosedListener(Callback callback) {
|
||||
this.closedCallback = Callback.dummyIfNull(callback);
|
||||
Swing.runNow(() -> closedCallback = Callback.dummyIfNull(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given object to this dialog. This method can be called from any thread.
|
||||
*
|
||||
*
|
||||
* @param rowObject the object to add
|
||||
*/
|
||||
public void add(AddressableRowObject rowObject) {
|
||||
|
@ -141,9 +141,9 @@ public class TableChooserDialog extends DialogComponentProvider
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes the given object from this dialog. Nothing will happen if the given item is not
|
||||
* Removes the given object from this dialog. Nothing will happen if the given item is not
|
||||
* in this dialog. This method can be called from any thread.
|
||||
*
|
||||
*
|
||||
* @param rowObject the object to remove
|
||||
*/
|
||||
public void remove(AddressableRowObject rowObject) {
|
||||
|
@ -153,8 +153,7 @@ public class TableChooserDialog extends DialogComponentProvider
|
|||
private void createTableModel() {
|
||||
|
||||
// note: the task monitor is installed later when this model is added to the threaded panel
|
||||
SystemUtilities.runSwingNow(
|
||||
() -> model = new TableChooserTableModel("Test", tool, program, null));
|
||||
Swing.runNow(() -> model = new TableChooserTableModel("Test", tool, program, null));
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
|
@ -175,8 +174,8 @@ public class TableChooserDialog extends DialogComponentProvider
|
|||
};
|
||||
|
||||
DockingAction selectionNavigationAction = new SelectionNavigationAction(owner, table);
|
||||
selectionNavigationAction.setHelpLocation(
|
||||
new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
|
||||
selectionNavigationAction
|
||||
.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation"));
|
||||
|
||||
addAction(selectAction);
|
||||
addAction(selectionNavigationAction);
|
||||
|
@ -296,7 +295,49 @@ public class TableChooserDialog extends DialogComponentProvider
|
|||
}
|
||||
|
||||
public void addCustomColumn(ColumnDisplay<?> columnDisplay) {
|
||||
model.addCustomColumn(columnDisplay);
|
||||
Swing.runNow(() -> model.addCustomColumn(columnDisplay));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default sorted column for this dialog.
|
||||
*
|
||||
* <P>This method should be called after all custom columns have been added via
|
||||
* {@link #addCustomColumn(ColumnDisplay)}.
|
||||
*
|
||||
* @param index the view's 0-based column index
|
||||
* @see #setSortState(TableSortState)
|
||||
* @throws IllegalArgumentException if an invalid column is requested for sorting
|
||||
*/
|
||||
public void setSortColumn(int index) {
|
||||
setSortState(TableSortState.createDefaultSortState(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column sort state for this dialog. The {@link TableSortState} allows for
|
||||
* combinations of sorted columns in ascending or descending order.
|
||||
*
|
||||
* <P>This method should be called after all custom columns have been added via
|
||||
* {@link #addCustomColumn(ColumnDisplay)}.
|
||||
*
|
||||
* @param state the sort state
|
||||
* @see #setSortColumn(int)
|
||||
* @throws IllegalArgumentException if an invalid column is requested for sorting
|
||||
*/
|
||||
public void setSortState(TableSortState state) {
|
||||
AtomicReference<IllegalArgumentException> ref = new AtomicReference<>();
|
||||
Swing.runNow(() -> {
|
||||
try {
|
||||
model.setTableSortState(state);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
ref.set(e);
|
||||
}
|
||||
});
|
||||
IllegalArgumentException exception = ref.get();
|
||||
if (exception != null) {
|
||||
// use a new exception so the stack trace points to this class, not the runnable above
|
||||
throw new IllegalArgumentException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -313,15 +354,17 @@ public class TableChooserDialog extends DialogComponentProvider
|
|||
}
|
||||
|
||||
public void clearSelection() {
|
||||
table.clearSelection();
|
||||
Swing.runNow(() -> table.clearSelection());
|
||||
}
|
||||
|
||||
public void selectRows(int... rows) {
|
||||
|
||||
ListSelectionModel selectionModel = table.getSelectionModel();
|
||||
for (int row : rows) {
|
||||
selectionModel.addSelectionInterval(row, row);
|
||||
}
|
||||
Swing.runNow(() -> {
|
||||
ListSelectionModel selectionModel = table.getSelectionModel();
|
||||
for (int row : rows) {
|
||||
selectionModel.addSelectionInterval(row, row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int[] getSelectedRows() {
|
||||
|
|
|
@ -33,12 +33,15 @@ import docking.action.*;
|
|||
import docking.actions.KeyEntryDialog;
|
||||
import docking.actions.ToolActions;
|
||||
import docking.tool.util.DockingToolConstants;
|
||||
import docking.widgets.table.TableSortState;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.DummyPluginTool;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.TestAddress;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import resources.Icons;
|
||||
|
@ -75,16 +78,48 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
|
||||
tool = new DummyPluginTool();
|
||||
tool.setVisible(true);
|
||||
Program program = new ToyProgramBuilder("Test", true).getProgram();
|
||||
|
||||
List<Address> addresses = new ArrayList<>();
|
||||
ToyProgramBuilder builder = new ToyProgramBuilder("Test", true);
|
||||
builder.createMemory(".text", "0x0", 0x110);
|
||||
Function f = createFunction(builder, 0x00);
|
||||
addresses.add(f.getEntryPoint());
|
||||
f = createFunction(builder, 0x10);
|
||||
addresses.add(f.getEntryPoint());
|
||||
f = createFunction(builder, 0x20);
|
||||
addresses.add(f.getEntryPoint());
|
||||
f = createFunction(builder, 0x30);
|
||||
addresses.add(f.getEntryPoint());
|
||||
f = createFunction(builder, 0x40);
|
||||
addresses.add(f.getEntryPoint());
|
||||
f = createFunction(builder, 0x50);
|
||||
addresses.add(f.getEntryPoint());
|
||||
|
||||
Program program = builder.getProgram();
|
||||
Navigatable navigatable = null;
|
||||
dialog = new TableChooserDialog(tool, executor, program, "Dialog Title", navigatable);
|
||||
|
||||
testAction = new TestAction();
|
||||
dialog.addAction(testAction);
|
||||
|
||||
dialog.addCustomColumn(new OffsetTestColumn());
|
||||
dialog.addCustomColumn(new SpaceTestColumn());
|
||||
|
||||
dialog.show();
|
||||
waitForDialogComponent(TableChooserDialog.class);
|
||||
loadData();
|
||||
loadData(addresses);
|
||||
}
|
||||
|
||||
private Function createFunction(ProgramBuilder builder, long addr) throws Exception {
|
||||
ProgramDB p = builder.getProgram();
|
||||
FunctionManager fm = p.getFunctionManager();
|
||||
Function f = fm.getFunctionAt(builder.addr(addr));
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
|
||||
String a = Long.toHexString(addr);
|
||||
return builder.createEmptyFunction("Function_" + a, "0x" + a, 5, DataType.DEFAULT);
|
||||
}
|
||||
|
||||
private void reCreateDialog(SpyTableChooserExecutor dialogExecutor) throws Exception {
|
||||
|
@ -92,9 +127,9 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
createDialog(dialogExecutor);
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
dialog.add(new TestStubRowObject());
|
||||
private void loadData(List<Address> addresses) {
|
||||
for (Address a : addresses) {
|
||||
dialog.add(new TestStubRowObject(a));
|
||||
}
|
||||
|
||||
waitForDialog();
|
||||
|
@ -151,7 +186,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
@Test
|
||||
public void testCalllbackRemovesItems_OtherItemSelected() {
|
||||
/*
|
||||
Select multiple items.
|
||||
Select multiple items.
|
||||
Have the first callback remove one of the remaining *unselected* items.
|
||||
The removed item should not itself get a callback.
|
||||
*/
|
||||
|
@ -187,7 +222,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
public void testCalllbackRemovesItems_OtherItemNotSelected() {
|
||||
|
||||
/*
|
||||
Select multiple items.
|
||||
Select multiple items.
|
||||
Have the first callback remove one of the remaining *selected* items.
|
||||
The removed item should not itself get a callback.
|
||||
*/
|
||||
|
@ -234,7 +269,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
CountDownLatch continueLatch = new CountDownLatch(1);
|
||||
testDecision = r -> {
|
||||
|
||||
//
|
||||
//
|
||||
// Signal that we have started and wait to continue
|
||||
//
|
||||
startLatch.countDown();
|
||||
|
@ -308,10 +343,40 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
assertTrue(newToolTip.contains("(A)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSortColumn() throws Exception {
|
||||
assertSortedColumn(0);
|
||||
dialog.setSortColumn(1);
|
||||
assertSortedColumn(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSortState() throws Exception {
|
||||
assertSortedColumn(0);
|
||||
dialog.setSortState(TableSortState.createDefaultSortState(2, false));
|
||||
assertSortedColumn(2);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testSetSortState_Invalid() throws Exception {
|
||||
assertSortedColumn(0);
|
||||
dialog.setSortState(TableSortState.createDefaultSortState(100));
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void assertSortedColumn(int expectedColumn) {
|
||||
waitForCondition(() -> expectedColumn == getSortColumn(),
|
||||
"Incorrect sorted column; expected " + expectedColumn + ", found " + getSortColumn());
|
||||
}
|
||||
|
||||
private int getSortColumn() {
|
||||
TableChooserTableModel model = getModel();
|
||||
return runSwing(() -> model.getPrimarySortColumnIndex());
|
||||
}
|
||||
|
||||
private void setKeyBindingViaF4Dialog(DockingAction action, KeyStroke ks) {
|
||||
|
||||
// simulate the user mousing over the toolbar button
|
||||
|
@ -415,6 +480,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
|
||||
private void waitForDialog() {
|
||||
waitForCondition(() -> !dialog.isBusy());
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void pressExecuteButton() {
|
||||
|
@ -443,7 +509,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
//==================================================================================================
|
||||
|
||||
private interface TestExecutorDecision {
|
||||
public boolean decide(AddressableRowObject rowObject);
|
||||
|
@ -485,16 +551,15 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
|
||||
private static class TestStubRowObject implements AddressableRowObject {
|
||||
|
||||
private static int counter;
|
||||
private long addr;
|
||||
private Address addr;
|
||||
|
||||
TestStubRowObject() {
|
||||
addr = ++counter;
|
||||
TestStubRowObject(Address a) {
|
||||
this.addr = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return new TestAddress(addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -503,6 +568,42 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
|||
}
|
||||
}
|
||||
|
||||
private static class OffsetTestColumn extends AbstractColumnDisplay<String> {
|
||||
|
||||
@Override
|
||||
public String getColumnValue(AddressableRowObject rowObject) {
|
||||
return Long.toString(rowObject.getAddress().getOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Offset";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(AddressableRowObject o1, AddressableRowObject o2) {
|
||||
return o1.getAddress().compareTo(o2.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpaceTestColumn extends AbstractColumnDisplay<String> {
|
||||
|
||||
@Override
|
||||
public String getColumnValue(AddressableRowObject rowObject) {
|
||||
return rowObject.getAddress().getAddressSpace().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Space";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(AddressableRowObject o1, AddressableRowObject o2) {
|
||||
return o1.getAddress().compareTo(o2.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
private class TestAction extends DockingAction {
|
||||
|
||||
private int invoked;
|
||||
|
|
|
@ -26,11 +26,14 @@ import docking.DialogComponentProvider;
|
|||
import docking.widgets.table.*;
|
||||
|
||||
/**
|
||||
* Dialog for displaying table data in a dialog for the purpose of the user selecting one or
|
||||
* more items from the table.
|
||||
* @param <T> the type
|
||||
*
|
||||
* @param <T> The type of row object in the table.
|
||||
*
|
||||
* @deprecated This class has been replaced by {@link TableSelectionDialog}. At the time of
|
||||
* writing, both classes are identical. This version introduced a naming conflict with another
|
||||
* API. Thus, the new version better matches the existing dialog choosing API.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "9.3")
|
||||
public class TableChooserDialog<T> extends DialogComponentProvider {
|
||||
|
||||
private RowObjectTableModel<T> model;
|
||||
|
@ -39,12 +42,14 @@ public class TableChooserDialog<T> extends DialogComponentProvider {
|
|||
|
||||
/**
|
||||
* Create a new Dialog for displaying and choosing table row items
|
||||
*
|
||||
*
|
||||
* @param title The title for the dialog
|
||||
* @param model a {@link RowObjectTableModel} that has the tRable data
|
||||
* @param allowMultipleSelection if true, the dialog allows the user to select more
|
||||
* than one row; otherwise, only single selection is allowed
|
||||
* @deprecated see the class header
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "9.3")
|
||||
public TableChooserDialog(String title, RowObjectTableModel<T> model,
|
||||
boolean allowMultipleSelection) {
|
||||
super(title);
|
||||
|
@ -57,7 +62,9 @@ public class TableChooserDialog<T> extends DialogComponentProvider {
|
|||
/**
|
||||
* Returns the list of selected items or null if the dialog was cancelled.
|
||||
* @return the list of selected items or null if the dialog was cancelled.
|
||||
* @deprecated see the class header
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "9.3")
|
||||
public List<T> getSelectionItems() {
|
||||
return selectedItems;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/* ###
|
||||
* 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 docking.widgets.dialogs;
|
||||
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.widgets.table.*;
|
||||
|
||||
/**
|
||||
* Dialog for displaying table data in a dialog for the purpose of the user selecting one or
|
||||
* more items from the table.
|
||||
*
|
||||
* @param <T> The type of row object in the table.
|
||||
*/
|
||||
public class TableSelectionDialog<T> extends DialogComponentProvider {
|
||||
|
||||
private RowObjectTableModel<T> model;
|
||||
private GFilterTable<T> gFilterTable;
|
||||
private List<T> selectedItems;
|
||||
|
||||
/**
|
||||
* Create a new Dialog for displaying and choosing table row items
|
||||
*
|
||||
* @param title The title for the dialog
|
||||
* @param model a {@link RowObjectTableModel} that has the tRable data
|
||||
* @param allowMultipleSelection if true, the dialog allows the user to select more
|
||||
* than one row; otherwise, only single selection is allowed
|
||||
*/
|
||||
public TableSelectionDialog(String title, RowObjectTableModel<T> model,
|
||||
boolean allowMultipleSelection) {
|
||||
super(title);
|
||||
this.model = model;
|
||||
addWorkPanel(buildTable(allowMultipleSelection));
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of selected items or null if the dialog was cancelled.
|
||||
* @return the list of selected items or null if the dialog was cancelled.
|
||||
*/
|
||||
public List<T> getSelectionItems() {
|
||||
return selectedItems;
|
||||
}
|
||||
|
||||
private void initializeTable(boolean allowMultipleSelection) {
|
||||
GTable table = gFilterTable.getTable();
|
||||
|
||||
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||
|
||||
int selectionMode = allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
|
||||
: ListSelectionModel.SINGLE_SELECTION;
|
||||
table.getSelectionModel().setSelectionMode(selectionMode);
|
||||
|
||||
}
|
||||
|
||||
protected void processMouseClicked(MouseEvent e) {
|
||||
|
||||
if (e.getClickCount() != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
int rowAtPoint = gFilterTable.getTable().rowAtPoint(e.getPoint());
|
||||
if (rowAtPoint < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
T selectedRowObject = gFilterTable.getSelectedRowObject();
|
||||
selectedItems = Arrays.asList(selectedRowObject);
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
selectedItems = gFilterTable.getSelectedRowObjects();
|
||||
close();
|
||||
gFilterTable.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
selectedItems = null;
|
||||
close();
|
||||
gFilterTable.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dialogShown() {
|
||||
gFilterTable.focusFilter();
|
||||
}
|
||||
|
||||
private JComponent buildTable(boolean allowMultipleSelection) {
|
||||
gFilterTable = new GFilterTable<>(model);
|
||||
initializeTable(allowMultipleSelection);
|
||||
gFilterTable.getTable().addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (!e.isShiftDown()) {
|
||||
processMouseClicked(e);
|
||||
}
|
||||
updateOkEnabled();
|
||||
}
|
||||
});
|
||||
setOkEnabled(false);
|
||||
return gFilterTable;
|
||||
}
|
||||
|
||||
protected void updateOkEnabled() {
|
||||
setOkEnabled(gFilterTable.getSelectedRowObject() != null);
|
||||
}
|
||||
}
|
|
@ -28,15 +28,15 @@ import ghidra.util.datastruct.WeakSet;
|
|||
|
||||
/**
|
||||
* Table models should extends this model when they want sorting, potentially across multiple
|
||||
* columns, but do not want Threading or do not work on Program-related data (Address,
|
||||
* columns, but do not want Threading or do not work on Program-related data (Address,
|
||||
* ProgramLocations, etc...).
|
||||
* <p>
|
||||
* In order to define custom comparators for a column, simply override
|
||||
* In order to define custom comparators for a column, simply override
|
||||
* {@link #createSortComparator(int)}. Otherwise, a default comparator will be created for you.
|
||||
*
|
||||
* <p>Note on sorting: it is possible that the user can disable sorting by de-selecting all
|
||||
* sorted columns. This can also be achieved programmatically by calling
|
||||
* {@link #setTableSortState(TableSortState)} with a value of
|
||||
*
|
||||
* <p>Note on sorting: it is possible that the user can disable sorting by de-selecting all
|
||||
* sorted columns. This can also be achieved programmatically by calling
|
||||
* {@link #setTableSortState(TableSortState)} with a value of
|
||||
* {@link TableSortState#createUnsortedSortState()}.
|
||||
*
|
||||
* @param <T> The row type upon which the table is based
|
||||
|
@ -92,11 +92,11 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the given row object in this model; a negative value if the model
|
||||
* does not contain the given object.
|
||||
*
|
||||
* <p>Warning: if the this model has no sort applied, then performance will be O(n). If
|
||||
* sorted, then performance is O(log n). You can call {@link #isSorted()} to know when
|
||||
* Returns the index of the given row object in this model; a negative value if the model
|
||||
* does not contain the given object.
|
||||
*
|
||||
* <p>Warning: if the this model has no sort applied, then performance will be O(n). If
|
||||
* sorted, then performance is O(log n). You can call {@link #isSorted()} to know when
|
||||
* this will happen.
|
||||
*/
|
||||
@Override
|
||||
|
@ -158,14 +158,15 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
// verify the requested columns are sortable
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
ColumnSortState state = tableSortState.getColumnSortState(i);
|
||||
if (state == null) {
|
||||
continue; // no sort state for this column--nothing to validate
|
||||
for (ColumnSortState state : tableSortState) {
|
||||
|
||||
int index = state.getColumnModelIndex();
|
||||
if (!isSortable(index)) {
|
||||
return false; // the state wants to sort on an unsortable column
|
||||
}
|
||||
|
||||
if (!isSortable(i)) {
|
||||
return false; // the state wants to sort on an unsortable column
|
||||
if (index >= columnCount) {
|
||||
return false; // requested a column that is larger than the number of columns
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,10 +175,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
|
||||
/**
|
||||
* A convenience method that will take a given sort state and remove from it any columns
|
||||
* that cannot be sorted. This is useful if the client is restoring a sort state that
|
||||
* contains columns that have been removed or are no longer sortable (such as during major
|
||||
* that cannot be sorted. This is useful if the client is restoring a sort state that
|
||||
* contains columns that have been removed or are no longer sortable (such as during major
|
||||
* table model rewrites).
|
||||
*
|
||||
*
|
||||
* @param state the state
|
||||
* @return the updated state
|
||||
*/
|
||||
|
@ -220,9 +221,9 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a pending change to the current sort state
|
||||
* Returns true if there is a pending change to the current sort state
|
||||
* (this includes a sort state that signals no sort will be applied)
|
||||
*
|
||||
*
|
||||
* @return true if there is a pending change to the current sort state
|
||||
*/
|
||||
public boolean isSortPending() {
|
||||
|
@ -232,7 +233,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
/**
|
||||
* Returns true if this model has been sorted and does not have a new pending sort that will
|
||||
* be applied
|
||||
*
|
||||
*
|
||||
* @return true if sorted
|
||||
* @see #isSortPending()
|
||||
*/
|
||||
|
@ -245,7 +246,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
/**
|
||||
* The default implementation of {@link TableModel#getValueAt(int, int)} that calls the
|
||||
* The default implementation of {@link TableModel#getValueAt(int, int)} that calls the
|
||||
* abstract {@link #getColumnValueForRow(Object, int)}.
|
||||
*/
|
||||
@Override
|
||||
|
@ -255,9 +256,9 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
/**
|
||||
* This method is an attempt to help models that forget to call fireTableDataChanged(). It
|
||||
* is expected that tables will fire the notification when they are ready to display data,
|
||||
* even if they have that data at construction time. We put this call here so that the
|
||||
* This method is an attempt to help models that forget to call fireTableDataChanged(). It
|
||||
* is expected that tables will fire the notification when they are ready to display data,
|
||||
* even if they have that data at construction time. We put this call here so that the
|
||||
* forgetful subclasses will have their data sorted for them the first time that this table
|
||||
* tries to render itself.
|
||||
*/
|
||||
|
@ -275,11 +276,11 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
/**
|
||||
* A convenience method for subclasses to quickly/efficiently search for the index of a given
|
||||
* row object <b>that is visible in the GUI</b>. The <i>visible</i> limitation is due to the
|
||||
* fact that the data searched is retrieved from {@link #getModelData()}, which may be
|
||||
* filtered.
|
||||
*
|
||||
* fact that the data searched is retrieved from {@link #getModelData()}, which may be
|
||||
* filtered.
|
||||
*
|
||||
* @param rowObject The object for which to search.
|
||||
* @return the index of the item in the data returned by
|
||||
* @return the index of the item in the data returned by
|
||||
*/
|
||||
@Override
|
||||
protected int getIndexForRowObject(T rowObject) {
|
||||
|
@ -287,8 +288,8 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the index for the given object in the given list
|
||||
*
|
||||
* Returns the index for the given object in the given list
|
||||
*
|
||||
* @param rowObject the item
|
||||
* @param data the data
|
||||
* @return the index
|
||||
|
@ -312,11 +313,11 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
/**
|
||||
* A default sort method that uses the {@link Collections#sort(List, Comparator)} method for
|
||||
* sorting. Implementors with reasonably sized data sets can rely on this method. For data
|
||||
* sets that can become large, the <code>ThreadedTableModel</code> is the recommended base class,
|
||||
* sets that can become large, the <code>ThreadedTableModel</code> is the recommended base class,
|
||||
* as it handles loading/sorting/filtering in a threaded way.
|
||||
*
|
||||
*
|
||||
* @param data The data to be sorted
|
||||
* @param sortingContext The context required to sort (it contains the sorting columns, a
|
||||
* @param sortingContext The context required to sort (it contains the sorting columns, a
|
||||
* comparator for sorting, etc...).
|
||||
*/
|
||||
protected void sort(List<T> data, TableSortingContext<T> sortingContext) {
|
||||
|
@ -344,7 +345,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
/**
|
||||
* Fires an event to let the listeners (like JTable) know that things have been changed.
|
||||
* Fires an event to let the listeners (like JTable) know that things have been changed.
|
||||
* This method exists so that subclasses have a way to call the various <code>tableChanged()</code>
|
||||
* methods without triggering this class's overridden version.
|
||||
* @param dataChanged True signals that the actual data has changed; false signals that the
|
||||
|
@ -366,10 +367,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
|
||||
/**
|
||||
* An extension point for subclasses to insert their own comparator objects for their data.
|
||||
* Subclasses can create comparators for a single or multiple columns, as desired.
|
||||
*
|
||||
* Subclasses can create comparators for a single or multiple columns, as desired.
|
||||
*
|
||||
* @param columnIndex the column index
|
||||
* @return the comparator
|
||||
* @return the comparator
|
||||
*/
|
||||
protected Comparator<T> createSortComparator(int columnIndex) {
|
||||
return new RowBasedColumnComparator<>(this, columnIndex, new DefaultColumnComparator(),
|
||||
|
@ -385,7 +386,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
// one column sorted and that column is 'reversed', then to provide consistent sorting,
|
||||
// and later searching, we need to reverse the tie-breaker comparator. Without this,
|
||||
// the tie-breaker always produces the same results, regardless of which direction the
|
||||
// search is going. That will break any clients that call Collections.reverse() in
|
||||
// search is going. That will break any clients that call Collections.reverse() in
|
||||
// order to quickly invert an existing sort.
|
||||
//
|
||||
if (parentChain.primaryComparator instanceof AbstractSortedTableModel.ReverseComparator) {
|
||||
|
@ -419,8 +420,8 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
}
|
||||
|
||||
/**
|
||||
* Builds a comparator for the given column sort state while allowing for subclasses to
|
||||
* provider their own comparators. This method also handles directionality of sorting, so
|
||||
* Builds a comparator for the given column sort state while allowing for subclasses to
|
||||
* provider their own comparators. This method also handles directionality of sorting, so
|
||||
* that the comparators used can be simple and generic.
|
||||
*/
|
||||
private Comparator<T> getComparator(ColumnSortState columnSortState) {
|
||||
|
@ -487,8 +488,8 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
|||
public int compare(T t1, T t2) {
|
||||
|
||||
// at this point we compare the rows, since all of the sorting column values are equal
|
||||
// (Warning: due to comparable being specific to the class upon which it is defined,
|
||||
// we have to make sure the class is the same to prevent class cast
|
||||
// (Warning: due to comparable being specific to the class upon which it is defined,
|
||||
// we have to make sure the class is the same to prevent class cast
|
||||
// exceptions when the table has mixed implementations of 'T')
|
||||
if (t1 instanceof Comparable && t1.getClass().equals(t2.getClass())) {
|
||||
return ((Comparable) t1).compareTo(t2);
|
||||
|
|
|
@ -55,6 +55,9 @@ public class ColumnSortState {
|
|||
private int sortOrder_OneBased = -1;
|
||||
|
||||
ColumnSortState(int columnModelIndex, SortDirection sortDirection, int sortOrder) {
|
||||
if (columnModelIndex < 0) {
|
||||
throw new IllegalArgumentException("Column index cannot be negative");
|
||||
}
|
||||
this.columnModelIndex = columnModelIndex;
|
||||
this.sortDirection = sortDirection;
|
||||
this.sortOrder_OneBased = sortOrder;
|
||||
|
|
Loading…
Reference in New Issue
Block a user