GT-2892 - Tables - updated default sorting to use the column's rendered

value when possible
This commit is contained in:
dragonmacher 2019-05-30 15:51:16 -04:00
parent e883885687
commit 2df2963f09
14 changed files with 486 additions and 234 deletions

View file

@ -20,7 +20,9 @@ import java.util.*;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableModel;
import ghidra.util.SystemUtilities;
import docking.widgets.table.sort.DefaultColumnComparator;
import docking.widgets.table.sort.RowToColumnComparator;
import ghidra.util.Swing;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
@ -178,8 +180,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
isSortPending = true;
pendingSortState = newSortState;
SystemUtilities.runSwingLater(
() -> sort(getModelData(), createSortingContext(newSortState)));
Swing.runLater(() -> sort(getModelData(), createSortingContext(newSortState)));
}
public TableSortState getPendingSortState() {
@ -223,7 +224,7 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
hasEverSorted = true;
isSortPending = true;
pendingSortState = sortState;
SystemUtilities.runSwingLater(() -> sort(getModelData(), createSortingContext(sortState)));
Swing.runLater(() -> sort(getModelData(), createSortingContext(sortState)));
}
/**
@ -320,14 +321,14 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
/**
* An extension point for subclasses to insert their own comparator objects for their data.
* Subclasses can create comparators for a single or multiple columns, as desired. The
* {@link DefaultColumnComparator} is used as a, well, default comparator.
* Subclasses can create comparators for a single or multiple columns, as desired.
*
* @param columnIndex the column index for which a comparator is desired.
* @return a comparator for the given index.
* @param columnIndex the column index
* @return the comparator
*/
protected Comparator<T> createSortComparator(int columnIndex) {
return new DefaultColumnComparator(columnIndex);
return new RowToColumnComparator<>(this, columnIndex, new DefaultColumnComparator(),
new StringBasedBackupRowToColumnComparator(columnIndex));
}
private Comparator<T> createLastResortComparator(ComparatorLink parentChain) {
@ -416,23 +417,6 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
}
}
int size() {
int count = 0;
if (primaryComparator != null) {
count++;
}
if (nextComparator == null) {
return count;
}
if (nextComparator instanceof AbstractSortedTableModel.ComparatorLink) {
count += ((ComparatorLink) nextComparator).size();
}
return count + 1; // +1 for the non-null comparator
}
@Override
public int compare(T t1, T t2) {
int result = primaryComparator.compare(t1, t2);
@ -451,18 +435,18 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
* when we get to this comparator, then we have to make a decision about reasonable default
* comparisons in order to maintain sorting consistency across sorts.
*/
@SuppressWarnings("unchecked")
// Comparable cast
@SuppressWarnings("unchecked") // Comparable cast
private class EndOfChainComparator implements Comparator<T> {
@SuppressWarnings("rawtypes")
@Override
public int compare(T t1, T t2) {
// at this point we compare the rows, since all of the sorting columns are
// completely equal
// at this point we compare the rows, since all of the sorting column values are equal
if (t1 instanceof Comparable) {
return ((Comparable) t1).compareTo(t2);
}
// use the identity hash to provide a consistent unique identifier within a JVM session
return System.identityHashCode(t1) - System.identityHashCode(t2);
}
}
@ -480,19 +464,35 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
}
}
private class DefaultColumnComparator implements Comparator<T> {
private final int columnIndex;
private class StringBasedBackupRowToColumnComparator implements Comparator<T> {
public DefaultColumnComparator(int columnIndex) {
this.columnIndex = columnIndex;
private int sortColumn;
StringBasedBackupRowToColumnComparator(int sortColumn) {
this.sortColumn = sortColumn;
}
@Override
public int compare(T t1, T t2) {
Object value1 = getColumnValueForRow(t1, columnIndex);
Object value2 = getColumnValueForRow(t2, columnIndex);
return DEFAULT_COMPARATOR.compare(value1, value2);
if (t1 == t2) {
return 0;
}
String s1 = getColumStringValue(t1);
String s2 = getColumStringValue(t2);
if (s1 == null || s2 == null) {
return TableComparators.compareWithNullValues(s1, s2);
}
return s1.compareToIgnoreCase(s2);
}
private String getColumStringValue(T t) {
// just use the toString(), which may or may not produce a good value (this will
// catch the cases where the column value is itself a string)
Object o = getColumnValueForRow(t, sortColumn);
return o == null ? null : o.toString();
}
}
}

View file

@ -20,7 +20,8 @@ package docking.widgets.table;
*
* @param <ROW_TYPE> the row type of the underlying table model
*/
public interface DynamicColumnTableModel<ROW_TYPE> extends ConfigurableColumnTableModel {
public interface DynamicColumnTableModel<ROW_TYPE>
extends ConfigurableColumnTableModel, RowObjectTableModel<ROW_TYPE> {
/**
* Returns the column for the given model index

View file

@ -22,6 +22,7 @@ import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.TableCellRenderer;
import docking.widgets.table.sort.*;
import ghidra.docking.settings.*;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.Msg;
@ -174,11 +175,14 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
@Override
protected Comparator<ROW_TYPE> createSortComparator(int columnIndex) {
Comparator<Object> comparator = createSortComparatorForColumn(columnIndex);
if (comparator != null) {
return new RowToColumnComparator(columnIndex, comparator);
Comparator<Object> columnComparator = createSortComparatorForColumn(columnIndex);
if (columnComparator != null) {
// the given column has its own comparator; wrap and us that
return new RowToColumnComparator<>(this, columnIndex, columnComparator);
}
return super.createSortComparator(columnIndex);
return new RowToColumnComparator<>(this, columnIndex, new DefaultColumnComparator(),
new ColumnRenderedValueBackupRowComparator<>(this, columnIndex));
}
/**
@ -187,7 +191,7 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
* column values.
*
* @param columnIndex the column index
* @return a comparator for the specific column values; may be null
* @return a comparator for the specific column values
*/
@SuppressWarnings("unchecked") // the column provides the values itself; safe cast
protected Comparator<Object> createSortComparatorForColumn(int columnIndex) {
@ -545,45 +549,4 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
DynamicTableColumn<ROW_TYPE, ?, ?> column = tableColumns.get(index);
return column.getMaxLines(columnSettings.get(column));
}
/**
* A comparator for a specific column that will take in a ROW_TYPE object, extract the value
* for the given column and then call the give comparator.
*/
private class RowToColumnComparator implements Comparator<ROW_TYPE> {
private int columnIndex;
private Comparator<Object> columnComparator;
RowToColumnComparator(int columnIndex, Comparator<Object> comparator) {
this.columnIndex = columnIndex;
this.columnComparator = comparator;
}
@Override
public int compare(ROW_TYPE t1, ROW_TYPE t2) {
Object value1 = getColumnValueForRow(t1, columnIndex);
Object value2 = getColumnValueForRow(t2, columnIndex);
if (value1 == null || value2 == null) {
return handleNullValues(value1, value2);
}
return columnComparator.compare(value1, value2);
}
private int handleNullValues(Object o1, Object o2) {
// If both values are null return 0
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) { // Define null less than everything.
return -1;
}
return 1; // o2 is null, so the o1 comes after
}
}
}

View file

@ -15,17 +15,13 @@
*/
package docking.widgets.table;
import java.util.Comparator;
import javax.swing.table.TableModel;
/**
* A table model that allows for setting the sorted column and direction.
* A table model that allows for setting the sorted column(s) and direction
*/
public interface SortedTableModel extends TableModel {
public static final Comparator<Object> DEFAULT_COMPARATOR = new DefaultComparator();
/**
* Sort order in ascending order.
*/
@ -43,10 +39,23 @@ public interface SortedTableModel extends TableModel {
*/
public boolean isSortable(int columnIndex);
/**
* Returns the column index that is the primary sorted column
*
* @return the index
*/
public int getPrimarySortColumnIndex();
public void setTableSortState(TableSortState tableSortState);
/**
* Sets the sort state for this table model
* @param state the sort state
*/
public void setTableSortState(TableSortState state);
/**
* Gets the sort state of this sorted model
* @return the current sort state
*/
public TableSortState getTableSortState();
/**
@ -58,54 +67,4 @@ public interface SortedTableModel extends TableModel {
* @param l the listener
*/
public void addSortListener(SortListener l);
//==================================================================================================
// Inner Classes
//==================================================================================================
public static class DefaultComparator implements Comparator<Object> {
@Override
@SuppressWarnings("unchecked")
// we checked cast to be safe
public int compare(Object o1, Object o2) {
if (o1 == null || o2 == null) {
return handleNullValues(o1, o2);
}
if (String.class == o1.getClass() && String.class == o2.getClass()) {
return compareAsStrings(o1, o2);
}
if (Comparable.class.isAssignableFrom(o1.getClass()) && o1.getClass() == o2.getClass()) {
@SuppressWarnings("rawtypes")
Comparable comparable = (Comparable) o1;
int result = comparable.compareTo(o2);
return result;
}
// give up and use the toString()
return compareAsStrings(o1, o2);
}
private int handleNullValues(Object o1, Object o2) {
// If both values are null return 0
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) { // Define null less than everything.
return -1;
}
return 1; // o2 is null, so the o1 comes after
}
private int compareAsStrings(Object o1, Object o2) {
String s1 = o1.toString();
String s2 = o2.toString();
return s1.compareToIgnoreCase(s2);
}
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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;
import java.util.Comparator;
/**
* A utility class for tables to use when sorting
*/
public class TableComparators {
private static final Comparator<Object> NO_SORT_COMPARATOR = (o1, o2) -> 0;
@SuppressWarnings("unchecked") // we are casting to Object; safe since everything is an Object
public static <T> Comparator<T> getNoSortComparator() {
return (Comparator<T>) NO_SORT_COMPARATOR;
}
public static int compareWithNullValues(Object o1, Object o2) {
// If both values are null return 0
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) { // Define null less than everything.
return -1;
}
return 1; // o2 is null, so the o1 comes after
}
}

View file

@ -0,0 +1,78 @@
/* ###
* 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.sort;
import java.util.Comparator;
import docking.widgets.table.*;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.GColumnRenderer;
/**
* A special version of the backup comparator that uses the column's rendered value for
* the backup sort, rather the just <code>toString</code>, which is what the default parent
* table model will do.
*
* @param <T> the row type
*/
public class ColumnRenderedValueBackupRowComparator<T> implements Comparator<T> {
protected int sortColumn;
protected DynamicColumnTableModel<T> model;
public ColumnRenderedValueBackupRowComparator(DynamicColumnTableModel<T> model,
int sortColumn) {
this.model = model;
this.sortColumn = sortColumn;
}
@Override
public int compare(T t1, T t2) {
if (t1 == t2) {
return 0;
}
String s1 = getRenderedColumnStringValue(t1);
String s2 = getRenderedColumnStringValue(t2);
if (s1 == null || s2 == null) {
return TableComparators.compareWithNullValues(s1, s2);
}
return s1.compareToIgnoreCase(s2);
}
// The case to Object is safe, but passing the object to the renderer below is potentially
// unsafe. We happen know that we retrieved the value from the column that we are passing
// it to, so the casting and usage is indeed safe.
@SuppressWarnings("unchecked")
private String getRenderedColumnStringValue(T t) {
DynamicTableColumn<T, ?, ?> column = model.getColumn(sortColumn);
GColumnRenderer<Object> renderer = (GColumnRenderer<Object>) column.getColumnRenderer();
Object o = model.getColumnValueForRow(t, sortColumn);
if (renderer == null) {
return o == null ? null : o.toString();
}
Settings settings = model.getColumnSettings(sortColumn);
return renderer.getFilterString(o, settings);
}
protected Object getColumnValue(T t) {
return model.getColumnValueForRow(t, sortColumn);
}
}

View file

@ -0,0 +1,60 @@
/* ###
* 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.sort;
import java.util.Comparator;
import docking.widgets.table.TableComparators;
/**
* A column comparator that is used when columns do not supply their own comparator. This
* comparator will use the natural sorting (i.e., the value implements Comparable),
* defaulting to the String representation for the given value.
*/
public class DefaultColumnComparator implements Comparator<Object> {
@Override
@SuppressWarnings("unchecked") // we checked cast to be safe
public int compare(Object o1, Object o2) {
if (o1 == null || o2 == null) {
return TableComparators.compareWithNullValues(o1, o2);
}
Class<? extends Object> c1 = o1.getClass();
Class<? extends Object> c2 = o2.getClass();
if (String.class == c1 && String.class == c2) {
return compareAsStrings(o1, o2);
}
if (Comparable.class.isAssignableFrom(c1) && c1 == c2) {
@SuppressWarnings("rawtypes")
Comparable comparable = (Comparable) o1;
int result = comparable.compareTo(o2);
return result;
}
// At this point we do not know how to compare these items well. Return 0, which
// will signal to any further comparators that more comparing is needed.
return 0;
}
private int compareAsStrings(Object o1, Object o2) {
String s1 = o1.toString();
String s2 = o2.toString();
return s1.compareToIgnoreCase(s2);
}
}

View file

@ -0,0 +1,106 @@
/* ###
* 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.sort;
import java.util.Comparator;
import java.util.Objects;
import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.TableComparators;
/**
* A comparator for a specific column that will take in a T row object, extract the value
* for the given column and then call the give comparator
*
* @param <T> the row type
*/
public class RowToColumnComparator<T> implements Comparator<T> {
protected RowObjectTableModel<T> model;
protected int sortColumn;
protected Comparator<Object> columnComparator;
protected Comparator<T> backupRowComparator = TableComparators.getNoSortComparator();
/**
* Constructs this class with the given column comparator that will get called after the
* given row is converted to the column value for the given sort column
*
* @param model the table model using this comparator
* @param sortColumn the column being sorted
* @param comparator the column comparator to use for sorting
*/
public RowToColumnComparator(RowObjectTableModel<T> model, int sortColumn,
Comparator<Object> comparator) {
this.model = model;
this.sortColumn = sortColumn;
this.columnComparator = Objects.requireNonNull(comparator);
}
/**
* This version of the constructor is used for the default case where the client will
* supply a backup row comparator that will get called if the given column comparator returns
* a '0' value.
*
* @param model the table model using this comparator
* @param sortColumn the column being sorted
* @param comparator the column comparator to use for sorting
* @param backupRowComparator the backup row comparator
*/
public RowToColumnComparator(RowObjectTableModel<T> model, int sortColumn,
Comparator<Object> comparator, Comparator<T> backupRowComparator) {
this.model = model;
this.sortColumn = sortColumn;
this.columnComparator = Objects.requireNonNull(comparator);
this.backupRowComparator = Objects.requireNonNull(backupRowComparator);
}
@Override
public int compare(T t1, T t2) {
if (t1 == t2) {
return 0;
}
Object value1 = getColumnValue(t1);
Object value2 = getColumnValue(t2);
if (value1 == null || value2 == null) {
return TableComparators.compareWithNullValues(value1, value2);
}
int result = columnComparator.compare(value1, value2);
if (result != 0) {
return result;
}
//
// At this point we have one of two cases:
// 1) the column comparator is a non-default comparator that has returned 0, which means
// the column values should sort the same, or
// 2) the column comparator is a default/non-specific comparator, which means that the
// column values should sort the same, or *that the default comparator could not
// figure out how to sort them.
//
// In case 1, this backup comparator will be just a stub comparator; in case 2, this
// backup comparator is not a stub and will do something reasonable for the sort,
// depending upon how the model created this class.
//
return backupRowComparator.compare(t1, t2);
}
protected Object getColumnValue(T t) {
return model.getColumnValueForRow(t, sortColumn);
}
}

View file

@ -1,92 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.table.threaded;
import java.util.Comparator;
import docking.widgets.table.SortedTableModel;
public class TableColumnComparator<T> implements Comparator<T> {
private ThreadedTableModel<T, ?> model;
private final int sortColumn;
private Comparator<Object> columnComparator;
public TableColumnComparator(ThreadedTableModel<T, ?> model,
Comparator<Object> columnComparator, int sortColumn) {
this.model = model;
this.columnComparator = columnComparator;
this.sortColumn = sortColumn;
}
@Override
public int compare(T t1, T t2) {
if (t1 == t2) {
return 0;
}
Object o1 = model.getCachedColumnValueForRow(t1, sortColumn);
Object o2 = model.getCachedColumnValueForRow(t2, sortColumn);
if (o1 == null || o2 == null) {
return handleNullValues(o1, o2);
}
if (columnComparator != null) {
return columnComparator.compare(o1, o2);
}
return SortedTableModel.DEFAULT_COMPARATOR.compare(o1, o2);
}
private int handleNullValues(Object o1, Object o2) {
// If both values are null return 0
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) { // Define null less than everything.
return -1;
}
return 1; // o2 is null, so the o1 comes after
}
public int getSortColumn() {
return sortColumn;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
@SuppressWarnings("rawtypes")
TableColumnComparator other = (TableColumnComparator) obj;
return (sortColumn == other.sortColumn);
}
@Override
public int hashCode() {
return sortColumn;
}
}

View file

@ -0,0 +1,48 @@
/* ###
* 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 docking.widgets.table.sort.ColumnRenderedValueBackupRowComparator;
import docking.widgets.table.sort.RowToColumnComparator;
/**
* A version of {@link ColumnRenderedValueBackupRowComparator} that uses the
* {@link ThreadedTableModel}'s cache for column lookups
*
* @param <T> the row type
*/
public class ThreadedBackupRowComparator<T> extends ColumnRenderedValueBackupRowComparator<T> {
private ThreadedTableModel<T, ?> threadedModel;
/**
* Constructs this class with the given column comparator that will get called after the
* given row is converted to the column value for the given sort column
*
* @param model the table model using this comparator
* @param sortColumn the column being sorted
* @see RowToColumnComparator
*/
public ThreadedBackupRowComparator(ThreadedTableModel<T, ?> model, int sortColumn) {
super(model, sortColumn);
this.threadedModel = model;
}
@Override
protected Object getColumnValue(T t) {
return threadedModel.getCachedColumnValueForRow(t, sortColumn);
}
}

View file

@ -0,0 +1,67 @@
/* ###
* 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 java.util.Comparator;
import docking.widgets.table.sort.RowToColumnComparator;
/**
* A comparator for comparing table column values for threaded table models. This comparator
* uses the column cache of the {@link ThreadedTableModel}.
*
* @param <T> the row type
*/
public class ThreadedTableColumnComparator<T> extends RowToColumnComparator<T> {
private ThreadedTableModel<T, ?> threadedModel;
/**
* Constructs this class with the given column comparator that will get called after the
* given row is converted to the column value for the given sort column
*
* @param model the table model using this comparator
* @param sortColumn the column being sorted
* @param comparator the column comparator to use for sorting
* @see RowToColumnComparator
*/
public ThreadedTableColumnComparator(ThreadedTableModel<T, ?> model, int sortColumn,
Comparator<Object> comparator) {
super(model, sortColumn, comparator);
this.threadedModel = model;
}
/**
* This version of the constructor is used for the default case where the client will
* supply a backup row comparator that will get called if the given column comparator returns
* a '0' value.
*
* @param model the table model using this comparator
* @param sortColumn the column being sorted
* @param comparator the column comparator to use for sorting
* @param backupRowComparator the backup row comparator
* @see RowToColumnComparator
*/
public ThreadedTableColumnComparator(ThreadedTableModel<T, ?> model, int sortColumn,
Comparator<Object> comparator, Comparator<T> backupRowComparator) {
super(model, sortColumn, comparator, backupRowComparator);
this.threadedModel = model;
}
@Override
protected Object getColumnValue(T t) {
return threadedModel.getCachedColumnValueForRow(t, sortColumn);
}
}

View file

@ -21,6 +21,7 @@ import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import docking.widgets.table.*;
import docking.widgets.table.sort.DefaultColumnComparator;
import generic.concurrent.ConcurrentListenerSet;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.SystemUtilities;
@ -290,8 +291,15 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
@Override
protected Comparator<ROW_OBJECT> createSortComparator(int columnIndex) {
Comparator<Object> columnComparator = createSortComparatorForColumn(columnIndex);
return new TableColumnComparator<>(this, columnComparator, columnIndex);
if (columnComparator != null) {
// the given column has its own comparator; wrap and us that
return new ThreadedTableColumnComparator<>(this, columnIndex, columnComparator);
}
return new ThreadedTableColumnComparator<>(this, columnIndex, new DefaultColumnComparator(),
new ThreadedBackupRowComparator<>(this, columnIndex));
}
@Override