GT-3226 - Symbol Table - Performance improvements - disable filtering on

some columns; turn on 'Name Only' filtering by default; disable sorting
by default; remove use of column mapping where not needed; fixed caching
for name column
This commit is contained in:
dragonmacher 2019-10-22 18:36:41 -04:00
parent 665a83d6c0
commit fc67c6aaeb
26 changed files with 687 additions and 403 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before After
Before After

View file

@ -31,7 +31,7 @@
<BLOCKQUOTE>
<BLOCKQUOTE>
<P>The columns in the table are:</P>
<P>Some of the columns in the table are:</P>
<CENTER>
<TABLE border="1" width="73%" height="159">
@ -54,13 +54,6 @@
<TD width="89%" height="22">Symbol type (Function, External, Class, etc).</TD>
</TR>
<TR>
<TD width="11%" height="15"><B>Datatype</B></TD>
<TD width="89%" height="15">Datatype (i.e., byte, float, etc.) applied at symbol
address.</TD>
</TR>
<TR>
<TD width="11%" height="21"><B>Namespace</B></TD>
@ -89,7 +82,15 @@
<P><I><IMG src="../../shared/note.png"> You can sort the table on any column by clicking on
the column header. The column can be sorted in ascending or descending order.</I></P>
<P><I><IMG src="../../shared/tip.png"></I> The colors for <B><FONT color="#ff0000">bad
<BLOCKQUOTE>
<P><IMG src="../../shared/warning.png">
Sorting a column in the symbol table when the program has a large number of symbols
can be slow. If you do not need sorting, then you can control-click the sorted column
to remove the sort.
</p>
</BLOCKQUOTE>
<P><IMG src="../../shared/tip.png"> The colors for <B><FONT color="#ff0000">bad
references</FONT></B>, <B><FONT color="#ff00ff">entry points</FONT></B>, <B>dead code</B>,
<B><FONT color="#c0c0c0">offcut code</FONT></B>, <B><FONT color="#0000ff">function
names</FONT></B>, <B><FONT color="#008040">local symbols</FONT></B>, <B><FONT color=
@ -113,6 +114,15 @@
<P>The <I>Name Only</I> checkbox allows you to toggle whether to filter on only the name
column or all the columns in the table.</P>
<BLOCKQUOTE>
<P><IMG src="../../shared/tip.png">
Filtering the symbol table when the program has a large number of symbols can be slow.
When only filtering on the symbol name, via the checkbox above, the overall filtering
is considerably faster.
</p>
</BLOCKQUOTE>
<P>The filter text field will accept basic globbing characters such as '<B>*</B>' and
'<B>?</B>' within the filter text unless the "Regular Expression" filter strategy is
selected, in which case you should use standard regular expression syntax.</P>

View file

@ -83,7 +83,8 @@
<p><img border="0" src="../../shared/tip.png" alt="">
To remove a sort column from a multiple column sort, Ctrl-left-click that column.
This will even work when only one column is sorted, <b><i>thus effectively disabling
sorting for the table</i></b>.
sorting for the table</i></b>. <u>Disabling sorting can greatly increase the
table's performance when the number of rows is large</u>
</p>
</blockquote>
@ -212,5 +213,11 @@
<br>
<br>
<P class="relatedtopic">Related Topics</P>
<UL>
<LI><A href="help/topics/Trees/GhidraTreeFilter.html">Table Filtering</A></LI>
</UL>
</body>
</html>

View file

@ -15,7 +15,11 @@
<BLOCKQUOTE>
<P>Most trees and tables in Ghidra support filtering and have a text filter located at the
bottom of the tree or table. Below is an example from the Data Type Manager. The filter is
bottom of the tree or table. Tables also support the concept of
<A HREF="#Column_Filters">Column Filters</A> described below.
</P>
<P>Below is an example from the Data Type Manager. The filter is
currently set to "Starts With", but you can select a different filter strategy.</P>
<CENTER>
@ -270,6 +274,17 @@
<BLOCKQUOTE>
<P>Most filterable tables in Ghidra support advanced filtering based on column values. This
allows for complex filtering where you can logically combine column specific clauses.</P>
<BLOCKQUOTE>
<P><IMG src="../../shared/warning.png">
Some columns in tables are not filterable via the filter text field below the table.
For example, many numeric columns are ignored by the text filter because they can be
slow to calculate and they are better filtered by using a range filter, as is available
using column filters.
</p>
</BLOCKQUOTE>
<BR>
@ -458,5 +473,20 @@
filter.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<!-- extra space at the bottom of the page (for readability) -->
<br>
<br>
<br>
<br>
<P class="relatedtopic">Related Topics</P>
<UL>
<LI><A href="help/topics/Tables/GhidraTableHeaders.html">Table Sorting</A></LI>
</UL>
</BODY>
</HTML>

View file

@ -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.
@ -16,12 +15,20 @@
*/
package ghidra.app.plugin.core.functionwindow;
import ghidra.program.model.listing.Function;
class FunctionRowObject implements Comparable<FunctionRowObject> {
private final Function function;
private final long key;
FunctionRowObject(long key) {
this.key = key;
FunctionRowObject(Function function) {
this.function = function;
this.key = function.getID();
}
Function getFunction() {
return function;
}
@Override
@ -34,15 +41,19 @@ class FunctionRowObject implements Comparable<FunctionRowObject> {
@Override
public boolean equals(Object obj) {
if (this == obj)
if (this == obj) {
return true;
if (obj == null)
}
if (obj == null) {
return false;
if (getClass() != obj.getClass())
}
if (getClass() != obj.getClass()) {
return false;
}
FunctionRowObject other = (FunctionRowObject) obj;
if (key != other.key)
if (key != other.key) {
return false;
}
return true;
}

View file

@ -17,16 +17,17 @@ package ghidra.app.plugin.core.functionwindow;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.table.ProgramLocationTableRowMapper;
public class FunctionRowObjectToAddressTableRowMapper extends
ProgramLocationTableRowMapper<FunctionRowObject, Address> {
public class FunctionRowObjectToAddressTableRowMapper
extends ProgramLocationTableRowMapper<FunctionRowObject, Address> {
@Override
public Address map(FunctionRowObject rowObject, Program program, ServiceProvider serviceProvider) {
FunctionManager functionManager = program.getFunctionManager();
Function function = functionManager.getFunction(rowObject.getKey());
public Address map(FunctionRowObject rowObject, Program program,
ServiceProvider serviceProvider) {
Function function = rowObject.getFunction();
if (function == null) {
return null;
}

View file

@ -16,16 +16,18 @@
package ghidra.app.plugin.core.functionwindow;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.table.ProgramLocationTableRowMapper;
public class FunctionRowObjectToFunctionTableRowMapper extends
ProgramLocationTableRowMapper<FunctionRowObject, Function> {
public class FunctionRowObjectToFunctionTableRowMapper
extends ProgramLocationTableRowMapper<FunctionRowObject, Function> {
@Override
public Function map(FunctionRowObject rowObject, Program program, ServiceProvider serviceProvider) {
FunctionManager functionManager = program.getFunctionManager();
return functionManager.getFunction(rowObject.getKey());
public Function map(FunctionRowObject rowObject, Program program,
ServiceProvider serviceProvider) {
Function function = rowObject.getFunction();
return function;
}
}

View file

@ -16,19 +16,19 @@
package ghidra.app.plugin.core.functionwindow;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.FunctionSignatureFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.table.ProgramLocationTableRowMapper;
public class FunctionRowObjectToProgramLocationTableRowMapper extends
ProgramLocationTableRowMapper<FunctionRowObject, ProgramLocation> {
public class FunctionRowObjectToProgramLocationTableRowMapper
extends ProgramLocationTableRowMapper<FunctionRowObject, ProgramLocation> {
@Override
public ProgramLocation map(FunctionRowObject rowObject, Program program,
ServiceProvider serviceProvider) {
FunctionManager functionManager = program.getFunctionManager();
Function function = functionManager.getFunction(rowObject.getKey());
Function function = rowObject.getFunction();
if (function == null) {
return null;
}

View file

@ -17,7 +17,9 @@ package ghidra.app.plugin.core.functionwindow;
import docking.widgets.table.DiscoverableTableUtils;
import docking.widgets.table.TableColumnDescriptor;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.util.LongIterator;
@ -43,20 +45,19 @@ class FunctionTableModel extends AddressBasedTableModel<FunctionRowObject> {
@Override
protected TableColumnDescriptor<FunctionRowObject> createTableColumnDescriptor() {
TableColumnDescriptor<FunctionRowObject> descriptor =
new TableColumnDescriptor<>();
TableColumnDescriptor<FunctionRowObject> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(DiscoverableTableUtils.adaptColumForModel(this,
new LabelTableColumn()));
descriptor.addVisibleColumn(new NameTableColumn());
descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new AddressTableColumn()), 1, true);
descriptor.addVisibleColumn(DiscoverableTableUtils.adaptColumForModel(this,
new FunctionSignatureTableColumn()));
//make function size a default column so that a user who wants to know the function size
//won't add the "Byte Count" column (which only display the number of bytes in the code
//unit at the function's entry point).
descriptor.addVisibleColumn(DiscoverableTableUtils.adaptColumForModel(this,
new FunctionBodySizeTableColumn()));
descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new FunctionSignatureTableColumn()));
// Make function size a default column so that a user who wants to know the function size
// won't add the "Byte Count" column (which only display the number of bytes in the code
// unit at the function's entry point).
descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new FunctionBodySizeTableColumn()));
// Function tag column is not something widely used, so make hidden by default
descriptor.addHiddenColumn(
@ -91,13 +92,15 @@ class FunctionTableModel extends AddressBasedTableModel<FunctionRowObject> {
if (functionMgr != null) {
it = new FunctionKeyIterator(functionMgr);
}
monitor.initialize(getKeyCount());
int progress = 0;
while (it.hasNext()) {
monitor.setProgress(progress++);
monitor.checkCanceled();
long key = it.next();
accumulator.add(new FunctionRowObject(key));
Function f = functionMgr.getFunction(key);
accumulator.add(new FunctionRowObject(f));
}
}
@ -133,16 +136,16 @@ class FunctionTableModel extends AddressBasedTableModel<FunctionRowObject> {
}
}
void functionAdded(Function function) {
addObject(new FunctionRowObject(function.getID()));
void functionAdded(Function f) {
addObject(new FunctionRowObject(f));
}
void functionRemoved(Function function) {
removeObject(new FunctionRowObject(function.getID()));
void functionRemoved(Function f) {
removeObject(new FunctionRowObject(f));
}
void update(Function function) {
updateObject(new FunctionRowObject(function.getID()));
void update(Function f) {
updateObject(new FunctionRowObject(f));
}
@Override
@ -152,4 +155,25 @@ class FunctionTableModel extends AddressBasedTableModel<FunctionRowObject> {
Function function = functionManager.getFunction(rowObject.getKey());
return function != null ? function.getEntryPoint() : null;
}
private class NameTableColumn
extends AbstractProgramBasedDynamicTableColumn<FunctionRowObject, String> {
@Override
public String getColumnName() {
return "Name";
}
@Override
public String getValue(FunctionRowObject rowObject, Settings settings, Program data,
ServiceProvider sp) throws IllegalArgumentException {
Function function = rowObject.getFunction();
if (function == null) {
return null;
}
return function.getName();
}
}
}

View file

@ -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.
@ -16,19 +15,19 @@
*/
package ghidra.app.plugin.core.symtable;
import ghidra.program.model.symbol.Symbol;
import java.awt.Component;
import javax.swing.*;
import ghidra.program.model.symbol.Symbol;
class SymbolEditor extends DefaultCellEditor {
private JTextField symbolField = null;
SymbolEditor() {
super(new JTextField());
symbolField = (JTextField)super.getComponent();
symbolField = (JTextField) super.getComponent();
symbolField.setBorder(BorderFactory.createEmptyBorder());
}
@ -38,9 +37,11 @@ class SymbolEditor extends DefaultCellEditor {
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Symbol) {
Symbol symbol = (Symbol) value;
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
if (value instanceof SymbolTableNameValue) {
SymbolTableNameValue cellValue = (SymbolTableNameValue) value;
Symbol symbol = cellValue.getSymbol();
symbolField.setText(symbol.getName());
}
else {

View file

@ -37,7 +37,7 @@ import ghidra.util.table.*;
class SymbolPanel extends JPanel {
private static final boolean FILTER_NAME_ONLY_DEFAULT = false;
private static final boolean FILTER_NAME_ONLY_DEFAULT = true;
private static final String FILTER_SETTINGS_ELEMENT_NAME = "FILTER_SETTINGS";
@ -46,7 +46,6 @@ class SymbolPanel extends JPanel {
private GhidraTable symTable;
private TableModelListener listener;
private FilterDialog filterDialog;
private PluginTool tool;
private GhidraThreadedTablePanel<SymbolRowObject> threadedTablePanel;
private GhidraTableFilterPanel<SymbolRowObject> tableFilterPanel;
@ -54,7 +53,7 @@ class SymbolPanel extends JPanel {
final PluginTool tool, GoToService gotoService) {
super(new BorderLayout());
this.tool = tool;
this.symProvider = provider;
this.tableModel = model;
@ -174,15 +173,13 @@ class SymbolPanel extends JPanel {
if (selectedRowCount == 1) {
int selectedRow = symTable.getSelectedRow();
Object obj = symTable.getValueAt(selectedRow,
symTable.convertColumnIndexToView(SymbolTableModel.LABEL_COL));
if (obj instanceof Symbol) {
symProvider.setCurrentSymbol((Symbol) obj);
return;
}
Symbol symbol = symProvider.getSymbolForRow(selectedRow);
symProvider.setCurrentSymbol(symbol);
}
else {
symProvider.setCurrentSymbol(null);
}
}
int getActualSymbolCount() {
return symTable.getRowCount();
@ -212,9 +209,10 @@ class SymbolPanel extends JPanel {
@Override
public List<String> transform(SymbolRowObject rowObject) {
list.clear();
Symbol symbol = model.getSymbolForRowObject(rowObject);
if (symbol != null) {
list.add(symbol.getName());
Object value = model.getColumnValueForRow(rowObject, SymbolTableModel.LABEL_COL);
if (value != null) {
// the toString() returns the value for the symbol, which may be cached
list.add(value.toString());
}
return list;
}

View file

@ -100,6 +100,10 @@ class SymbolProvider extends ComponentProviderAdapter {
return null;
}
Symbol getSymbolForRow(int row) {
return symbolKeyModel.getRowObject(row).getSymbol();
}
void setCurrentSymbol(Symbol symbol) {
plugin.getReferenceProvider().setCurrentSymbol(symbol);
}
@ -126,9 +130,15 @@ class SymbolProvider extends ComponentProviderAdapter {
}
}
void symbolRemoved(long symbolID) {
void symbolRemoved(Symbol s) {
if (isVisible()) {
symbolKeyModel.symbolRemoved(symbolID);
symbolKeyModel.symbolRemoved(s);
}
}
void symbolRemoved(long symbolId) {
if (isVisible()) {
symbolKeyModel.symbolRemoved(symbolId);
}
}

View file

@ -52,6 +52,10 @@ class SymbolRenderer extends GhidraTableCellRenderer {
if (value == null && column == SymbolTableModel.LABEL_COL) {
setText("<< REMOVED >>");
}
else if (value instanceof SymbolTableNameValue) {
Symbol symbol = ((SymbolTableNameValue) value).getSymbol();
handleSymbol(symbol, isSelected);
}
else if (value instanceof Symbol) {
handleSymbol(value, isSelected);
}

View file

@ -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.
@ -16,12 +15,28 @@
*/
package ghidra.app.plugin.core.symtable;
import ghidra.program.model.symbol.Symbol;
class SymbolRowObject implements Comparable<SymbolRowObject> {
// symbol can be null after it is deleted
private final Symbol symbol;
private final long key;
SymbolRowObject(long key) {
this.key = key;
SymbolRowObject(Symbol s) {
this.symbol = s;
this.key = s.getID();
}
// this constructor is used to create a row object to serve as a key for deleting items
// in the model after a symbol has been deleted
SymbolRowObject(long symbolId) {
this.symbol = null;
this.key = symbolId;
}
Symbol getSymbol() {
return symbol;
}
long getKey() {
@ -38,15 +53,19 @@ class SymbolRowObject implements Comparable<SymbolRowObject> {
@Override
public boolean equals(Object obj) {
if (this == obj)
if (this == obj) {
return true;
if (obj == null)
}
if (obj == null) {
return false;
if (getClass() != obj.getClass())
}
if (getClass() != obj.getClass()) {
return false;
}
SymbolRowObject other = (SymbolRowObject) obj;
if (key != other.key)
if (key != other.key) {
return false;
}
return true;
}

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.symtable;
import java.util.ConcurrentModificationException;
import java.util.LinkedList;
import java.util.List;
import docking.widgets.table.*;
@ -35,7 +35,6 @@ import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.Msg;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.LongArrayList;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.AddressBasedTableModel;
import ghidra.util.table.column.*;
@ -46,19 +45,11 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
static final int LABEL_COL = 0;
static final int LOCATION_COL = 1;
static final int TYPE_COL = 2;
static final int DATATYPE_COL = 3;
static final int DATA_TYPE_COL = 3;
static final int NAMESPACE_COL = 4;
static final int SOURCE_COL = 5;
static final int REFS_COL = 6;
static final String LABEL_COL_NAME = "Labels";
static final String LOCATION_COL_NAME = "Location";
static final String TYPE_COL_NAME = "Type";
static final String DATATYPE_COL_NAME = "Datatype";
static final String REFS_COL_NAME = "# Refs";
static final String NAMESPACE_COL_NAME = "Namespace";
static final String SOURCE_COL_NAME = "Source";
private SymbolProvider provider;
private PluginTool tool;
private SymbolTable symbolTable;
@ -71,6 +62,9 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
this.provider = provider;
this.tool = tool;
this.filter = new NewSymbolFilter();
// leave off default sorting, as this can be slow; the user can sort as desired
setDefaultTableSortState(null);
}
@Override
@ -79,9 +73,8 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
descriptor.addVisibleColumn(new NameTableColumn(), 1, true);
descriptor.addVisibleColumn(new LocationTableColumn());
descriptor.addVisibleColumn(
DiscoverableTableUtils.adaptColumForModel(this, new SymbolTypeTableColumn()));
descriptor.addVisibleColumn(new DataTypeTableColumn());
descriptor.addVisibleColumn(new SymbolTypeTableColumn());
descriptor.addHiddenColumn(new DataTypeTableColumn());
descriptor.addVisibleColumn(new NamespaceTableColumn());
descriptor.addVisibleColumn(new SourceTableColumn());
descriptor.addVisibleColumn(new ReferenceCountTableColumn());
@ -163,7 +156,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
monitor.checkCanceled();
Symbol s = it.next();
if (filter.accepts(s, getProgram())) {
accumulator.add(new SymbolRowObject(s.getID()));
accumulator.add(new SymbolRowObject(s));
}
}
if (filter.acceptsDefaultLabelSymbols()) {
@ -175,7 +168,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
Address a = addrIt.next();
Symbol s = symbolTable.getPrimarySymbol(a);
if (s.isDynamic() && filter.accepts(s, getProgram())) {
accumulator.add(new SymbolRowObject(s.getID()));
accumulator.add(new SymbolRowObject(s));
}
}
}
@ -206,34 +199,25 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
return;
}
switch (columnIndex) {
case LABEL_COL:
try {
if (columnIndex == LABEL_COL) {
String newName = aValue.toString();
if (!symbol.getName().equals(newName)) {
Command renameCmd =
new RenameLabelCmd(symbol.getAddress(), symbol.getName(), newName,
symbol.getParentNamespace(), SourceType.USER_DEFINED);
Command renameCmd = new RenameLabelCmd(symbol.getAddress(), symbol.getName(),
newName, symbol.getParentNamespace(), SourceType.USER_DEFINED);
if (!tool.execute(renameCmd, getProgram())) {
Msg.showError(getClass(), provider.getComponent(),
"Error Renaming Symbol", renameCmd.getStatusMsg());
Msg.showError(getClass(), provider.getComponent(), "Error Renaming Symbol",
renameCmd.getStatusMsg());
}
}
}
catch (ConcurrentModificationException exc) {
Msg.showError(getClass(), provider.getComponent(), "Invalid Symbol",
"Symbol no longer valid.");
}
break;
}
}
@Override
public ProgramLocation getProgramLocation(int row, int column) {
Symbol s = (Symbol) getValueAt(row, LABEL_COL);
SymbolTableNameValue s = (SymbolTableNameValue) getValueAt(row, LABEL_COL);
if (s != null) {
return s.getProgramLocation();
return s.getSymbol().getProgramLocation();
}
return null;
}
@ -262,20 +246,27 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
void symbolAdded(Symbol s) {
if (filter.accepts(s, getProgram())) {
addObject(new SymbolRowObject(s.getID()));
addObject(new SymbolRowObject(s));
lastSymbol = s;
}
}
void symbolRemoved(long symbolID) {
if (lastSymbol != null && lastSymbol.getID() == symbolID) {
void symbolRemoved(Symbol s) {
if (lastSymbol != null && lastSymbol.getID() == s.getID()) {
lastSymbol = null;
}
removeObject(new SymbolRowObject(symbolID));
removeObject(new SymbolRowObject(s));
}
void symbolRemoved(long symbolId) {
if (lastSymbol != null && lastSymbol.getID() == symbolId) {
lastSymbol = null;
}
removeObject(new SymbolRowObject(symbolId));
}
void symbolChanged(Symbol s) {
SymbolRowObject symbolRowObject = new SymbolRowObject(s.getID());
SymbolRowObject symbolRowObject = new SymbolRowObject(s);
if (filter.accepts(s, getProgram())) {
updateObject(symbolRowObject);
}
@ -288,8 +279,9 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
if (rowObjects == null || rowObjects.size() == 0) {
return;
}
tool.setStatusInfo("");
LongArrayList deleteList = new LongArrayList();
List<Symbol> deleteList = new LinkedList<>();
CompoundCmd cmd = new CompoundCmd("Delete symbol(s)");
for (int i = 0; i < rowObjects.size(); i++) {
Symbol symbol = symbolTable.getSymbol(rowObjects.get(i).getKey());
@ -303,7 +295,8 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
continue;//can't delete dynamic symbols...
}
}
deleteList.add(rowObjects.get(i).getKey());
deleteList.add(rowObjects.get(i).getSymbol());
String label = symbol.getName();
if (symbol.getSymbolType() == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject();
@ -339,32 +332,6 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
return filter;
}
@Override
public Class<?> getSortedColumnClass(int columnIndex) {
if (columnIndex == LOCATION_COL) {
return Address.class;
}
return super.getSortedColumnClass(columnIndex);
}
public static int getPreferredWidth(int columnIndex) {
switch (columnIndex) {
case LABEL_COL:
return 140;
case LOCATION_COL:
return 40;
case DATATYPE_COL:
case TYPE_COL:
case SOURCE_COL:
return 30;
case NAMESPACE_COL:
return 80;
case REFS_COL:
return 20;
}
return 40;
}
@Override
public Address getAddress(int row) {
Symbol symbol = symbolTable.getSymbol(getRowObject(row).getKey());
@ -374,21 +341,8 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
return symbol.getAddress();
}
Symbol getSymbolForRowObject(SymbolRowObject storageObject) {
if (symbolTable == null) {
return null;
}
long key = storageObject.getKey();
Symbol localSymbol = lastSymbol;
if (localSymbol == null || localSymbol.getID() != key) {
localSymbol = lastSymbol = symbolTable.getSymbol(key);
}
return localSymbol;
}
AddressBasedLocation getSymbolLocation(SymbolRowObject rowObject) {
Symbol s = getSymbolForRowObject(rowObject);
private AddressBasedLocation getSymbolLocation(SymbolRowObject rowObject) {
Symbol s = rowObject.getSymbol();
if (s == null) {
return new AddressBasedLocation();
}
@ -406,7 +360,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
//==================================================================================================
private class NameTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Symbol> {
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SymbolTableNameValue> {
@Override
public String getColumnName() {
@ -414,9 +368,20 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
@Override
public Symbol getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
return getSymbolForRowObject(rowObject);
public SymbolTableNameValue getValue(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol s = rowObject.getSymbol();
if (s == null) {
return null;
}
// Note: this call is slow, especially for dynamic symbols. Caching the dynamic
// symbols in the SymbolRowObject *greatly* increases sorting and filtering performance.
// For now we assume that most users are not loading dynamic labels. If we add
// caching, then we have to deal with the stickiness of when to clear/update the cache
String name = s.toString();
return new SymbolTableNameValue(s, name);
}
}
@ -433,7 +398,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public Boolean getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -468,7 +433,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public ProgramLocation getProgramLocation(SymbolRowObject rowObject, Settings settings,
Program p, ServiceProvider svcProvider) {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -476,6 +441,29 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
}
private class SymbolTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override
public String getColumnName() {
return "Type";
}
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol s = rowObject.getSymbol();
if (s == null) {
return null;
}
// Note: this call is slow. If we decide that filtering/sorting on this value is
// important, then this should be cached
return SymbolUtilities.getSymbolTypeDisplayName(s);
}
}
private class VariableSymbolLocation extends AddressBasedLocation {
VariableSymbolLocation(Variable variable) {
@ -495,7 +483,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -532,7 +520,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -572,7 +560,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public SourceType getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -584,6 +572,8 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
private class ReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private ReferenceCountRenderer renderer = new ReferenceCountRenderer();
@Override
public String getColumnName() {
return "Reference Count";
@ -592,18 +582,32 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@Override
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
return Integer.valueOf(symbol.getReferenceCount());
}
@Override
public GColumnRenderer<Integer> getColumnRenderer() {
return renderer;
}
// this renderer disables the default text filtering; this column is only filterable
// via the column constraint filtering
private class ReferenceCountRenderer extends GTableCellRenderer
implements AbstractWrapperTypeColumnRenderer<Integer> {
// body is handled by parents
}
}
private class OffcutReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer();
@Override
public String getColumnName() {
return "Offcut Ref Count";
@ -613,7 +617,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
public Integer getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -637,6 +641,18 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
}
return Integer.valueOf(count);
}
@Override
public GColumnRenderer<Integer> getColumnRenderer() {
return renderer;
}
// this renderer disables the default text filtering; this column is only filterable
// via the column constraint filtering
private class OffcutReferenceCountRenderer extends GTableCellRenderer
implements AbstractWrapperTypeColumnRenderer<Integer> {
// body is handled by parents
}
}
private class UserTableColumn
@ -656,7 +672,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null) {
return null;
}
@ -694,7 +710,7 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
public String getValue(SymbolRowObject rowObject, Settings settings, Program p,
ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = getSymbolForRowObject(rowObject);
Symbol symbol = rowObject.getSymbol();
if (symbol == null || !symbol.isExternal()) {
return null;
}

View file

@ -0,0 +1,52 @@
/* ###
* 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.symtable;
import ghidra.program.model.symbol.Symbol;
/**
* A simple data object for the Name column table cell. This class allows us to control
* how sorting is performed by caching the slow (potentially) to calculate symbol name.
*/
class SymbolTableNameValue implements Comparable<SymbolTableNameValue> {
private Symbol symbol;
private String name;
SymbolTableNameValue(Symbol symbol, String name) {
this.symbol = symbol;
this.name = name;
// name will be non-null when cached by the table model
if (name == null) {
name = symbol.toString();
}
}
Symbol getSymbol() {
return symbol;
}
@Override
public int compareTo(SymbolTableNameValue o) {
return name.compareToIgnoreCase(o.name);
}
@Override
public String toString() {
return name;
}
}

View file

@ -190,7 +190,10 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
if (!symProvider.isVisible()) {
return;
}
if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) {
if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_ADDED) ||
ev.containsEvent(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED)) {
symProvider.reload();
refProvider.reload();
return;
@ -223,7 +226,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
Address addAddr = rec.getStart();
Symbol primaryAtAdd = currentProgram.getSymbolTable().getPrimarySymbol(addAddr);
if (primaryAtAdd != null && primaryAtAdd.isDynamic()) {
symProvider.symbolRemoved(primaryAtAdd.getID());
symProvider.symbolRemoved(primaryAtAdd);
}
symbol = (Symbol) rec.getNewValue();
symProvider.symbolAdded(symbol);
@ -301,13 +304,6 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
refProvider.symbolChanged(element);
}
break;
case ChangeManager.DOCR_MEMORY_BLOCK_ADDED:
case ChangeManager.DOCR_MEMORY_BLOCK_REMOVED:
symProvider.reload();
refProvider.reload();
break;
}
}
}

View file

@ -15,14 +15,19 @@
*/
package ghidra.util.table.field;
import docking.widgets.table.GTableCellRenderer;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.table.column.AbstractWrapperTypeColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
public class FunctionBodySizeTableColumn
extends ProgramBasedDynamicTableColumnExtensionPoint<Function, Integer> {
private FunctionBodySizeRenderer renderer = new FunctionBodySizeRenderer();
@Override
public String getColumnName() {
return "Function Size";
@ -34,4 +39,15 @@ public class FunctionBodySizeTableColumn
return (int) rowObject.getBody().getNumAddresses();
}
@Override
public GColumnRenderer<Integer> getColumnRenderer() {
return renderer;
}
// this renderer disables the default text filtering; this column is only filterable
// via the column constraint filtering
private class FunctionBodySizeRenderer extends GTableCellRenderer
implements AbstractWrapperTypeColumnRenderer<Integer> {
// body is handled by parents
}
}

View file

@ -18,7 +18,8 @@ package ghidra.app.plugin.core.symtable;
import static org.junit.Assert.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -26,7 +27,8 @@ import java.util.function.BiConsumer;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.table.*;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableModel;
import org.jdom.Element;
import org.junit.*;
@ -130,40 +132,49 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
public void testSortingLabelColumn() throws Exception {
openProgram("sample");
Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LABEL_COL);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
TableColumn column =
symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LABEL_COL);
GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer();
assertTrue(renderer.isSortedAscending());
sortAscending(SymbolTableModel.LABEL_COL);
TableModel model = symbolTable.getModel();
for (int i = 0; i < model.getRowCount() - 1; ++i) {
Symbol sym1 = (Symbol) model.getValueAt(i + 0, SymbolTableModel.LABEL_COL);
Symbol sym2 = (Symbol) model.getValueAt(i + 1, SymbolTableModel.LABEL_COL);
Symbol sym1 = getSymbol(i);
Symbol sym2 = getSymbol(i + 1);
int compare = sym1.getName().compareToIgnoreCase(sym2.getName());
assertTrue("row " + i + " not sorted correctly", (compare < 0 || compare == 0));
}
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
assertTrue(!renderer.isSortedAscending());
sortDescending(SymbolTableModel.LABEL_COL);
model = symbolTable.getModel();
for (int i = 0; i < model.getRowCount() - 1; ++i) {
Symbol sym1 = (Symbol) model.getValueAt(i + 0, SymbolTableModel.LABEL_COL);
Symbol sym2 = (Symbol) model.getValueAt(i + 1, SymbolTableModel.LABEL_COL);
Symbol sym1 = getSymbol(i);
Symbol sym2 = getSymbol(i + 1);
int compare = sym1.getName().compareToIgnoreCase(sym2.getName());
assertTrue("row " + i + " not sorted correctly", (compare > 0 || compare == 0));
}
}
private void sortAscending(int column) {
runSwing(() -> symbolModel.setTableSortState(
TableSortState.createDefaultSortState(column, true)));
waitForTableModel(symbolModel);
waitForCondition(() -> {
TableSortState sort = runSwing(() -> symbolModel.getTableSortState());
return sort.getColumnSortState(column).isAscending();
});
}
private void sortDescending(int column) {
runSwing(() -> symbolModel.setTableSortState(
TableSortState.createDefaultSortState(column, false)));
waitForTableModel(symbolModel);
waitForCondition(() -> {
TableSortState sort = runSwing(() -> symbolModel.getTableSortState());
return !sort.getColumnSortState(column).isAscending();
});
}
@Test
public void testColumnDiscovery() throws Exception {
//
@ -205,35 +216,21 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
public void testSortingAddressColumn() throws Exception {
openProgram("sample");
Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LOCATION_COL);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
TableColumn column =
symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LOCATION_COL);
GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer();
assertTrue(renderer.isSortedAscending());
sortAscending(SymbolTableModel.LOCATION_COL);
SymbolTableModel model = (SymbolTableModel) symbolTable.getModel();
for (int i = 0; i < model.getRowCount() - 1; ++i) {
AddressBasedLocation refs1 =
(AddressBasedLocation) model.getValueAt(i + 0, SymbolTableModel.LOCATION_COL);
AddressBasedLocation refs2 =
(AddressBasedLocation) model.getValueAt(i + 1, SymbolTableModel.LOCATION_COL);
assertTrue(refs1.compareTo(refs2) <= 0);
AddressBasedLocation loc1 = getLocation(i);
AddressBasedLocation loc2 = getLocation(i + 0);
assertTrue(loc1.compareTo(loc2) <= 0);
}
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
assertTrue(!renderer.isSortedAscending());
sortDescending(SymbolTableModel.LOCATION_COL);
for (int i = 0; i < model.getRowCount() - 1; ++i) {
AddressBasedLocation refs1 =
(AddressBasedLocation) model.getValueAt(i + 0, SymbolTableModel.LOCATION_COL);
AddressBasedLocation refs2 =
(AddressBasedLocation) model.getValueAt(i + 1, SymbolTableModel.LOCATION_COL);
assertTrue(refs1.compareTo(refs2) >= 0);
AddressBasedLocation loc1 = getLocation(i);
AddressBasedLocation loc2 = getLocation(i + 0);
assertTrue(loc1.compareTo(loc2) >= 0);
}
}
@ -241,26 +238,20 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
public void testSortingReferenceColumn() throws Exception {
openProgram("sample");
sortOnColumn(SymbolTableModel.REFS_COL);
TableColumn column =
symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.REFS_COL);
GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer();
assertTrue(renderer.isSortedAscending());
sortAscending(SymbolTableModel.REFS_COL);
TableModel model = symbolTable.getModel();
for (int i = 0; i < model.getRowCount() - 1; ++i) {
Integer refs1 = (Integer) model.getValueAt(i + 0, SymbolTableModel.REFS_COL);
Integer refs2 = (Integer) model.getValueAt(i + 1, SymbolTableModel.REFS_COL);
Integer refs1 = getRefCount(i);
Integer refs2 = getRefCount(i + 1);
assertTrue(refs1.compareTo(refs2) <= 0);
}
sortOnColumn(SymbolTableModel.REFS_COL);
assertTrue(!renderer.isSortedAscending());
sortDescending(SymbolTableModel.REFS_COL);
for (int i = 0; i < model.getRowCount() - 1; ++i) {
Integer refs1 = (Integer) model.getValueAt(i + 0, SymbolTableModel.REFS_COL);
Integer refs2 = (Integer) model.getValueAt(i + 1, SymbolTableModel.REFS_COL);
Integer refs1 = getRefCount(i);
Integer refs2 = getRefCount(i + 1);
assertTrue(refs1.compareTo(refs2) >= 0);
}
}
@ -359,7 +350,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForNotBusy(symbolTable);
int row = findRow("ghidra", "Global");
String symbolName = "ghidra";
int row = findRow(symbolName, "Global");
doubleClick(symbolTable, row, SymbolTableModel.LABEL_COL);
waitForSwing();
@ -368,6 +360,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
Component editor = symbolTable.getEditorComponent();
assertNotNull(editor);
JTextField textField = (JTextField) editor;
String currentText = getText(textField);
assertEquals(symbolName, currentText);
triggerActionKey(textField, 0, KeyEvent.VK_END);
myTypeText(editor, ".Is.Cool");
runSwing(() -> symbolTable.editingStopped(new ChangeEvent(symbolTable)));
@ -376,7 +371,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(!symbolTable.isEditing());
Symbol s = (Symbol) symbolTable.getValueAt(row, SymbolTableModel.LABEL_COL);
Symbol s = getSymbol(row);
assertEquals("ghidra.Is.Cool", s.getName());
}
@ -567,7 +562,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
int[] selectedRows = symbolTable.getSelectedRows();
assertEquals(3, selectedRows.length);
for (int selectedRow : selectedRows) {
Symbol symbol = (Symbol) symbolTable.getValueAt(selectedRow, 0);
Symbol symbol = getSymbol(selectedRow);
assertTrue(!symbol.isPinned());
}
assertTrue(setPinnedAction.isEnabledForContext(actionContext));
@ -576,14 +571,14 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
performAction(setPinnedAction, actionContext, true);
waitForSwing();
for (int selectedRow : selectedRows) {
Symbol symbol = (Symbol) symbolTable.getValueAt(selectedRow, 0);
Symbol symbol = getSymbol(selectedRow);
assertTrue(symbol.isPinned());
}
performAction(clearPinnedAction, actionContext, true);
waitForSwing();
for (int selectedRow : selectedRows) {
Symbol symbol = (Symbol) symbolTable.getValueAt(selectedRow, 0);
Symbol symbol = getSymbol(selectedRow);
assertTrue(!symbol.isPinned());
}
@ -600,11 +595,11 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
int[] selectedRows = symbolTable.getSelectedRows();
for (int selectedRow : selectedRows) {
Symbol symbol = (Symbol) symbolTable.getValueAt(selectedRow, 0);
assertTrue(!symbol.isPinned());
Symbol symbol = getSymbol(selectedRow);
assertFalse(symbol.isPinned());
}
assertTrue(!setPinnedAction.isEnabledForContext(actionContext));
assertTrue(!clearPinnedAction.isEnabledForContext(actionContext));
assertFalse(setPinnedAction.isEnabledForContext(actionContext));
assertFalse(clearPinnedAction.isEnabledForContext(actionContext));
}
@ -620,13 +615,13 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
sym = st.createLabel(sample.getNewAddress(0x01007000), "Zeus", SourceType.USER_DEFINED);
waitForNotBusy(symbolTable);
assertEquals(rowCount + 1, symbolTable.getRowCount());
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) >= 0);
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym)) >= 0);
sym =
st.createLabel(sample.getNewAddress(0x01007100), "Athena", SourceType.USER_DEFINED);
waitForNotBusy(symbolTable);
assertEquals(rowCount + 2, symbolTable.getRowCount());
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) >= 0);
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym)) >= 0);
}
finally {
prog.endTransaction(id, true);
@ -659,7 +654,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
sym =
st.createLabel(sample.getNewAddress(0x01007000), "saaaa", SourceType.USER_DEFINED);
waitForNotBusy(symbolTable);
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) >= 0);
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym)) >= 0);
}
finally {
prog.endTransaction(id, true);
@ -722,7 +717,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
// entry symbol replaced by dynamic External Entry symbol
assertNull(getUniqueSymbol(prog, "entry"));
assertNotNull(getUniqueSymbol(prog, "EXT_00000051"));
assertTrue(symbolModel.getRowIndex(new SymbolRowObject(sym.getID())) == -1);
assertTrue("Deleted symbol not removed from table",
symbolModel.getRowIndex(new SymbolRowObject(sym)) < 0);
}
@Test
@ -732,8 +728,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
Symbol s = getUniqueSymbol(prog, "entry");
int row = symbolModel.getRowIndex(new SymbolRowObject(s.getID()));
Integer refCount = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL);
int row = symbolModel.getRowIndex(new SymbolRowObject(s));
Integer refCount = getRefCount(row);
assertNotNull(refCount);
assertEquals(3, refCount.intValue());
@ -749,9 +745,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
waitForNotBusy(symbolTable);
row = symbolModel.getRowIndex(new SymbolRowObject(s.getID()));
row = symbolModel.getRowIndex(new SymbolRowObject(s));
refCount = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL);
refCount = getRefCount(row);
assertNotNull(refCount);
assertEquals(4, refCount.intValue());
}
@ -763,9 +759,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
Symbol s = getUniqueSymbol(prog, "doStuff");
int row = symbolModel.getRowIndex(new SymbolRowObject(s.getID()));
int row = symbolModel.getRowIndex(new SymbolRowObject(s));
Integer refCount = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL);
Integer refCount = getRefCount(row);
assertNotNull(refCount);
assertEquals(4, refCount.intValue());
@ -791,7 +787,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
waitForNotBusy(symbolTable);
refCount = (Integer) symbolTable.getValueAt(row, SymbolTableModel.REFS_COL);
refCount = getRefCount(row);
assertNotNull(refCount);
assertEquals(3, refCount.intValue());
}
@ -853,23 +849,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
/************** LABEL **********************/
Rectangle rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LABEL_COL);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
TableColumn column =
symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LABEL_COL);
GTableHeaderRenderer renderer = (GTableHeaderRenderer) column.getHeaderRenderer();
assertTrue(renderer.isSortedAscending());
sortAscending(SymbolTableModel.LABEL_COL);
TableModel model = symbolTable.getModel();
for (int i = 0; i < model.getRowCount() - 1; ++i) {
Symbol sym1 = (Symbol) model.getValueAt(i + 0, SymbolTableModel.LABEL_COL);
Symbol sym2 = (Symbol) model.getValueAt(i + 1, SymbolTableModel.LABEL_COL);
Symbol sym1 = getSymbol(i);
Symbol sym2 = getSymbol(i + 1);
int compare = sym1.getName().compareToIgnoreCase(sym2.getName());
assertTrue("Symbol \"" + sym1 + "\" is not sorted as less than symbol \"" + sym2 + "\"",
compare <= 0);
@ -877,21 +862,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
/************** ADDRESS **********************/
rect = symbolTableHeader.getHeaderRect(SymbolTableModel.LOCATION_COL);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
column = symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.LOCATION_COL);
renderer = (GTableHeaderRenderer) column.getHeaderRenderer();
assertTrue(renderer.isSortedAscending());
sortAscending(SymbolTableModel.LOCATION_COL);
model = symbolTable.getModel();
for (int i = 0; i < model.getRowCount() - 1; ++i) {
AddressBasedLocation loc1 =
(AddressBasedLocation) model.getValueAt(i, SymbolTableModel.LOCATION_COL);
AddressBasedLocation loc2 =
(AddressBasedLocation) model.getValueAt(i + 1, SymbolTableModel.LOCATION_COL);
AddressBasedLocation loc1 = getLocation(i);
AddressBasedLocation loc2 = getLocation(i + 1);
int compare = SystemUtilities.compareTo(loc1, loc2);
assertTrue(
"Location1 \"" + loc1 + "\"is not sorted as less than location2 \"" + loc2 + "\"",
@ -900,18 +876,12 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
/************** REFERENCES **********************/
rect = symbolTableHeader.getHeaderRect(SymbolTableModel.REFS_COL);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
column = symbolTableHeader.getColumnModel().getColumn(SymbolTableModel.REFS_COL);
renderer = (GTableHeaderRenderer) column.getHeaderRenderer();
assertTrue(renderer.isSortedAscending());
sortAscending(SymbolTableModel.REFS_COL);
model = symbolTable.getModel();
for (int i = 0; i < model.getRowCount() - 1; ++i) {
Integer refs1 = (Integer) model.getValueAt(i + 0, SymbolTableModel.REFS_COL);
Integer refs2 = (Integer) model.getValueAt(i + 1, SymbolTableModel.REFS_COL);
Integer refs1 = getRefCount(i);
Integer refs2 = getRefCount(i + 1);
assertTrue(
"The number of references (\"" + refs1 + "\") for row did not " +
"compare as less than the number for the following row (\"" + refs2 + "\")",
@ -1035,7 +1005,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
deleteText(textField);
// sort on a different column to trigger the other kind of filtering
sortOnColumn(SymbolTableModel.REFS_COL);
sortAscending(SymbolTableModel.REFS_COL);
text = "_";
myTypeText(textField, text);
@ -1106,7 +1076,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
deleteText(textField);
// sort on a different column to trigger the other kind of filtering
sortOnColumn(SymbolTableModel.LOCATION_COL);
sortAscending(SymbolTableModel.LOCATION_COL);
text = "_";
myTypeText(textField, text);
@ -1285,6 +1255,24 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
private Symbol getSymbol(int row) {
SymbolRowObject rowObject = symbolModel.getRowObject(row);
return rowObject.getSymbol();
}
private Integer getRefCount(int row) {
Integer count =
runSwing(() -> (Integer) symbolModel.getValueAt(row, SymbolTableModel.REFS_COL));
return count;
}
private AddressBasedLocation getLocation(int row) {
AddressBasedLocation location =
runSwing(() -> (AddressBasedLocation) symbolModel.getValueAt(row,
SymbolTableModel.LOCATION_COL));
return location;
}
private void assertReferencesAddressColumnValue(int row, long value) {
Address addr =
(Address) referenceTable.getValueAt(row, SymbolReferenceModel.ADDRESS_COLUMN);
@ -1292,12 +1280,6 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
addr.getOffset());
}
private void sortOnColumn(int column) throws Exception {
Rectangle rect = symbolTableHeader.getHeaderRect(column);
clickMouse(symbolTableHeader, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
waitForNotBusy(symbolTable);
}
private void deleteText(JTextField field) throws Exception {
deleteText(field, false, false);
}
@ -1328,14 +1310,10 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
}
private void setFilterOptions(TextFilterStrategy filterStrategy, boolean caseSensitive) {
private void setFilterOptions(TextFilterStrategy filterStrategy, boolean caseSensitive)
throws Exception {
filterPanel.setFilterOptions(new FilterOptions(filterStrategy, true, caseSensitive, false));
waitForTable();
}
private void waitForTable() {
waitForSwing();
waitForNotBusy(symbolTable);
}
private JTextField getFilterTextField() {
@ -1376,14 +1354,14 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private int getRowForSymbol(Symbol symbol) {
for (int i = 0; i < symbolTable.getRowCount(); i++) {
Object name = symbolTable.getValueAt(i, 0);
if (name.toString().equals(symbol.getName())) {
Object namespace = symbolTable.getValueAt(i, 4);
if (namespace.toString().equals(symbol.getParentNamespace().getName())) {
Symbol rowSymbol = getSymbol(i);
if (rowSymbol.equals(symbol)) {
// if (rowSymbol.getParentNamespace().equals(symbol.getParentNamespace())) {
// return i;
// }
return i;
}
}
}
Assert.fail("Didn't find symbol in symbol table: " + symbol.getName());
return -1;
}
@ -1478,7 +1456,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
"There are no filtered matches as expected from filter string: " + filterString,
rowCount > 0);
for (int i = 0; i < rowCount; i++) {
Symbol symbol = (Symbol) symbolModel.getValueAt(i, SymbolTableModel.LABEL_COL);
Symbol symbol = getSymbol(i);
assertTrue(
"Found an entry in the symbol table model that " + "does not match the given " +
"filter: " + filterString + " and symbol: " + symbol.getName(),
@ -1495,7 +1473,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
String filterText = string;
for (int i = 0; i < rowCount; i++) {
Symbol symbol = (Symbol) symbolModel.getValueAt(i, SymbolTableModel.LABEL_COL);
Symbol symbol = getSymbol(i);
assertTrue(
"Found an entry in the symbol table model that does not match the given " +
"filter: " + string + " and symbol: " + symbol.getName(),
@ -1504,8 +1482,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
}
private void waitForNotBusy(GTable table) throws Exception {
prog.flushEvents();
waitForProgram(prog);
ThreadedTableModel<?, ?> model = (ThreadedTableModel<?, ?>) table.getModel();
waitForTableModel(model);
}
@ -1632,6 +1609,8 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
waitForNotBusy(symbolTable);
symbolTableHeader = symbolTable.getTableHeader();
sortAscending(SymbolTableModel.LABEL_COL);
}
private void showReferencesTable() {
@ -1679,7 +1658,9 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
private int findRow(String symbolName, String namespace) {
int max = symbolTable.getRowCount();
for (int i = 0; i < max; i++) {
Symbol s = (Symbol) symbolTable.getValueAt(i, SymbolTableModel.LABEL_COL);
SymbolTableNameValue symbolNameValue =
(SymbolTableNameValue) symbolTable.getValueAt(i, SymbolTableModel.LABEL_COL);
Symbol s = symbolNameValue.getSymbol();
if (symbolName.equals(s.getName()) &&
namespace.equals(s.getParentNamespace().getName())) {
return i;

View file

@ -92,8 +92,8 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
}
/**
* Returns the index of the given row object in this model; -1 if the model does not contain
* the given object.
* 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
@ -132,6 +132,9 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
@Override
public int getPrimarySortColumnIndex() {
if (sortState.isUnsorted()) {
return -1;
}
return sortState.iterator().next().getColumnModelIndex();
}

View file

@ -57,6 +57,13 @@ public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransfo
}
}
if (columnUsesConstraintFilteringOnly(column)) {
// This allows columns to be ignored for default text filtering while still being
// filterable through the column constraints API
return null;
}
// note: this call can be slow when columns dynamically calculate values from the database
Object value = model.getColumnValueForRow(rowObject, column);
if (value == null) {
return null;
@ -96,7 +103,22 @@ public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransfo
return value.toString();
}
@SuppressWarnings("unchecked")
private boolean columnUsesConstraintFilteringOnly(int column) {
if (!(model instanceof DynamicColumnTableModel)) {
return false;
}
DynamicColumnTableModel<ROW_OBJECT> columnBasedModel =
(DynamicColumnTableModel<ROW_OBJECT>) model;
GColumnRenderer<Object> renderer = getColumnRenderer(columnBasedModel, column);
if (renderer == null) {
return false;
}
ColumnConstraintFilterMode mode = renderer.getColumnConstraintFilterMode();
return mode == ColumnConstraintFilterMode.USE_COLUMN_CONSTRAINTS_ONLY;
}
private String getRenderedColumnValue(Object columnValue, int columnIndex) {
if (!(model instanceof DynamicColumnTableModel)) {
@ -110,11 +132,6 @@ public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransfo
return null;
}
ColumnConstraintFilterMode mode = renderer.getColumnConstraintFilterMode();
if (mode == ColumnConstraintFilterMode.USE_COLUMN_CONSTRAINTS_ONLY) {
return null; // this renderer does not support text
}
Settings settings = columnBasedModel.getColumnSettings(columnIndex);
String s = renderer.getFilterString(columnValue, settings);
return s;

View file

@ -33,7 +33,7 @@ import ghidra.util.datastruct.WeakSet;
public class GTableColumnModel
implements TableColumnModel, PropertyChangeListener, ListSelectionListener {
private List<TableColumn> visibleList = new ArrayList<>();
private VisibleColumns visibleColumns = new VisibleColumns();
private List<TableColumn> completeList = new ArrayList<>();
private int totalColumnWidth;
private int columnMargin;
@ -67,18 +67,8 @@ public class GTableColumnModel
void removeAllColumns() {
TableColumn[] asArray = visibleList.toArray(new TableColumn[visibleList.size()]);
for (int i = 0; i < asArray.length; i++) {
TableColumn column = asArray[i];
visibleList.remove(column);
fireColumnRemoved(new TableColumnModelEvent(this, i, i));
}
/*
TODO replace the above snippet with this code, after the upcoming release
fireColumnRemoved(new TableColumnModelEvent(this, 0, visibleList.size() - 1));
visibleList.clear();
*/
fireColumnRemoved(new TableColumnModelEvent(this, 0, visibleColumns.size() - 1));
visibleColumns.clear();
// no need to fire the removed event for items in the complete list, as the clients
// only know about the visible columns
@ -90,7 +80,7 @@ public class GTableColumnModel
void dispose() {
listeners.clear();
visibleList.clear();
visibleColumns.clear();
completeList.clear();
columnModelState.dispose();
}
@ -101,7 +91,7 @@ public class GTableColumnModel
* @return true if the given column is visible.
*/
public boolean isVisible(TableColumn column) {
return visibleList.contains(column);
return visibleColumns.contains(column);
}
/**
@ -119,7 +109,7 @@ public class GTableColumnModel
}
public void setVisible(TableColumn column, boolean visible) {
boolean isVisible = visibleList.contains(column);
boolean isVisible = visibleColumns.contains(column);
if (visible == isVisible) {
return;
@ -127,12 +117,12 @@ public class GTableColumnModel
if (visible) {
int insertIndex = findVisibleInsertionIndex(column);
visibleList.add(insertIndex, column);
visibleColumns.add(insertIndex, column);
fireColumnAdded(new TableColumnModelEvent(this, insertIndex, insertIndex));
}
else {
int columnIndex = visibleList.indexOf(column);
visibleList.remove(columnIndex);
int columnIndex = visibleColumns.indexOf(column);
visibleColumns.remove(columnIndex);
// Adjust for the selection
if (selectionModel != null) {
selectionModel.removeIndexInterval(columnIndex, columnIndex);
@ -146,12 +136,12 @@ public class GTableColumnModel
}
private int findVisibleInsertionIndex(TableColumn column) {
int completeIndex = completeList.indexOf(column);
int completeIndex = visibleColumns.indexOf(column);
int size = visibleList.size();
int size = visibleColumns.size();
for (int i = completeIndex + 1; i < size; i++) {
TableColumn nextColumn = completeList.get(i);
int visibleIndex = visibleList.indexOf(nextColumn);
int visibleIndex = visibleColumns.indexOf(nextColumn);
if (visibleIndex != -1) {
return visibleIndex;
}
@ -169,7 +159,7 @@ public class GTableColumnModel
removeColumnWithModelIndex(aColumn.getModelIndex()); // dedup
completeList.add(aColumn);
visibleList.add(aColumn);
visibleColumns.add(aColumn);
aColumn.addPropertyChangeListener(this);
@ -205,7 +195,7 @@ public class GTableColumnModel
}
completeList.remove(tableColumn);
visibleList.remove(tableColumn);
visibleColumns.remove(tableColumn);
tableColumn.removePropertyChangeListener(this);
}
@ -216,15 +206,15 @@ public class GTableColumnModel
@Override
public TableColumn getColumn(int columnIndex) {
if ((columnIndex < 0) || (columnIndex >= visibleList.size())) {
if ((columnIndex < 0) || (columnIndex >= visibleColumns.size())) {
return null;
}
return visibleList.get(columnIndex);
return visibleColumns.get(columnIndex);
}
@Override
public int getColumnCount() {
return visibleList.size();
return visibleColumns.size();
}
@Override
@ -232,8 +222,8 @@ public class GTableColumnModel
if (columnIdentifier == null) {
throw new IllegalArgumentException("Identifier is null");
}
for (int i = 0; i < visibleList.size(); i++) {
TableColumn tableColumn = visibleList.get(i);
for (int i = 0; i < visibleColumns.size(); i++) {
TableColumn tableColumn = visibleColumns.get(i);
if (columnIdentifier.equals(tableColumn.getIdentifier())) {
return i;
}
@ -271,7 +261,7 @@ public class GTableColumnModel
@Override
public Enumeration<TableColumn> getColumns() {
return Collections.enumeration(visibleList);
return visibleColumns.toEnumeration();
}
/**
@ -355,8 +345,8 @@ public class GTableColumnModel
}
// update the visible list
TableColumn movedColumn = visibleList.remove(columnIndex);
visibleList.add(newIndex, movedColumn);
TableColumn movedColumn = visibleColumns.remove(columnIndex);
visibleColumns.add(newIndex, movedColumn);
// update the complete list
completeList.remove(movedColumn);
@ -364,7 +354,7 @@ public class GTableColumnModel
// get the item at the index after the new index (since we are moving up, we know
// that there are columns below the new index)
TableColumn column = visibleList.get(newIndex + 1);
TableColumn column = visibleColumns.get(newIndex + 1);
// find this column in the complete list and then place the moved column before that
// position in the complete list
@ -375,7 +365,7 @@ public class GTableColumnModel
// get the item at the index before the new index (since we are moving down, we know
// that there are columns above the new index)
TableColumn column = visibleList.get(newIndex - 1);
TableColumn column = visibleColumns.get(newIndex - 1);
// find this column in the complete list and then place the moved column after that
// position in the complete list
@ -402,9 +392,9 @@ public class GTableColumnModel
public void removeColumn(TableColumn column) {
completeList.remove(column);
int index = visibleList.indexOf(column);
int index = visibleColumns.indexOf(column);
if (index >= 0) {
visibleList.remove(index);
visibleColumns.remove(index);
// Adjust for the selection
if (selectionModel != null) {
selectionModel.removeIndexInterval(index, index);
@ -459,7 +449,7 @@ public class GTableColumnModel
*/
private void recalcWidthCache() {
totalColumnWidth = 0;
for (TableColumn tableColumn : visibleList) {
for (TableColumn tableColumn : visibleColumns.getColumns()) {
totalColumnWidth += tableColumn.getWidth();
}
}
@ -471,7 +461,7 @@ public class GTableColumnModel
void restoreState(List<TableColumn> newCompleteList, List<Settings> newSettingsList,
List<TableColumn> newVisibleList) {
this.completeList = newCompleteList;
this.visibleList = newVisibleList;
this.visibleColumns = new VisibleColumns(newVisibleList);
TableModel model = table.getModel();
if (model instanceof ConfigurableColumnTableModel) {
@ -484,10 +474,6 @@ public class GTableColumnModel
configurableModel.setAllColumnSettings(columnIndexAndSettings);
}
// TODO: at some point in the future (like a year or more) we can remove this, when
// we know the new code below it works
// fireColumnMarginChanged(); // let the system know to rebuild the GUI (Java HACK!)
// signal a change; we've added/removed columns, but we don't need to be specific
TableColumnModelEvent e = new TableColumnModelEvent(this, 0, getColumnCount() - 1);
fireColumnAdded(e);
@ -515,6 +501,74 @@ public class GTableColumnModel
return oldValue;
}
/*
* A small class to provide a method to quickly see if a column is visible by calling contains
* on a hash set
*/
private class VisibleColumns {
private Set<TableColumn> visibleSet = new HashSet<>();
private List<TableColumn> visibleList = new ArrayList<>();
public VisibleColumns() {
}
public VisibleColumns(List<TableColumn> newVisibleList) {
this.visibleList = newVisibleList;
visibleSet.addAll(visibleList);
}
List<TableColumn> getColumns() {
return visibleList;
}
int size() {
return visibleList.size();
}
public void remove(TableColumn column) {
visibleList.remove(column);
visibleSet.remove(column);
}
public void add(TableColumn column) {
visibleList.add(column);
visibleSet.add(column);
}
public Enumeration<TableColumn> toEnumeration() {
return Collections.enumeration(visibleList);
}
public TableColumn get(int index) {
return visibleList.get(index);
}
public int indexOf(TableColumn column) {
return visibleList.indexOf(column);
}
public TableColumn remove(int index) {
TableColumn column = visibleList.remove(index);
visibleSet.remove(column);
return column;
}
public void add(int insertIndex, TableColumn column) {
visibleList.add(insertIndex, column);
visibleSet.add(column);
}
void clear() {
visibleList.clear();
visibleSet.clear();
}
boolean contains(TableColumn c) {
return visibleSet.contains(c);
}
}
//==================================================================================================
// Listener and event methods
//==================================================================================================

View file

@ -40,7 +40,7 @@ public interface SortedTableModel extends TableModel {
public boolean isSortable(int columnIndex);
/**
* Returns the column index that is the primary sorted column
* Returns the column index that is the primary sorted column; -1 if no column is sorted
*
* @return the index
*/

View file

@ -20,6 +20,7 @@ import java.util.Comparator;
import docking.widgets.table.*;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.GColumnRenderer;
import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
/**
* A special version of the backup comparator that uses the column's rendered value for
@ -33,10 +34,25 @@ public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T>
protected int sortColumn;
protected DynamicColumnTableModel<T> model;
// columns do not support sorting via their rendered value if the are marked
// for column filtering only
private boolean supportsColumnSorting = true;
public ColumnRenderedValueBackupRowComparator(DynamicColumnTableModel<T> model,
int sortColumn) {
this.model = model;
this.sortColumn = sortColumn;
DynamicTableColumn<T, ?, ?> column = model.getColumn(sortColumn);
@SuppressWarnings("unchecked")
GColumnRenderer<Object> renderer = (GColumnRenderer<Object>) column.getColumnRenderer();
if (renderer != null) {
if (renderer.getColumnConstraintFilterMode() == ColumnConstraintFilterMode.USE_COLUMN_CONSTRAINTS_ONLY) {
// this implies that the column has signaled that it does not support
// filtering/sorting using its rendered value
supportsColumnSorting = false;
}
}
}
@Override
@ -61,9 +77,13 @@ public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T>
@SuppressWarnings("unchecked")
private String getRenderedColumnStringValue(T t) {
if (!supportsColumnSorting) {
return null;
}
DynamicTableColumn<T, ?, ?> column = model.getColumn(sortColumn);
GColumnRenderer<Object> renderer = (GColumnRenderer<Object>) column.getColumnRenderer();
Object o = model.getColumnValueForRow(t, sortColumn);
Object o = getColumnValue(t);
if (renderer == null) {
return o == null ? null : o.toString();
}
@ -72,6 +92,7 @@ public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T>
return renderer.getFilterString(o, settings);
}
// this may be overridden to use caching
protected Object getColumnValue(T t) {
return model.getColumnValueForRow(t, sortColumn);
}

View file

@ -239,7 +239,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
}
void initializeCache() {
threadLocalColumnCache.set(new LRUMap<ROW_OBJECT, Map<Integer, Object>>(20000));
threadLocalColumnCache.set(new LRUMap<ROW_OBJECT, Map<Integer, Object>>(1000000));
}
void clearCache() {
@ -747,10 +747,6 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
return maxUpdateDelayMillis;
}
protected Class<?> getSortedColumnClass(int columnIndex) {
return getColumnClass(columnIndex);
}
ThreadedTableModelUpdateMgr<ROW_OBJECT> getUpdateManager() {
return updateManager;
}

View file

@ -96,7 +96,7 @@ public class SymbolTablePluginScreenShots extends GhidraScreenShotGenerator {
ComponentProvider provider = getProvider("Symbol Table");
tool.showComponentProvider(provider, true);
moveProviderToItsOwnWindow(provider, 950, 750);
moveProviderToItsOwnWindow(provider, 950, 400);
GTable table = getTable(provider);
setColumnSizes(table);
@ -172,13 +172,28 @@ public class SymbolTablePluginScreenShots extends GhidraScreenShotGenerator {
TableColumn column = columnModel.getColumn(i);
Object headerValue = column.getHeaderValue();
if ("Name".equals(headerValue)) {
column.setPreferredWidth(175);
column.setPreferredWidth(300);
}
else if ("Reference Count".equals(headerValue)) {
column.setPreferredWidth(15);
column.setPreferredWidth(25);
}
else if ("Offcut Ref Count".equals(headerValue)) {
column.setPreferredWidth(15);
column.setPreferredWidth(25);
}
else if ("Namespace".equals(headerValue)) {
column.setPreferredWidth(160);
}
else if ("Location".equals(headerValue)) {
column.setPreferredWidth(170);
}
else if ("Namespace".equals(headerValue)) {
column.setPreferredWidth(170);
}
else if ("Source".equals(headerValue)) {
column.setPreferredWidth(170);
}
else if ("Type".equals(headerValue)) {
column.setPreferredWidth(170);
}
}
});