Merge remote-tracking branch 'origin/GP-2147_ghidragon_resizing_byteview_views--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-12-11 17:25:53 -05:00
commit 8bfcb02166
10 changed files with 714 additions and 658 deletions

View file

@ -277,11 +277,14 @@
</blockquote>
</blockquote>
<h2>Reorder Views</h2>
<h2>Reorder / Resize Views</h2>
<blockquote>
<p>The various views in the ByteViewer can be reordered by dragging
the view header to the left or right of its current position. The view
positions are swapped.<br></p>
<p>The width of each view can also be changed by dragging the separator
bars in the view header to the left or right. This will resize the view that is to
the left of the separator bar.<P>
</blockquote>
<h2><a name="WriteYourOwn"></a>Writing Your Own Format Plugin</h2>

View file

@ -48,6 +48,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
protected static final String X_OFFSET = "X Offset";
protected static final String Y_OFFSET = "Y Offset";
private static final String VIEW_NAMES = "View Names";
private static final String VIEW_WIDTHS = "View_Widths";
private static final String HEX_VIEW_GROUPSIZE = "Hex view groupsize";
private static final String BYTES_PER_LINE_NAME = "Bytes Per Line";
private static final String OFFSET_NAME = "Offset";
@ -69,7 +70,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
static final GColor CURRENT_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
//@formatter:on
static final String DEFAULT_INDEX_NAME = "Addresses";
static final String INDEX_COLUMN_NAME = "Addresses";
static final String SEPARATOR_COLOR_OPTION_NAME = "Block Separator Color";
static final String CHANGED_VALUE_COLOR_OPTION_NAME = "Changed Values Color";
@ -209,19 +210,17 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
help, "Color of cursor in the active view.");
opt.registerThemeColorBinding(CURSOR_NON_ACTIVE_COLOR_OPTION_NAME,
CURSOR_NON_ACTIVE_COLOR.getId(),
help, "Color of cursor in the non-active views.");
CURSOR_NON_ACTIVE_COLOR.getId(), help, "Color of cursor in the non-active views.");
opt.registerThemeColorBinding(CURSOR_NOT_FOCUSED_COLOR_OPTION_NAME,
CURSOR_NOT_FOCUSED_COLOR.getId(),
help, "Color of cursor when the byteview does not have focus.");
CURSOR_NOT_FOCUSED_COLOR.getId(), help,
"Color of cursor when the byteview does not have focus.");
opt.registerThemeColorBinding(CURRENT_LINE_COLOR_OPTION_NAME,
GhidraOptions.DEFAULT_CURSOR_LINE_COLOR.getId(), help,
"Color of the line containing the cursor");
opt.registerThemeFontBinding(OPTION_FONT, DEFAULT_FONT_ID, help,
"Font used in the views.");
opt.registerThemeFontBinding(OPTION_FONT, DEFAULT_FONT_ID, help, "Font used in the views.");
opt.registerOption(OPTION_HIGHLIGHT_CURSOR_LINE, true, help,
"Toggles highlighting background color of line containing the cursor");
@ -332,11 +331,19 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
protected void writeConfigState(SaveState saveState) {
DataModelInfo info = panel.getDataModelInfo();
saveState.putStrings(VIEW_NAMES, info.getNames());
List<String> viewNames = panel.getViewNamesInDisplayOrder();
saveState.putStrings(VIEW_NAMES, viewNames.toArray(new String[viewNames.size()]));
saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize);
saveState.putInt(BYTES_PER_LINE_NAME, bytesPerLine);
saveState.putInt(OFFSET_NAME, offset);
SaveState columnState = new SaveState(VIEW_WIDTHS);
int indexWidth = panel.getViewWidth(INDEX_COLUMN_NAME);
columnState.putInt(INDEX_COLUMN_NAME, indexWidth);
for (String viewName : viewNames) {
int width = panel.getViewWidth(viewName);
columnState.putInt(viewName, width);
}
saveState.putSaveState(VIEW_WIDTHS, columnState);
}
protected void readConfigState(SaveState saveState) {
@ -346,6 +353,17 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
bytesPerLine = saveState.getInt(BYTES_PER_LINE_NAME, DEFAULT_BYTES_PER_LINE);
offset = saveState.getInt(OFFSET_NAME, 0);
panel.restoreConfigState(bytesPerLine, offset);
SaveState viewWidths = saveState.getSaveState(VIEW_WIDTHS);
if (viewWidths != null) {
String[] viewNames = viewWidths.getNames();
for (String viewName : viewNames) {
int width = viewWidths.getInt(viewName, 0);
if (width > 0) {
panel.setViewWidth(viewName, width);
}
}
}
}
/**
@ -422,9 +440,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
public Set<String> getCurrentViews() {
DataModelInfo info = panel.getDataModelInfo();
HashSet<String> currentViewNames = new HashSet<>(Arrays.asList(info.getNames()));
return currentViewNames;
return new HashSet<String>(panel.getViewNamesInDisplayOrder());
}
private void refreshView() {

View file

@ -1,204 +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 ghidra.app.plugin.core.byteviewer;
import java.awt.*;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.*;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.*;
import generic.theme.Gui;
import ghidra.util.table.GhidraTable;
/**
* JTableHeader that uses the default table column model to manage
* TableColumns. Sizes the column according to its corresponding viewer
* component. Allows columns to be moved.
*/
class ByteViewerHeader extends JTableHeader implements Scrollable {
private static final String FONT_ID = "font.byteviewer.header";
private TableColumnModel columnModel;
private Component container;
private int separatorWidth;
private HashMap<Component, TableColumn> components; // table of components that map to columns
/**
* Constructor
* @param container Container that will be used to calculate the
* preferred size
*/
ByteViewerHeader(Component container) {
super();
this.container = container;
components = new HashMap<Component, TableColumn>();
Gui.registerFont(this, FONT_ID);
setResizingAllowed(false);
table = new GhidraTable();
setTable(table);
table.setTableHeader(this);
columnModel = getColumnModel();
columnModel.setColumnMargin(0);
JSeparator s = new JSeparator(SwingConstants.VERTICAL);
separatorWidth = s.getPreferredSize().width;
}
/**
* Add a new column.
* @param name name that will be displayed in the column
* @param c corresponding viewer component
*/
public void addColumn(String name, Component c) {
TableColumn col = new TableColumn(components.size());
col.setHeaderValue(name);
// col.setMinWidth((2*margin) + name.length() * unitWidth);
col.setIdentifier(c);
columnModel.addColumn(col);
components.put(c, col);
resizeAndRepaint();
}
/**
* Remove a column.
* @param c component that corresponds to a column to be removed.
*/
public void removeColumn(Component c) {
TableColumn col = components.get(c);
if (col != null) {
columnModel.removeColumn(col);
components.remove(c);
setSize(getPreferredSize());
resizeAndRepaint();
}
}
/**
* Get the preferred size of the table header.
*/
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height += 4;
return d;
}
/**
* Set the name on the column.
* @param c component that maps to the column
* @param name name to set on the column header
*/
public void setColumnName(Component c, String name) {
TableColumn col = components.get(c);
if (col != null) {
col.setHeaderValue(name);
recomputeColumnHeaders();
resizeAndRepaint();
}
}
/**
* Add a column model listener.
*/
public void addColumnModelListener(TableColumnModelListener l) {
columnModel.addColumnModelListener(l);
}
/**
* Remove a column model listener.
*/
public void removeColumnModelListener(TableColumnModelListener l) {
columnModel.removeColumnModelListener(l);
}
@Override
public void paint(Graphics g) {
recomputeColumnHeaders();
super.paint(g);
}
// Scrollable interface methods
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 1;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 1;
}
/////////////////////////////////////////////////////////////////
// *** private methods ***
/////////////////////////////////////////////////////////////////
/**
* Recompute the width of the column headers based on the width
* of the corresponding component.
*/
private void recomputeColumnHeaders() {
Iterator<Component> iter = components.keySet().iterator();
while (iter.hasNext()) {
Component c = iter.next();
TableColumn col = components.get(c);
int width = c.getPreferredSize().width;
int index = columnModel.getColumnIndex(col.getIdentifier());
if (index == 0) {
width += separatorWidth / 2;
}
else if (index == components.size() - 1) {
width += separatorWidth / 2;
}
else {
width += separatorWidth;
}
col.setMinWidth(width);
col.setMaxWidth(width);
col.setPreferredWidth(width);
}
}
}

View file

@ -0,0 +1,226 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import java.awt.BorderLayout;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.Layout;
import docking.widgets.indexedscrollpane.*;
import generic.theme.GColor;
import generic.theme.Gui;
import ghidra.app.plugin.core.format.DataFormatModel;
/**
* Main ByteViewer component that is scrolled in a {@link IndexedScrollPane}. Similar to the view
* component in a {@link JScrollPane}. This component manages two or more {@link FieldPanel}
* components. It always contains the "index" field panel which displays the addresses of the
* values being displayed. Then it has one or more data FieldPanels; one for each format being
* displayed (e.g., hex, octal, binary).
* <P>
* There is also header component that displays the name of each column and allows the user to
* reorder and resize the views. This class uses an {@link InteractivePanelManager} to handle
* the reordering and resizing of the views in coordination with the header component. It is the
* client's responsibility to get the header component and install it into the IndexedScrollPane.
*/
class ByteViewerIndexedView extends JPanel implements IndexedScrollable, IndexScrollListener {
private static final String HEADER_FONT_ID = "font.byteviewer.header";
private FieldPanel indexPanel;
private List<FieldPanel> allPanels = new ArrayList<>();
private boolean processingIndexRangeChanged;
private InteractivePanelManager panelManager;
ByteViewerIndexedView(FieldPanel indexPanel) {
super(new BorderLayout());
this.indexPanel = indexPanel;
allPanels.add(indexPanel);
panelManager = new InteractivePanelManager();
panelManager.setHeaderFont(Gui.getFont(HEADER_FONT_ID));
indexPanel.addIndexScrollListener(this);
panelManager.addComponent(ByteViewerComponentProvider.INDEX_COLUMN_NAME, indexPanel);
JComponent mainPanel = panelManager.getMainPanel();
add(mainPanel, BorderLayout.CENTER);
mainPanel.setBackground(new GColor("color.bg.byteviewer"));
addMouseWheelListener(e -> {
// this lets us scroll the byte viewer when the user is not over any panel, but still
// over the view
Layout firstLayout = indexPanel.getLayoutModel().getLayout(BigInteger.ZERO);
int layoutScrollHt = firstLayout != null //
? firstLayout.getScrollableUnitIncrement(0, 1)
: 0;
double wheelRotation = e.getPreciseWheelRotation();
int scrollAmount =
(int) (wheelRotation * layoutScrollHt * FieldPanel.MOUSEWHEEL_LINES_TO_SCROLL);
indexPanel.scrollView(scrollAmount);
e.consume();
});
}
void addView(String viewName, ByteViewerComponent c) {
panelManager.addComponent(viewName, c);
allPanels.add(c);
c.addIndexScrollListener(this);
}
void removeView(ByteViewerComponent c) {
panelManager.removeComponent(c);
allPanels.remove(c);
c.removeIndexScrollListener(this);
}
JComponent getColumnHeader() {
return panelManager.getColumnHeader();
}
public List<String> getViewNamesInDisplayOrder() {
List<String> viewNames = new ArrayList<>();
List<JComponent> components = panelManager.getComponents();
for (JComponent component : components) {
if (component == indexPanel) {
continue;
}
if (component instanceof ByteViewerComponent byteViewerComponent) {
DataFormatModel model = byteViewerComponent.getDataModel();
viewNames.add(model.getName());
}
}
return viewNames;
}
void resetViewWidthToDefaults() {
List<String> viewNames = getViewNamesInDisplayOrder();
for (String viewName : viewNames) {
panelManager.resetColumnWidthToPreferredWidth(viewName);
}
}
void setIndexName(String indexName) {
panelManager.setName(indexPanel, indexName);
}
int getViewWidth(String viewName) {
return panelManager.getColumnWidth(viewName);
}
void setColumnWidth(String viewName, int width) {
panelManager.setColumnWidth(viewName, width);
}
@Override
public void indexRangeChanged(BigInteger startIndex, BigInteger endIndex, int yStart,
int yEnd) {
if (processingIndexRangeChanged) {
return;
}
processingIndexRangeChanged = true;
try {
// need to sync up the view position of all views when any view is scrolled
for (FieldPanel fieldPanel : allPanels) {
fieldPanel.showIndex(startIndex, yStart);
}
}
finally {
processingIndexRangeChanged = false;
}
}
@Override
public void addIndexScrollListener(IndexScrollListener listener) {
indexPanel.addIndexScrollListener(listener);
}
@Override
public int getHeight(BigInteger index) {
return indexPanel.getHeight(index);
}
@Override
public BigInteger getIndexAfter(BigInteger index) {
return indexPanel.getIndexAfter(index);
}
@Override
public BigInteger getIndexBefore(BigInteger index) {
return indexPanel.getIndexBefore(index);
}
@Override
public BigInteger getIndexCount() {
return indexPanel.getIndexCount();
}
@Override
public boolean isUniformIndex() {
return true;
}
@Override
public void removeIndexScrollListener(IndexScrollListener listener) {
indexPanel.removeIndexScrollListener(listener);
}
@Override
public void scrollLineDown() {
indexPanel.scrollLineDown();
}
@Override
public void scrollLineUp() {
indexPanel.scrollLineUp();
}
@Override
public void scrollPageDown() {
indexPanel.scrollPageDown();
}
@Override
public void scrollPageUp() {
indexPanel.scrollPageUp();
}
@Override
public void showIndex(BigInteger index, int verticalOffset) {
indexPanel.showIndex(index, verticalOffset);
}
@Override
public void indexModelChanged() {
// handled by indexPanel
}
@Override
public void indexModelDataChanged(BigInteger start, BigInteger end) {
// handled by indexPanel
}
@Override
public void mouseWheelMoved(double preciseWheelRotation, boolean isHorizontal) {
indexPanel.mouseWheelMoved(preciseWheelRotation, isHorizontal);
}
}

View file

@ -21,17 +21,15 @@ import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.EmptyTextField;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.*;
import docking.widgets.fieldpanel.support.*;
import docking.widgets.indexedscrollpane.*;
import docking.widgets.indexedscrollpane.IndexedScrollPane;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import generic.theme.GColor;
import generic.theme.Gui;
import ghidra.app.plugin.core.format.*;
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
@ -40,7 +38,6 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.HorizontalLayout;
import ghidra.util.layout.PairLayout;
import help.Help;
import help.HelpService;
@ -49,10 +46,8 @@ import help.HelpService;
* Top level component that has a scrolled pane for the panel of components that show the
* view for each format.
*/
public class ByteViewerPanel extends JPanel
implements TableColumnModelListener, LayoutModel, LayoutListener {
public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListener {
private static final String FONT_STATUS_ID = "font.byteviewer.status";
// private ByteViewerPlugin plugin;
private List<ByteViewerComponent> viewList; // list of field viewers
private FieldPanel indexPanel; // panel for showing indexes
private IndexFieldFactory indexFactory;
@ -61,12 +56,9 @@ public class ByteViewerPanel extends JPanel
private JLabel offsetField;
private JLabel insertionField;
private JPanel statusPanel;
private CompositePanel compPanel;
private int fontHeight;
private FontMetrics fm;
private FontMetrics fontMetrics;
private int bytesPerLine;
private IndexedScrollPane scrollp;
private ByteViewerHeader columnHeader;
private ByteBlockSet blockSet;
private ByteBlock[] blocks;
private IndexMap indexMap; // maps indexes to the correct block and offset
@ -78,12 +70,12 @@ public class ByteViewerPanel extends JPanel
private Color highlightColor;
private int highlightButton;
private List<LayoutModelListener> layoutListeners = new ArrayList<>(1);
private int indexPanelWidth;
private boolean addingView; // don't respond to cursor location
// changes while this flag is true
private final ByteViewerComponentProvider provider;
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
private ByteViewerIndexedView indexedView;
protected ByteViewerPanel(ByteViewerComponentProvider provider) {
super();
@ -136,45 +128,6 @@ public class ByteViewerPanel extends JPanel
super.paintComponent(g);
}
// TableColumnModelListener interface methods
@Override
public void columnAdded(TableColumnModelEvent e) {
// no-op
}
@Override
public void columnMarginChanged(ChangeEvent e) {
// no-op
}
/**
* Interface method called when the columns move.
*/
@Override
public void columnMoved(TableColumnModelEvent e) {
int fromIndex = e.getFromIndex();
int toIndex = e.getToIndex();
if (fromIndex == toIndex) {
return;
}
compPanel.swapView(fromIndex, toIndex);
invalidate();
validate();
repaint();
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
// no-op
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
// no-op
}
//////////////////////////////////////////////////////////////////////////
// ** package-level methods **
//////////////////////////////////////////////////////////////////////////
@ -252,17 +205,14 @@ public class ByteViewerPanel extends JPanel
endField.setText(lastBlock
.getLocationRepresentation(lastBlock.getLength().subtract(BigInteger.ONE)));
indexPanelWidth = getIndexPanelWidth(blocks);
int center = indexPanelWidth / 2;
int startx = center - getMaxIndexSize() / 2;
indexFactory.setStartX(startx);
clearSelection();
}
}
if (indexMap == null) {
indexMap = new IndexMap();
}
indexFactory.setIndexMap(indexMap, indexPanelWidth);
indexFactory.setIndexMap(indexMap);
indexFactory.setSize(getIndexSizeInChars());
// Do the following loop twice - once with update off and then with update on.
// need to do this because all the byte view components must have their models
@ -278,7 +228,7 @@ public class ByteViewerPanel extends JPanel
c.setIndexMap(indexMap);
}
if (blocks != null && blocks.length > 0) {
columnHeader.setColumnName(indexPanel, blocks[0].getIndexName());
indexedView.setIndexName(blocks[0].getIndexName());
}
indexPanel.dataChanged(BigInteger.ZERO, indexMap.getNumIndexes());
indexSetChanged();
@ -366,7 +316,8 @@ public class ByteViewerPanel extends JPanel
}
protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) {
return new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine, fm);
return new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine,
fontMetrics);
}
/**
@ -399,10 +350,7 @@ public class ByteViewerPanel extends JPanel
c.setHighlightButton(highlightButton);
viewList.add(c);
c.setSize(c.getPreferredSize());
compPanel.addByteViewerComponent(c);
// tell column header it needs to grow
columnHeader.addColumn(viewName, c);
indexedView.addView(viewName, c);
c.addListeners();
if (viewList.size() == 1) {
@ -441,8 +389,7 @@ public class ByteViewerPanel extends JPanel
void removeView(ByteViewerComponent comp) {
viewList.remove(comp);
compPanel.removeByteViewerComponent(comp);
columnHeader.removeColumn(comp);
indexedView.removeView(comp);
if (currentView == comp) {
currentView = null;
@ -485,10 +432,6 @@ public class ByteViewerPanel extends JPanel
ByteViewerComponent c = viewList.get(i);
c.refreshView();
}
// PluginEvent lastSelectionEvent = plugin.getLastSelectionEvent();
// if (lastSelectionEvent != null) {
// plugin.firePluginEvent(lastSelectionEvent);
// }
}
int getNumberOfViews() {
@ -509,9 +452,11 @@ public class ByteViewerPanel extends JPanel
this.bytesPerLine = bytesPerLine;
updateIndexMap();
}
// reset view column widths to preferred width for new bytesPerline
indexedView.resetViewWidthToDefaults();
// force everything to get validated, or else the
// header columns do not get repainted properly...
invalidate();
validate();
repaint();
@ -615,36 +560,11 @@ public class ByteViewerPanel extends JPanel
}
FontMetrics getCurrentFontMetrics() {
return fm;
return fontMetrics;
}
/**
* Return array of names of views in the order that they appear in the panel. The name array
* includes an entry for the index panel.
* @return a DataModelInfo object that describes the current views
*/
DataModelInfo getDataModelInfo() {
DataModelInfo info = new DataModelInfo(viewList.size());
Component[] c = compPanel.getComponents();
int index = 0;
for (Component element : c) {
if (element instanceof JSeparator) {
continue;
}
if (element == indexPanel) {
// don't put the index panel into the data model info, as it is not configurable
continue;
}
else if (element instanceof ByteViewerComponent) {
DataFormatModel model = ((ByteViewerComponent) element).getDataModel();
String name = model.getName();
int groupSize = model.getGroupSize();
info.set(index, name, groupSize);
++index;
}
}
return info;
List<String> getViewNamesInDisplayOrder() {
return indexedView.getViewNamesInDisplayOrder();
}
/**
@ -679,11 +599,11 @@ public class ByteViewerPanel extends JPanel
/**
* Restore the configuration of the plugin.
*
* @param fontMetrics font metrics
* @param metrics font metrics
* @param newEditColor color for showing edits
*/
void restoreConfigState(FontMetrics fontMetrics, Color newEditColor) {
setFontMetrics(fontMetrics);
void restoreConfigState(FontMetrics metrics, Color newEditColor) {
setFontMetrics(metrics);
setEditColor(newEditColor);
}
@ -704,21 +624,13 @@ public class ByteViewerPanel extends JPanel
}
void setFontMetrics(FontMetrics fm) {
this.fm = fm;
this.fontMetrics = fm;
for (int i = 0; i < viewList.size(); i++) {
ByteViewerComponent c = viewList.get(i);
c.setFontMetrics(fm);
}
indexFactory = new IndexFieldFactory(fm);
int charWidth = fm.charWidth('W');
indexFactory.setStartX(charWidth);
indexPanelWidth =
ByteViewerComponentProvider.DEFAULT_NUMBER_OF_CHARS * charWidth + (2 * charWidth);
if (blocks != null) {
indexPanelWidth = getIndexPanelWidth(blocks);
}
indexFactory.setIndexMap(indexMap, indexPanelWidth);
indexFactory.setSize(getIndexSizeInChars());
indexPanel.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
}
@ -731,7 +643,7 @@ public class ByteViewerPanel extends JPanel
}
protected FontMetrics getFontMetrics() {
return fm;
return fontMetrics;
}
protected int getBytesPerLine() {
@ -745,13 +657,11 @@ public class ByteViewerPanel extends JPanel
setLayout(new BorderLayout(10, 0));
columnHeader = new ByteViewerHeader(this);
fm = getFontMetrics(Gui.getFont(ByteViewerComponentProvider.DEFAULT_FONT_ID));
fontHeight = fm.getHeight();
fontMetrics = getFontMetrics(Gui.getFont(ByteViewerComponentProvider.DEFAULT_FONT_ID));
fontHeight = fontMetrics.getHeight();
// for the index/address column
indexFactory = new IndexFieldFactory(fm);
indexFactory = new IndexFieldFactory(fontMetrics);
indexPanel = new FieldPanel(this, "Byte Viewer");
indexPanel.enableSelection(false);
@ -759,21 +669,13 @@ public class ByteViewerPanel extends JPanel
indexPanel.setFocusable(false);
indexPanel.addLayoutListener(this);
compPanel = new CompositePanel(indexPanel);
scrollp = new IndexedScrollPane(compPanel);
scrollp.setWheelScrollingEnabled(false);
columnHeader = new ByteViewerHeader(this);
columnHeader.addColumnModelListener(this);
columnHeader.addColumn(ByteViewerComponentProvider.DEFAULT_INDEX_NAME, indexPanel);
scrollp.setColumnHeaderComp(columnHeader);
compPanel.setBackground(new GColor("color.bg.byteviewer"));
indexedView = new ByteViewerIndexedView(indexPanel);
IndexedScrollPane indexedScrollPane = new IndexedScrollPane(indexedView);
indexedScrollPane.setWheelScrollingEnabled(false);
indexedScrollPane.setColumnHeaderComp(indexedView.getColumnHeader());
statusPanel = createStatusPanel();
add(scrollp, BorderLayout.CENTER);
add(indexedScrollPane, BorderLayout.CENTER);
add(statusPanel, BorderLayout.SOUTH);
HelpService help = Help.getHelpService();
@ -852,8 +754,7 @@ public class ByteViewerPanel extends JPanel
}
indexMap = new IndexMap(blockSet, bytesPerLine, blockOffset);
indexPanelWidth = getIndexPanelWidth(blocks);
indexFactory.setIndexMap(indexMap, indexPanelWidth);
indexFactory.setIndexMap(indexMap);
ByteBlock block = null;
BigInteger offset = BigInteger.ZERO;
if (info != null) {
@ -887,7 +788,7 @@ public class ByteViewerPanel extends JPanel
@Override
public Dimension getPreferredViewSize() {
return new Dimension(indexPanelWidth, 500);
return new Dimension(500, 500);
}
@Override
@ -926,26 +827,16 @@ public class ByteViewerPanel extends JPanel
}
}
private int getIndexPanelWidth(ByteBlock[] blocks1) {
FontMetrics headerFm = columnHeader.getFontMetrics(columnHeader.getFont());
String indexName = ByteViewerComponentProvider.DEFAULT_INDEX_NAME;
if (blocks1.length > 0) {
indexName = blocks1[0].getIndexName();
private int getIndexSizeInChars() {
// set the field size at least the size of the column header name
int minChars = ByteViewerComponentProvider.INDEX_COLUMN_NAME.length();
if (blocks != null) {
for (ByteBlock element : blocks) {
int charCount = element.getMaxLocationRepresentationSize();
minChars = Math.max(minChars, charCount);
}
}
int nameWidth = headerFm.stringWidth(indexName);
int charWidth = fm.charWidth('W');
return Math.max(nameWidth, getMaxIndexSize() + 2 * charWidth);
}
private int getMaxIndexSize() {
int maxWidth = 0;
int charWidth = fm.charWidth('W');
for (ByteBlock element : blocks) {
int width = element.getMaxLocationRepresentationSize() * charWidth;
maxWidth = Math.max(maxWidth, width);
}
return maxWidth;
return minChars;
}
@Override
@ -1024,175 +915,12 @@ public class ByteViewerPanel extends JPanel
public void removeDisplayListener(AddressSetDisplayListener listener) {
displayListeners.add(listener);
}
}
class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollListener {
FieldPanel indexPanel;
BoundedRangeModel verticalScrollBarModel;
BoundedRangeModel horizontalScrollBarModel;
List<ByteViewerComponent> viewList = new ArrayList<>();
List<FieldPanel> allPanels = new ArrayList<>();
private boolean processingIndexRangeChanged;
CompositePanel(FieldPanel indexPanel) {
super(new HorizontalLayout(0));
this.indexPanel = indexPanel;
indexPanel.addIndexScrollListener(this);
addMouseWheelListener(e -> {
// this lets us scroll the byte viewer when the user is not over any panel, but still over the view
Layout firstLayout = indexPanel.getLayoutModel().getLayout(BigInteger.ZERO);
int layoutScrollHt = firstLayout != null //
? firstLayout.getScrollableUnitIncrement(0, 1)
: 0;
double wheelRotation = e.getPreciseWheelRotation();
int scrollAmount =
(int) (wheelRotation * layoutScrollHt * FieldPanel.MOUSEWHEEL_LINES_TO_SCROLL);
indexPanel.scrollView(scrollAmount);
e.consume();
});
allPanels.add(indexPanel);
rebuildPanels();
}
public void swapView(int fromIndex, int toIndex) {
FieldPanel from = allPanels.get(fromIndex);
FieldPanel to = allPanels.get(toIndex);
allPanels.set(fromIndex, to);
allPanels.set(toIndex, from);
rebuildPanels();
}
void addByteViewerComponent(ByteViewerComponent comp) {
comp.addIndexScrollListener(this);
viewList.add(comp);
allPanels.add(comp);
rebuildPanels();
}
void removeByteViewerComponent(ByteViewerComponent comp) {
comp.removeIndexScrollListener(this);
viewList.remove(comp);
allPanels.remove(comp);
rebuildPanels();
}
private void rebuildPanels() {
removeAll();
int count = 0;
for (FieldPanel panel : allPanels) {
if (count++ != 0) {
super.add(new JSeparator(SwingConstants.VERTICAL));
}
super.add(panel);
}
// setSize(getPreferredSize());
invalidate();
}
@Override
public Component add(Component comp) {
throw new UnsupportedOperationException("External call to add(Component) not allowed");
}
@Override
public void remove(Component comp) {
throw new UnsupportedOperationException("External call to remove(Component) not allowed");
}
@Override
public void addIndexScrollListener(IndexScrollListener listener) {
indexPanel.addIndexScrollListener(listener);
}
@Override
public int getHeight(BigInteger index) {
return indexPanel.getHeight(index);
}
@Override
public BigInteger getIndexAfter(BigInteger index) {
return indexPanel.getIndexAfter(index);
}
@Override
public BigInteger getIndexBefore(BigInteger index) {
return indexPanel.getIndexBefore(index);
}
@Override
public BigInteger getIndexCount() {
return indexPanel.getIndexCount();
}
@Override
public boolean isUniformIndex() {
return true;
}
@Override
public void removeIndexScrollListener(IndexScrollListener listener) {
indexPanel.removeIndexScrollListener(listener);
}
@Override
public void scrollLineDown() {
indexPanel.scrollLineDown();
}
@Override
public void scrollLineUp() {
indexPanel.scrollLineUp();
}
@Override
public void scrollPageDown() {
indexPanel.scrollPageDown();
}
@Override
public void scrollPageUp() {
indexPanel.scrollPageUp();
}
@Override
public void showIndex(BigInteger index, int verticalOffset) {
indexPanel.showIndex(index, verticalOffset);
}
@Override
public void indexModelChanged() {
// handled by indexPanel
}
@Override
public void indexModelDataChanged(BigInteger start, BigInteger end) {
// handled by indexPanel
}
@Override
public void mouseWheelMoved(double preciseWheelRotation, boolean isHorizontal) {
indexPanel.mouseWheelMoved(preciseWheelRotation, isHorizontal);
}
@Override
public void indexRangeChanged(BigInteger startIndex, BigInteger endIndex, int yStart,
int yEnd) {
if (processingIndexRangeChanged) {
return;
}
processingIndexRangeChanged = true;
try {
// need to update all views
for (FieldPanel fieldPanel : allPanels) {
fieldPanel.showIndex(startIndex, yStart);
}
}
finally {
processingIndexRangeChanged = false;
}
}
public int getViewWidth(String viewName) {
return indexedView.getViewWidth(viewName);
}
public void setViewWidth(String viewName, int width) {
indexedView.setColumnWidth(viewName, width);
}
}

View file

@ -1,53 +0,0 @@
/* ###
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import java.io.Serializable;
/**
* Class used only during serialization to keep the model name and the
* group size of the model.
*/
class DataModelInfo implements Serializable {
private String[] names;
private int[] groupSizes;
/**
* Constructor
* @param id name of the model
* @param groupSize group size for the model
*/
DataModelInfo(int size) {
names = new String[size];
groupSizes = new int[size];
}
void set(int index, String name, int groupSize) {
names[index] = name;
groupSizes[index] = groupSize;
}
/**
* Get the name of the model.
*
* @return String
*/
String[] getNames() {
return names;
}
}

View file

@ -19,10 +19,12 @@ import java.awt.Color;
import java.awt.FontMetrics;
import java.math.BigInteger;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.field.SimpleTextField;
import docking.widgets.fieldpanel.support.Highlight;
import docking.widgets.fieldpanel.support.FieldHighlightFactory;
import docking.widgets.fieldpanel.support.Highlight;
import ghidra.app.plugin.core.format.ByteBlockInfo;
/**
@ -30,7 +32,7 @@ import ghidra.app.plugin.core.format.ByteBlockInfo;
*/
class IndexFieldFactory {
private FontMetrics fm;
private FontMetrics metrics;
private int width;
private IndexMap indexMap;
private int charWidth;
@ -41,21 +43,21 @@ class IndexFieldFactory {
/**
* Constructor
* @param fieldModel field model
* @param metrics the FontMetrics this field should use to do size computations
*/
IndexFieldFactory(FontMetrics fm) {
this.fm = fm;
IndexFieldFactory(FontMetrics metrics) {
this.metrics = metrics;
charWidth = fm.charWidth('W');
charWidth = metrics.charWidth('W');
width = ByteViewerComponentProvider.DEFAULT_NUMBER_OF_CHARS * charWidth;
missingValueColor = ByteViewerComponentProvider.SEPARATOR_COLOR;
startX = charWidth;
}
/**
* Gets a Field object for the given index.
* This method is called for n times where n is the number of fields
* in the model. In this case, we only have one field, so it gets
* called once for each index.
* @param index the index to get a Field for
* @return The Field for the given index
*/
public Field getField(BigInteger index) {
@ -74,8 +76,8 @@ class IndexFieldFactory {
}
if (info == null) {
if (indexMap.isBlockSeparatorIndex(index)) {
SimpleTextField sf =
new SimpleTextField(noValueStr, fm, startX, width, false, highlightFactory);
SimpleTextField sf = new SimpleTextField(noValueStr, metrics, startX, width,
false, highlightFactory);
sf.setForeground(missingValueColor);
return sf;
@ -87,15 +89,16 @@ class IndexFieldFactory {
if (locRep == null) {
return null;
}
return new SimpleTextField(locRep, fm, startX, width, false, highlightFactory);
return new SimpleTextField(locRep, metrics, startX, width, false, highlightFactory);
}
public FontMetrics getMetrics() {
return fm;
return metrics;
}
/**
* Sets the starting x position for the fields generated by this factory
* @param x The
*/
public void setStartX(int x) {
startX = x;
@ -103,6 +106,7 @@ class IndexFieldFactory {
/**
* Returns the starting x position for the fields generated by this factory.
* @return the starting x position for the fields generated by this factory.
*/
public int getStartX() {
return startX;
@ -110,26 +114,30 @@ class IndexFieldFactory {
/**
* Returns the width of the fields associated with this Factory.
* @return the width of the fields associated with this Factory
*/
public int getWidth() {
return width;
}
/**
* Set byte blocks.
* @param blocks
* Sets the indexMap which is the data source for the generated fields.
* @param indexMap indexMap which produces the index string for specific indexes
*/
void setIndexMap(IndexMap indexMap, int maxWidth) {
void setIndexMap(IndexMap indexMap) {
this.indexMap = indexMap;
width = maxWidth - (2 * charWidth);
int nchars = width / charWidth;
if (indexMap != null) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < nchars; i++) {
sb.append(".");
}
noValueStr = sb.toString();
}
}
/**
* Sets the size of this field based on the number of chars to display
* @param charCount the number of chars to
*/
void setSize(int charCount) {
width = charCount * charWidth;
// add in space for margins
width += 2 * charWidth;
noValueStr = StringUtils.repeat('.', charCount);
}
void setMissingValueColor(Color c) {

View file

@ -0,0 +1,304 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.byteviewer;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
/**
* Creates a container for holding multiple horizontally aligned components and provides
* a {@link JTableHeader} that can be used to interactively reorder and resize the
* components that are managed by this container.
*/
public class InteractivePanelManager {
private JPanel mainPanel;
private JTableHeader header;
private TableColumnModel columnModel;
private int separatorWidth;
public InteractivePanelManager() {
JTable table = new JTable();
header = new JTableHeader();
table.setTableHeader(header);
columnModel = header.getColumnModel();
separatorWidth = (new JSeparator(SwingConstants.VERTICAL)).getPreferredSize().width;
mainPanel = new JPanel(new HeaderLayoutManager());
columnModel.addColumnModelListener(new PanelManagerColumnModelListener());
}
/**
* Sets the font for the header component.
* @param font the font to be used to display view names in the header
*/
public void setHeaderFont(Font font) {
header.setFont(font);
}
/**
* Adds a component and it's name to the set of managed components.
* @param name the name to display in its column header.
* @param component the component to add
*/
public void addComponent(String name, JComponent component) {
TableColumn column = new TableColumn();
column.addPropertyChangeListener(e -> columnPropertyChanged(e));
column.setHeaderValue(new ComponentData(name, component));
column.setPreferredWidth(component.getPreferredSize().width + separatorWidth);
column.setWidth(component.getPreferredSize().width);
columnModel.addColumn(column);
mainPanel.add(component);
mainPanel.add(new JSeparator(SwingConstants.VERTICAL));
}
/**
* Returns a list of the components being managed.
* @return a list of the components being managed.
*/
public List<JComponent> getComponents() {
List<JComponent> components = new ArrayList<>();
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
components.add(((ComponentData) column.getHeaderValue()).component);
}
return components;
}
/**
* Sets the name for a component
* @param component the component for which to change its associated name
* @param newName the new name for the component
*/
public void setName(JComponent component, String newName) {
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
ComponentData componentData = (ComponentData) column.getHeaderValue();
if (componentData.component() == component) {
column.setHeaderValue(new ComponentData(newName, component));
break;
}
}
}
/**
* Removes the given component from being managed.
* @param component the component to be removed
*/
public void removeComponent(JComponent component) {
int componentCount = mainPanel.getComponentCount();
for (int i = 0; i < componentCount; i++) {
if (mainPanel.getComponent(i) == component) {
mainPanel.remove(i);
mainPanel.remove(i); // also remove the JSeparator the follows it
break;
}
}
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
if (((ComponentData) column.getHeaderValue()).component == component) {
columnModel.removeColumn(column);
break;
}
}
}
/**
* Returns the mainPanel containing the horizontally laid components.
* @return the mainPanel containing the horizontally laid components
*/
public JComponent getMainPanel() {
return mainPanel;
}
/**
* Returns the {@link JTableHeader} component that can be used to reorder and resize
* the components
* @return the JTableHeader for managing the order and size of the components
*/
public JComponent getColumnHeader() {
return header;
}
/**
* Returns the current width of the component with the given name.
* @param viewName the name of the component for which to get its width
* @return the current width of the component with the given name
*/
public int getColumnWidth(String viewName) {
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
ComponentData data = (ComponentData) column.getHeaderValue();
if (data.name().equals(viewName)) {
return column.getWidth();
}
}
return 0;
}
/**
* Sets the width of the named component.
* @param viewName the name of the component to resize
* @param width the new width for the given component
*/
public void setColumnWidth(String viewName, int width) {
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
ComponentData data = (ComponentData) column.getHeaderValue();
if (data.name().equals(viewName)) {
column.setWidth(width);
return;
}
}
}
/**
* Resets the named component back to its preferred size.
* @param viewName the name of the component to restore to its preferred size
*/
public void resetColumnWidthToPreferredWidth(String viewName) {
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
ComponentData data = (ComponentData) column.getHeaderValue();
if (data.name().equals(viewName)) {
column.setWidth(data.component().getPreferredSize().width);
return;
}
}
}
private void columnPropertyChanged(PropertyChangeEvent e) {
if ("width".equals(e.getPropertyName())) {
TableColumn column = (TableColumn) e.getSource();
column.setPreferredWidth((int) e.getNewValue());
update();
}
}
private void update() {
mainPanel.invalidate();
Container parent = mainPanel.getParent();
if (parent != null) {
parent.validate();
parent.repaint();
}
}
private class HeaderLayoutManager implements LayoutManager {
@Override
public void addLayoutComponent(String name, Component comp) {
// don't care
}
@Override
public void removeLayoutComponent(Component comp) {
// don't care
}
@Override
public Dimension preferredLayoutSize(Container parent) {
Insets insets = parent.getInsets();
int n = parent.getComponentCount();
int height = 0;
int width = columnModel.getTotalColumnWidth();
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
Dimension d = c.getPreferredSize();
height = Math.max(height, d.height);
}
return new Dimension(width + insets.left + insets.right,
height + insets.top + insets.bottom);
}
@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
@Override
public void layoutContainer(Container parent) {
int n = parent.getComponentCount();
Dimension d = parent.getSize();
Insets insets = parent.getInsets();
int height = d.height - insets.top - insets.bottom;
int x = insets.left;
int y = insets.top;
for (int i = 0; i < n; i++) {
// even components are the actual managed components, odd components are JSeparators
if (i % 2 == 0) {
TableColumn column = columnModel.getColumn(i / 2);
ComponentData componentData = (ComponentData) column.getHeaderValue();
JComponent c = componentData.component();
int width = column.getWidth() - separatorWidth;
c.setBounds(x, y, width, height);
x += width;
}
else {
Component c = parent.getComponent(i);
d = c.getPreferredSize();
c.setBounds(x, y, d.width, height);
x += d.width;
}
}
}
}
private class PanelManagerColumnModelListener implements TableColumnModelListener {
@Override
public void columnAdded(TableColumnModelEvent e) {
update();
}
@Override
public void columnRemoved(TableColumnModelEvent e) {
update();
}
@Override
public void columnMoved(TableColumnModelEvent e) {
update();
}
@Override
public void columnMarginChanged(ChangeEvent e) {
// ignore
}
@Override
public void columnSelectionChanged(ListSelectionEvent e) {
// ignore
}
}
record ComponentData(String name, JComponent component) {
public String toString() {
return name;
}
}
}

View file

@ -24,7 +24,8 @@ import java.util.List;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import org.junit.*;
@ -167,10 +168,9 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals(3, panel.getNumberOfViews());
DataModelInfo info = panel.getDataModelInfo();
String[] names = info.getNames();
assertEquals(3, names.length);
Set<String> viewNames = new HashSet<>(Arrays.asList(names));
List<String> names = panel.getViewNamesInDisplayOrder();
assertEquals(3, names.size());
Set<String> viewNames = new HashSet<>(names);
assertTrue(viewNames.contains("Hex"));
assertTrue(viewNames.contains("Octal"));
assertTrue(viewNames.contains("Ascii"));
@ -196,10 +196,9 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
assertEquals(1, panel.getNumberOfViews());
info = panel.getDataModelInfo();
names = info.getNames();
assertEquals(1, names.length);
assertEquals("Octal", names[0]);
names = panel.getViewNamesInDisplayOrder();
assertEquals(1, names.size());
assertEquals("Octal", names.get(0));
}
@ -618,56 +617,73 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
@Test
public void testReorderViews() throws Exception {
loadViews("Ascii", "Octal");
final ByteViewerHeader columnHeader =
(ByteViewerHeader) findContainer(panel, ByteViewerHeader.class);
List<String> names = panel.getViewNamesInDisplayOrder();
assertEquals("Hex", names.get(0));
assertEquals("Octal", names.get(1));
assertEquals("Ascii", names.get(2));
final JTableHeader columnHeader = (JTableHeader) findContainer(panel, JTableHeader.class);
// move column 3 to 2
runSwing(() -> {
TableColumnModelEvent ev =
new TableColumnModelEvent(columnHeader.getColumnModel(), 3, 2);
panel.columnMoved(ev);
TableColumnModel columnModel = columnHeader.getColumnModel();
columnModel.moveColumn(3, 2);
});
String[] names = panel.getDataModelInfo().getNames();
names = panel.getViewNamesInDisplayOrder();
assertEquals("Hex", names.get(0));
assertEquals("Ascii", names.get(1));
assertEquals("Octal", names.get(2));
// move column 1 to 0
// move column 2 to 1
runSwing(() -> {
TableColumnModelEvent ev =
new TableColumnModelEvent(columnHeader.getColumnModel(), 2, 1);
panel.columnMoved(ev);
TableColumnModel columnModel = columnHeader.getColumnModel();
columnModel.moveColumn(2, 1);
});
String[] newNames = panel.getDataModelInfo().getNames();
assertEquals(names[0], newNames[1]);
assertEquals(names[1], newNames[0]);
assertEquals(names[2], newNames[2]);
names = panel.getViewNamesInDisplayOrder();
assertEquals("Ascii", names.get(0));
assertEquals("Hex", names.get(1));
assertEquals("Octal", names.get(2));
}
@Test
public void testResizeViews() {
loadViews("Ascii", "Octal");
assertNotEquals(200, getViewWidth("Ascii"));
setViewWidth("Ascii", 200);
assertEquals(200, panel.getViewWidth("Ascii"));
}
@Test
public void testResizeViewsSaveState() {
loadViews("Ascii", "Octal");
assertNotEquals(200, getViewWidth("Ascii"));
setViewWidth("Ascii", 200);
env.saveRestoreToolState();
assertEquals(200, panel.getViewWidth("Ascii"));
}
@Test
public void testReorderViewsSaveState() throws Exception {
loadViews("Ascii", "Octal");
final ByteViewerHeader columnHeader =
(ByteViewerHeader) findContainer(panel, ByteViewerHeader.class);
// move column 3 to 2
runSwing(() -> {
TableColumnModelEvent ev =
new TableColumnModelEvent(columnHeader.getColumnModel(), 3, 2);
panel.columnMoved(ev);
});
final JTableHeader columnHeader = (JTableHeader) findContainer(panel, JTableHeader.class);
// move column 1 to 0
runSwing(() -> {
TableColumnModelEvent ev =
new TableColumnModelEvent(columnHeader.getColumnModel(), 1, 0);
panel.columnMoved(ev);
TableColumnModel columnModel = columnHeader.getColumnModel();
columnModel.moveColumn(3, 2);
});
String[] names = panel.getDataModelInfo().getNames();
List<String> names = panel.getViewNamesInDisplayOrder();
env.saveRestoreToolState();
String[] newNames = panel.getDataModelInfo().getNames();
for (int i = 0; i < names.length; i++) {
assertEquals(names[i], newNames[i]);
}
List<String> newNames = panel.getViewNamesInDisplayOrder();
assertEquals(names, newNames);
}
@Test
@ -861,8 +877,8 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
}
private Address convertToAddr(ByteBlockInfo info) {
return ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddress(
info.getBlock(), info.getOffset());
return ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet())
.getAddress(info.getBlock(), info.getOffset());
}
private boolean byteBlockSelectionEquals(ByteBlockSelection b1, ByteBlockSelection b2) {
@ -942,4 +958,15 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
return null;
}
private void setViewWidth(String name, int width) {
runSwing(() -> {
panel.setViewWidth(name, width);
});
}
private int getViewWidth(String name) {
return runSwing(() -> {
return panel.getViewWidth("Ascii");
});
}
}

View file

@ -103,4 +103,5 @@ public interface IndexedScrollable {
* @param isHorizontal true if the rotation was horizontal, false for vertical
*/
public void mouseWheelMoved(double preciseWheelRotation, boolean isHorizontal);
}