// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.gui;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashMap;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import org.infinity.NearInfinity;
import org.infinity.datatype.Datatype;
import org.infinity.datatype.DecNumber;
import org.infinity.datatype.Editable;
import org.infinity.datatype.EffectType;
import org.infinity.datatype.HexNumber;
import org.infinity.datatype.InlineEditable;
import org.infinity.datatype.Readable;
import org.infinity.datatype.ResourceRef;
import org.infinity.datatype.SectionCount;
import org.infinity.datatype.TextString;
import org.infinity.datatype.Unknown;
import org.infinity.datatype.UnknownBinary;
import org.infinity.datatype.UnknownDecimal;
import org.infinity.icon.Icons;
import org.infinity.resource.AbstractStruct;
import org.infinity.resource.AddRemovable;
import org.infinity.resource.Closeable;
import org.infinity.resource.HasAddRemovable;
import org.infinity.resource.HasViewerTabs;
import org.infinity.resource.Resource;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.StructEntry;
import org.infinity.resource.Viewable;
import org.infinity.resource.dlg.AbstractCode;
import org.infinity.resource.dlg.DlgResource;
import org.infinity.resource.dlg.State;
import org.infinity.resource.dlg.Transition;
import org.infinity.search.AttributeSearcher;
import org.infinity.search.DialogItemRefSearcher;
import org.infinity.search.DialogStateReferenceSearcher;
import org.infinity.search.ReferenceSearcher;
import org.infinity.util.Pair;
import org.infinity.util.StructClipboard;
import org.infinity.util.io.ByteBufferOutputStream;
import org.infinity.util.io.StreamUtils;
public final class StructViewer extends JPanel implements ListSelectionListener, ActionListener,
ItemListener, ChangeListener, TableModelListener,
ComponentListener
{
// Commonly used tab names
public static final String TAB_EDIT = "Edit";
public static final String TAB_VIEW = "View";
public static final String TAB_RAW = "Raw";
// Menu commands
public static final String CMD_COPYVALUE = "VCopy";
public static final String CMD_PASTEVALUE = "VPaste";
public static final String CMD_CUT = "Cut";
public static final String CMD_COPY = "Copy";
public static final String CMD_PASTE = "Paste";
public static final String CMD_TOHEX = "ToHex";
public static final String CMD_TOSTRING = "ToStr";
public static final String CMD_TOBIN = "ToBin";
public static final String CMD_TODEC = "ToDec";
public static final String CMD_TOINT = "ToInt";
public static final String CMD_RESET = "ResetType";
public static final String CMD_SHOWVIEWER = "ShowView";
public static final String CMD_SHOWNEWVIEWER = "ShowNewView";
public static final String UPDATE_VALUE = "UpdateValue";
// Identifiers for card layout elements
private static final String CARD_EMPTY = "Empty";
private static final String CARD_EDIT = "Edit";
private static final String CARD_TEXT = "Text";
private static Class<? extends StructEntry> lastNameStruct, lastIndexStruct;
private static String lastName;
private static int lastIndex;
private final AbstractStruct struct;
private final CardLayout cards = new CardLayout();
private final JMenuItem miCopyValue = createMenuItem(CMD_COPYVALUE, "Copy value", Icons.getIcon(Icons.ICON_COPY_16), this);
private final JMenuItem miPasteValue = createMenuItem(CMD_PASTEVALUE, "Replace value", Icons.getIcon(Icons.ICON_PASTE_16), this);
private final JMenuItem miCut = createMenuItem(CMD_CUT, "Cut", Icons.getIcon(Icons.ICON_CUT_16), this);
private final JMenuItem miCopy = createMenuItem(CMD_COPY, "Copy", Icons.getIcon(Icons.ICON_COPY_16), this);
private final JMenuItem miPaste = createMenuItem(CMD_PASTE, "Paste", Icons.getIcon(Icons.ICON_PASTE_16), this);
private final JMenuItem miToHex = createMenuItem(CMD_TOHEX, "Edit as hex", Icons.getIcon(Icons.ICON_REFRESH_16), this);
private final JMenuItem miToString = createMenuItem(CMD_TOSTRING, "Edit as string", Icons.getIcon(Icons.ICON_REFRESH_16), this);
private final JMenuItem miToBin = createMenuItem(CMD_TOBIN, "Edit as binary", Icons.getIcon(Icons.ICON_REFRESH_16), this);
private final JMenuItem miToDec = createMenuItem(CMD_TODEC, "Edit as decimal", Icons.getIcon(Icons.ICON_REFRESH_16), this);
private final JMenuItem miToInt = createMenuItem(CMD_TOINT, "Edit as number", Icons.getIcon(Icons.ICON_REFRESH_16), this);
private final JMenuItem miReset = createMenuItem(CMD_RESET, "Reset field type", Icons.getIcon(Icons.ICON_REFRESH_16), this);
private final JMenuItem miShowViewer = createMenuItem(CMD_SHOWVIEWER, "Show in viewer", null, this);
private final JMenuItem miShowNewViewer = createMenuItem(CMD_COPYVALUE, "Show in new viewer", null, this);
private final JPanel lowerpanel = new JPanel(cards);
private final JPanel editpanel = new JPanel();
private final ButtonPanel buttonPanel = new ButtonPanel();
private final JPopupMenu popupmenu = new JPopupMenu();
private final InfinityTextArea tatext = new InfinityTextArea(true);
private final StructTable table = new StructTable();
private final HashMap<Integer, StructEntry> entryMap = new HashMap<Integer, StructEntry>();
private final HashMap<Viewable, ViewFrame> viewMap = new HashMap<Viewable, ViewFrame>();
private AddRemovable emptyTypes[];
private JMenuItem miFindAttribute, miFindReferences, miFindStateReferences, miFindRefToItem;
private Editable editable;
private JTabbedPane tabbedPane;
private JSplitPane splitv;
private boolean splitterSet;
private int oldSplitterHeight;
private Pair<Integer> storedSelection;
private static JMenuItem createMenuItem(String cmd, String text, Icon icon, ActionListener l)
{
JMenuItem m = new JMenuItem(text, icon);
m.setActionCommand(cmd);
if (l != null)
m.addActionListener(l);
return m;
}
public StructViewer(AbstractStruct struct)
{
this(struct, null);
}
public StructViewer(AbstractStruct struct, Collection<Component> extraComponents)
{
this.struct = struct;
struct.addTableModelListener(this);
table.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
table.getSelectionModel().addListSelectionListener(this);
table.setFont(BrowserMenuBar.getInstance().getScriptFont());
table.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() == 2 && table.getSelectedRowCount() == 1) {
Object selected = table.getModel().getValueAt(table.getSelectedRow(), 1);
if (selected instanceof Viewable) {
createViewFrame(table.getTopLevelAncestor(), (Viewable)selected);
}
}
}
});
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer()
{
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row,
int column)
{
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (column == 2)
setHorizontalAlignment(JLabel.TRAILING);
else
setHorizontalAlignment(JLabel.LEADING);
return this;
}
});
popupmenu.add(miCopyValue);
popupmenu.add(miPasteValue);
popupmenu.add(miCut);
popupmenu.add(miCopy);
popupmenu.add(miPaste);
popupmenu.add(miToHex);
popupmenu.add(miToBin);
popupmenu.add(miToDec);
popupmenu.add(miToInt);
popupmenu.add(miToString);
popupmenu.add(miReset);
if (struct instanceof DlgResource) {
popupmenu.add(miShowViewer);
popupmenu.add(miShowNewViewer);
}
table.addMouseListener(new PopupListener());
miCopyValue.setEnabled(false);
miPasteValue.setEnabled(false);
miCut.setEnabled(false);
miCopy.setEnabled(false);
miPaste.setEnabled(
StructClipboard.getInstance().getContentType(struct) == StructClipboard.CLIPBOARD_ENTRIES);
miToHex.setEnabled(false);
miToBin.setEnabled(false);
miToDec.setEnabled(false);
miToInt.setEnabled(false);
miToString.setEnabled(false);
miReset.setEnabled(false);
miShowViewer.setEnabled(false);
miShowNewViewer.setEnabled(false);
tatext.setHighlightCurrentLine(false);
tatext.setEOLMarkersVisible(false);
tatext.setEditable(false);
tatext.setMargin(new Insets(3, 3, 3, 3));
tatext.setFont(BrowserMenuBar.getInstance().getScriptFont());
InfinityScrollPane scroll = new InfinityScrollPane(tatext, true);
scroll.setLineNumbersEnabled(false);
table.setModel(struct);
table.getTableHeader().setReorderingAllowed(false);
table.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
table.addComponentListener(this);
table.getColumnModel().getColumn(0).setPreferredWidth(NearInfinity.getInstance().getTableColumnWidth(0));
table.getColumnModel().getColumn(1).setPreferredWidth(NearInfinity.getInstance().getTableColumnWidth(1));
if (table.getColumnCount() == 3) {
table.getColumnModel().getColumn(2).setPreferredWidth(NearInfinity.getInstance().getTableColumnWidth(2));
}
lowerpanel.add(scroll, CARD_TEXT);
lowerpanel.add(editpanel, CARD_EDIT);
lowerpanel.add(new JPanel(), CARD_EMPTY);
lowerpanel.addComponentListener(this);
cards.show(lowerpanel, CARD_EMPTY);
if (struct instanceof HasAddRemovable && struct.getFieldCount() > 0) {
try {
emptyTypes = ((HasAddRemovable)struct).getAddRemovables();
} catch (Exception e) {
e.printStackTrace();
}
if (emptyTypes == null)
emptyTypes = new AddRemovable[0];
JMenuItem menuItems[] = new JMenuItem[emptyTypes.length];
for (int i = 0; i < menuItems.length; i++) {
menuItems[i] = new JMenuItem(emptyTypes[i].getName());
}
if (emptyTypes.length > 0) {
ButtonPopupMenu bpmAdd = (ButtonPopupMenu)buttonPanel.addControl(ButtonPanel.Control.ADD);
bpmAdd.setMenuItems(menuItems);
bpmAdd.addItemListener(this);
JButton bRemove = (JButton)buttonPanel.addControl(ButtonPanel.Control.REMOVE);
bRemove.setEnabled(false);
bRemove.addActionListener(this);
}
}
ButtonPopupMenu bpmFind = (ButtonPopupMenu)buttonPanel.addControl(ButtonPanel.Control.FIND_MENU);
bpmFind.addItemListener(this);
if (struct instanceof DlgResource) {
miFindAttribute = new JMenuItem("selected attribute");
miFindAttribute.setEnabled(false);
miFindReferences = new JMenuItem("references to this file");
miFindReferences.setEnabled(struct instanceof Resource && struct.getSuperStruct() == null);
miFindStateReferences = new JMenuItem("references to this state");
miFindStateReferences.setEnabled(false);
miFindRefToItem = new JMenuItem("references to selected item in this file");
miFindRefToItem.setEnabled(false);
bpmFind.setMenuItems(new JMenuItem[]{miFindAttribute, miFindReferences,
miFindStateReferences, miFindRefToItem});
} else {
miFindAttribute = new JMenuItem("selected attribute");
miFindAttribute.setEnabled(false);
miFindReferences = new JMenuItem("references to this file");
miFindReferences.setEnabled(struct instanceof Resource && struct.getSuperStruct() == null);
bpmFind.setMenuItems(new JMenuItem[]{miFindAttribute, miFindReferences});
}
JButton bView = (JButton)buttonPanel.addControl(ButtonPanel.Control.VIEW_EDIT);
bView.setEnabled(false);
bView.addActionListener(this);
((JButton)buttonPanel.addControl(ButtonPanel.Control.PRINT)).addActionListener(this);
if (struct instanceof Resource && struct.getFieldCount() > 0 && struct.getSuperStruct() == null) {
((JButton)buttonPanel.addControl(ButtonPanel.Control.EXPORT_BUTTON)).addActionListener(this);
((JButton)buttonPanel.addControl(ButtonPanel.Control.SAVE)).addActionListener(this);
}
if (extraComponents != null) {
for (final Component c: extraComponents) {
buttonPanel.add(c);
}
}
JScrollPane scrollTable = new JScrollPane(table);
scrollTable.getViewport().setBackground(table.getBackground());
scrollTable.setBorder(BorderFactory.createEmptyBorder());
splitv = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollTable, lowerpanel);
splitv.setDividerLocation(2 * (NearInfinity.getInstance().getHeight() - 100) / 3);
setLayout(new BorderLayout());
if (struct instanceof HasViewerTabs) {
HasViewerTabs tabs = (HasViewerTabs)struct;
JPanel panel = new JPanel(new BorderLayout());
panel.add(splitv, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
tabbedPane = new JTabbedPane();
// adding custom tabs
int editIndex = -1;
for (int i = 0; i < tabs.getViewerTabCount(); i++) {
if (tabs.viewerTabAddedBefore(i)) {
// adding before "Edit"
if (editIndex < 0) {
tabbedPane.addTab(tabs.getViewerTabName(i), tabs.getViewerTab(i));
} else {
tabbedPane.insertTab(tabs.getViewerTabName(i), null, tabs.getViewerTab(i), null, editIndex);
editIndex++;
}
} else {
// adding after "Edit"
if (editIndex < 0) {
tabbedPane.addTab(TAB_EDIT, panel);
editIndex = tabbedPane.getTabCount() - 1;
}
tabbedPane.addTab(tabs.getViewerTabName(i), tabs.getViewerTab(i));
}
}
// add "Edit" tab if not yet added
if (editIndex < 0) {
tabbedPane.addTab(TAB_EDIT, panel);
editIndex = tabbedPane.getTabCount() - 1;
}
add(tabbedPane, BorderLayout.CENTER);
if (struct.getSuperStruct() != null && struct.getSuperStruct() instanceof HasViewerTabs) {
StructViewer sViewer = struct.getSuperStruct().getViewer();
if (sViewer == null) {
sViewer = struct.getSuperStruct().getSuperStruct().getViewer();
}
if (sViewer != null && sViewer.tabbedPane != null) {
tabbedPane.setSelectedIndex(sViewer.tabbedPane.getSelectedIndex());
}
} else if (lastIndexStruct == struct.getClass()) {
tabbedPane.setSelectedIndex(lastIndex);
} else if (BrowserMenuBar.getInstance().getDefaultStructView() == BrowserMenuBar.DEFAULT_EDIT) {
tabbedPane.setSelectedIndex(getEditTabIndex());
}
if (isEditTabSelected()) {
if (lastNameStruct == struct.getClass()) {
selectEntry(lastName);
} else {
table.getSelectionModel().setSelectionInterval(0, 0);
}
}
} else {
add(splitv, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.SOUTH);
if (lastNameStruct == struct.getClass()) {
selectEntry(lastName);
} else {
table.getSelectionModel().setSelectionInterval(0, 0);
}
}
addComponentListener(this);
StructClipboard.getInstance().addChangeListener(this);
table.repaint();
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() instanceof JComponent &&
buttonPanel.getControlPosition((JComponent)event.getSource()) >= 0) {
if (buttonPanel.getControlByType(ButtonPanel.Control.VIEW_EDIT) == event.getSource()) {
Viewable selected = (Viewable)table.getModel().getValueAt(table.getSelectedRow(), 1);
createViewFrame(getTopLevelAncestor(), selected);
} else if (buttonPanel.getControlByType(ButtonPanel.Control.REMOVE) == event.getSource()) {
if (!(struct instanceof HasAddRemovable)) {
return;
}
Window wnd = SwingUtilities.getWindowAncestor(this);
if (wnd == null) {
wnd = NearInfinity.getInstance();
}
WindowBlocker.blockWindow(wnd, true);
try {
int[] rows = table.getSelectedRows();
for (int i = rows.length - 1; i >= 0; i--) {
Object entry = table.getModel().getValueAt(rows[i], 1);
if (entry instanceof AddRemovable) {
try {
if (((HasAddRemovable)struct).confirmRemoveEntry((AddRemovable)entry)) {
struct.removeDatatype((AddRemovable)entry, true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
} finally {
WindowBlocker.blockWindow(wnd, false);
}
} else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == event.getSource()) {
if (ResourceFactory.saveResource((Resource)struct, getTopLevelAncestor())) {
struct.setStructChanged(false);
}
} else if (buttonPanel.getControlByType(ButtonPanel.Control.EXPORT_BUTTON) == event.getSource()) {
ResourceFactory.exportResource(struct.getResourceEntry(), getTopLevelAncestor());
} else if (buttonPanel.getControlByType(ButtonPanel.Control.PRINT) == event.getSource()) {
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(table);
if (pj.printDialog()) {
try {
pj.print();
} catch (Exception e) {
e.printStackTrace();
}
}
}
} else if (event.getActionCommand().equals(UPDATE_VALUE)) {
if (editable.updateValue(struct)) {
struct.setStructChanged(true);
struct.fireTableRowsUpdated(struct.getIndexOf(editable), struct.getIndexOf(editable));
if (editable instanceof EffectType) {
// don't lose current selection
if (struct.getViewer() != null) {
struct.getViewer().storeCurrentSelection();
}
// Updates multiple lines - could be done better?
struct.fireTableDataChanged();
if (struct.getViewer() != null) {
struct.getViewer().restoreCurrentSelection();
}
}
}
else
JOptionPane.showMessageDialog(this, "Error updating value", "Error", JOptionPane.ERROR_MESSAGE);
} else if (event.getActionCommand().equals(CMD_COPYVALUE)) {
StructClipboard.getInstance().copyValue(struct, table.getSelectionModel().getMinSelectionIndex(),
table.getSelectionModel().getMaxSelectionIndex());
} else if (event.getActionCommand().equals(CMD_PASTEVALUE)) {
int changed = StructClipboard.getInstance().pasteValue(struct,
table.getSelectionModel().getMinSelectionIndex());
if (changed == 0)
JOptionPane.showMessageDialog(this, "Attributes doesn't match!", "Error", JOptionPane.ERROR_MESSAGE);
else {
struct.fireTableRowsUpdated(table.getSelectionModel().getMinSelectionIndex(),
table.getSelectionModel().getMinSelectionIndex() + changed);
struct.setStructChanged(true);
}
} else if (event.getActionCommand().equals(CMD_CUT)) {
ListSelectionModel lsm = table.getSelectionModel();
int min = lsm.getMinSelectionIndex();
int max = lsm.getMaxSelectionIndex();
lsm.removeIndexInterval(min, max);
table.clearSelection();
StructClipboard.getInstance().cut(struct, min, max);
} else if (event.getActionCommand().equals(CMD_COPY)) {
StructClipboard.getInstance().copy(struct, table.getSelectionModel().getMinSelectionIndex(),
table.getSelectionModel().getMaxSelectionIndex());
} else if (event.getActionCommand().equals(CMD_PASTE)) {
table.clearSelection();
table.scrollRectToVisible(table.getCellRect(StructClipboard.getInstance().paste(struct), 1, true));
} else if (event.getActionCommand().equals(CMD_TOHEX)) {
convertAttribute(table.getSelectedRow(), miToHex);
} else if (event.getActionCommand().equals(CMD_TOBIN)) {
convertAttribute(table.getSelectedRow(), miToBin);
} else if (event.getActionCommand().equals(CMD_TODEC)) {
convertAttribute(table.getSelectedRow(), miToDec);
} else if (event.getActionCommand().equals(CMD_TOINT)) {
convertAttribute(table.getSelectedRow(), miToInt);
} else if (event.getActionCommand().equals(CMD_TOSTRING)) {
convertAttribute(table.getSelectedRow(), miToString);
} else if (event.getActionCommand().equals(CMD_RESET)) {
convertAttribute(table.getSelectedRow(), miReset);
} else if (event.getActionCommand().equals(CMD_SHOWVIEWER)) {
// this should only be available for DlgResources
DlgResource dlgRes = (DlgResource) struct;
dlgRes.showStateWithStructEntry((StructEntry)table.getValueAt(table.getSelectedRow(), 1));
JComponent detailViewer = dlgRes.getViewerTab(0);
JTabbedPane parent = (JTabbedPane) detailViewer.getParent();
parent.getModel().setSelectedIndex(parent.indexOfComponent(detailViewer));
} else if (event.getActionCommand().equals(CMD_SHOWNEWVIEWER)) {
// get a copy of the resource first
DlgResource dlgRes = (DlgResource) ResourceFactory.getResource(struct.getResourceEntry());
createViewFrame(getTopLevelAncestor(), dlgRes);
dlgRes.showStateWithStructEntry((StructEntry)table.getValueAt(table.getSelectedRow(), 1));
}
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface ChangeListener ---------------------
@Override
public void stateChanged(ChangeEvent event)
{
considerMenuEnabled();
}
// --------------------- End Interface ChangeListener ---------------------
// --------------------- Begin Interface ItemListener ---------------------
@Override
public void itemStateChanged(ItemEvent event)
{
if (event.getSource() instanceof ButtonPopupMenu &&
buttonPanel.getControlPosition((JComponent)event.getSource()) >= 0) {
if (buttonPanel.getControlByType(ButtonPanel.Control.ADD) == event.getSource()) {
if (!(struct instanceof HasAddRemovable)) {
return;
}
ButtonPopupMenu bpmAdd = (ButtonPopupMenu)event.getSource();
JMenuItem item = bpmAdd.getSelectedItem();
AddRemovable toadd = null;
for (final AddRemovable emptyType : emptyTypes) {
if (emptyType != null && emptyType.getName().equals(item.getText())) {
toadd = emptyType;
break;
}
}
try {
toadd = ((HasAddRemovable)struct).confirmAddEntry(toadd);
if (toadd != null) {
toadd = (AddRemovable)toadd.clone();
}
} catch (Exception e) {
e.printStackTrace();
return;
}
int index = struct.addDatatype(toadd);
table.getSelectionModel().setSelectionInterval(index, index);
table.scrollRectToVisible(table.getCellRect(index, 1, true));
} else if (buttonPanel.getControlByType(ButtonPanel.Control.FIND_MENU) == event.getSource()) {
ButtonPopupMenu bpmFind = (ButtonPopupMenu)event.getSource();
JMenuItem item = bpmFind.getSelectedItem();
if (item == miFindAttribute) {
new AttributeSearcher(struct, (StructEntry)table.getValueAt(table.getSelectedRow(), 1),
getTopLevelAncestor());
} else if (item == miFindReferences) {
new ReferenceSearcher(struct.getResourceEntry(), getTopLevelAncestor());
} else if (item == miFindStateReferences) {
State state = (State)table.getValueAt(table.getSelectedRow(), 1);
new DialogStateReferenceSearcher(struct.getResourceEntry(), state.getNumber(), getTopLevelAncestor());
} else if (item == miFindRefToItem) {
new DialogItemRefSearcher((DlgResource) struct, table.getValueAt(table.getSelectedRow(), 1),
getTopLevelAncestor());
}
}
}
}
// --------------------- End Interface ItemListener ---------------------
// --------------------- Begin Interface ListSelectionListener ---------------------
@Override
public void valueChanged(ListSelectionEvent event)
{
if (event.getValueIsAdjusting())
return;
considerMenuEnabled();
ListSelectionModel lsm = (ListSelectionModel)event.getSource();
if (lsm.isSelectionEmpty() || lsm.getMaxSelectionIndex() != lsm.getMinSelectionIndex()) {
tatext.setText("");
// allow removal of multiple AddRemovable entries
boolean removeEnabled = !lsm.isSelectionEmpty();
for (int cur = lsm.getMinSelectionIndex(), max = lsm.getMaxSelectionIndex(); cur <= max && removeEnabled; cur++) {
removeEnabled = table.getModel().getValueAt(cur, 1) instanceof AddRemovable;
}
JButton bRemove = (JButton)buttonPanel.getControlByType(ButtonPanel.Control.REMOVE);
if (bRemove != null) {
bRemove.setEnabled(removeEnabled);
}
JButton bView = (JButton)buttonPanel.getControlByType(ButtonPanel.Control.VIEW_EDIT);
if (bView != null) {
bView.setEnabled(false);
}
cards.show(lowerpanel, CARD_EMPTY);
miToHex.setEnabled(false);
miToBin.setEnabled(false);
miToDec.setEnabled(false);
miToInt.setEnabled(false);
miToString.setEnabled(false);
miReset.setEnabled(false);
miShowViewer.setEnabled(false);
if (miShowNewViewer != null) {
miShowNewViewer.setEnabled(false);
}
if (miFindAttribute != null) {
miFindAttribute.setEnabled(false);
}
if (miFindStateReferences != null) {
miFindStateReferences.setEnabled(false);
}
if (miFindRefToItem != null) {
miFindRefToItem.setEnabled(false);
}
}
else {
table.scrollRectToVisible(table.getCellRect(lsm.getMinSelectionIndex(), 0, true));
Object selected = table.getModel().getValueAt(lsm.getMinSelectionIndex(), 1);
miPaste.setEnabled(
StructClipboard.getInstance().getContentType(struct) == StructClipboard.CLIPBOARD_ENTRIES);
JButton bRemove = (JButton)buttonPanel.getControlByType(ButtonPanel.Control.REMOVE);
if (bRemove != null) {
bRemove.setEnabled(selected instanceof AddRemovable && ((AddRemovable)selected).canRemove());
}
JButton bView = (JButton)buttonPanel.getControlByType(ButtonPanel.Control.VIEW_EDIT);
if (bView != null) {
bView.setEnabled(selected instanceof Viewable);
}
if (miFindAttribute != null) {
miFindAttribute.setEnabled(!(selected instanceof AbstractStruct));
}
if (miFindStateReferences != null) {
miFindStateReferences.setEnabled(selected instanceof State);
}
boolean isDataType = (selected instanceof Datatype);
boolean isReadable = (selected instanceof Readable);
miToHex.setEnabled(isDataType && isReadable && !(selected instanceof HexNumber ||
selected instanceof Unknown ||
selected instanceof SectionCount ||
selected instanceof AbstractCode));
if (selected instanceof UnknownBinary || selected instanceof UnknownDecimal) {
miToHex.setEnabled(true);
}
miToBin.setEnabled(isDataType && isReadable &&
!(selected instanceof UnknownBinary ||
selected instanceof SectionCount ||
selected instanceof AbstractCode));
miToDec.setEnabled(isDataType && isReadable &&
!(selected instanceof UnknownDecimal ||
selected instanceof SectionCount ||
selected instanceof AbstractCode));
miToInt.setEnabled(isDataType && isReadable &&
(selected instanceof Datatype && ((Datatype)selected).getSize() <= 4) &&
!(selected instanceof DecNumber ||
selected instanceof SectionCount ||
selected instanceof AbstractCode));
miToString.setEnabled(isDataType && isReadable &&
(selected instanceof Unknown || selected instanceof ResourceRef) &&
!(selected instanceof AbstractCode));
miReset.setEnabled(isDataType && isReadable &&
isCachedStructEntry(((Datatype)selected).getOffset()) &&
getCachedStructEntry(((Datatype)selected).getOffset()) instanceof Readable &&
!(selected instanceof AbstractCode));
boolean isSpecialDlgStruct = (selected instanceof State
|| selected instanceof Transition
|| selected instanceof AbstractCode);
if (miFindRefToItem != null) {
miFindRefToItem.setEnabled(isSpecialDlgStruct);
}
miShowViewer.setEnabled(isSpecialDlgStruct);
miShowNewViewer.setEnabled(isSpecialDlgStruct);
if (selected instanceof Editable) {
editable = (Editable)selected;
editpanel.removeAll();
JComponent editor = editable.edit(this);
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
editpanel.setLayout(gbl);
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.VERTICAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(3, 3, 3, 3);
gbl.setConstraints(editor, gbc);
editpanel.add(editor);
editpanel.revalidate();
editpanel.repaint();
cards.show(lowerpanel, CARD_EDIT);
editable.select();
}
else if (selected instanceof InlineEditable) {
tatext.setText("");
cards.show(lowerpanel, CARD_EMPTY);
}
else {
editable = null;
if (selected instanceof AbstractStruct)
tatext.setText(((AbstractStruct)selected).toMultiLineString());
else
tatext.setText(selected.toString());
tatext.setCaretPosition(0);
cards.show(lowerpanel, CARD_TEXT);
}
}
}
// --------------------- End Interface ListSelectionListener ---------------------
// --------------------- Begin Interface TableModelListener ---------------------
@Override
public void tableChanged(TableModelEvent event)
{
if (event.getType() == TableModelEvent.UPDATE) {
StructEntry structEntry = struct.getField(event.getFirstRow());
if (structEntry instanceof Editable && (editable == null || (structEntry.getOffset() == editable.getOffset() &&
structEntry != editable))) {
editable = (Editable)structEntry;
editpanel.removeAll();
JComponent editor = editable.edit(this);
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
editpanel.setLayout(gbl);
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.VERTICAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(3, 3, 3, 3);
gbl.setConstraints(editor, gbc);
editpanel.add(editor);
editpanel.revalidate();
editpanel.repaint();
cards.show(lowerpanel, CARD_EDIT);
editable.select();
}
}
}
// --------------------- End Interface TableModelListener ---------------------
// --------------------- Begin Interface ComponentListener ---------------------
@Override
public void componentShown(ComponentEvent e)
{
}
@Override
public void componentResized(ComponentEvent e)
{
if (e.getSource() == this) {
// ensure fixed lower panel height
int loc = Math.max(50, splitv.getHeight() - NearInfinity.getInstance().getTablePanelHeight());
splitv.setDividerLocation(loc);
splitterSet = true; // XXX: work-around to prevent storing uninitialized splitter location in prefs
} else if (e.getSource() == lowerpanel) {
if (oldSplitterHeight > 0 && splitv.getHeight() == oldSplitterHeight) {
int v = Math.max(50, splitv.getHeight() - splitv.getDividerLocation());
NearInfinity.getInstance().updateTablePanelHeight(v);
} else {
// don't update splitter location when resizing window
oldSplitterHeight = splitv.getHeight();
}
} else if (e.getSource() == table) {
// ensure fixed "Attribute" and "Offset" column widths
int w = table.getWidth();
int w0 = table.getColumnModel().getColumn(0).getPreferredWidth();
int w2 = (table.getColumnCount() == 3) ? table.getColumnModel().getColumn(2).getPreferredWidth() : 0;
int w1 = w - (w0 + w2);
table.getColumnModel().getColumn(0).setPreferredWidth(w0);
table.getColumnModel().getColumn(1).setPreferredWidth(w1);
if (table.getColumnCount() == 3) {
table.getColumnModel().getColumn(2).setPreferredWidth(w2);
}
}
}
@Override
public void componentMoved(ComponentEvent e)
{
}
@Override
public void componentHidden(ComponentEvent e)
{
}
// --------------------- End Interface ComponentListener ---------------------
/**
* Stores the currently selected list items internally.
* Intended to be used with {@link #restoreCurrentSelection()}.
*/
public void storeCurrentSelection()
{
if (storedSelection == null) {
int[] selection = table.getSelectedRows();
if (selection.length > 0) {
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
for (final int idx: selection) {
min = Math.min(min, idx);
max = Math.max(max, idx);
}
storedSelection = new Pair<Integer>(min, max);
}
}
}
/** Restores the selection state saved by the method {@link #storeCurrentSelection()}. */
public void restoreCurrentSelection()
{
if (storedSelection != null) {
table.setRowSelectionInterval(storedSelection.getFirst(), storedSelection.getSecond());
storedSelection = null;
}
}
public ButtonPanel getButtonPanel()
{
return buttonPanel;
}
public void close()
{
// storing current table column widths and divider location
NearInfinity.getInstance().updateTableColumnWidth(0, table.getColumnModel().getColumn(0).getPreferredWidth());
NearInfinity.getInstance().updateTableColumnWidth(1, table.getColumnModel().getColumn(1).getPreferredWidth());
if (table.getColumnCount() == 3) {
NearInfinity.getInstance().updateTableColumnWidth(2, table.getColumnModel().getColumn(2).getPreferredWidth());
}
if (splitterSet) {
NearInfinity.getInstance().updateTablePanelHeight(splitv.getHeight() - splitv.getDividerLocation());
}
StructClipboard.getInstance().removeChangeListener(this);
if (struct instanceof Resource) {
if (tabbedPane != null && struct instanceof HasViewerTabs) {
lastIndex = tabbedPane.getSelectedIndex();
lastIndexStruct = struct.getClass();
} else {
lastIndexStruct = null;
}
if (table.getSelectionModel().getMinSelectionIndex() != -1) {
lastName = ((StructEntry)table.getModel().getValueAt(table.getSelectionModel()
.getMinSelectionIndex(), 1)).getName();
lastNameStruct = struct.getClass();
} else {
lastName = null;
lastNameStruct = null;
}
}
if (tabbedPane != null) {
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
Component c = tabbedPane.getComponentAt(i);
if (c instanceof Closeable) {
try {
((Closeable)c).close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
tabbedPane = null;
}
}
public StructEntry getSelectedEntry()
{
if (table.getSelectedRow() == -1)
return null;
return (StructEntry)table.getModel().getValueAt(table.getSelectedRow(), 1);
}
public int getSelectedRow()
{
return table.getSelectedRow();
}
public void selectEntry(String name)
{
for (int i = 0; i < struct.getFieldCount(); i++) {
StructEntry entry = struct.getField(i);
if (entry.getName().equals(name)) {
selectEntry(entry);
return;
}
}
}
public void selectEntry(int offset)
{
selectEntry(offset, true);
}
public void selectEntry(int offset, boolean recursive)
{
for (int i = 0; i < struct.getFieldCount(); i++) {
StructEntry entry = struct.getField(i);
if (entry instanceof AbstractStruct && recursive) {
selectEntry((AbstractStruct)entry, offset);
} else if (entry.getOffset() == offset) {
selectEntry(entry);
}
}
}
/** Helper method for finding out if a "View" tab is available. */
public boolean hasViewTab()
{
return hasTab(TAB_VIEW);
}
/** Helper method for finding out if "View" tab is selected. */
public boolean isViewTabSelected()
{
return isTabSelected(getTabIndex(TAB_VIEW));
}
/** Helper method for selecting "View" tab if available. */
public void selectViewTab()
{
selectTab(getTabIndex(TAB_VIEW));
}
/** Returns whether "Edit" tab is selected. */
public boolean isEditTabSelected()
{
return isTabSelected(getTabIndex(TAB_EDIT));
}
/** Selects the "Edit" tab. */
public void selectEditTab()
{
selectTab(getTabIndex(TAB_EDIT));
}
/** Returns tab index of "Edit" tab. */
public int getEditTabIndex()
{
return getTabIndex(TAB_EDIT);
}
/** Helper method for finding out if a "Raw" tab is available. */
public boolean hasRawTab()
{
return hasTab(TAB_RAW);
}
/** Helper method for finding out if "Raw" tab is selected. */
public boolean isRawTabSelected()
{
return isTabSelected(getTabIndex(TAB_RAW));
}
/** Helper method for selecting "Raw" tab if available. */
public void selectRawTab()
{
selectTab(getTabIndex(TAB_RAW));
}
/** Returns whether the tab with the given name exists. */
public boolean hasTab(String name)
{
return (getTabIndex(name) >= 0);
}
/** Returns whether the specified tab is currently selected. */
public boolean isTabSelected(int index)
{
if (index >= 0 && index < tabbedPane.getTabCount()) {
return (index == tabbedPane.getSelectedIndex());
}
return false;
}
/** Selects the specified tab if available. */
public void selectTab(int index)
{
if (tabbedPane != null) {
if (index >= 0 && index < tabbedPane.getTabCount()) {
tabbedPane.setSelectedIndex(index);
}
}
}
/** Returns tab index of specified tab name. */
public int getTabIndex(String name)
{
if (tabbedPane != null && name != null) {
for (int i = 0, count = tabbedPane.getTabCount(); i < count; i++) {
if (name.equals(tabbedPane.getTitleAt(i))) {
return i;
}
}
}
return -1;
}
/** Adds a ChangeListener to the TabbedPane if available. */
public void addTabChangeListener(ChangeListener l)
{
if (tabbedPane != null && l != null) {
tabbedPane.addChangeListener(l);
}
}
/** Removes a ChangeListener from the TabbedPane if available. */
public void removeTabChangeListener(ChangeListener l)
{
if (tabbedPane != null && l != null) {
tabbedPane.removeChangeListener(l);
}
}
/** Returns an array of ChangeListeners added to the TabbedPane if available. */
public ChangeListener[] getTabChangeListeners()
{
if (tabbedPane != null) {
return tabbedPane.getChangeListeners();
} else {
return new ChangeListener[0];
}
}
/**
* Returns an already existing ViewFrame of the given Viewable object if available.
* Returns a new ViewFrame object otherwise. Assumes top level ancestor of the given view as parent.
*/
public ViewFrame getViewFrame(Viewable view)
{
return getViewFrame(getTopLevelAncestor(), view);
}
/**
* Returns an already existing ViewFrame of the given Viewable object if available.
* Returns a new ViewFrame object otherwise.
*/
public ViewFrame getViewFrame(Component parent, Viewable view)
{
return createViewFrame(parent, view);
}
private void considerMenuEnabled()
{
ListSelectionModel lsm = table.getSelectionModel();
if (lsm.isSelectionEmpty()) {
miCopyValue.setEnabled(false);
miPasteValue.setEnabled(false);
miCut.setEnabled(false);
miCopy.setEnabled(false);
miPaste.setEnabled(
StructClipboard.getInstance().getContentType(struct) == StructClipboard.CLIPBOARD_ENTRIES);
}
else if (lsm.getMaxSelectionIndex() != lsm.getMinSelectionIndex()) {
boolean isRemovable = true;
boolean isValue = true;
for (int i = lsm.getMinSelectionIndex(); i <= lsm.getMaxSelectionIndex(); i++) {
Object o = table.getModel().getValueAt(i, 1);
if (!(o instanceof AddRemovable))
isRemovable = false;
else
isRemovable = ((AddRemovable)o).canRemove();
if (o instanceof AbstractStruct)
isValue = false;
}
miCopyValue.setEnabled(isValue);
miPasteValue.setEnabled(false);
miCut.setEnabled(isRemovable);
miCopy.setEnabled(isRemovable);
miPaste.setEnabled(
StructClipboard.getInstance().getContentType(struct) == StructClipboard.CLIPBOARD_ENTRIES);
}
else {
Object selected = table.getModel().getValueAt(lsm.getMinSelectionIndex(), 1);
miPaste.setEnabled(
StructClipboard.getInstance().getContentType(struct) == StructClipboard.CLIPBOARD_ENTRIES);
if (selected instanceof AddRemovable) {
miCopyValue.setEnabled(false);
miPasteValue.setEnabled(false);
miCut.setEnabled(((AddRemovable)selected).canRemove());
miCopy.setEnabled(((AddRemovable)selected).canRemove());
}
else {
miCopyValue.setEnabled(!(selected instanceof AbstractStruct));
miPasteValue.setEnabled(miCopyValue.isEnabled() &&
StructClipboard.getInstance().getContentType(struct) ==
StructClipboard.CLIPBOARD_VALUES);
miCut.setEnabled(false);
miCopy.setEnabled(false);
}
}
}
private void convertAttribute(int index, JMenuItem menuitem)
{
StructEntry entry = struct.getField(index);
if (!isCachedStructEntry(entry.getOffset())) setCachedStructEntry(entry);
ByteBuffer bb = StreamUtils.getByteBuffer(entry.getSize());
try {
try (ByteBufferOutputStream bbos = new ByteBufferOutputStream(bb)) {
entry.write(bbos);
}
bb.position(0);
StructEntry newentry;
if (menuitem == miToHex) {
newentry = new Unknown(bb, 0, entry.getSize(), entry.getName());
} else if (menuitem == miToBin) {
newentry = new UnknownBinary(bb, 0, entry.getSize(), entry.getName());
} else if (menuitem == miToDec) {
newentry = new UnknownDecimal(bb, 0, entry.getSize(), entry.getName());
} else if (menuitem == miToInt) {
newentry = new DecNumber(bb, 0, entry.getSize(), entry.getName());
} else if (menuitem == miToString) {
newentry = new TextString(bb, 0, entry.getSize(), entry.getName());
} else if (menuitem == miReset) {
newentry = removeCachedStructEntry(entry.getOffset());
if (newentry == null || !(newentry instanceof Readable)) {
newentry = entry;
} else {
((Readable)newentry).read(bb, 0);
}
} else {
throw new NullPointerException();
}
newentry.setOffset(entry.getOffset());
struct.setListEntry(index, newentry);
table.getSelectionModel().removeSelectionInterval(index, index);
table.getSelectionModel().addSelectionInterval(index, index);
} catch (Exception e) {
e.printStackTrace();
}
}
private void selectEntry(StructEntry structEntry)
{
for (int i = 0; i < struct.getFieldCount(); i++) {
StructEntry o = struct.getField(i);
if (structEntry == o) {
table.getSelectionModel().setSelectionInterval(i, i);
selectEditTab();
return;
}
else if (o instanceof AbstractStruct)
selectSubEntry((AbstractStruct)o, structEntry);
}
}
private void selectEntry(AbstractStruct subStruct, int offset)
{
for (int i = 0; i < subStruct.getFieldCount(); i++) {
StructEntry entry = subStruct.getField(i);
if (entry instanceof AbstractStruct)
selectEntry((AbstractStruct)entry, offset);
else if (entry.getOffset() == offset)
selectSubEntry(subStruct, entry);
}
}
private void selectSubEntry(AbstractStruct subStruct, StructEntry structEntry)
{
for (int i = 0; i < subStruct.getFieldCount(); i++) {
StructEntry o = subStruct.getField(i);
if (structEntry == o) {
createViewFrame(getTopLevelAncestor(), subStruct);
// new ViewFrame(getTopLevelAncestor(), subStruct);
StructViewer viewer = subStruct.getViewer();
viewer.table.getSelectionModel().setSelectionInterval(i, i);
table.scrollRectToVisible(table.getCellRect(i, 0, true));
viewer.selectEditTab();
return;
}
else if (o instanceof AbstractStruct)
selectSubEntry((AbstractStruct)o, structEntry);
}
}
// Caches the given StructEntry object
private void setCachedStructEntry(StructEntry struct)
{
if (struct != null) {
if (!entryMap.containsKey(Integer.valueOf(struct.getOffset()))) {
entryMap.put(struct.getOffset(), struct);
}
}
}
private StructEntry getCachedStructEntry(int offset)
{
return entryMap.get(Integer.valueOf(offset));
}
// Removes the StructEntry object at the given offset and returns it
private StructEntry removeCachedStructEntry(int offset)
{
return entryMap.remove(Integer.valueOf(offset));
}
// Indicates whether the given StructEntry object is equal to the cached object
private boolean isCachedStructEntry(int offset)
{
return entryMap.containsKey(Integer.valueOf(offset));
}
// Recycles existing ViewFrame constructs if possible
private ViewFrame createViewFrame(Component parent, Viewable view)
{
ViewFrame frame = null;
if (view != null) {
if (parent == null) {
parent = getTopLevelAncestor();
}
frame = viewMap.get(view);
if (frame == null || !frame.isVisible()) {
frame = new ViewFrame(parent, view);
viewMap.put(view, frame);
} else {
frame.toFront();
}
}
return frame;
}
// -------------------------- INNER CLASSES --------------------------
private final class PopupListener extends MouseAdapter
{
@Override
public void mousePressed(MouseEvent e)
{
maybeShowPopup(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e)
{
if (e.isPopupTrigger())
popupmenu.show(e.getComponent(), e.getX(), e.getY());
}
}
private final class StructTable extends JTable implements Printable
{
private StructTable()
{
}
@Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException
{
Graphics2D g2 = (Graphics2D)graphics;
g2.setColor(Color.black);
int fontHeight = g2.getFontMetrics().getHeight();
int fontDesent = g2.getFontMetrics().getDescent();
//leave room for page number
double pageHeight = pageFormat.getImageableHeight() - (double)fontHeight;
double pageWidth = pageFormat.getImageableWidth();
double tableWidth = (double)getColumnModel().getTotalColumnWidth();
double scale = (double)1;
if (tableWidth >= pageWidth)
scale = pageWidth / tableWidth;
double headerHeightOnPage = (double)getTableHeader().getHeight() * scale;
double tableWidthOnPage = tableWidth * scale;
double oneRowHeight = (double)getRowHeight() * scale;
int numRowsOnAPage = (int)((pageHeight - headerHeightOnPage) / oneRowHeight);
double pageHeightForTable = oneRowHeight * (double)numRowsOnAPage;
int totalNumPages = (int)Math.ceil((double)getRowCount() / (double)numRowsOnAPage);
if (pageIndex >= totalNumPages)
return NO_SUCH_PAGE;
g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
//bottom center
g2.drawString(struct.getName() + " - Page " + (pageIndex + 1) + '/' + totalNumPages,
((int)pageWidth >> 1) - 35, (int)(pageHeight + (double)fontHeight - (double)fontDesent));
g2.translate((double)0.0f, headerHeightOnPage - (double)pageIndex * pageHeightForTable);
//If this piece of the table is smaller than the size available, clip to the appropriate bounds.
if (pageIndex + 1 == totalNumPages) {
int lastRowPrinted = numRowsOnAPage * pageIndex;
int numRowsLeft = getRowCount() - lastRowPrinted;
g2.setClip(0, (int)(pageHeightForTable * (double)pageIndex), (int)Math.ceil(tableWidthOnPage),
(int)Math.ceil(oneRowHeight * (double)numRowsLeft));
}
//else clip to the entire area available.
else
g2.setClip(0, (int)(pageHeightForTable * (double)pageIndex), (int)Math.ceil(tableWidthOnPage),
(int)Math.ceil(pageHeightForTable));
g2.scale(scale, scale);
paint(g2);
g2.scale((double)1 / scale, (double)1 / scale);
g2.translate((double)0.0f, (double)pageIndex * pageHeightForTable - headerHeightOnPage);
g2.setClip(0, 0, (int)Math.ceil(tableWidthOnPage), (int)Math.ceil(headerHeightOnPage));
g2.scale(scale, scale);
getTableHeader().paint(g2);
//paint header at top
return Printable.PAGE_EXISTS;
}
}
}