mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-02 16:33:49 +00:00
Merge remote-tracking branch 'origin/GP-2147_ghidragon_resizing_byteview_views--SQUASHED'
This commit is contained in:
commit
8bfcb02166
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.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) {
|
||||
|
|
|
@ -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.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");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue