mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-2147 added ability to resize views in the byteviewer by dragging inthe header bar.
This commit is contained in:
parent
2ac8d4a2ec
commit
f9af85a31d
10 changed files with 714 additions and 658 deletions
|
@ -277,11 +277,14 @@
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
<h2>Reorder Views</h2>
|
<h2>Reorder / Resize Views</h2>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>The various views in the ByteViewer can be reordered by dragging
|
<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
|
the view header to the left or right of its current position. The view
|
||||||
positions are swapped.<br></p>
|
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>
|
</blockquote>
|
||||||
|
|
||||||
<h2><a name="WriteYourOwn"></a>Writing Your Own Format Plugin</h2>
|
<h2><a name="WriteYourOwn"></a>Writing Your Own Format Plugin</h2>
|
||||||
|
|
|
@ -48,6 +48,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
protected static final String X_OFFSET = "X Offset";
|
protected static final String X_OFFSET = "X Offset";
|
||||||
protected static final String Y_OFFSET = "Y Offset";
|
protected static final String Y_OFFSET = "Y Offset";
|
||||||
private static final String VIEW_NAMES = "View Names";
|
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 HEX_VIEW_GROUPSIZE = "Hex view groupsize";
|
||||||
private static final String BYTES_PER_LINE_NAME = "Bytes Per Line";
|
private static final String BYTES_PER_LINE_NAME = "Bytes Per Line";
|
||||||
private static final String OFFSET_NAME = "Offset";
|
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;
|
static final GColor CURRENT_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
|
||||||
//@formatter:on
|
//@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 SEPARATOR_COLOR_OPTION_NAME = "Block Separator Color";
|
||||||
static final String CHANGED_VALUE_COLOR_OPTION_NAME = "Changed Values 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.");
|
help, "Color of cursor in the active view.");
|
||||||
|
|
||||||
opt.registerThemeColorBinding(CURSOR_NON_ACTIVE_COLOR_OPTION_NAME,
|
opt.registerThemeColorBinding(CURSOR_NON_ACTIVE_COLOR_OPTION_NAME,
|
||||||
CURSOR_NON_ACTIVE_COLOR.getId(),
|
CURSOR_NON_ACTIVE_COLOR.getId(), help, "Color of cursor in the non-active views.");
|
||||||
help, "Color of cursor in the non-active views.");
|
|
||||||
|
|
||||||
opt.registerThemeColorBinding(CURSOR_NOT_FOCUSED_COLOR_OPTION_NAME,
|
opt.registerThemeColorBinding(CURSOR_NOT_FOCUSED_COLOR_OPTION_NAME,
|
||||||
CURSOR_NOT_FOCUSED_COLOR.getId(),
|
CURSOR_NOT_FOCUSED_COLOR.getId(), help,
|
||||||
help, "Color of cursor when the byteview does not have focus.");
|
"Color of cursor when the byteview does not have focus.");
|
||||||
|
|
||||||
opt.registerThemeColorBinding(CURRENT_LINE_COLOR_OPTION_NAME,
|
opt.registerThemeColorBinding(CURRENT_LINE_COLOR_OPTION_NAME,
|
||||||
GhidraOptions.DEFAULT_CURSOR_LINE_COLOR.getId(), help,
|
GhidraOptions.DEFAULT_CURSOR_LINE_COLOR.getId(), help,
|
||||||
"Color of the line containing the cursor");
|
"Color of the line containing the cursor");
|
||||||
|
|
||||||
opt.registerThemeFontBinding(OPTION_FONT, DEFAULT_FONT_ID, help,
|
opt.registerThemeFontBinding(OPTION_FONT, DEFAULT_FONT_ID, help, "Font used in the views.");
|
||||||
"Font used in the views.");
|
|
||||||
opt.registerOption(OPTION_HIGHLIGHT_CURSOR_LINE, true, help,
|
opt.registerOption(OPTION_HIGHLIGHT_CURSOR_LINE, true, help,
|
||||||
"Toggles highlighting background color of line containing the cursor");
|
"Toggles highlighting background color of line containing the cursor");
|
||||||
|
|
||||||
|
@ -332,11 +331,19 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeConfigState(SaveState saveState) {
|
protected void writeConfigState(SaveState saveState) {
|
||||||
DataModelInfo info = panel.getDataModelInfo();
|
List<String> viewNames = panel.getViewNamesInDisplayOrder();
|
||||||
saveState.putStrings(VIEW_NAMES, info.getNames());
|
saveState.putStrings(VIEW_NAMES, viewNames.toArray(new String[viewNames.size()]));
|
||||||
saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize);
|
saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize);
|
||||||
saveState.putInt(BYTES_PER_LINE_NAME, bytesPerLine);
|
saveState.putInt(BYTES_PER_LINE_NAME, bytesPerLine);
|
||||||
saveState.putInt(OFFSET_NAME, offset);
|
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) {
|
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);
|
bytesPerLine = saveState.getInt(BYTES_PER_LINE_NAME, DEFAULT_BYTES_PER_LINE);
|
||||||
offset = saveState.getInt(OFFSET_NAME, 0);
|
offset = saveState.getInt(OFFSET_NAME, 0);
|
||||||
panel.restoreConfigState(bytesPerLine, offset);
|
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() {
|
public Set<String> getCurrentViews() {
|
||||||
DataModelInfo info = panel.getDataModelInfo();
|
return new HashSet<String>(panel.getViewNamesInDisplayOrder());
|
||||||
HashSet<String> currentViewNames = new HashSet<>(Arrays.asList(info.getNames()));
|
|
||||||
return currentViewNames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshView() {
|
private void refreshView() {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,17 +21,15 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.*;
|
|
||||||
|
|
||||||
import docking.widgets.fieldpanel.*;
|
import docking.widgets.fieldpanel.*;
|
||||||
import docking.widgets.fieldpanel.field.EmptyTextField;
|
import docking.widgets.fieldpanel.field.EmptyTextField;
|
||||||
import docking.widgets.fieldpanel.field.Field;
|
import docking.widgets.fieldpanel.field.Field;
|
||||||
import docking.widgets.fieldpanel.listener.*;
|
import docking.widgets.fieldpanel.listener.*;
|
||||||
import docking.widgets.fieldpanel.support.*;
|
import docking.widgets.fieldpanel.support.*;
|
||||||
import docking.widgets.indexedscrollpane.*;
|
import docking.widgets.indexedscrollpane.IndexedScrollPane;
|
||||||
import docking.widgets.label.GDLabel;
|
import docking.widgets.label.GDLabel;
|
||||||
import docking.widgets.label.GLabel;
|
import docking.widgets.label.GLabel;
|
||||||
import generic.theme.GColor;
|
|
||||||
import generic.theme.Gui;
|
import generic.theme.Gui;
|
||||||
import ghidra.app.plugin.core.format.*;
|
import ghidra.app.plugin.core.format.*;
|
||||||
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
|
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
|
||||||
|
@ -40,7 +38,6 @@ import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.InvalidInputException;
|
import ghidra.util.exception.InvalidInputException;
|
||||||
import ghidra.util.layout.HorizontalLayout;
|
|
||||||
import ghidra.util.layout.PairLayout;
|
import ghidra.util.layout.PairLayout;
|
||||||
import help.Help;
|
import help.Help;
|
||||||
import help.HelpService;
|
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
|
* Top level component that has a scrolled pane for the panel of components that show the
|
||||||
* view for each format.
|
* view for each format.
|
||||||
*/
|
*/
|
||||||
public class ByteViewerPanel extends JPanel
|
public class ByteViewerPanel extends JPanel implements LayoutModel, LayoutListener {
|
||||||
implements TableColumnModelListener, LayoutModel, LayoutListener {
|
|
||||||
private static final String FONT_STATUS_ID = "font.byteviewer.status";
|
private static final String FONT_STATUS_ID = "font.byteviewer.status";
|
||||||
// private ByteViewerPlugin plugin;
|
|
||||||
private List<ByteViewerComponent> viewList; // list of field viewers
|
private List<ByteViewerComponent> viewList; // list of field viewers
|
||||||
private FieldPanel indexPanel; // panel for showing indexes
|
private FieldPanel indexPanel; // panel for showing indexes
|
||||||
private IndexFieldFactory indexFactory;
|
private IndexFieldFactory indexFactory;
|
||||||
|
@ -61,12 +56,9 @@ public class ByteViewerPanel extends JPanel
|
||||||
private JLabel offsetField;
|
private JLabel offsetField;
|
||||||
private JLabel insertionField;
|
private JLabel insertionField;
|
||||||
private JPanel statusPanel;
|
private JPanel statusPanel;
|
||||||
private CompositePanel compPanel;
|
|
||||||
private int fontHeight;
|
private int fontHeight;
|
||||||
private FontMetrics fm;
|
private FontMetrics fontMetrics;
|
||||||
private int bytesPerLine;
|
private int bytesPerLine;
|
||||||
private IndexedScrollPane scrollp;
|
|
||||||
private ByteViewerHeader columnHeader;
|
|
||||||
private ByteBlockSet blockSet;
|
private ByteBlockSet blockSet;
|
||||||
private ByteBlock[] blocks;
|
private ByteBlock[] blocks;
|
||||||
private IndexMap indexMap; // maps indexes to the correct block and offset
|
private IndexMap indexMap; // maps indexes to the correct block and offset
|
||||||
|
@ -78,12 +70,12 @@ public class ByteViewerPanel extends JPanel
|
||||||
private Color highlightColor;
|
private Color highlightColor;
|
||||||
private int highlightButton;
|
private int highlightButton;
|
||||||
private List<LayoutModelListener> layoutListeners = new ArrayList<>(1);
|
private List<LayoutModelListener> layoutListeners = new ArrayList<>(1);
|
||||||
private int indexPanelWidth;
|
|
||||||
private boolean addingView; // don't respond to cursor location
|
private boolean addingView; // don't respond to cursor location
|
||||||
// changes while this flag is true
|
// changes while this flag is true
|
||||||
private final ByteViewerComponentProvider provider;
|
private final ByteViewerComponentProvider provider;
|
||||||
|
|
||||||
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
|
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
|
||||||
|
private ByteViewerIndexedView indexedView;
|
||||||
|
|
||||||
protected ByteViewerPanel(ByteViewerComponentProvider provider) {
|
protected ByteViewerPanel(ByteViewerComponentProvider provider) {
|
||||||
super();
|
super();
|
||||||
|
@ -136,45 +128,6 @@ public class ByteViewerPanel extends JPanel
|
||||||
super.paintComponent(g);
|
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 **
|
// ** package-level methods **
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -252,17 +205,14 @@ public class ByteViewerPanel extends JPanel
|
||||||
endField.setText(lastBlock
|
endField.setText(lastBlock
|
||||||
.getLocationRepresentation(lastBlock.getLength().subtract(BigInteger.ONE)));
|
.getLocationRepresentation(lastBlock.getLength().subtract(BigInteger.ONE)));
|
||||||
|
|
||||||
indexPanelWidth = getIndexPanelWidth(blocks);
|
|
||||||
int center = indexPanelWidth / 2;
|
|
||||||
int startx = center - getMaxIndexSize() / 2;
|
|
||||||
indexFactory.setStartX(startx);
|
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (indexMap == null) {
|
if (indexMap == null) {
|
||||||
indexMap = new IndexMap();
|
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.
|
// 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
|
// 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);
|
c.setIndexMap(indexMap);
|
||||||
}
|
}
|
||||||
if (blocks != null && blocks.length > 0) {
|
if (blocks != null && blocks.length > 0) {
|
||||||
columnHeader.setColumnName(indexPanel, blocks[0].getIndexName());
|
indexedView.setIndexName(blocks[0].getIndexName());
|
||||||
}
|
}
|
||||||
indexPanel.dataChanged(BigInteger.ZERO, indexMap.getNumIndexes());
|
indexPanel.dataChanged(BigInteger.ZERO, indexMap.getNumIndexes());
|
||||||
indexSetChanged();
|
indexSetChanged();
|
||||||
|
@ -366,7 +316,8 @@ public class ByteViewerPanel extends JPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) {
|
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);
|
c.setHighlightButton(highlightButton);
|
||||||
viewList.add(c);
|
viewList.add(c);
|
||||||
c.setSize(c.getPreferredSize());
|
c.setSize(c.getPreferredSize());
|
||||||
compPanel.addByteViewerComponent(c);
|
indexedView.addView(viewName, c);
|
||||||
// tell column header it needs to grow
|
|
||||||
columnHeader.addColumn(viewName, c);
|
|
||||||
|
|
||||||
c.addListeners();
|
c.addListeners();
|
||||||
|
|
||||||
if (viewList.size() == 1) {
|
if (viewList.size() == 1) {
|
||||||
|
@ -441,8 +389,7 @@ public class ByteViewerPanel extends JPanel
|
||||||
void removeView(ByteViewerComponent comp) {
|
void removeView(ByteViewerComponent comp) {
|
||||||
|
|
||||||
viewList.remove(comp);
|
viewList.remove(comp);
|
||||||
compPanel.removeByteViewerComponent(comp);
|
indexedView.removeView(comp);
|
||||||
columnHeader.removeColumn(comp);
|
|
||||||
|
|
||||||
if (currentView == comp) {
|
if (currentView == comp) {
|
||||||
currentView = null;
|
currentView = null;
|
||||||
|
@ -485,10 +432,6 @@ public class ByteViewerPanel extends JPanel
|
||||||
ByteViewerComponent c = viewList.get(i);
|
ByteViewerComponent c = viewList.get(i);
|
||||||
c.refreshView();
|
c.refreshView();
|
||||||
}
|
}
|
||||||
// PluginEvent lastSelectionEvent = plugin.getLastSelectionEvent();
|
|
||||||
// if (lastSelectionEvent != null) {
|
|
||||||
// plugin.firePluginEvent(lastSelectionEvent);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int getNumberOfViews() {
|
int getNumberOfViews() {
|
||||||
|
@ -509,9 +452,11 @@ public class ByteViewerPanel extends JPanel
|
||||||
this.bytesPerLine = bytesPerLine;
|
this.bytesPerLine = bytesPerLine;
|
||||||
updateIndexMap();
|
updateIndexMap();
|
||||||
}
|
}
|
||||||
|
// reset view column widths to preferred width for new bytesPerline
|
||||||
|
indexedView.resetViewWidthToDefaults();
|
||||||
|
|
||||||
// force everything to get validated, or else the
|
// force everything to get validated, or else the
|
||||||
// header columns do not get repainted properly...
|
// header columns do not get repainted properly...
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
validate();
|
validate();
|
||||||
repaint();
|
repaint();
|
||||||
|
@ -615,36 +560,11 @@ public class ByteViewerPanel extends JPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
FontMetrics getCurrentFontMetrics() {
|
FontMetrics getCurrentFontMetrics() {
|
||||||
return fm;
|
return fontMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
List<String> getViewNamesInDisplayOrder() {
|
||||||
* Return array of names of views in the order that they appear in the panel. The name array
|
return indexedView.getViewNamesInDisplayOrder();
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -679,11 +599,11 @@ public class ByteViewerPanel extends JPanel
|
||||||
/**
|
/**
|
||||||
* Restore the configuration of the plugin.
|
* Restore the configuration of the plugin.
|
||||||
*
|
*
|
||||||
* @param fontMetrics font metrics
|
* @param metrics font metrics
|
||||||
* @param newEditColor color for showing edits
|
* @param newEditColor color for showing edits
|
||||||
*/
|
*/
|
||||||
void restoreConfigState(FontMetrics fontMetrics, Color newEditColor) {
|
void restoreConfigState(FontMetrics metrics, Color newEditColor) {
|
||||||
setFontMetrics(fontMetrics);
|
setFontMetrics(metrics);
|
||||||
setEditColor(newEditColor);
|
setEditColor(newEditColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -704,21 +624,13 @@ public class ByteViewerPanel extends JPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
void setFontMetrics(FontMetrics fm) {
|
void setFontMetrics(FontMetrics fm) {
|
||||||
this.fm = fm;
|
this.fontMetrics = fm;
|
||||||
for (int i = 0; i < viewList.size(); i++) {
|
for (int i = 0; i < viewList.size(); i++) {
|
||||||
ByteViewerComponent c = viewList.get(i);
|
ByteViewerComponent c = viewList.get(i);
|
||||||
c.setFontMetrics(fm);
|
c.setFontMetrics(fm);
|
||||||
}
|
}
|
||||||
indexFactory = new IndexFieldFactory(fm);
|
indexFactory = new IndexFieldFactory(fm);
|
||||||
|
indexFactory.setSize(getIndexSizeInChars());
|
||||||
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);
|
|
||||||
indexPanel.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
|
indexPanel.modelSizeChanged(IndexMapper.IDENTITY_MAPPER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,7 +643,7 @@ public class ByteViewerPanel extends JPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
protected FontMetrics getFontMetrics() {
|
protected FontMetrics getFontMetrics() {
|
||||||
return fm;
|
return fontMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getBytesPerLine() {
|
protected int getBytesPerLine() {
|
||||||
|
@ -745,13 +657,11 @@ public class ByteViewerPanel extends JPanel
|
||||||
|
|
||||||
setLayout(new BorderLayout(10, 0));
|
setLayout(new BorderLayout(10, 0));
|
||||||
|
|
||||||
columnHeader = new ByteViewerHeader(this);
|
fontMetrics = getFontMetrics(Gui.getFont(ByteViewerComponentProvider.DEFAULT_FONT_ID));
|
||||||
|
fontHeight = fontMetrics.getHeight();
|
||||||
fm = getFontMetrics(Gui.getFont(ByteViewerComponentProvider.DEFAULT_FONT_ID));
|
|
||||||
fontHeight = fm.getHeight();
|
|
||||||
|
|
||||||
// for the index/address column
|
// for the index/address column
|
||||||
indexFactory = new IndexFieldFactory(fm);
|
indexFactory = new IndexFieldFactory(fontMetrics);
|
||||||
indexPanel = new FieldPanel(this, "Byte Viewer");
|
indexPanel = new FieldPanel(this, "Byte Viewer");
|
||||||
|
|
||||||
indexPanel.enableSelection(false);
|
indexPanel.enableSelection(false);
|
||||||
|
@ -759,21 +669,13 @@ public class ByteViewerPanel extends JPanel
|
||||||
indexPanel.setFocusable(false);
|
indexPanel.setFocusable(false);
|
||||||
indexPanel.addLayoutListener(this);
|
indexPanel.addLayoutListener(this);
|
||||||
|
|
||||||
compPanel = new CompositePanel(indexPanel);
|
indexedView = new ByteViewerIndexedView(indexPanel);
|
||||||
|
IndexedScrollPane indexedScrollPane = new IndexedScrollPane(indexedView);
|
||||||
scrollp = new IndexedScrollPane(compPanel);
|
indexedScrollPane.setWheelScrollingEnabled(false);
|
||||||
scrollp.setWheelScrollingEnabled(false);
|
indexedScrollPane.setColumnHeaderComp(indexedView.getColumnHeader());
|
||||||
|
|
||||||
columnHeader = new ByteViewerHeader(this);
|
|
||||||
columnHeader.addColumnModelListener(this);
|
|
||||||
|
|
||||||
columnHeader.addColumn(ByteViewerComponentProvider.DEFAULT_INDEX_NAME, indexPanel);
|
|
||||||
scrollp.setColumnHeaderComp(columnHeader);
|
|
||||||
|
|
||||||
compPanel.setBackground(new GColor("color.bg.byteviewer"));
|
|
||||||
|
|
||||||
statusPanel = createStatusPanel();
|
statusPanel = createStatusPanel();
|
||||||
add(scrollp, BorderLayout.CENTER);
|
add(indexedScrollPane, BorderLayout.CENTER);
|
||||||
add(statusPanel, BorderLayout.SOUTH);
|
add(statusPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
HelpService help = Help.getHelpService();
|
HelpService help = Help.getHelpService();
|
||||||
|
@ -852,8 +754,7 @@ public class ByteViewerPanel extends JPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
indexMap = new IndexMap(blockSet, bytesPerLine, blockOffset);
|
indexMap = new IndexMap(blockSet, bytesPerLine, blockOffset);
|
||||||
indexPanelWidth = getIndexPanelWidth(blocks);
|
indexFactory.setIndexMap(indexMap);
|
||||||
indexFactory.setIndexMap(indexMap, indexPanelWidth);
|
|
||||||
ByteBlock block = null;
|
ByteBlock block = null;
|
||||||
BigInteger offset = BigInteger.ZERO;
|
BigInteger offset = BigInteger.ZERO;
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
|
@ -887,7 +788,7 @@ public class ByteViewerPanel extends JPanel
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dimension getPreferredViewSize() {
|
public Dimension getPreferredViewSize() {
|
||||||
return new Dimension(indexPanelWidth, 500);
|
return new Dimension(500, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -926,26 +827,16 @@ public class ByteViewerPanel extends JPanel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getIndexPanelWidth(ByteBlock[] blocks1) {
|
private int getIndexSizeInChars() {
|
||||||
FontMetrics headerFm = columnHeader.getFontMetrics(columnHeader.getFont());
|
// set the field size at least the size of the column header name
|
||||||
String indexName = ByteViewerComponentProvider.DEFAULT_INDEX_NAME;
|
int minChars = ByteViewerComponentProvider.INDEX_COLUMN_NAME.length();
|
||||||
if (blocks1.length > 0) {
|
if (blocks != null) {
|
||||||
indexName = blocks1[0].getIndexName();
|
for (ByteBlock element : blocks) {
|
||||||
|
int charCount = element.getMaxLocationRepresentationSize();
|
||||||
|
minChars = Math.max(minChars, charCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int nameWidth = headerFm.stringWidth(indexName);
|
return minChars;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1024,175 +915,12 @@ public class ByteViewerPanel extends JPanel
|
||||||
public void removeDisplayListener(AddressSetDisplayListener listener) {
|
public void removeDisplayListener(AddressSetDisplayListener listener) {
|
||||||
displayListeners.add(listener);
|
displayListeners.add(listener);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public int getViewWidth(String viewName) {
|
||||||
class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollListener {
|
return indexedView.getViewWidth(viewName);
|
||||||
FieldPanel indexPanel;
|
}
|
||||||
BoundedRangeModel verticalScrollBarModel;
|
|
||||||
BoundedRangeModel horizontalScrollBarModel;
|
public void setViewWidth(String viewName, int width) {
|
||||||
List<ByteViewerComponent> viewList = new ArrayList<>();
|
indexedView.setColumnWidth(viewName, width);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,10 +19,12 @@ import java.awt.Color;
|
||||||
import java.awt.FontMetrics;
|
import java.awt.FontMetrics;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import docking.widgets.fieldpanel.field.Field;
|
import docking.widgets.fieldpanel.field.Field;
|
||||||
import docking.widgets.fieldpanel.field.SimpleTextField;
|
import docking.widgets.fieldpanel.field.SimpleTextField;
|
||||||
import docking.widgets.fieldpanel.support.Highlight;
|
|
||||||
import docking.widgets.fieldpanel.support.FieldHighlightFactory;
|
import docking.widgets.fieldpanel.support.FieldHighlightFactory;
|
||||||
|
import docking.widgets.fieldpanel.support.Highlight;
|
||||||
import ghidra.app.plugin.core.format.ByteBlockInfo;
|
import ghidra.app.plugin.core.format.ByteBlockInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +32,7 @@ import ghidra.app.plugin.core.format.ByteBlockInfo;
|
||||||
*/
|
*/
|
||||||
class IndexFieldFactory {
|
class IndexFieldFactory {
|
||||||
|
|
||||||
private FontMetrics fm;
|
private FontMetrics metrics;
|
||||||
private int width;
|
private int width;
|
||||||
private IndexMap indexMap;
|
private IndexMap indexMap;
|
||||||
private int charWidth;
|
private int charWidth;
|
||||||
|
@ -41,21 +43,21 @@ class IndexFieldFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param fieldModel field model
|
* @param metrics the FontMetrics this field should use to do size computations
|
||||||
*/
|
*/
|
||||||
IndexFieldFactory(FontMetrics fm) {
|
IndexFieldFactory(FontMetrics metrics) {
|
||||||
this.fm = fm;
|
this.metrics = metrics;
|
||||||
|
|
||||||
charWidth = fm.charWidth('W');
|
charWidth = metrics.charWidth('W');
|
||||||
width = ByteViewerComponentProvider.DEFAULT_NUMBER_OF_CHARS * charWidth;
|
width = ByteViewerComponentProvider.DEFAULT_NUMBER_OF_CHARS * charWidth;
|
||||||
missingValueColor = ByteViewerComponentProvider.SEPARATOR_COLOR;
|
missingValueColor = ByteViewerComponentProvider.SEPARATOR_COLOR;
|
||||||
|
startX = charWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a Field object for the given index.
|
* Gets a Field object for the given index.
|
||||||
* This method is called for n times where n is the number of fields
|
* @param index the index to get a Field for
|
||||||
* in the model. In this case, we only have one field, so it gets
|
* @return The Field for the given index
|
||||||
* called once for each index.
|
|
||||||
*/
|
*/
|
||||||
public Field getField(BigInteger index) {
|
public Field getField(BigInteger index) {
|
||||||
|
|
||||||
|
@ -74,8 +76,8 @@ class IndexFieldFactory {
|
||||||
}
|
}
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
if (indexMap.isBlockSeparatorIndex(index)) {
|
if (indexMap.isBlockSeparatorIndex(index)) {
|
||||||
SimpleTextField sf =
|
SimpleTextField sf = new SimpleTextField(noValueStr, metrics, startX, width,
|
||||||
new SimpleTextField(noValueStr, fm, startX, width, false, highlightFactory);
|
false, highlightFactory);
|
||||||
|
|
||||||
sf.setForeground(missingValueColor);
|
sf.setForeground(missingValueColor);
|
||||||
return sf;
|
return sf;
|
||||||
|
@ -87,15 +89,16 @@ class IndexFieldFactory {
|
||||||
if (locRep == null) {
|
if (locRep == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new SimpleTextField(locRep, fm, startX, width, false, highlightFactory);
|
return new SimpleTextField(locRep, metrics, startX, width, false, highlightFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FontMetrics getMetrics() {
|
public FontMetrics getMetrics() {
|
||||||
return fm;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the starting x position for the fields generated by this factory
|
* Sets the starting x position for the fields generated by this factory
|
||||||
|
* @param x The
|
||||||
*/
|
*/
|
||||||
public void setStartX(int x) {
|
public void setStartX(int x) {
|
||||||
startX = x;
|
startX = x;
|
||||||
|
@ -103,6 +106,7 @@ class IndexFieldFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the starting x position for the fields generated by this factory.
|
* 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() {
|
public int getStartX() {
|
||||||
return startX;
|
return startX;
|
||||||
|
@ -110,26 +114,30 @@ class IndexFieldFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the width of the fields associated with this Factory.
|
* Returns the width of the fields associated with this Factory.
|
||||||
|
* @return the width of the fields associated with this Factory
|
||||||
*/
|
*/
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set byte blocks.
|
* Sets the indexMap which is the data source for the generated fields.
|
||||||
* @param blocks
|
* @param indexMap indexMap which produces the index string for specific indexes
|
||||||
*/
|
*/
|
||||||
void setIndexMap(IndexMap indexMap, int maxWidth) {
|
void setIndexMap(IndexMap indexMap) {
|
||||||
this.indexMap = indexMap;
|
this.indexMap = indexMap;
|
||||||
width = maxWidth - (2 * charWidth);
|
}
|
||||||
int nchars = width / charWidth;
|
|
||||||
if (indexMap != null) {
|
/**
|
||||||
StringBuffer sb = new StringBuffer();
|
* Sets the size of this field based on the number of chars to display
|
||||||
for (int i = 0; i < nchars; i++) {
|
* @param charCount the number of chars to
|
||||||
sb.append(".");
|
*/
|
||||||
}
|
void setSize(int charCount) {
|
||||||
noValueStr = sb.toString();
|
width = charCount * charWidth;
|
||||||
}
|
// add in space for margins
|
||||||
|
width += 2 * charWidth;
|
||||||
|
|
||||||
|
noValueStr = StringUtils.repeat('.', charCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMissingValueColor(Color c) {
|
void setMissingValueColor(Color c) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,7 +24,8 @@ import java.util.List;
|
||||||
|
|
||||||
import javax.swing.JCheckBox;
|
import javax.swing.JCheckBox;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.event.TableColumnModelEvent;
|
import javax.swing.table.JTableHeader;
|
||||||
|
import javax.swing.table.TableColumnModel;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
@ -167,10 +168,9 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
assertEquals(3, panel.getNumberOfViews());
|
assertEquals(3, panel.getNumberOfViews());
|
||||||
|
|
||||||
DataModelInfo info = panel.getDataModelInfo();
|
List<String> names = panel.getViewNamesInDisplayOrder();
|
||||||
String[] names = info.getNames();
|
assertEquals(3, names.size());
|
||||||
assertEquals(3, names.length);
|
Set<String> viewNames = new HashSet<>(names);
|
||||||
Set<String> viewNames = new HashSet<>(Arrays.asList(names));
|
|
||||||
assertTrue(viewNames.contains("Hex"));
|
assertTrue(viewNames.contains("Hex"));
|
||||||
assertTrue(viewNames.contains("Octal"));
|
assertTrue(viewNames.contains("Octal"));
|
||||||
assertTrue(viewNames.contains("Ascii"));
|
assertTrue(viewNames.contains("Ascii"));
|
||||||
|
@ -196,10 +196,9 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
assertEquals(1, panel.getNumberOfViews());
|
assertEquals(1, panel.getNumberOfViews());
|
||||||
|
|
||||||
info = panel.getDataModelInfo();
|
names = panel.getViewNamesInDisplayOrder();
|
||||||
names = info.getNames();
|
assertEquals(1, names.size());
|
||||||
assertEquals(1, names.length);
|
assertEquals("Octal", names.get(0));
|
||||||
assertEquals("Octal", names[0]);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,56 +617,73 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testReorderViews() throws Exception {
|
public void testReorderViews() throws Exception {
|
||||||
loadViews("Ascii", "Octal");
|
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
|
// move column 3 to 2
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
TableColumnModelEvent ev =
|
TableColumnModel columnModel = columnHeader.getColumnModel();
|
||||||
new TableColumnModelEvent(columnHeader.getColumnModel(), 3, 2);
|
columnModel.moveColumn(3, 2);
|
||||||
panel.columnMoved(ev);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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(() -> {
|
runSwing(() -> {
|
||||||
TableColumnModelEvent ev =
|
TableColumnModel columnModel = columnHeader.getColumnModel();
|
||||||
new TableColumnModelEvent(columnHeader.getColumnModel(), 2, 1);
|
columnModel.moveColumn(2, 1);
|
||||||
panel.columnMoved(ev);
|
|
||||||
});
|
});
|
||||||
String[] newNames = panel.getDataModelInfo().getNames();
|
names = panel.getViewNamesInDisplayOrder();
|
||||||
assertEquals(names[0], newNames[1]);
|
assertEquals("Ascii", names.get(0));
|
||||||
assertEquals(names[1], newNames[0]);
|
assertEquals("Hex", names.get(1));
|
||||||
assertEquals(names[2], newNames[2]);
|
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
|
@Test
|
||||||
public void testReorderViewsSaveState() throws Exception {
|
public void testReorderViewsSaveState() throws Exception {
|
||||||
|
|
||||||
loadViews("Ascii", "Octal");
|
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
|
// move column 1 to 0
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
TableColumnModelEvent ev =
|
TableColumnModel columnModel = columnHeader.getColumnModel();
|
||||||
new TableColumnModelEvent(columnHeader.getColumnModel(), 1, 0);
|
columnModel.moveColumn(3, 2);
|
||||||
panel.columnMoved(ev);
|
|
||||||
});
|
});
|
||||||
String[] names = panel.getDataModelInfo().getNames();
|
List<String> names = panel.getViewNamesInDisplayOrder();
|
||||||
|
|
||||||
env.saveRestoreToolState();
|
env.saveRestoreToolState();
|
||||||
String[] newNames = panel.getDataModelInfo().getNames();
|
List<String> newNames = panel.getViewNamesInDisplayOrder();
|
||||||
for (int i = 0; i < names.length; i++) {
|
assertEquals(names, newNames);
|
||||||
assertEquals(names[i], newNames[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -861,8 +877,8 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Address convertToAddr(ByteBlockInfo info) {
|
private Address convertToAddr(ByteBlockInfo info) {
|
||||||
return ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet()).getAddress(
|
return ((ProgramByteBlockSet) plugin.getProvider().getByteBlockSet())
|
||||||
info.getBlock(), info.getOffset());
|
.getAddress(info.getBlock(), info.getOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean byteBlockSelectionEquals(ByteBlockSelection b1, ByteBlockSelection b2) {
|
private boolean byteBlockSelectionEquals(ByteBlockSelection b1, ByteBlockSelection b2) {
|
||||||
|
@ -942,4 +958,15 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
return null;
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,4 +103,5 @@ public interface IndexedScrollable {
|
||||||
* @param isHorizontal true if the rotation was horizontal, false for vertical
|
* @param isHorizontal true if the rotation was horizontal, false for vertical
|
||||||
*/
|
*/
|
||||||
public void mouseWheelMoved(double preciseWheelRotation, boolean isHorizontal);
|
public void mouseWheelMoved(double preciseWheelRotation, boolean isHorizontal);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue