mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
GT-2763 - Tables - Added the ability to turn off sorting on tables
This commit is contained in:
parent
538cbc1226
commit
4a8144c288
@ -112,7 +112,7 @@ class CommentWindowProvider extends ComponentProviderAdapter {
|
||||
|
||||
commentModel.addTableModelListener(e -> {
|
||||
int rowCount = commentModel.getRowCount();
|
||||
int unfilteredCount = commentModel.getUnfilteredCount();
|
||||
int unfilteredCount = commentModel.getUnfilteredRowCount();
|
||||
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
|
||||
|
@ -118,7 +118,7 @@ class DataWindowProvider extends ComponentProviderAdapter {
|
||||
|
||||
dataModel.addTableModelListener(e -> {
|
||||
int rowCount = dataModel.getRowCount();
|
||||
int unfilteredCount = dataModel.getUnfilteredCount();
|
||||
int unfilteredCount = dataModel.getUnfilteredRowCount();
|
||||
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
|
||||
|
@ -133,7 +133,7 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
|
||||
|
||||
functionModel.addTableModelListener(e -> {
|
||||
int rowCount = functionModel.getRowCount();
|
||||
int unfilteredCount = functionModel.getUnfilteredCount();
|
||||
int unfilteredCount = functionModel.getUnfilteredRowCount();
|
||||
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
|
||||
|
@ -134,7 +134,7 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
int rowCount = stringModel.getRowCount();
|
||||
int unfilteredCount = stringModel.getUnfilteredCount();
|
||||
int unfilteredCount = stringModel.getUnfilteredRowCount();
|
||||
|
||||
builder.append(rowCount);
|
||||
builder.append(" items");
|
||||
|
@ -124,7 +124,7 @@ public class ViewStringsProvider extends ComponentProviderAdapter {
|
||||
|
||||
stringModel.addTableModelListener(e -> {
|
||||
int rowCount = stringModel.getRowCount();
|
||||
int unfilteredCount = stringModel.getUnfilteredCount();
|
||||
int unfilteredCount = stringModel.getUnfilteredRowCount();
|
||||
|
||||
setSubTitle("" + rowCount + " items" +
|
||||
(rowCount != unfilteredCount ? " (of " + unfilteredCount + ")" : ""));
|
||||
|
@ -152,7 +152,7 @@ class SymbolProvider extends ComponentProviderAdapter {
|
||||
private String generateSubTitle() {
|
||||
SymbolFilter filter = symbolKeyModel.getFilter();
|
||||
int rowCount = symbolKeyModel.getRowCount();
|
||||
int unfilteredCount = symbolKeyModel.getUnfilteredCount();
|
||||
int unfilteredCount = symbolKeyModel.getUnfilteredRowCount();
|
||||
|
||||
if (rowCount != unfilteredCount) {
|
||||
return " (Text filter matched " + rowCount + " of " + unfilteredCount + " symbols)";
|
||||
|
@ -65,18 +65,15 @@ public abstract class AddressBasedTableModel<ROW_TYPE> extends GhidraProgramTabl
|
||||
|
||||
ROW_TYPE rowObject = filteredData.get(row);
|
||||
DynamicTableColumn<ROW_TYPE, ?, ?> tableColumn = getColumn(column);
|
||||
if (tableColumn != null) {
|
||||
if (tableColumn instanceof ProgramLocationTableColumn<?, ?>) {
|
||||
if (tableColumn instanceof ProgramLocationTableColumn<?, ?>) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// we checked
|
||||
ProgramLocationTableColumn<ROW_TYPE, ?> programField =
|
||||
(ProgramLocationTableColumn<ROW_TYPE, ?>) tableColumn;
|
||||
ProgramLocation loc = programField.getProgramLocation(rowObject,
|
||||
getColumnSettings(column), getProgram(), serviceProvider);
|
||||
if (loc != null) {
|
||||
return loc;
|
||||
}
|
||||
@SuppressWarnings("unchecked") // we checked
|
||||
ProgramLocationTableColumn<ROW_TYPE, ?> programField =
|
||||
(ProgramLocationTableColumn<ROW_TYPE, ?>) tableColumn;
|
||||
ProgramLocation loc = programField.getProgramLocation(rowObject,
|
||||
getColumnSettings(column), getProgram(), serviceProvider);
|
||||
if (loc != null) {
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -795,7 +795,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
|
||||
private void getTableFilterString(String tableName, ThreadedTableModel<?, ?> model,
|
||||
StringBuffer buffy) {
|
||||
int filteredCount = model.getRowCount();
|
||||
int unfilteredCount = model.getUnfilteredCount();
|
||||
int unfilteredCount = model.getUnfilteredRowCount();
|
||||
|
||||
buffy.append(tableName).append(" - ").append(filteredCount).append(" functions");
|
||||
if (filteredCount != unfilteredCount) {
|
||||
|
@ -263,7 +263,7 @@ public class VTImpliedMatchesTableProvider extends ComponentProviderAdapter
|
||||
|
||||
impliedMatchTableModel.addTableModelListener(e -> {
|
||||
int filteredCount = impliedMatchTableModel.getRowCount();
|
||||
int unfilteredCount = impliedMatchTableModel.getUnfilteredCount();
|
||||
int unfilteredCount = impliedMatchTableModel.getUnfilteredRowCount();
|
||||
|
||||
String sessionName = controller.getVersionTrackingSessionName();
|
||||
StringBuffer buffy = new StringBuffer();
|
||||
|
@ -321,14 +321,14 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
||||
markupItemsTableModel = new VTMarkupItemsTableModel(controller);
|
||||
markupItemsTableModel.addTableModelListener(e -> {
|
||||
int filteredCount = markupItemsTableModel.getRowCount();
|
||||
int unfilteredCount = markupItemsTableModel.getUnfilteredCount();
|
||||
int unfilteredCount = markupItemsTableModel.getUnfilteredRowCount();
|
||||
|
||||
String sessionName = controller.getVersionTrackingSessionName();
|
||||
StringBuffer buffy = new StringBuffer();
|
||||
buffy.append("[Session: ").append(sessionName).append("] ");
|
||||
buffy.append('-').append(markupItemsTableModel.getRowCount()).append(" markup items");
|
||||
if (filteredCount != unfilteredCount) {
|
||||
buffy.append(" (of ").append(markupItemsTableModel.getUnfilteredCount()).append(
|
||||
buffy.append(" (of ").append(markupItemsTableModel.getUnfilteredRowCount()).append(
|
||||
')');
|
||||
}
|
||||
|
||||
@ -848,7 +848,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
|
||||
|
||||
if (filtered) {
|
||||
int filteredCount = markupItemsTableModel.getRowCount();
|
||||
int unfilteredCount = markupItemsTableModel.getUnfilteredCount();
|
||||
int unfilteredCount = markupItemsTableModel.getUnfilteredRowCount();
|
||||
int filteredOutCount = unfilteredCount - filteredCount;
|
||||
ancillaryFilterButton.setToolTipText(
|
||||
"More Filters - " + filteredOutCount + " item(s) hidden");
|
||||
|
@ -181,7 +181,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
|
||||
|
||||
if (filtered) {
|
||||
int filteredCount = matchesTableModel.getRowCount();
|
||||
int unfilteredCount = matchesTableModel.getUnfilteredCount();
|
||||
int unfilteredCount = matchesTableModel.getUnfilteredRowCount();
|
||||
int filteredOutCount = unfilteredCount - filteredCount;
|
||||
ancillaryFilterButton.setToolTipText(
|
||||
"More Filters - " + filteredOutCount + " item(s) hidden");
|
||||
@ -231,7 +231,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
|
||||
matchesTableModel = new VTMatchTableModel(controller);
|
||||
matchesTableModel.addTableModelListener(e -> {
|
||||
int filteredCount = matchesTableModel.getRowCount();
|
||||
int unfilteredCount = matchesTableModel.getUnfilteredCount();
|
||||
int unfilteredCount = matchesTableModel.getUnfilteredRowCount();
|
||||
|
||||
String sessionName = controller.getVersionTrackingSessionName();
|
||||
StringBuffer buffy = new StringBuffer();
|
||||
|
@ -93,13 +93,13 @@ public abstract class AbstractGTableModel<T> extends AbstractTableModel
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for subclasses to quickly/efficiently search for the index of a given
|
||||
* A convenience method to 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.
|
||||
* <p>
|
||||
* If a need for access to all of the data is required in the future, then an overloaded
|
||||
* version of this method should be created that takes the data to be searched.
|
||||
*
|
||||
* <p>Note: this operation is O(n). For quick lookups, consider using the sorted version
|
||||
* of this class.
|
||||
*
|
||||
* @param rowObject The object for which to search.
|
||||
* @return the index of the item in the data returned by
|
||||
@ -108,6 +108,14 @@ public abstract class AbstractGTableModel<T> extends AbstractTableModel
|
||||
return getIndexForRowObject(rowObject, getModelData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index for the given object in the given list; -1 when the item is not in
|
||||
* the list.
|
||||
*
|
||||
* @param rowObject the item
|
||||
* @param data the data
|
||||
* @return the index
|
||||
*/
|
||||
protected int getIndexForRowObject(T rowObject, List<T> data) {
|
||||
return data.indexOf(rowObject);
|
||||
}
|
||||
|
@ -17,10 +17,10 @@ package docking.widgets.table;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
|
||||
@ -31,15 +31,21 @@ import ghidra.util.datastruct.WeakSet;
|
||||
* <p>
|
||||
* 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
|
||||
* {@link TableSortState#createUnsortedSortState()}.
|
||||
*
|
||||
* @param <T> The row type upon which the table is based
|
||||
*/
|
||||
public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
implements SortedTableModel {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Comparator<T> NO_SORT_COMPARATOR = (o1, o2) -> 0;
|
||||
|
||||
private TableSortState pendingSortState;
|
||||
private TableSortState sortState;
|
||||
private TableSortState sortState = TableSortState.createUnsortedSortState();
|
||||
private boolean isSortPending;
|
||||
|
||||
protected boolean hasEverSorted;
|
||||
@ -58,6 +64,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
|
||||
protected void setDefaultTableSortState(TableSortState defaultSortState) {
|
||||
sortState = defaultSortState;
|
||||
if (sortState == null) {
|
||||
sortState = TableSortState.createUnsortedSortState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -81,14 +91,17 @@ 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.
|
||||
* 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
|
||||
public int getRowIndex(T rowObject) {
|
||||
if (rowObject == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return getIndexForRowObject(rowObject);
|
||||
}
|
||||
|
||||
@ -121,7 +134,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTableSortState(final TableSortState newSortState) {
|
||||
public void setTableSortState(TableSortState newSortState) {
|
||||
|
||||
if (!isValidSortState(newSortState)) {
|
||||
// if the user calls this method with an invalid value, then let them know!
|
||||
@ -165,7 +178,8 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
|
||||
isSortPending = true;
|
||||
pendingSortState = newSortState;
|
||||
SwingUtilities.invokeLater(() -> sort(getModelData(), createSortingContext(newSortState)));
|
||||
SystemUtilities.runSwingLater(
|
||||
() -> sort(getModelData(), createSortingContext(newSortState)));
|
||||
}
|
||||
|
||||
public TableSortState getPendingSortState() {
|
||||
@ -176,6 +190,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
return isSortPending;
|
||||
}
|
||||
|
||||
public boolean isSorted() {
|
||||
return !isSortPending && !sortState.isUnsorted();
|
||||
}
|
||||
|
||||
protected TableSortingContext<T> createSortingContext(TableSortState newSortState) {
|
||||
return new TableSortingContext<>(newSortState, getComparatorChain(newSortState));
|
||||
}
|
||||
@ -205,7 +223,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
hasEverSorted = true;
|
||||
isSortPending = true;
|
||||
pendingSortState = sortState;
|
||||
SwingUtilities.invokeLater(() -> sort(getModelData(), createSortingContext(sortState)));
|
||||
SystemUtilities.runSwingLater(() -> sort(getModelData(), createSortingContext(sortState)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -213,9 +231,9 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
* 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.
|
||||
* <p>
|
||||
* If a need for access to all of the data is required in the future, then an overloaded
|
||||
* version of this method should be created that takes the data to be searched.
|
||||
*
|
||||
* <p>Warning: if the this model has no sort applied, then -1 will be returned. You can call
|
||||
* {@link #isSorted()} to know when this will happen.
|
||||
*
|
||||
* @param rowObject The object for which to search.
|
||||
* @return the index of the item in the data returned by
|
||||
@ -225,10 +243,27 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
return getIndexForRowObject(rowObject, getModelData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index for the given object in the given list
|
||||
*
|
||||
* @param rowObject the item
|
||||
* @param data the data
|
||||
* @return the index
|
||||
*/
|
||||
@Override
|
||||
protected int getIndexForRowObject(T rowObject, List<T> data) {
|
||||
Comparator<T> comparator = getComparatorChain(sortState);
|
||||
return Collections.binarySearch(data, rowObject, comparator);
|
||||
if (isSorted()) {
|
||||
Comparator<T> comparator = getComparatorChain(sortState);
|
||||
return Collections.binarySearch(data, rowObject, comparator);
|
||||
}
|
||||
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
T t = data.get(i);
|
||||
if (rowObject.equals(t)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -242,6 +277,14 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
* comparator for sorting, etc...).
|
||||
*/
|
||||
protected void sort(List<T> data, TableSortingContext<T> sortingContext) {
|
||||
|
||||
if (sortingContext.isUnsorted()) {
|
||||
// this is the 'no sort' state
|
||||
sortCompleted(sortingContext);
|
||||
notifyModelSorted(false);
|
||||
return;
|
||||
}
|
||||
|
||||
hasEverSorted = true; // signal that we have sorted at least one time
|
||||
Collections.sort(data, sortingContext.getComparator());
|
||||
sortCompleted(sortingContext);
|
||||
@ -315,6 +358,11 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
* data is sorted).
|
||||
*/
|
||||
private Comparator<T> getComparatorChain(TableSortState newSortState) {
|
||||
|
||||
if (newSortState.isUnsorted()) {
|
||||
return NO_SORT_COMPARATOR;
|
||||
}
|
||||
|
||||
ComparatorLink comparatorLink = new ComparatorLink();
|
||||
for (ColumnSortState columnSortState : newSortState) {
|
||||
Comparator<T> nextComparator = getComparator(columnSortState);
|
||||
|
@ -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.
|
||||
@ -29,7 +28,7 @@ public class ColumnSortState {
|
||||
private static final String XML_SORT_ORDER = "SORT_ORDER";
|
||||
|
||||
public enum SortDirection {
|
||||
ASCENDING, DESCENDING;
|
||||
ASCENDING, DESCENDING, UNSORTED;
|
||||
|
||||
public boolean isAscending() {
|
||||
return this == ASCENDING;
|
||||
@ -82,9 +81,8 @@ public class ColumnSortState {
|
||||
}
|
||||
|
||||
public ColumnSortState createFlipState() {
|
||||
ColumnSortState newSortState =
|
||||
new ColumnSortState(columnModelIndex, isAscending() ? SortDirection.DESCENDING
|
||||
: SortDirection.ASCENDING, sortOrder_OneBased);
|
||||
ColumnSortState newSortState = new ColumnSortState(columnModelIndex,
|
||||
isAscending() ? SortDirection.DESCENDING : SortDirection.ASCENDING, sortOrder_OneBased);
|
||||
return newSortState;
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,35 @@ public interface RowObjectFilterModel<ROW_OBJECT> extends RowObjectTableModel<RO
|
||||
|
||||
public int getViewRow(int modelRow);
|
||||
|
||||
/**
|
||||
* Returns the view index of the given item. When filtered, this is the index is the smaller,
|
||||
* visible set of data; when unfiltered, this index is the same as that returned by
|
||||
* {@link #getModelIndex(Object)}.
|
||||
*
|
||||
* <p>This operation will be O(n) unless the implementation is sorted, in which case the
|
||||
* operation is O(log n), as it uses a binary search.
|
||||
*
|
||||
* <p>Note: if a sorted implementation is moved to an unsorted state, then -1 will be returned
|
||||
* from this method.
|
||||
*
|
||||
* @param t the item
|
||||
* @return the view index
|
||||
*/
|
||||
public int getViewIndex(ROW_OBJECT t);
|
||||
|
||||
/**
|
||||
* Returns the model index of the given item. When filtered, this is the index is the larger,
|
||||
* set of data; when unfiltered, this index is the same as that returned by
|
||||
* {@link #getModelIndex(Object)}.
|
||||
*
|
||||
* <p>This operation will be O(n) unless the implementation is sorted, in which case the
|
||||
* operation is O(log n), as it uses a binary search.
|
||||
*
|
||||
* <p>Note: if a sorted implementation is moved to an unsorted state, then -1 will be returned
|
||||
* from this method.
|
||||
*
|
||||
* @param t the item
|
||||
* @return the model index
|
||||
*/
|
||||
public int getModelIndex(ROW_OBJECT t);
|
||||
}
|
||||
|
@ -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.
|
||||
@ -32,7 +31,15 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
|
||||
private static final String XML_TABLE_SORT_STATE = "TABLE_SORT_STATE";
|
||||
|
||||
private List<ColumnSortState> columnSortStates = new ArrayList<ColumnSortState>();
|
||||
private List<ColumnSortState> columnSortStates = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a sort state that represents being unsorted
|
||||
* @return a sort state that represents being unsorted
|
||||
*/
|
||||
public static TableSortState createUnsortedSortState() {
|
||||
return new TableSortState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sort state with the given column as the sorted column (sorted ascending).
|
||||
@ -64,8 +71,8 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
}
|
||||
|
||||
public TableSortState(List<ColumnSortState> sortStates) {
|
||||
List<Integer> sortOrders = new ArrayList<Integer>();
|
||||
List<Integer> columnIndices = new ArrayList<Integer>();
|
||||
List<Integer> sortOrders = new ArrayList<>();
|
||||
List<Integer> columnIndices = new ArrayList<>();
|
||||
for (ColumnSortState state : sortStates) {
|
||||
int sortOrder = state.getSortOrder();
|
||||
if (sortOrders.contains(sortOrder)) {
|
||||
@ -84,7 +91,7 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
columnIndices.add(columnModelIndex);
|
||||
}
|
||||
|
||||
this.columnSortStates = new ArrayList<ColumnSortState>(sortStates);
|
||||
this.columnSortStates = new ArrayList<>(sortStates);
|
||||
}
|
||||
|
||||
public TableSortState(ColumnSortState columnSortState) {
|
||||
@ -100,6 +107,10 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
return columnSortStates.size();
|
||||
}
|
||||
|
||||
public boolean isUnsorted() {
|
||||
return columnSortStates.isEmpty();
|
||||
}
|
||||
|
||||
public ColumnSortState getColumnSortState(int columnIndex) {
|
||||
for (ColumnSortState sortState : columnSortStates) {
|
||||
if (sortState.getColumnModelIndex() == columnIndex) {
|
||||
@ -110,7 +121,7 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
}
|
||||
|
||||
public List<ColumnSortState> getAllSortStates() {
|
||||
return new ArrayList<ColumnSortState>(columnSortStates);
|
||||
return new ArrayList<>(columnSortStates);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -140,7 +151,7 @@ public class TableSortState implements Iterable<ColumnSortState> {
|
||||
Element sortStateElement = (Element) tableSortStateElementList.get(0);
|
||||
List<?> children = sortStateElement.getChildren(ColumnSortState.XML_COLUMN_SORT_STATE);
|
||||
|
||||
List<ColumnSortState> columnStates = new ArrayList<ColumnSortState>(children.size());
|
||||
List<ColumnSortState> columnStates = new ArrayList<>(children.size());
|
||||
for (Object object : children) {
|
||||
columnStates.add(ColumnSortState.restoreFromXML((Element) object));
|
||||
}
|
||||
|
@ -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,8 +15,7 @@
|
||||
*/
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
|
||||
public class TableSortingContext<T> {
|
||||
|
||||
@ -25,8 +23,8 @@ public class TableSortingContext<T> {
|
||||
private Comparator<T> comparator;
|
||||
|
||||
public TableSortingContext(TableSortState sortState, Comparator<T> comparator) {
|
||||
this.sortState = sortState;
|
||||
this.comparator = comparator;
|
||||
this.sortState = Objects.requireNonNull(sortState);
|
||||
this.comparator = Objects.requireNonNull(comparator);
|
||||
}
|
||||
|
||||
public Comparator<T> getComparator() {
|
||||
@ -37,6 +35,15 @@ public class TableSortingContext<T> {
|
||||
return sortState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are no columns marked as sorted, which represents a 'no sort' state
|
||||
*
|
||||
* @return true if there are no columns sorted
|
||||
*/
|
||||
public boolean isUnsorted() {
|
||||
return sortState.isUnsorted();
|
||||
}
|
||||
|
||||
public boolean isReverseOf(TableSortingContext<T> otherContext) {
|
||||
int sortedColumnCount = sortState.getSortedColumnCount();
|
||||
if (sortedColumnCount != 1) {
|
||||
|
@ -15,8 +15,6 @@
|
||||
*/
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.awt.Toolkit;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.*;
|
||||
|
||||
@ -113,11 +111,17 @@ public class TableUtils {
|
||||
TableSortStateEditor editor = new TableSortStateEditor(columnSortStates);
|
||||
|
||||
if (editor.isColumnSorted(modelColumnIndex)) {
|
||||
// remove it. If there is only one, don't remove the last one
|
||||
if (editor.getSortedColumnCount() == 1) {
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Note: this code allows us to disable the 'unsorting' of a table via the UI
|
||||
|
||||
// remove it. If there is only one, don't remove the last one
|
||||
if (editor.getSortedColumnCount() == 1) {
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
editor.removeSortedColumn(modelColumnIndex);
|
||||
}
|
||||
else {
|
||||
|
@ -104,7 +104,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
}
|
||||
|
||||
boolean isSorted() {
|
||||
return sortContext != null;
|
||||
return sortContext != null && !sortContext.isUnsorted();
|
||||
}
|
||||
|
||||
void setSortContext(TableSortingContext<ROW_OBJECT> sortContext) {
|
||||
@ -128,12 +128,24 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the current sort to perform a fast lookup of the given item in the given list.
|
||||
* Uses the current sort to perform a fast lookup of the given item in the given list
|
||||
* @param t the item
|
||||
* @return the index
|
||||
*/
|
||||
int indexOf(ROW_OBJECT t) {
|
||||
Comparator<ROW_OBJECT> comparator = sortContext.getComparator();
|
||||
int index = Collections.binarySearch(data, t, comparator);
|
||||
return index;
|
||||
if (!sortContext.isUnsorted()) {
|
||||
Comparator<ROW_OBJECT> comparator = sortContext.getComparator();
|
||||
return Collections.binarySearch(data, t, comparator);
|
||||
}
|
||||
|
||||
// brute force
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
ROW_OBJECT item = data.get(i);
|
||||
if (t.equals(item)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
boolean remove(ROW_OBJECT o) {
|
||||
@ -145,9 +157,9 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the new <tt>value</tt> to the data at the appropriate location based on the sort.
|
||||
* @param value the row Object to insert.
|
||||
* @param comparator the comparator to use to find the appropriate location to insert the new data.
|
||||
* Adds the new <tt>value</tt> to the data at the appropriate location based on the sort
|
||||
*
|
||||
* @param value the row Object to insert
|
||||
*/
|
||||
void insert(ROW_OBJECT value) {
|
||||
|
||||
@ -160,10 +172,8 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
return; // this item is filtered out of this data
|
||||
}
|
||||
|
||||
if (sortContext == null) {
|
||||
//
|
||||
// Not yet sorted; just add the item anywhere and it will get sorted later
|
||||
//
|
||||
if (!isSorted()) {
|
||||
// Not yet sorted or intentionally unsorted; just add the item, it will get sorted later
|
||||
data.add(value);
|
||||
return;
|
||||
}
|
||||
@ -180,7 +190,7 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
// The search thinks the item is in the list because a compareTo() result of 0 was
|
||||
// found. If the two objects are not equal(), then add the new value.
|
||||
ROW_OBJECT existingValue = data.get(index);
|
||||
if (!SystemUtilities.isEqual(value, existingValue)) {
|
||||
if (!Objects.equals(value, existingValue)) {
|
||||
data.add(index, value);
|
||||
}
|
||||
}
|
||||
@ -310,4 +320,10 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
// of 'data', as that could be expensive.
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int hashCode() {
|
||||
// Made final to match equals()
|
||||
return super.hashCode();
|
||||
}
|
||||
}
|
||||
|
@ -498,6 +498,11 @@ public class TableUpdateJob<T> {
|
||||
}
|
||||
|
||||
private void doSortData(List<T> data) {
|
||||
|
||||
if (newSortContext.isUnsorted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int size = data.size();
|
||||
monitor.setMessage("Sorting " + model.getName() + " (" + size + " rows)" + "...");
|
||||
monitor.initialize(size);
|
||||
@ -512,7 +517,7 @@ public class TableUpdateJob<T> {
|
||||
//
|
||||
// Usually the source data is sorted before any filter is applied. However, this is not
|
||||
// the case when a load of new data is followed directly by a filter action. We rely on
|
||||
// the source data being filtered in order to perform fast translations from the table's
|
||||
// the source data being sorted in order to perform fast translations from the table's
|
||||
// view to the table's model when it is filtered. Thus, make sure that any time we are
|
||||
// sorting the filtered data, that the source data too is sorted.
|
||||
//
|
||||
|
@ -27,7 +27,6 @@ import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.*;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
import ghidra.util.worker.Worker;
|
||||
|
||||
/**
|
||||
@ -36,14 +35,14 @@ import ghidra.util.worker.Worker;
|
||||
* You can optionally set this model to load data incrementally by passing the correct
|
||||
* constructor argument. Note, if you make this model incremental, then you need to set an
|
||||
* incremental task monitor in order to get feedback about loading
|
||||
* (see {@link #setIncrementalTaskMonitor(TaskMonitor)). Alternatively, you can use
|
||||
* (see {@link #setIncrementalTaskMonitor(TaskMonitor)}. Alternatively, you can use
|
||||
* a {@link GThreadedTablePanel}, which will install the proper monitor for you.
|
||||
*
|
||||
* @param ROW_OBJECT the row object class for this table model.
|
||||
* @param DATA_SOURCE the type of data that will be returned from {@link #getDataSource()}. This
|
||||
* @param <ROW_OBJECT> the row object class for this table model.
|
||||
* @param <DATA_SOURCE> the type of data that will be returned from {@link #getDataSource()}. This
|
||||
* object will be given to the {@link DynamicTableColumn} objects used by this
|
||||
* table model when
|
||||
* {@link DynamicTableColumn#getValue(Object, generic.settings.Settings, Object, ServiceProvider)}
|
||||
* {@link DynamicTableColumn#getValue(Object, ghidra.docking.settings.Settings, Object, ServiceProvider)}
|
||||
* is called.
|
||||
*/
|
||||
public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
@ -52,7 +51,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
|
||||
private ThreadedTableModelUpdateMgr<ROW_OBJECT> updateManager;
|
||||
private boolean loadIncrementally;
|
||||
private TaskMonitor incrementalMonitor = TaskMonitorAdapter.DUMMY_MONITOR;
|
||||
private TaskMonitor incrementalMonitor = TaskMonitor.DUMMY;
|
||||
private ConcurrentListenerSet<ThreadedTableModelListener> listeners =
|
||||
new ConcurrentListenerSet<>();
|
||||
|
||||
@ -157,8 +156,12 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
|
||||
/**
|
||||
* A package-level method. Subclasses should not call this.
|
||||
* <p>
|
||||
* This exists to handle whether this model should load incrementally.
|
||||
*
|
||||
* <p>This exists to handle whether this model should load incrementally.
|
||||
*
|
||||
* @param monitor the monitor
|
||||
* @return the loaded data
|
||||
* @throws CancelledException
|
||||
*/
|
||||
final List<ROW_OBJECT> load(TaskMonitor monitor) throws CancelledException {
|
||||
if (loadIncrementally) {
|
||||
@ -261,7 +264,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
*
|
||||
* @param rowObject The object for which to search
|
||||
* @return The index for the given object; a negative value if the object is not in the list
|
||||
* @see #getIndexForRowObject(Object);
|
||||
* @see #getIndexForRowObject(Object)
|
||||
*/
|
||||
protected int getUnfilteredIndexForRowObject(ROW_OBJECT rowObject) {
|
||||
return getIndexForRowObject(rowObject, getUnfilteredData());
|
||||
@ -327,6 +330,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
/**
|
||||
* Returns the current sorting context, which is the next one to be applied, if a sort is
|
||||
* pending; otherwise the current sorting context.
|
||||
* @return the sort context
|
||||
*/
|
||||
TableSortingContext<ROW_OBJECT> getSortingContext() {
|
||||
if (pendingSortContext != null) {
|
||||
@ -365,8 +369,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
/**
|
||||
* Override this to change how filtering is performed. This implementation will do nothing
|
||||
* if a <tt>TableFilter</tt> has not been set via a call to {@link #setTableFilter(TableFilter)}.
|
||||
* Also, no filtering will happen if there is no filter text set via a call to
|
||||
* {@link #setFilterText(String)}.
|
||||
*
|
||||
*
|
||||
* @param data The list of data to be filtered.
|
||||
*
|
||||
@ -629,10 +632,6 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
return filteredData.size();
|
||||
}
|
||||
|
||||
public int getUnfilteredCount() {
|
||||
return allData.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a row index for the raw (unfiltered) model, return the corresponding index in the
|
||||
* view (filtered) model.
|
||||
@ -643,11 +642,12 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
*/
|
||||
@Override
|
||||
public int getViewRow(int modelRow) {
|
||||
if (getRowCount() == getUnfilteredCount()) {
|
||||
int unfilteredCount = getUnfilteredRowCount();
|
||||
if (getRowCount() == unfilteredCount) {
|
||||
return modelRow; // same list; no need to translate values
|
||||
}
|
||||
|
||||
if (modelRow >= allData.size()) {
|
||||
if (modelRow >= unfilteredCount) {
|
||||
return -1; // out-of-bounds request
|
||||
}
|
||||
|
||||
@ -665,7 +665,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
*/
|
||||
@Override
|
||||
public int getModelRow(int viewRow) {
|
||||
if (getRowCount() == getUnfilteredCount()) {
|
||||
if (getRowCount() == getUnfilteredRowCount()) {
|
||||
return viewRow; // same list; no need to translate values
|
||||
}
|
||||
|
||||
@ -715,8 +715,10 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
|
||||
/**
|
||||
* Sets the update delay, which is how long the model should wait before updating, after
|
||||
* a change has been made the data.
|
||||
* a change has been made the data
|
||||
*
|
||||
* @param updateDelayMillis the new update delay
|
||||
* @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time
|
||||
*/
|
||||
void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) {
|
||||
this.minUpdateDelayMillis = updateDelayMillis;
|
||||
@ -724,16 +726,11 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
updateManager.setUpdateDelay(updateDelayMillis, maxUpdateDelayMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #setMinDelay(int)
|
||||
*/
|
||||
// see setUpdateDelay
|
||||
long getMinDelay() {
|
||||
return minUpdateDelayMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #setMaxDelay(int)
|
||||
*/
|
||||
long getMaxDelay() {
|
||||
return maxUpdateDelayMillis;
|
||||
}
|
||||
|
@ -297,7 +297,8 @@ class ThreadedTableModelUpdateMgr<T> {
|
||||
|
||||
/**
|
||||
* Sets the delay for the swing update manager.
|
||||
* @param updateDelayMillis the new delay for the swing update manager.
|
||||
* @param updateDelayMillis the new delay for the swing update manager
|
||||
* @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time
|
||||
*/
|
||||
void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) {
|
||||
updateManager.dispose();
|
||||
|
@ -32,7 +32,7 @@ import ghidra.docking.spy.SpyEventRecorder;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
|
||||
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName());
|
||||
private SpyTaskMonitor monitor = new SpyTaskMonitor();
|
||||
@ -98,7 +98,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
@Test
|
||||
public void testRefilterHappensAfterAddItem_ItemAddedPassesFilter() throws Exception {
|
||||
|
||||
int newRowIndex = model.getRowCount() + 1;
|
||||
int newRowIndex = getRowCount() + 1;
|
||||
|
||||
filterOnRawColumnValue(newRowIndex);
|
||||
resetSpies();
|
||||
@ -114,7 +114,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
@Test
|
||||
public void testRefilterHappensAfterRemoveAddItem_ItemAddedPassesFilter() throws Exception {
|
||||
|
||||
int newRowIndex = model.getRowCount() + 1;
|
||||
int newRowIndex = getRowCount() + 1;
|
||||
|
||||
filterOnRawColumnValue(newRowIndex);
|
||||
resetSpies();
|
||||
@ -137,7 +137,7 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
@Test
|
||||
public void testRefilterHappensAfterAdd_ItemAddedFailsFilter() throws Exception {
|
||||
|
||||
int newRowIndex = model.getRowCount() + 1;
|
||||
int newRowIndex = getRowCount() + 1;
|
||||
|
||||
long nonMatchingFilter = 1;
|
||||
filterOnRawColumnValue(nonMatchingFilter);
|
||||
@ -380,18 +380,18 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
}
|
||||
|
||||
private void assertFilteredEntireModel() {
|
||||
int allCount = model.getUnfilteredCount();
|
||||
int allCount = getUnfilteredRowCount();
|
||||
assertNumberOfItemsPassedThroughFilter(allCount);
|
||||
}
|
||||
|
||||
private void assertTableContainsValue(long expected) {
|
||||
List<Long> modelValues = model.getModelData();
|
||||
List<Long> modelValues = getModelData();
|
||||
assertTrue("Value not in the model--filtered out? - Expected " + expected + "; found " +
|
||||
modelValues, modelValues.contains(expected));
|
||||
}
|
||||
|
||||
private void assertTableDoesNotContainValue(long expected) {
|
||||
List<Long> modelValues = model.getModelData();
|
||||
List<Long> modelValues = getModelData();
|
||||
assertFalse("Value in the model--should not be there - Value " + expected + "; found " +
|
||||
modelValues, modelValues.contains(expected));
|
||||
}
|
||||
@ -464,9 +464,11 @@ public class ThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder);
|
||||
|
||||
CombinedTableFilter<Long> combinedFilter =
|
||||
new CombinedTableFilter<Long>(spyFilter, secondFilter, null);
|
||||
new CombinedTableFilter<>(spyFilter, secondFilter, null);
|
||||
|
||||
recorder.record("Before setting the new filter");
|
||||
runSwing(() -> model.setTableFilter(combinedFilter));
|
||||
recorder.record("\tafter setting filter");
|
||||
|
||||
waitForNotBusy();
|
||||
waitForTableModel(model);
|
@ -0,0 +1,34 @@
|
||||
/* ###
|
||||
* 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.table.threaded;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
import docking.widgets.table.TableSortState;
|
||||
|
||||
public class NonSortedThreadedTableFilterTest extends DefaultThreadedTableFilterTest {
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
super.setUp();
|
||||
|
||||
TableSortState sortState = TableSortState.createUnsortedSortState();
|
||||
runSwing(() -> model.setTableSortState(sortState));
|
||||
waitForTableModel(model);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/* ###
|
||||
* 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.table.threaded;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
import docking.widgets.table.TableSortState;
|
||||
|
||||
public class NonSortedThreadedTableTest extends ThreadedTableTest {
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
super.setUp();
|
||||
|
||||
TableSortState sortState = TableSortState.createUnsortedSortState();
|
||||
runSwing(() -> model.setTableSortState(sortState));
|
||||
waitForTableModel(model);
|
||||
}
|
||||
|
||||
}
|
@ -29,8 +29,7 @@ public class SpyTextFilter<T> extends TableTextFilter<T> {
|
||||
private SpyEventRecorder recorder;
|
||||
|
||||
SpyTextFilter(TextFilter textFilter, RowFilterTransformer<T> transformer) {
|
||||
super(textFilter, transformer);
|
||||
this.recorder = new SpyEventRecorder("Stub");
|
||||
this(textFilter, transformer, new SpyEventRecorder("Stub"));
|
||||
}
|
||||
|
||||
SpyTextFilter(TextFilter textFilter, RowFilterTransformer<T> transformer,
|
||||
|
@ -45,7 +45,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
|
||||
private final Pattern SORT_SIZE_PATTERN = Pattern.compile(".*\\((\\d+) rows\\).*");
|
||||
|
||||
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName());
|
||||
private SpyEventRecorder recorder = new SpyEventRecorder(testName.getMethodName());
|
||||
private SpyTaskMonitor spyMonitor = new SpyTaskMonitor(recorder);
|
||||
private SpyTextFilter<Long> spyFilter;
|
||||
private SortListener spySortListener =
|
||||
@ -102,6 +102,12 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
public void setIncrementalTaskMonitor(TaskMonitor monitor) {
|
||||
// no! some of our tests use a spy monitor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTableSortState(TableSortState newSortState) {
|
||||
record("model.setTableSortState() - " + newSortState);
|
||||
super.setTableSortState(newSortState);
|
||||
}
|
||||
});
|
||||
return box[0];
|
||||
}
|
||||
@ -111,7 +117,6 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
//
|
||||
// make sure the sort is on the filtered data and not *all* data
|
||||
//
|
||||
waitForNotBusy();
|
||||
assertSortSize(12);
|
||||
|
||||
filter_ten();
|
||||
@ -512,7 +517,6 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
//
|
||||
// we need to use a model that loads slowly enough to trigger the pending panel to show
|
||||
//
|
||||
waitForNotBusy();// if we are working while adding, we will not get the pending notification
|
||||
model.setDelayTimeBetweenAddingDataItemsWhileLoading(60000);
|
||||
model.setUpdateDelay(100000000, 100000001);// make sure we don't update after repeated requests arrive
|
||||
|
||||
@ -534,15 +538,15 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
|
||||
@Override
|
||||
protected void sortByClick(int columnToClick, int modifiers) throws Exception {
|
||||
recorder.record(
|
||||
"Test." + testName + " - clicking column=" + columnToClick + "; modifiers=" + 0);
|
||||
recorder.record("Test." + testName.getMethodName() + " - clicking column=" + columnToClick +
|
||||
"; modifiers=" + 0);
|
||||
super.sortByClick(columnToClick, modifiers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeSortByClicking(int columnToClick) throws Exception {
|
||||
recorder.record(
|
||||
"Test." + testName + " - clicking column to remove sort - column=" + columnToClick);
|
||||
recorder.record("Test." + testName.getMethodName() +
|
||||
" - clicking column to remove sort - column=" + columnToClick);
|
||||
super.removeSortByClicking(columnToClick);
|
||||
}
|
||||
|
||||
@ -607,7 +611,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
|
||||
private void toggleStringColumnSort() throws Exception {
|
||||
Rectangle rect = header.getHeaderRect(TestDataKeyModel.STRING_COL);
|
||||
testTableModelListener.reset(model);
|
||||
resetBusyListener();
|
||||
clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, 0);
|
||||
waitForNotBusy();
|
||||
waitForSwing();
|
||||
@ -753,6 +757,8 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
}
|
||||
|
||||
private void clearFilter() throws Exception {
|
||||
resetSpies();
|
||||
resetBusyListener();
|
||||
runSwing(() -> model.setTableFilter(null));
|
||||
|
||||
waitForNotBusy();
|
||||
@ -782,6 +788,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
|
||||
@Override
|
||||
protected void doTestSorting(int columnIndex) throws Exception {
|
||||
|
||||
sortByNormalClicking(columnIndex);
|
||||
|
||||
SortedTableModel sortedModel = (SortedTableModel) table.getModel();
|
||||
@ -829,7 +836,7 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
TextFilterFactory textFactory = options.getTextFilterFactory();
|
||||
TextFilter textFilter = textFactory.getTextFilter(text);
|
||||
|
||||
testTableModelListener.reset(model);
|
||||
resetBusyListener();
|
||||
|
||||
spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder);
|
||||
|
||||
@ -859,6 +866,11 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
assertTrue("Table did not filter; requested filter on '" + text + "'", hasFiltered);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void record(String message) {
|
||||
recorder.record("Test - " + message);
|
||||
}
|
||||
|
||||
private void resetSpies() {
|
||||
spyFilter.reset();
|
||||
spyMonitor.clearMessages();
|
||||
|
@ -90,12 +90,12 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||
}
|
||||
|
||||
protected void addItemToModel(long value) {
|
||||
model.addObject(new Long(value));
|
||||
model.addObject(Long.valueOf(value));
|
||||
waitForTableModel(model);
|
||||
}
|
||||
|
||||
protected void removeItemFromModel(int value) {
|
||||
model.removeObject(new Long(value));
|
||||
model.removeObject(Long.valueOf(value));
|
||||
waitForTableModel(model);
|
||||
}
|
||||
|
||||
@ -161,10 +161,20 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||
|
||||
SortedTableModel sortedModel = (SortedTableModel) table.getModel();
|
||||
TableSortState sortState = getSortState(sortedModel);
|
||||
ColumnSortState originalColumnSortState = sortState.iterator().next();
|
||||
int currentSortedIndex = originalColumnSortState.getColumnModelIndex();
|
||||
boolean checkSortDirection = (columnToClick == currentSortedIndex);
|
||||
boolean isAscending = originalColumnSortState.isAscending();
|
||||
record("sortByClick() - initial sort state: " + sortState);
|
||||
|
||||
int currentSortColunn = -1;
|
||||
boolean isAscending = true;
|
||||
boolean checkSortDirection = false;
|
||||
if (!sortState.isUnsorted()) {
|
||||
|
||||
// check to see if the tests is clicking the same column twice (to change the
|
||||
// sort direction)
|
||||
ColumnSortState originalColumnSortState = sortState.iterator().next();
|
||||
currentSortColunn = originalColumnSortState.getColumnModelIndex();
|
||||
checkSortDirection = (columnToClick == currentSortColunn);
|
||||
isAscending = originalColumnSortState.isAscending();
|
||||
}
|
||||
|
||||
testTableModelListener.reset(model);
|
||||
Rectangle rect = header.getHeaderRect(columnToClick);
|
||||
@ -172,10 +182,14 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||
waitForPostedSwingRunnables();
|
||||
}
|
||||
|
||||
record("Clicking table at column " + columnToClick);
|
||||
clickMouse(header, MouseEvent.BUTTON1, rect.x + 10, rect.y + 10, 1, modifiers);
|
||||
waitForNotBusy();
|
||||
record("\tafter click; table not busy");
|
||||
|
||||
sortState = getSortState(sortedModel);
|
||||
record("Updated sort state: " + sortState);
|
||||
|
||||
ColumnSortState columnSortState = sortState.iterator().next();
|
||||
int sortedIndex = columnSortState.getColumnModelIndex();
|
||||
verifyColumnSorted(sortedIndex, sortState);
|
||||
@ -188,10 +202,8 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected TableSortState getSortState(final SortedTableModel sortedModel) {
|
||||
final TableSortState[] box = new TableSortState[1];
|
||||
runSwing(() -> box[0] = sortedModel.getTableSortState());
|
||||
return box[0];
|
||||
protected TableSortState getSortState(SortedTableModel sortedModel) {
|
||||
return runSwing(() -> sortedModel.getTableSortState());
|
||||
}
|
||||
|
||||
protected void removeSortByClicking(int columnToClick) throws Exception {
|
||||
@ -212,20 +224,35 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||
assertNotNull(columnSortState);
|
||||
}
|
||||
|
||||
protected void resetBusyListener() {
|
||||
testTableModelListener.reset(model);
|
||||
}
|
||||
|
||||
protected void waitForNotBusy() {
|
||||
sleep(50);
|
||||
int nWaits = 0;
|
||||
int maxWaits = 500;
|
||||
while (!testTableModelListener.doneWork() && nWaits++ < maxWaits) {
|
||||
sleep(50);
|
||||
}
|
||||
|
||||
assertTrue("Timed-out waiting for table model to update.", nWaits < maxWaits);
|
||||
waitForCondition(() -> testTableModelListener.doneWork(),
|
||||
"Timed-out waiting for table model to update.");
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
protected void addLong(final long value) {
|
||||
runSwing(() -> model.addObject(new Long(value)));
|
||||
runSwing(() -> model.addObject(Long.valueOf(value)));
|
||||
}
|
||||
|
||||
protected int getRowCount() {
|
||||
return runSwing(() -> model.getRowCount());
|
||||
}
|
||||
|
||||
protected int getUnfilteredRowCount() {
|
||||
return runSwing(() -> model.getUnfilteredRowCount());
|
||||
}
|
||||
|
||||
protected List<Long> getModelData() {
|
||||
return runSwing(() -> model.getModelData());
|
||||
}
|
||||
|
||||
protected void record(String message) {
|
||||
// no-op for base class; subclasses know how to record debug
|
||||
}
|
||||
|
||||
protected void assertRowCount(int expectedCount) {
|
||||
|
@ -43,18 +43,27 @@ public class SpyEventRecorder {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
// synchronized because we spy on multiple threads (like Test and Swing)
|
||||
public synchronized void dumpEvents() {
|
||||
private synchronized String eventsToString() {
|
||||
StringBuilder buffy = new StringBuilder("Recorded Events - " + recorderName + '\n');
|
||||
for (SpyEvent event : events) {
|
||||
buffy.append(event.toString()).append('\n');
|
||||
}
|
||||
Msg.debug(this, buffy.toString());
|
||||
return buffy.toString();
|
||||
}
|
||||
|
||||
// synchronized because we spy on multiple threads (like Test and Swing)
|
||||
public void dumpEvents() {
|
||||
Msg.debug(this, eventsToString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return eventsToString();
|
||||
}
|
||||
|
||||
private class SpyEvent {
|
||||
|
||||
private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ssZZ");
|
||||
private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ss:SSS");
|
||||
|
||||
private int id;
|
||||
private String message;
|
||||
|
Loading…
Reference in New Issue
Block a user