mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge remote-tracking branch
'origin/GP-5481_ghidragon_data_graph--SQUASHED'
This commit is contained in:
commit
3e50533187
102 changed files with 9268 additions and 366 deletions
|
@ -1,120 +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;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.GeneralPath;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
/**
|
||||
* Icon for a close button
|
||||
*/
|
||||
public class CloseIcon implements Icon {
|
||||
private int size;
|
||||
private Color color;
|
||||
private Shape shape;
|
||||
|
||||
/**
|
||||
* Creates a close icon.
|
||||
* @param isSmall false signals to use a 16x16 size; true signals to use an 8x8 size
|
||||
* @param color the color of the "x"
|
||||
*/
|
||||
public CloseIcon(boolean isSmall, Color color) {
|
||||
this.size = isSmall ? 8 : 16;
|
||||
this.color = color;
|
||||
this.shape = buildShape();
|
||||
}
|
||||
|
||||
private Shape buildShape() {
|
||||
GeneralPath path = new GeneralPath();
|
||||
|
||||
/*
|
||||
We use trial and error sizing. This class allows clients to specify the icon size. At
|
||||
the time of writing, there were only 2 sizes in use: 16 and 8 pixels. If more size
|
||||
needs arise, we can revisit how the values below are chosen.
|
||||
*/
|
||||
|
||||
double margin = 2;
|
||||
double shapeSize = 11;
|
||||
double thickness = 1.7;
|
||||
if (size == 8) {
|
||||
margin = 0;
|
||||
shapeSize = 7;
|
||||
thickness = 1;
|
||||
}
|
||||
|
||||
double p1x = margin;
|
||||
double p1y = margin + thickness;
|
||||
double p2x = margin + thickness;
|
||||
double p2y = margin;
|
||||
double p3x = margin + shapeSize;
|
||||
double p3y = margin + shapeSize - thickness;
|
||||
double p4x = margin + shapeSize - thickness;
|
||||
double p4y = margin + shapeSize;
|
||||
|
||||
path.moveTo(p1x, p1y);
|
||||
path.lineTo(p2x, p2y);
|
||||
path.lineTo(p3x, p3y);
|
||||
path.lineTo(p4x, p4y);
|
||||
path.lineTo(p1x, p1y);
|
||||
|
||||
p1x = margin + shapeSize - thickness;
|
||||
p1y = margin;
|
||||
p2x = margin + shapeSize;
|
||||
p2y = margin + thickness;
|
||||
p3x = margin + thickness;
|
||||
p3y = margin + shapeSize;
|
||||
p4x = margin;
|
||||
p4y = margin + shapeSize - thickness;
|
||||
|
||||
path.moveTo(p1x, p1y);
|
||||
path.lineTo(p2x, p2y);
|
||||
path.lineTo(p3x, p3y);
|
||||
path.lineTo(p4x, p4y);
|
||||
path.lineTo(p1x, p1y);
|
||||
|
||||
path.closePath();
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
try {
|
||||
g2d.translate(x, y);
|
||||
g2d.setColor(color);
|
||||
g2d.fill(shape);
|
||||
}
|
||||
finally {
|
||||
g2d.translate(-x, -y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
|
@ -147,6 +147,7 @@ public class DockableComponent extends JPanel implements ContainerListener {
|
|||
}
|
||||
|
||||
private void showContextMenu(MouseEvent e) {
|
||||
|
||||
if (e.isConsumed()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import javax.swing.*;
|
|||
|
||||
import docking.action.*;
|
||||
import docking.menu.*;
|
||||
import generic.theme.CloseIcon;
|
||||
import generic.theme.GColor;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.task.SwingUpdateManager;
|
||||
|
@ -32,7 +33,7 @@ import ghidra.util.task.SwingUpdateManager;
|
|||
*/
|
||||
class DockableToolBarManager {
|
||||
private static final Color BUTTON_COLOR = new GColor("color.fg.button");
|
||||
private static final Icon CLOSE_ICON = new CloseIcon(false, BUTTON_COLOR);
|
||||
private static final Icon CLOSE_ICON = new CloseIcon(false);
|
||||
private Icon MENU_ICON = new DropDownMenuIcon(BUTTON_COLOR);
|
||||
private GenericHeader dockableHeader;
|
||||
private ToolBarManager toolBarManager;
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -277,6 +277,10 @@ public class GenericHeader extends JPanel {
|
|||
titlePanel.setTitle(title);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return titlePanel.getTitle();
|
||||
}
|
||||
|
||||
public void setIcon(Icon icon) {
|
||||
titlePanel.setIcon(icon);
|
||||
}
|
||||
|
@ -396,6 +400,10 @@ public class GenericHeader extends JPanel {
|
|||
titleLabel.setToolTipText(s);
|
||||
}
|
||||
|
||||
String getTitle() {
|
||||
return titleLabel.getText();
|
||||
}
|
||||
|
||||
void setIcon(Icon icon) {
|
||||
|
||||
icon = DockingUtils.scaleIconAsNeeded(icon);
|
||||
|
|
|
@ -20,10 +20,9 @@ import java.awt.event.*;
|
|||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.CloseIcon;
|
||||
import docking.widgets.EmptyBorderButton;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.CloseIcon;
|
||||
|
||||
/**
|
||||
* A widget that can be used to render an icon, title and close button for JTabbedPane. You would
|
||||
|
@ -32,7 +31,7 @@ import generic.theme.GColor;
|
|||
public class DockingTabRenderer extends JPanel {
|
||||
|
||||
private static final int MAX_TITLE_LENGTH = 25;
|
||||
private Icon CLOSE_ICON = new CloseIcon(true, new GColor("color.fg.button"));
|
||||
private Icon CLOSE_ICON = new CloseIcon(true);
|
||||
|
||||
private JLabel titleLabel;
|
||||
private JLabel iconLabel;
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base class for GTrable models. Adds support for listeners.
|
||||
*
|
||||
* @param <T> the row data object type
|
||||
*/
|
||||
public abstract class AbstractGTrableRowModel<T> implements GTrableRowModel<T> {
|
||||
private List<GTrableModeRowlListener> listeners = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void addListener(GTrableModeRowlListener l) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(GTrableModeRowlListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
protected void fireModelChanged() {
|
||||
for (GTrableModeRowlListener listener : listeners) {
|
||||
listener.trableChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
/**
|
||||
* Base class for GTrable cell renderers.
|
||||
*
|
||||
* @param <T> the data model row object type
|
||||
*/
|
||||
public class DefaultGTrableCellRenderer<T> extends DefaultTableCellRenderer
|
||||
implements GTrableCellRenderer<T> {
|
||||
|
||||
@Override
|
||||
public Component getCellRenderer(GTrable<?> trable, T value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column) {
|
||||
|
||||
if (trable == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Color fg = isSelected ? trable.getSelectionForeground() : trable.getForeground();
|
||||
Color bg = isSelected ? trable.getSelectionBackground() : trable.getBackground();
|
||||
super.setForeground(fg);
|
||||
super.setBackground(bg);
|
||||
|
||||
setFont(trable.getFont());
|
||||
setValue(value);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default implementation for a simple {@link GTrable} row data model.
|
||||
*
|
||||
* @param <T> the row object type
|
||||
*/
|
||||
public class DefaultGTrableRowModel<T extends GTrableRow<T>> extends AbstractGTrableRowModel<T> {
|
||||
protected List<T> rows;
|
||||
|
||||
public DefaultGTrableRowModel(List<T> roots) {
|
||||
this.rows = new ArrayList<>(roots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return rows.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getRow(int index) {
|
||||
return rows.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentLevel(int rowIndex) {
|
||||
return rows.get(rowIndex).getIndentLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpanded(int rowIndex) {
|
||||
return rows.get(rowIndex).isExpanded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpandable(int rowIndex) {
|
||||
return rows.get(rowIndex).isExpandable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int collapseRow(int lineIndex) {
|
||||
T row = rows.get(lineIndex);
|
||||
int indentLevel = row.getIndentLevel();
|
||||
int removedCount = removeIndentedRows(lineIndex + 1, indentLevel + 1);
|
||||
row.setExpanded(false);
|
||||
fireModelChanged();
|
||||
return removedCount;
|
||||
|
||||
}
|
||||
|
||||
protected int removeIndentedRows(int startIndex, int indentLevel) {
|
||||
int endIndex = findNextIndexAtLowerIndentLevel(startIndex, indentLevel);
|
||||
rows.subList(startIndex, endIndex).clear();
|
||||
return endIndex - startIndex;
|
||||
}
|
||||
|
||||
protected int findNextIndexAtLowerIndentLevel(int startIndex, int indentLevel) {
|
||||
for (int i = startIndex; i < rows.size(); i++) {
|
||||
T line = rows.get(i);
|
||||
if (line.getIndentLevel() < indentLevel) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return rows.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expandRow(int lineIndex) {
|
||||
T row = rows.get(lineIndex);
|
||||
if (!row.isExpandable() || row.isExpanded()) {
|
||||
return 0;
|
||||
}
|
||||
List<T> children = row.getChildRows();
|
||||
rows.addAll(lineIndex + 1, children);
|
||||
row.setExpanded(true);
|
||||
fireModelChanged();
|
||||
return children.size();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,646 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DockingUtils;
|
||||
import ghidra.util.datastruct.Range;
|
||||
|
||||
/**
|
||||
* Component that combines the display of a tree and a table. Data is presented in columns like a
|
||||
* table, but rows can have child rows like a tree which are displayed indented in the first
|
||||
* column.
|
||||
* <P>
|
||||
* A GTrable uses two different models: a row model and a column model. The row model contains
|
||||
* row objects that contains the data to be displayed on a given row. The column model specifies
|
||||
* how to display the data in the row object as a series of column values.
|
||||
* <P>
|
||||
* The row model also provides information about the parent child relationship of rows. If the
|
||||
* model reports that a row can be expanded, an expand control is show on that row. If the row
|
||||
* is then expanded, the model will then report additional rows immediately below the parent row,
|
||||
* pushing any existing rows further down (i.e. all rows below the row being opened have their row
|
||||
* indexes increased by the number of rows added.)
|
||||
*
|
||||
* @param <T> The row object type
|
||||
*/
|
||||
public class GTrable<T> extends JComponent
|
||||
implements Scrollable, GTrableModeRowlListener {
|
||||
private static final int ICON_WIDTH = 16;
|
||||
private static final int INDENT_WIDTH = 12;
|
||||
private static final int DEFAULT_MAX_VISIBLE_ROWS = 10;
|
||||
private static final int DEFAULT_MIN_VISIBLE_ROWS = 10;
|
||||
private static OpenCloseIcon OPEN_ICON = new OpenCloseIcon(true, ICON_WIDTH, ICON_WIDTH);
|
||||
private static OpenCloseIcon CLOSED_ICON = new OpenCloseIcon(false, ICON_WIDTH, ICON_WIDTH);
|
||||
|
||||
private Color selectionForground = UIManager.getColor("List.selectionForeground");
|
||||
private Color selectionBackground = UIManager.getColor("List.selectionBackground");
|
||||
private int minVisibleRows = DEFAULT_MIN_VISIBLE_ROWS;
|
||||
private int maxVisibleRows = DEFAULT_MAX_VISIBLE_ROWS;
|
||||
|
||||
private int rowHeight = 20;
|
||||
private GTrableRowModel<T> rowModel;
|
||||
private GTrableColumnModel<T> columnModel;
|
||||
private CellRendererPane renderPane;
|
||||
private int selectedRow = -1;
|
||||
private List<GTrableCellClickedListener> cellClickedListeners = new ArrayList<>();
|
||||
private List<Consumer<Integer>> selectedRowConsumers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param rowModel the model that provides the row data.
|
||||
* @param columnModel the model the provides the column information for displaying the data
|
||||
* stored in the row data.
|
||||
*/
|
||||
public GTrable(GTrableRowModel<T> rowModel, GTrableColumnModel<T> columnModel) {
|
||||
this.rowModel = rowModel;
|
||||
this.columnModel = columnModel;
|
||||
this.rowModel.addListener(this);
|
||||
renderPane = new CellRendererPane();
|
||||
add(renderPane);
|
||||
GTrableMouseListener l = new GTrableMouseListener();
|
||||
addMouseListener(l);
|
||||
addMouseMotionListener(l);
|
||||
addKeyListener(new GTrableKeyListener());
|
||||
setFocusable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new row model.
|
||||
* @param newRowModel the new row model to use
|
||||
*/
|
||||
public void setRowModel(GTrableRowModel<T> newRowModel) {
|
||||
rowModel.removeListener(this);
|
||||
rowModel = newRowModel;
|
||||
newRowModel.addListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new column model.
|
||||
* @param columnModel the new column model to use
|
||||
*/
|
||||
public void setColumnModel(GTrableColumnModel<T> columnModel) {
|
||||
this.columnModel = columnModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preferred number of visible rows to be displayed in the scrollable area.
|
||||
* @param minVisibleRows the minimum number of visible rows.
|
||||
* @param maxVisibleRows the maximum number of visible rows.
|
||||
*/
|
||||
public void setPreferredVisibleRowCount(int minVisibleRows, int maxVisibleRows) {
|
||||
this.minVisibleRows = minVisibleRows;
|
||||
this.maxVisibleRows = maxVisibleRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to be notified if the user clicks on a cell in the GTrable.
|
||||
* @param listener the listener to be notified
|
||||
*/
|
||||
public void addCellClickedListener(GTrableCellClickedListener listener) {
|
||||
cellClickedListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a cell clicked listener.
|
||||
* @param listener the listener to be removed
|
||||
*/
|
||||
public void removeCellClickedListener(GTrableCellClickedListener listener) {
|
||||
cellClickedListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a consumer to be notified when the selected row changes.
|
||||
* @param consumer the consumer to be notified when the selected row changes
|
||||
*/
|
||||
public void addSelectedRowConsumer(Consumer<Integer> consumer) {
|
||||
selectedRowConsumers.add(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the consumer to be notified when the selected row changes.
|
||||
* @param consumer the consumer to be removed
|
||||
*/
|
||||
public void removeSelectedRowConsumer(Consumer<Integer> consumer) {
|
||||
selectedRowConsumers.remove(consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension(columnModel.getPreferredWidth(), rowModel.getRowCount() * rowHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
Rectangle clipBounds = g.getClipBounds();
|
||||
int startIndex = getStartIndex(clipBounds);
|
||||
int endIndex = getEndIndex(clipBounds);
|
||||
for (int index = startIndex; index <= endIndex; index++) {
|
||||
drawRow(g, index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the range of visible row indices.}
|
||||
*/
|
||||
public Range getVisibleRows() {
|
||||
Container parent = getParent();
|
||||
Rectangle rect;
|
||||
if (parent instanceof JViewport viewport) {
|
||||
rect = viewport.getViewRect();
|
||||
}
|
||||
else {
|
||||
rect = getVisibleRect();
|
||||
}
|
||||
return new Range(getStartIndex(rect), getEndIndex(rect));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the currently selected row or -1 if not row is selected.}
|
||||
*/
|
||||
public int getSelectedRow() {
|
||||
return selectedRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected row to the given row index
|
||||
* @param rowIndex the row index to select
|
||||
*/
|
||||
public void setSelectedRow(int rowIndex) {
|
||||
if (rowIndex >= 0 && rowIndex < rowModel.getRowCount()) {
|
||||
this.selectedRow = rowIndex;
|
||||
repaint();
|
||||
notifySelectedRowConsumers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deselects any selected row
|
||||
*/
|
||||
public void clearSelectedRow() {
|
||||
this.selectedRow = -1;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the selection foreground color}
|
||||
*/
|
||||
public Color getSelectionForeground() {
|
||||
return selectionForground;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the selection background color}
|
||||
*/
|
||||
public Color getSelectionBackground() {
|
||||
return selectionBackground;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the height of a row in the trable.}
|
||||
*/
|
||||
public int getRowHeight() {
|
||||
return rowHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the amount the view is scrolled such that the first line is not fully visible.}
|
||||
*/
|
||||
public int getRowOffcut() {
|
||||
Rectangle visibleRect = getVisibleRect();
|
||||
int y = visibleRect.y;
|
||||
return y % rowHeight;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredScrollableViewportSize() {
|
||||
int size = Math.min(rowModel.getRowCount(), maxVisibleRows);
|
||||
size = Math.max(size, minVisibleRows);
|
||||
return new Dimension(columnModel.getPreferredWidth(), size * rowHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
|
||||
int direction) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation,
|
||||
int direction) {
|
||||
return 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getScrollableTracksViewportWidth() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getScrollableTracksViewportHeight() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the row at the given index.
|
||||
* @param rowIndex the index of the row to expand
|
||||
*/
|
||||
public void expandRow(int rowIndex) {
|
||||
int numRowsAdded = rowModel.expandRow(rowIndex);
|
||||
if (selectedRow > rowIndex) {
|
||||
setSelectedRow(selectedRow + numRowsAdded);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the row (remove any of its descendants) at the given row index.
|
||||
* @param rowIndex the index of the row to collapse
|
||||
*/
|
||||
public void collapseRow(int rowIndex) {
|
||||
int numRowsDeleted = rowModel.collapseRow(rowIndex);
|
||||
if (selectedRow > rowIndex) {
|
||||
int newSelectedRow = selectedRow - numRowsDeleted;
|
||||
if (newSelectedRow < rowIndex) {
|
||||
newSelectedRow = rowIndex;
|
||||
}
|
||||
setSelectedRow(newSelectedRow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully expands the given row and all its descendants.
|
||||
* @param rowIndex the index of the row to fully expand
|
||||
*/
|
||||
public void expandRowRecursively(int rowIndex) {
|
||||
int startIndentLevel = rowModel.getIndentLevel(rowIndex);
|
||||
int numRowsAdded = rowModel.expandRow(rowIndex);
|
||||
if (selectedRow > rowIndex) {
|
||||
setSelectedRow(selectedRow + numRowsAdded);
|
||||
}
|
||||
int nextRow = rowIndex + 1;
|
||||
while (nextRow < rowModel.getRowCount() &&
|
||||
rowModel.getIndentLevel(nextRow) > startIndentLevel) {
|
||||
numRowsAdded = rowModel.expandRow(nextRow);
|
||||
if (selectedRow > nextRow) {
|
||||
setSelectedRow(selectedRow + numRowsAdded);
|
||||
}
|
||||
nextRow++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands all rows fully.
|
||||
*/
|
||||
public void expandAll() {
|
||||
int rowIndex = 0;
|
||||
for (rowIndex = 0; rowIndex < rowModel.getRowCount(); rowIndex++) {
|
||||
int indentLevel = rowModel.getIndentLevel(rowIndex);
|
||||
if (indentLevel == 0) {
|
||||
expandRowRecursively(rowIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses all rows.
|
||||
*/
|
||||
public void collapseAll() {
|
||||
int rowIndex = 0;
|
||||
for (rowIndex = 0; rowIndex < rowModel.getRowCount(); rowIndex++) {
|
||||
int indentLevel = rowModel.getIndentLevel(rowIndex);
|
||||
if (indentLevel == 0) {
|
||||
collapseRow(rowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the view to make the currently selected row visible.
|
||||
*/
|
||||
public void scrollToSelectedRow() {
|
||||
if (selectedRow < 0) {
|
||||
return;
|
||||
}
|
||||
Container parent = getParent();
|
||||
if (!(parent instanceof JViewport viewport)) {
|
||||
return;
|
||||
}
|
||||
Rectangle viewRect = viewport.getViewRect();
|
||||
int yStart = selectedRow * rowHeight;
|
||||
int yEnd = yStart + rowHeight;
|
||||
if (yStart < viewRect.y) {
|
||||
viewport.setViewPosition(new Point(0, yStart));
|
||||
}
|
||||
else if (yEnd > viewRect.y + viewRect.height) {
|
||||
viewport.setViewPosition(new Point(0, yEnd - viewRect.height));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trableChanged() {
|
||||
setSize(getWidth(), rowModel.getRowCount() * rowHeight);
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBounds(int x, int y, int width, int height) {
|
||||
super.setBounds(x, y, width, height);
|
||||
columnModel.setWidth(width);
|
||||
}
|
||||
|
||||
private void notifySelectedRowConsumers() {
|
||||
for (Consumer<Integer> consumer : selectedRowConsumers) {
|
||||
consumer.accept(selectedRow);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyCellClicked(int row, int column, MouseEvent e) {
|
||||
for (GTrableCellClickedListener listener : cellClickedListeners) {
|
||||
listener.cellClicked(row, column, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawRow(Graphics g, int rowIndex) {
|
||||
T row = rowModel.getRow(rowIndex);
|
||||
int width = getWidth();
|
||||
boolean isSelected = rowIndex == selectedRow;
|
||||
|
||||
int y = rowIndex * rowHeight;
|
||||
|
||||
Color fg = isSelected ? selectionForground : getForeground();
|
||||
Color bg = isSelected ? selectionBackground : getBackground();
|
||||
g.setColor(bg);
|
||||
g.fillRect(0, y, width, rowHeight);
|
||||
GTrableColumn<T, ?> firstColumn = columnModel.getColumn(0);
|
||||
int colWidth = firstColumn.getWidth();
|
||||
|
||||
int marginWidth = paintLeftMargin(g, rowIndex, y, colWidth, fg);
|
||||
int x = marginWidth;
|
||||
paintColumn(g, x, y, colWidth - marginWidth, firstColumn, row, isSelected);
|
||||
|
||||
x = colWidth;
|
||||
for (int i = 1; i < columnModel.getColumnCount(); i++) {
|
||||
GTrableColumn<T, ?> column = columnModel.getColumn(i);
|
||||
colWidth = column.getWidth();
|
||||
paintColumn(g, x, y, colWidth, column, row, isSelected);
|
||||
x += colWidth;
|
||||
}
|
||||
}
|
||||
|
||||
private <C> void paintColumn(Graphics g, int x, int y, int width, GTrableColumn<T, C> column,
|
||||
T row, boolean isSelected) {
|
||||
|
||||
GTrableCellRenderer<C> renderer = column.getRenderer();
|
||||
C columnValue = column.getValue(row);
|
||||
Component component = renderer.getCellRenderer(this, columnValue, isSelected, false, 0, 0);
|
||||
|
||||
renderPane.paintComponent(g, component, this, x, y, width, rowHeight);
|
||||
}
|
||||
|
||||
private int paintLeftMargin(Graphics g, int rowIndex, int y, int width, Color fg) {
|
||||
int x = rowModel.getIndentLevel(rowIndex) * INDENT_WIDTH;
|
||||
drawOpenCloseControl(g, rowIndex, x, y, fg);
|
||||
return x + ICON_WIDTH;
|
||||
}
|
||||
|
||||
private void drawOpenCloseControl(Graphics g, int rowIndex, int x, int y, Color fg) {
|
||||
if (!rowModel.isExpandable(rowIndex)) {
|
||||
return;
|
||||
}
|
||||
OpenCloseIcon icon = rowModel.isExpanded(rowIndex) ? OPEN_ICON : CLOSED_ICON;
|
||||
icon.setColor(fg);
|
||||
icon.paintIcon(this, g, x, y + rowHeight / 2 - icon.getIconHeight() / 2);
|
||||
}
|
||||
|
||||
private int getStartIndex(Rectangle clipBounds) {
|
||||
if (clipBounds.height == 0) {
|
||||
return 0;
|
||||
}
|
||||
int index = clipBounds.y / rowHeight;
|
||||
return Math.min(index, rowModel.getRowCount() - 1);
|
||||
}
|
||||
|
||||
private int getEndIndex(Rectangle clipBounds) {
|
||||
if (clipBounds.height == 0) {
|
||||
return 0;
|
||||
}
|
||||
int y = clipBounds.y + clipBounds.height - 1;
|
||||
return Math.min(y / rowHeight, rowModel.getRowCount() - 1);
|
||||
}
|
||||
|
||||
private void toggleOpen(int rowIndex) {
|
||||
if (rowIndex < 0) {
|
||||
return;
|
||||
}
|
||||
if (!rowModel.isExpandable(rowIndex)) {
|
||||
return;
|
||||
}
|
||||
if (rowModel.isExpanded(rowIndex)) {
|
||||
collapseRow(rowIndex);
|
||||
}
|
||||
else {
|
||||
expandRow(rowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private class GTrableMouseListener extends MouseAdapter {
|
||||
private static final int TRIGGER_MARGIN = 10;
|
||||
private int startDragX = -1;
|
||||
private int originalColumnStart;
|
||||
private int boundaryIndex;
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getButton() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point point = e.getPoint();
|
||||
int rowIndex = point.y / rowHeight;
|
||||
if (isOnOpenClose(rowIndex, point.x)) {
|
||||
toggleOpen(rowIndex);
|
||||
}
|
||||
else {
|
||||
|
||||
int columnIndex = getColumnIndex(rowIndex, point.x);
|
||||
if (columnIndex >= 0) {
|
||||
notifyCellClicked(rowIndex, columnIndex, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getColumnIndex(int rowIndex, int x) {
|
||||
int columnIndex = columnModel.getIndex(x);
|
||||
if (columnIndex == 0) {
|
||||
int indent = rowModel.getIndentLevel(rowIndex) * INDENT_WIDTH;
|
||||
if (x < indent) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return columnIndex;
|
||||
}
|
||||
|
||||
private boolean isOnOpenClose(int rowIndex, int x) {
|
||||
if (!rowModel.isExpandable(rowIndex)) {
|
||||
return false;
|
||||
}
|
||||
int indent = rowModel.getIndentLevel(rowIndex) * INDENT_WIDTH;
|
||||
return x >= indent && x < indent + ICON_WIDTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
Point p = e.getPoint();
|
||||
int rowIndex = p.y / rowHeight;
|
||||
|
||||
if (e.getButton() == 1 && isOnOpenClose(rowIndex, p.x)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = findClosestColumnBoundary(e.getPoint().x);
|
||||
|
||||
if (index >= 0) {
|
||||
boundaryIndex = index;
|
||||
startDragX = e.getPoint().x;
|
||||
originalColumnStart = columnModel.getColumn(index).getStartX();
|
||||
return;
|
||||
}
|
||||
|
||||
if (DockingUtils.isControlModifier(e) && rowIndex == selectedRow) {
|
||||
clearSelectedRow();
|
||||
}
|
||||
else {
|
||||
setSelectedRow(rowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public int findClosestColumnBoundary(int x) {
|
||||
for (int i = 1; i < columnModel.getColumnCount(); i++) {
|
||||
GTrableColumn<T, ?> column = columnModel.getColumn(i);
|
||||
int columnStart = column.getStartX();
|
||||
if (x > columnStart - TRIGGER_MARGIN && x < columnStart + TRIGGER_MARGIN) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
int index = findClosestColumnBoundary(e.getPoint().x);
|
||||
if (index >= 0) {
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
|
||||
}
|
||||
else {
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
startDragX = -1;
|
||||
boundaryIndex = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
if (startDragX < 0) {
|
||||
return;
|
||||
}
|
||||
int x = e.getPoint().x;
|
||||
int diff = x - startDragX;
|
||||
int newColumnStart = originalColumnStart + diff;
|
||||
columnModel.moveColumnStart(boundaryIndex, newColumnStart);
|
||||
repaint();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class GTrableKeyListener extends KeyAdapter {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_DOWN:
|
||||
if (selectedRow < rowModel.getRowCount() - 1) {
|
||||
setSelectedRow(selectedRow + 1);
|
||||
scrollToSelectedRow();
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_UP:
|
||||
if (selectedRow > 0) {
|
||||
setSelectedRow(selectedRow - 1);
|
||||
scrollToSelectedRow();
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_ENTER:
|
||||
toggleOpen(selectedRow);
|
||||
e.consume();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_DOWN:
|
||||
if (selectedRow < rowModel.getRowCount() - 1) {
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_UP:
|
||||
if (selectedRow > 0) {
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_ENTER:
|
||||
e.consume();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_DOWN:
|
||||
if (selectedRow < rowModel.getRowCount() - 1) {
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_UP:
|
||||
if (selectedRow > 0) {
|
||||
e.consume();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_ENTER:
|
||||
e.consume();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
/**
|
||||
* Listener for {@link GTrable} cell clicked
|
||||
*/
|
||||
public interface GTrableCellClickedListener {
|
||||
|
||||
/**
|
||||
* Notification the a GTrable cell was clicked.
|
||||
* @param row the row index of the cell that was clicked
|
||||
* @param column the column index of the cell that was clicked
|
||||
* @param event the mouse event of the click
|
||||
*/
|
||||
public void cellClicked(int row, int column, MouseEvent event);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
/**
|
||||
* Interface for {@link GTrable} cell renderers
|
||||
*
|
||||
* @param <C> the type of the column value for this cell
|
||||
*/
|
||||
public interface GTrableCellRenderer<C> {
|
||||
|
||||
/**
|
||||
* Gets and prepares the renderer component for the given column value
|
||||
* @param trable the GTrable
|
||||
* @param value the column value
|
||||
* @param isSelected true if the row is selected
|
||||
* @param hasFocus true if the cell has focus
|
||||
* @param row the row of the cell being painted
|
||||
* @param column the column of the cell being painted
|
||||
* @return the component to use to paint the cell value
|
||||
*/
|
||||
public Component getCellRenderer(GTrable<?> trable, C value,
|
||||
boolean isSelected, boolean hasFocus, int row, int column);
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link GTrable} column objects in the {@link GTrableColumnModel}
|
||||
*
|
||||
* @param <R> the row object type
|
||||
* @param <C> the column value type
|
||||
*/
|
||||
public abstract class GTrableColumn<R, C> {
|
||||
private static final int DEFAULT_MIN_SIZE = 20;
|
||||
private static GTrableCellRenderer<Object> DEFAULT_RENDERER =
|
||||
new DefaultGTrableCellRenderer<>();
|
||||
|
||||
private int startX;
|
||||
private int width;
|
||||
|
||||
public GTrableColumn() {
|
||||
width = getPreferredWidth();
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public GTrableCellRenderer<C> getRenderer() {
|
||||
return (GTrableCellRenderer<C>) DEFAULT_RENDERER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column value given the row object
|
||||
* @param row the row object containing the data for the entire row
|
||||
* @return the value to be displayed in this column
|
||||
*/
|
||||
public abstract C getValue(R row);
|
||||
|
||||
protected int getPreferredWidth() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
void setWidth(int width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public int getMinWidth() {
|
||||
return DEFAULT_MIN_SIZE;
|
||||
}
|
||||
|
||||
public boolean isResizable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setStartX(int x) {
|
||||
this.startX = x;
|
||||
}
|
||||
|
||||
int getStartX() {
|
||||
return startX;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link GTrable} column models
|
||||
*
|
||||
* @param <T> the row object type
|
||||
*/
|
||||
public abstract class GTrableColumnModel<T> {
|
||||
private List<GTrableColumn<T, ?>> columns = new ArrayList<>();
|
||||
private int totalWidth;
|
||||
|
||||
public GTrableColumnModel() {
|
||||
reloadColumns();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the number of columns in this model.}
|
||||
*/
|
||||
public int getColumnCount() {
|
||||
return columns.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the column object for the given column index.}
|
||||
* @param column the index of the column
|
||||
*/
|
||||
public GTrableColumn<T, ?> getColumn(int column) {
|
||||
return columns.get(column);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the preferred width of the model which is the sum of the preferred widths of each
|
||||
* column.}
|
||||
*/
|
||||
public int getPreferredWidth() {
|
||||
int preferredWidth = 0;
|
||||
for (GTrableColumn<T, ?> column : columns) {
|
||||
preferredWidth += column.getPreferredWidth();
|
||||
}
|
||||
return preferredWidth;
|
||||
}
|
||||
|
||||
protected int computeWidth() {
|
||||
int width = 0;
|
||||
for (GTrableColumn<T, ?> column : columns) {
|
||||
width += column.getWidth();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
protected void reloadColumns() {
|
||||
columns.clear();
|
||||
populateColumns(columns);
|
||||
computeColumnStarts();
|
||||
totalWidth = computeWidth();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses implement this method to define the columns for this model.
|
||||
* @param columnList a list to populate with column objects
|
||||
*/
|
||||
protected abstract void populateColumns(List<GTrableColumn<T, ?>> columnList);
|
||||
|
||||
protected void removeAllColumns() {
|
||||
columns.removeAll(columns);
|
||||
totalWidth = 0;
|
||||
}
|
||||
|
||||
protected int getWidth() {
|
||||
return totalWidth;
|
||||
}
|
||||
|
||||
protected int getIndex(int x) {
|
||||
for (int i = columns.size() - 1; i >= 0; i--) {
|
||||
GTrableColumn<T, ?> column = columns.get(i);
|
||||
if (x >= column.getStartX()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void setWidth(int newWidth) {
|
||||
int diff = newWidth - totalWidth;
|
||||
if (diff == 0) {
|
||||
return;
|
||||
}
|
||||
if (diff > 0) {
|
||||
int amount = growLeftPreferred(columns.size() - 1, diff);
|
||||
growLeft(columns.size() - 1, amount);
|
||||
}
|
||||
else {
|
||||
shrinkLeft(columns.size() - 1, -diff);
|
||||
}
|
||||
computeColumnStarts();
|
||||
}
|
||||
|
||||
void moveColumnStart(int columnIndex, int x) {
|
||||
GTrableColumn<T, ?> column = columns.get(columnIndex);
|
||||
int currentStartX = column.getStartX();
|
||||
int diff = x - currentStartX;
|
||||
if (diff > 0 && canGrowLeft(columnIndex - 1)) {
|
||||
int actualAmount = shrinkRight(columnIndex, diff);
|
||||
growLeft(columnIndex - 1, actualAmount);
|
||||
}
|
||||
else if (diff < 0 && canGrowRight(columnIndex)) {
|
||||
int actualAmount = shrinkLeft(columnIndex - 1, -diff);
|
||||
growRight(columnIndex, actualAmount);
|
||||
}
|
||||
computeColumnStarts();
|
||||
}
|
||||
|
||||
private boolean canGrowLeft(int index) {
|
||||
return canGrow(0, index);
|
||||
}
|
||||
|
||||
private boolean canGrowRight(int index) {
|
||||
return canGrow(index, columns.size() - 1);
|
||||
}
|
||||
|
||||
private boolean canGrow(int index1, int index2) {
|
||||
for (int i = index1; i <= index2; i++) {
|
||||
if (columns.get(i).isResizable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void computeColumnStarts() {
|
||||
int x = 0;
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
GTrableColumn<T, ?> column = columns.get(i);
|
||||
column.setStartX(x);
|
||||
int width = column.getWidth();
|
||||
x += width;
|
||||
}
|
||||
totalWidth = x;
|
||||
modelColumnsChaged();
|
||||
}
|
||||
|
||||
protected void modelColumnsChaged() {
|
||||
// subclasses can override if they need to react to changes in the column positions or
|
||||
// sizes
|
||||
}
|
||||
|
||||
private void growRight(int columnIndex, int amount) {
|
||||
for (int i = columnIndex; i < columns.size(); i++) {
|
||||
GTrableColumn<T, ?> column = columns.get(i);
|
||||
if (column.isResizable()) {
|
||||
column.setWidth(column.getWidth() + amount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void growLeft(int columnIndex, int amount) {
|
||||
for (int i = columnIndex; i >= 0; i--) {
|
||||
GTrableColumn<T, ?> column = columns.get(i);
|
||||
if (column.isResizable()) {
|
||||
column.setWidth(column.getWidth() + amount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int growLeftPreferred(int columnIndex, int amount) {
|
||||
for (int i = columnIndex; i >= 0 && amount > 0; i--) {
|
||||
GTrableColumn<T, ?> column = columns.get(i);
|
||||
if (!column.isResizable()) {
|
||||
continue;
|
||||
}
|
||||
int width = column.getWidth();
|
||||
int preferredWidth = column.getPreferredWidth();
|
||||
if (width < preferredWidth) {
|
||||
int adjustment = Math.min(amount, preferredWidth - width);
|
||||
column.setWidth(width + adjustment);
|
||||
amount -= adjustment;
|
||||
}
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
private int growRightPreferred(int columnIndex, int amount) {
|
||||
for (int i = columnIndex; i < columns.size() && amount > 0; i++) {
|
||||
GTrableColumn<T, ?> column = columns.get(i);
|
||||
if (!column.isResizable()) {
|
||||
continue;
|
||||
}
|
||||
int width = column.getWidth();
|
||||
int preferredWidth = column.getPreferredWidth();
|
||||
if (width < preferredWidth) {
|
||||
int adjustment = Math.min(amount, preferredWidth - width);
|
||||
column.setWidth(width + adjustment);
|
||||
amount -= adjustment;
|
||||
}
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
private int shrinkLeft(int columnIndex, int amount) {
|
||||
int remainingAmount = amount;
|
||||
for (int i = columnIndex; i >= 0 && remainingAmount > 0; i--) {
|
||||
remainingAmount -= shrinkColumn(i, remainingAmount);
|
||||
}
|
||||
return amount - remainingAmount;
|
||||
}
|
||||
|
||||
private int shrinkRight(int columnIndex, int amount) {
|
||||
int remainingAmount = amount;
|
||||
for (int i = columnIndex; i < columns.size() && remainingAmount > 0; i++) {
|
||||
remainingAmount -= shrinkColumn(i, remainingAmount);
|
||||
}
|
||||
return amount - remainingAmount;
|
||||
}
|
||||
|
||||
private int shrinkColumn(int columnIndex, int amount) {
|
||||
GTrableColumn<T, ?> column = columns.get(columnIndex);
|
||||
if (!column.isResizable()) {
|
||||
return 0;
|
||||
}
|
||||
int currentWidth = column.getWidth();
|
||||
int minWidth = column.getMinWidth();
|
||||
|
||||
if (currentWidth >= minWidth + amount) {
|
||||
column.setWidth(currentWidth - amount);
|
||||
return amount;
|
||||
}
|
||||
column.setWidth(minWidth);
|
||||
return currentWidth - minWidth;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
/**
|
||||
* The listener interface for when the row model changes.
|
||||
*/
|
||||
public interface GTrableModeRowlListener {
|
||||
|
||||
/**
|
||||
* Notification that the row model changed
|
||||
*/
|
||||
public void trableChanged();
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link GTrable} row objects.
|
||||
*
|
||||
* @param <T> the row object type
|
||||
*/
|
||||
public abstract class GTrableRow<T extends GTrableRow<T>> {
|
||||
private final int indentLevel;
|
||||
private boolean isExpanded = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param indentLevel the indent level of this row
|
||||
*/
|
||||
protected GTrableRow(int indentLevel) {
|
||||
this.indentLevel = indentLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the indent level for this row}
|
||||
*/
|
||||
public int getIndentLevel() {
|
||||
return indentLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if this row is expandable}
|
||||
*/
|
||||
public abstract boolean isExpandable();
|
||||
|
||||
/**
|
||||
* {@return true if this node is expanded.}
|
||||
*/
|
||||
public boolean isExpanded() {
|
||||
return isExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expanded state.
|
||||
* @param expanded true if this row is expanded
|
||||
*/
|
||||
void setExpanded(boolean expanded) {
|
||||
this.isExpanded = expanded;
|
||||
}
|
||||
|
||||
protected abstract List<T> getChildRows();
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
/**
|
||||
* Row model for a {@link GTrable}.
|
||||
*
|
||||
* @param <T> the row object type
|
||||
*/
|
||||
public interface GTrableRowModel<T> {
|
||||
|
||||
/**
|
||||
* {@return the total number of rows include open child rows.}
|
||||
*/
|
||||
public int getRowCount();
|
||||
|
||||
/**
|
||||
* {@return the row object for the given index.}
|
||||
* @param rowIndex the index of the row to retrieve
|
||||
*/
|
||||
public T getRow(int rowIndex);
|
||||
|
||||
/**
|
||||
* {@return true if the row at the given index can be expanded}
|
||||
* @param rowIndex the row to test if expandable
|
||||
*/
|
||||
public boolean isExpandable(int rowIndex);
|
||||
|
||||
/**
|
||||
* {@return true if the row at the given index is expanded.}
|
||||
* @param rowIndex the index of the row to test for expanded
|
||||
*/
|
||||
public boolean isExpanded(int rowIndex);
|
||||
|
||||
/**
|
||||
* Collapse the row at the given row index.
|
||||
* @param rowIndex the index of the row to collapse
|
||||
* @return the total number of rows removed due to collapsing the row
|
||||
*/
|
||||
public int collapseRow(int rowIndex);
|
||||
|
||||
/**
|
||||
* Expand the row at the given row index.
|
||||
* @param rowIndex the index of the row to expand
|
||||
* @return the total number of rows added due to the expand
|
||||
*/
|
||||
public int expandRow(int rowIndex);
|
||||
|
||||
/**
|
||||
* {@return the indent level of the row at the given index.}
|
||||
* @param rowIndex the index of the row to get its indent level
|
||||
*/
|
||||
public int getIndentLevel(int rowIndex);
|
||||
|
||||
/**
|
||||
* Adds a listener to the list that is notified each time a change
|
||||
* to the data model occurs.
|
||||
*
|
||||
* @param l the listener to be notified
|
||||
*/
|
||||
public void addListener(GTrableModeRowlListener l);
|
||||
|
||||
/**
|
||||
* Removes a listener from the list that is notified each time a
|
||||
* change to the model occurs.
|
||||
*
|
||||
* @param l the listener to remove
|
||||
*/
|
||||
public void removeListener(GTrableModeRowlListener l);
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
|
||||
/**
|
||||
* Icon used for the expand/collapse control in a {@link GTrable}
|
||||
*/
|
||||
public class OpenCloseIcon implements Icon {
|
||||
private int width;
|
||||
private int height;
|
||||
private int[] xPoints;
|
||||
private int[] yPoints;
|
||||
private Color color = Colors.FOREGROUND;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param isOpen if true, draws an icon that indicates the row is open, otherwise draws an
|
||||
* icon that the icon indicates the row is closed
|
||||
* @param width the width to draw the icon
|
||||
* @param height the height to draw the icon
|
||||
*/
|
||||
public OpenCloseIcon(boolean isOpen, int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
if (isOpen) {
|
||||
buildDownPointingTriangle();
|
||||
}
|
||||
else {
|
||||
buildRightPointingTriangle();
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
private void buildDownPointingTriangle() {
|
||||
int triangleWidth = 8;
|
||||
int triangleHeight = 4;
|
||||
|
||||
int startX = width / 2 - triangleWidth / 2;
|
||||
int endX = startX + triangleWidth;
|
||||
|
||||
int startY = height / 2 - triangleHeight / 2;
|
||||
int endY = startY + triangleHeight;
|
||||
|
||||
xPoints = new int[] { startX, endX, (startX + endX) / 2 };
|
||||
yPoints = new int[] { startY, startY, endY };
|
||||
|
||||
}
|
||||
|
||||
private void buildRightPointingTriangle() {
|
||||
int triangleWidth = 4;
|
||||
int triangleHeight = 8;
|
||||
|
||||
int startX = width / 2 - triangleWidth / 2;
|
||||
int endX = startX + triangleWidth;
|
||||
|
||||
int startY = height / 2 - triangleHeight / 2;
|
||||
int endY = startY + triangleHeight;
|
||||
|
||||
xPoints = new int[] { startX, endX, startX };
|
||||
yPoints = new int[] { startY, (startY + endY) / 2, endY };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
g.setColor(color);
|
||||
g.translate(x, y);
|
||||
Graphics2D graphics2D = (Graphics2D) g;
|
||||
graphics2D.drawPolygon(xPoints, yPoints, 3);
|
||||
graphics2D.fillPolygon(xPoints, yPoints, 3);
|
||||
g.translate(-x, -y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
/* ###
|
||||
* 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.trable;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGuiTest;
|
||||
import ghidra.util.datastruct.Range;
|
||||
|
||||
public class GTrableTest extends AbstractGuiTest {
|
||||
|
||||
private GTrableRowModel<TestDataRow> rowModel;
|
||||
private TestColumnModel columnModel;
|
||||
private GTrable<TestDataRow> gTrable;
|
||||
private JFrame frame;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
rowModel = createRowModel();
|
||||
columnModel = new TestColumnModel();
|
||||
gTrable = new GTrable<TestDataRow>(rowModel, columnModel);
|
||||
gTrable.setPreferredVisibleRowCount(3, 3);
|
||||
JScrollPane scroll = new JScrollPane(gTrable);
|
||||
frame = new JFrame("Test");
|
||||
frame.getContentPane().add(scroll);
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialState() {
|
||||
|
||||
//@formatter:off
|
||||
assertAllRows(
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
Range visibleRows = gTrable.getVisibleRows();
|
||||
assertEquals(0, visibleRows.min);
|
||||
assertEquals(2, visibleRows.max);
|
||||
|
||||
assertTrue(rowModel.getRow(0).isExpandable());
|
||||
assertFalse(rowModel.getRow(1).isExpandable());
|
||||
assertTrue(rowModel.getRow(2).isExpandable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandRow() {
|
||||
selectRow(1);
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
assertTrue(rowModel.getRow(0).isExpandable());
|
||||
expandRow(0);
|
||||
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
" a.1",
|
||||
" a.2"
|
||||
);
|
||||
assertAllRows(
|
||||
"a",
|
||||
" a.1",
|
||||
" a.2",
|
||||
" a.3",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
assertEquals(4, gTrable.getSelectedRow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollapseRow() {
|
||||
expandRow(0);
|
||||
selectRow(5);
|
||||
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
" a.1",
|
||||
" a.2"
|
||||
);
|
||||
assertAllRows(
|
||||
"a",
|
||||
" a.1",
|
||||
" a.2",
|
||||
" a.3",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
assertTrue(rowModel.isExpanded(0));
|
||||
|
||||
collapseRow(0);
|
||||
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
assertAllRows(
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
//@formatter:on
|
||||
assertEquals(2, gTrable.getSelectedRow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandAllRow() {
|
||||
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
expandAll();
|
||||
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
" a.1",
|
||||
" a.2"
|
||||
);
|
||||
assertAllRows(
|
||||
"a",
|
||||
" a.1",
|
||||
" a.2",
|
||||
" a.2.A",
|
||||
" a.2.B",
|
||||
" a.2.C",
|
||||
" a.3",
|
||||
"b",
|
||||
"c",
|
||||
" c.1",
|
||||
" c.2"
|
||||
);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScrollToSelectedRow() {
|
||||
expandAll();
|
||||
selectRow(5);
|
||||
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
"a",
|
||||
" a.1",
|
||||
" a.2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
scrollToSelectedRow();
|
||||
|
||||
//@formatter:off
|
||||
assertVisibleRows(
|
||||
" a.2.A",
|
||||
" a.2.B",
|
||||
" a.2.C"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
}
|
||||
|
||||
private void scrollToSelectedRow() {
|
||||
runSwing(() -> {
|
||||
gTrable.scrollToSelectedRow();
|
||||
});
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void expandRow(int row) {
|
||||
runSwing(() -> {
|
||||
gTrable.expandRow(row);
|
||||
});
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void collapseRow(int row) {
|
||||
runSwing(() -> {
|
||||
gTrable.collapseRow(row);
|
||||
});
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void expandAll() {
|
||||
runSwing(() -> {
|
||||
gTrable.expandAll();
|
||||
});
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void selectRow(int row) {
|
||||
runSwing(() -> {
|
||||
gTrable.setSelectedRow(row);
|
||||
});
|
||||
}
|
||||
|
||||
private void assertAllRows(String... expectedRows) {
|
||||
List<String> actualRows = getRowsAsText(0, rowModel.getRowCount() - 1);
|
||||
assertEquals(expectedRows.length, actualRows.size());
|
||||
List<String> expectedList = Arrays.asList(expectedRows);
|
||||
assertListEqualOrdered(expectedList, actualRows);
|
||||
}
|
||||
|
||||
private void assertVisibleRows(String... expectedRows) {
|
||||
Range visibleRows = gTrable.getVisibleRows();
|
||||
List<String> actualRows = getRowsAsText(visibleRows.min, visibleRows.max);
|
||||
assertEquals(expectedRows.length, actualRows.size());
|
||||
List<String> expectedList = Arrays.asList(expectedRows);
|
||||
assertListEqualOrdered(expectedList, actualRows);
|
||||
}
|
||||
|
||||
private List<String> getRowsAsText(int startRow, int endRow) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (int i = startRow; i <= endRow; i++) {
|
||||
TestDataRow row = rowModel.getRow(i);
|
||||
int indent = row.getIndentLevel();
|
||||
String name = row.getName();
|
||||
String indentation = StringUtils.repeat("\t", indent);
|
||||
list.add(indentation + name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private GTrableRowModel<TestDataRow> createRowModel() {
|
||||
TestDataRow a2A = new TestDataRow("a.2.A", 2, null);
|
||||
TestDataRow a2B = new TestDataRow("a.2.B", 2, null);
|
||||
TestDataRow a2C = new TestDataRow("a.2.C", 2, null);
|
||||
|
||||
TestDataRow a1 = new TestDataRow("a.1", 1, null);
|
||||
TestDataRow a2 = new TestDataRow("a.2", 1, List.of(a2A, a2B, a2C));
|
||||
TestDataRow a3 = new TestDataRow("a.3", 1, null);
|
||||
|
||||
TestDataRow c1 = new TestDataRow("c.1", 1, null);
|
||||
TestDataRow c2 = new TestDataRow("c.2", 1, null);
|
||||
|
||||
TestDataRow a = new TestDataRow("a", 0, List.of(a1, a2, a3));
|
||||
TestDataRow b = new TestDataRow("b", 0, null);
|
||||
TestDataRow c = new TestDataRow("c", 0, List.of(c1, c2));
|
||||
|
||||
return new DefaultGTrableRowModel<>(List.of(a, b, c));
|
||||
}
|
||||
|
||||
class TestDataRow extends GTrableRow<TestDataRow> {
|
||||
|
||||
private List<TestDataRow> children;
|
||||
private String name;
|
||||
|
||||
protected TestDataRow(String name, int indentLevel, List<TestDataRow> children) {
|
||||
super(indentLevel);
|
||||
this.name = name;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpandable() {
|
||||
return children != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TestDataRow> getChildRows() {
|
||||
return children;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class NameColumn extends GTrableColumn<TestDataRow, String> {
|
||||
@Override
|
||||
public String getValue(TestDataRow row) {
|
||||
return row.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestColumnModel extends GTrableColumnModel<TestDataRow> {
|
||||
|
||||
@Override
|
||||
protected void populateColumns(List<GTrableColumn<TestDataRow, ?>> columnList) {
|
||||
columnList.add(new NameColumn());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue