blob: f91ff538f521227affd2c2dba3ec6bd5b2ab8a1f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Teodor Madan (Freescale) - Bug 292360 - [Memory View] platform renderings do not implement correctly IMemoryRendering#getControl
* Teodor Madan (Freescale) - Bug 292426 - [Memory View] platform renderings cannot be repositioned from non-UI thread through calls to IRepositionableMemoryRendering#goToAddress
* Teodor Madan (Freescale) & Jeremiah Swan (IBM) - Bug 300036 - [Memory View] NPE in AbstractAsyncTableRendering#getSelectedAddress on rendering creation
*******************************************************************************/
package org.eclipse.debug.internal.ui.memory.provisional;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
import org.eclipse.debug.internal.ui.DebugUIMessages;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.IInternalDebugUIConstants;
import org.eclipse.debug.internal.ui.memory.IPersistableDebugElement;
import org.eclipse.debug.internal.ui.preferences.IDebugPreferenceConstants;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil;
import org.eclipse.debug.internal.ui.views.memory.renderings.AbstractBaseTableRendering;
import org.eclipse.debug.internal.ui.views.memory.renderings.AbstractVirtualContentTableModel;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncCopyTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncPrintTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncTableRenderingCellModifier;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncTableRenderingViewer;
import org.eclipse.debug.internal.ui.views.memory.renderings.CopyTableRenderingToClipboardAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.FormatTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.FormatTableRenderingDialog;
import org.eclipse.debug.internal.ui.views.memory.renderings.GoToAddressAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.GoToAddressComposite;
import org.eclipse.debug.internal.ui.views.memory.renderings.IPresentationErrorListener;
import org.eclipse.debug.internal.ui.views.memory.renderings.IVirtualContentListener;
import org.eclipse.debug.internal.ui.views.memory.renderings.MemorySegment;
import org.eclipse.debug.internal.ui.views.memory.renderings.PendingPropertyChanges;
import org.eclipse.debug.internal.ui.views.memory.renderings.PrintTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.ReformatAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.ResetToBaseAddressAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingContentDescriptor;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingLine;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.memory.AbstractTableRendering;
import org.eclipse.debug.ui.memory.IMemoryBlockTablePresentation;
import org.eclipse.debug.ui.memory.IMemoryRendering;
import org.eclipse.debug.ui.memory.IMemoryRenderingContainer;
import org.eclipse.debug.ui.memory.IMemoryRenderingSite;
import org.eclipse.debug.ui.memory.IMemoryRenderingSynchronizationService;
import org.eclipse.debug.ui.memory.IMemoryRenderingType;
import org.eclipse.debug.ui.memory.IResettableMemoryRendering;
import org.eclipse.debug.ui.memory.MemoryRenderingElement;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.IBasicPropertyConstants;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.dialogs.PropertyDialogAction;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.progress.UIJob;
/**
* Abstract implementation of a table rendering.
* <p>
* Clients should subclass from this class if they wish to provide a table
* rendering.
* </p>
* <p>
*
* The label of the rendering is constructed by retrieving the expression from
* <code>IMemoryBlockExtension</code>. For IMemoryBlock, the label is
* constructed using the memory block's start address.
*
* This rendering manages the change states of its memory bytes if the memory
* block does not opt to manage the change states. For IMemoryBlockExtension, if
* the memory block returns false when #supportsChangeManagement() is called,
* this rendering will calculate the change state for each byte when its content
* is updated. Clients may manages the change states of its memory block by
* returning true when #supportsChangeManagement() is called. This will cause
* this rendering to stop calculating the change states of the memory block.
* Instead it would rely on the attributes returned in the MemoryByte array to
* determine if a byte has changed. For IMemoryBlock, this rendering will manage
* the change states its content.
*
* When firing change event, be aware of the following: - whenever a change
* event is fired, the content provider for Memory View view checks to see if
* memory has actually changed. - If memory has actually changed, a refresh will
* commence. Changes to the memory block will be computed and will be shown with
* the delta icons. - If memory has not changed, content will not be refreshed.
* However, previous delta information will be erased. The screen will be
* refreshed to show that no memory has been changed. (All delta icons will be
* removed.)
*
* Please note that these APIs will be called multiple times by the Memory View.
* To improve performance, debug adapters need to cache the content of its
* memory block and only retrieve updated data when necessary.
* </p>
*
* @since 3.2
*/
public abstract class AbstractAsyncTableRendering extends AbstractBaseTableRendering implements IPropertyChangeListener, IResettableMemoryRendering {
/**
* Property identifier for the selected address in a table rendering This
* property is used for synchronization between renderings.
*/
public static final String PROPERTY_SELECTED_ADDRESS = AbstractTableRendering.PROPERTY_SELECTED_ADDRESS;
/**
* Property identifier for the column size in a table rendering This
* property is used for synchronization between renderings.
*/
public static final String PROPERTY_COL_SIZE = AbstractTableRendering.PROPERTY_COL_SIZE;
/**
* Property identifier for the top row address in a table rendering. This
* property is used for synchronization between renderings.
*/
public static final String PROPERTY_TOP_ADDRESS = AbstractTableRendering.PROPERTY_TOP_ADDRESS;
private static final String ID_ASYNC_TABLE_RENDERING_CONTEXT = "org.eclipse.debug.ui.memory.abstractasynctablerendering"; //$NON-NLS-1$
private static final String ID_GO_TO_ADDRESS_COMMAND = "org.eclipse.debug.ui.command.gotoaddress"; //$NON-NLS-1$
private static final String ID_NEXT_PAGE_COMMAND = "org.eclipse.debug.ui.command.nextpage"; //$NON-NLS-1$
private static final String ID_PREV_PAGE_COMMAND = "org.eclipse.debug.ui.command.prevpage"; //$NON-NLS-1$
/**
* Property identifier for the row size in a table rendering This property
* is used for synchronization between renderings.
*/
public static final String PROPERTY_ROW_SIZE = AbstractTableRendering.PROPERTY_ROW_SIZE;
private static final int DEFAULT_BUFFER_THRESHOLD = 1;
private boolean fActivated = false;
// TODO: review use of MemorySegment, need to be careful to ensure flexible
// hierarchy
private class ToggleAddressColumnAction extends Action {
public ToggleAddressColumnAction() {
super();
PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IDebugUIConstants.PLUGIN_ID + ".ShowAddressColumnAction_context"); //$NON-NLS-1$
updateActionLabel();
}
@Override
public void run() {
fIsShowAddressColumn = !fIsShowAddressColumn;
if (!fIsShowAddressColumn) {
fTableViewer.getTable().getColumn(0).setWidth(0);
} else {
fTableViewer.getTable().getColumn(0).pack();
}
updateActionLabel();
}
private void updateActionLabel() {
if (fIsShowAddressColumn) {
setText(DebugUIMessages.ShowAddressColumnAction_0);
} else {
setText(DebugUIMessages.ShowAddressColumnAction_1);
}
}
}
private class NextPageAction extends Action {
private NextPageAction() {
super();
setText(DebugUIMessages.AbstractTableRendering_4);
PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IDebugUIConstants.PLUGIN_ID + ".NextPageAction_context"); //$NON-NLS-1$
}
@Override
public void run() {
BigInteger address = fContentDescriptor.getLoadAddress();
address = address.add(BigInteger.valueOf(getPageSizeInUnits()));
handlePageStartAddressChanged(address);
}
}
private class PrevPageAction extends Action {
private PrevPageAction() {
super();
setText(DebugUIMessages.AbstractTableRendering_6);
PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IDebugUIConstants.PLUGIN_ID + ".PrevPageAction_context"); //$NON-NLS-1$
}
@Override
public void run() {
BigInteger address = fContentDescriptor.getLoadAddress();
address = address.subtract(BigInteger.valueOf(getPageSizeInUnits()));
handlePageStartAddressChanged(address);
}
}
private class RenderingGoToAddressAction extends GoToAddressAction {
public RenderingGoToAddressAction(IMemoryRenderingContainer container, AbstractBaseTableRendering rendering) {
super(container, rendering);
}
@Override
public void run() {
showGoToAddressComposite();
}
}
private class SwitchPageJob extends UIJob {
private Object fLock = new Object();
private boolean fShowMessagePage = false;
private String fMessage = IInternalDebugCoreConstants.EMPTY_STRING;
private SwitchPageJob() {
super("SwitchPageJob");//$NON-NLS-1$
setSystem(true);
}
private void setShowMessagePage(boolean showMsg) {
synchronized (fLock) {
fShowMessagePage = showMsg;
}
}
private void setMessage(String message) {
synchronized (fLock) {
fMessage = message;
}
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (fPageBook.isDisposed()) {
return Status.OK_STATUS;
}
String msgToShow = null;
boolean showMsgPage = false;
synchronized (fLock) {
msgToShow = fMessage;
showMsgPage = fShowMessagePage;
}
if (showMsgPage) {
StyledText styleText = null;
fShowMessage = true;
styleText = fTextViewer.getTextWidget();
if (styleText != null) {
styleText.setText(msgToShow);
}
fPageBook.showPage(fTextViewer.getControl());
} else {
fShowMessage = false;
fPageBook.showPage(fTableViewer.getControl().getParent());
}
return Status.OK_STATUS;
}
}
private static class SerialByObjectRule implements ISchedulingRule {
private Object fObject = null;
public SerialByObjectRule(Object lock) {
fObject = lock;
}
@Override
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
if (rule instanceof SerialByObjectRule) {
SerialByObjectRule rRule = (SerialByObjectRule) rule;
return fObject == rRule.fObject;
}
return false;
}
}
private PageBook fPageBook;
private AsyncTableRenderingViewer fTableViewer;
private TextViewer fTextViewer;
private Shell fToolTipShell;
private MemoryViewPresentationContext fPresentationContext;
private int fAddressableSize;
private TableRenderingContentDescriptor fContentDescriptor;
private int fBytePerLine;
private int fColumnSize;
private boolean fShowMessage = false;
private String fLabel;
private IWorkbenchAdapter fWorkbenchAdapter;
private int fPageSize;
private int fPreBufferSize = -1;
private int fPostBufferSize = -1;
private SashForm fSashForm;
private GoToAddressComposite fGoToAddressComposite;
// actions
private GoToAddressAction fGoToAddressAction;
private PrintTableRenderingAction fPrintViewTabAction;
private CopyTableRenderingToClipboardAction fCopyToClipboardAction;
private FormatTableRenderingAction fFormatRenderingAction;
private ReformatAction fReformatAction;
private ToggleAddressColumnAction fToggleAddressColumnAction;
private ResetToBaseAddressAction fResetMemoryBlockAction;
private PropertyDialogAction fPropertiesDialogAction;
private NextPageAction fNextAction;
private PrevPageAction fPrevAction;
private ArrayList<IContextActivation> fContext = new ArrayList<>();
private AbstractHandler fGoToAddressHandler;
private AbstractHandler fNextPageHandler;
private AbstractHandler fPrevPageHandler;
private boolean fIsCreated = false;
private boolean fIsDisposed = false;
private boolean fIsShowAddressColumn = true;
private SwitchPageJob fSwitchPageJob = new SwitchPageJob();
private boolean fError = false;
private PendingPropertyChanges fPendingSyncProperties;
// list of menu listeners for popupMenuMgr.
private ArrayList<IMenuListener> fMenuListeners;
private MenuManager fMenuMgr;
private ISchedulingRule serialByRenderingRule = new SerialByObjectRule(this);
/**
* Identifier for an empty group preceding all context menu actions (value
* <code>"popUpBegin"</code>).
*/
public static final String EMPTY_MEMORY_GROUP = "popUpBegin"; //$NON-NLS-1$
/**
* Identifier for an empty group following navigation actions in the
* rendering (value <code>navigationGroup</code>).
*/
public static final String EMPTY_NAVIGATION_GROUP = "navigationGroup"; //$NON-NLS-1$
/**
* Identifier for an empty group following actions that are only applicable
* in non-auto loading mode (value <code>nonAutoLoadGroup</code>).
*/
public static final String EMPTY_NON_AUTO_LOAD_GROUP = "nonAutoLoadGroup"; //$NON-NLS-1$
/**
* Identifier for an empty group following properties actions (value
* <code>propertyGroup</code>).
*/
public static final String EMPTY_PROPERTY_GROUP = "propertyGroup"; //$NON-NLS-1$
private ISelectionChangedListener fViewerSelectionChangedListener = event -> {
updateSyncTopAddress(getTopVisibleAddress());
updateSyncSelectedAddress(getSelectedAddress());
};
private SelectionAdapter fScrollBarSelectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateSyncTopAddress(getTopVisibleAddress());
}
};
private IModelChangedListener fModelChangedListener = (delta, proxy) -> {
if (delta.getElement() == getMemoryBlock()) {
showTable();
updateRenderingLabel(isVisible());
}
};
private IVirtualContentListener fViewerListener = new IVirtualContentListener() {
private int startThreshold;
private int endThreshold;
@Override
public void handledAtBufferStart() {
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
if (isDynamicLoad() && startThreshold != 0) {
BigInteger address = getTopVisibleAddress();
if (address != null && !isAtTopLimit()) {
reloadTable(address);
}
}
}
}
@Override
public void handleAtBufferEnd() {
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
if (isDynamicLoad() && endThreshold != 0) {
BigInteger address = getTopVisibleAddress();
if (address != null && !isAtBottomLimit()) {
reloadTable(address);
}
}
}
}
@Override
public int getThreshold(int bufferEndOrStart) {
int threshold = DEFAULT_BUFFER_THRESHOLD;
if (bufferEndOrStart == IVirtualContentListener.BUFFER_START) {
if (threshold > getPreBufferSize()) {
threshold = getPreBufferSize();
}
} else {
if (threshold > getPostBufferSize()) {
threshold = getPostBufferSize();
}
}
if (bufferEndOrStart == IVirtualContentListener.BUFFER_START) {
startThreshold = threshold;
} else {
endThreshold = threshold;
}
return threshold;
}
};
private IPresentationErrorListener fPresentationErrorListener = (monitor,
status) -> showMessage(status.getMessage());
/**
* Constructs a new table rendering of the specified type.
*
* @param renderingId memory rendering type identifier
*/
public AbstractAsyncTableRendering(String renderingId) {
super(renderingId);
}
@Override
public void resetRendering() throws DebugException {
if (!fIsCreated) {
return;
}
BigInteger baseAddress = fContentDescriptor.getContentBaseAddress();
fTableViewer.setSelection(baseAddress);
reloadTable(baseAddress);
fTableViewer.setTopIndex(baseAddress);
if (!isDynamicLoad()) {
updateSyncPageStartAddress(baseAddress);
}
updateSyncSelectedAddress(baseAddress);
updateSyncTopAddress(baseAddress);
}
@Override
public Control createControl(Composite parent) {
fPageBook = new PageBook(parent, SWT.NONE);
createMessagePage(fPageBook);
createTableViewer(fPageBook);
addListeners();
return fPageBook;
}
/**
* Create the error page of this rendering
*
* @param parent the parent to add the page to
*/
private void createMessagePage(Composite parent) {
if (fTextViewer == null) {
fTextViewer = new TextViewer(parent, SWT.WRAP);
fTextViewer.setDocument(new Document());
StyledText styleText = fTextViewer.getTextWidget();
styleText.setEditable(false);
styleText.setEnabled(false);
}
}
/**
* @param parent the parent composite
*/
private void createTableViewer(final Composite parent) {
StringBuilder buffer = new StringBuilder();
IMemoryRenderingType type = DebugUITools.getMemoryRenderingManager().getRenderingType(getRenderingId());
buffer.append(type.getLabel());
buffer.append(": "); //$NON-NLS-1$
buffer.append(DebugUIMessages.AbstractAsyncTableRendering_2);
Job job = new Job(buffer.toString()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
// gather information from memory block
initAddressableSize();
final BigInteger topVisibleAddress = getInitialTopVisibleAddress();
BigInteger mbBaseAddress = null;
try {
mbBaseAddress = getMemoryBlockBaseAddress();
} catch (DebugException e) {
fError = true;
showMessage(e.getMessage());
}
// if it takes too long to get the base address, and user has
// canceled
// remove this rendering.
if (monitor.isCanceled()) {
getMemoryRenderingContainer().removeMemoryRendering(AbstractAsyncTableRendering.this);
return Status.CANCEL_STATUS;
}
final BigInteger finalMbBaseAddress = mbBaseAddress;
final BigInteger initialSelectedAddress = getInitialSelectedAddress();
if (monitor.isCanceled()) {
getMemoryRenderingContainer().removeMemoryRendering(AbstractAsyncTableRendering.this);
return Status.CANCEL_STATUS;
}
createContentDescriptor(topVisibleAddress);
// if it takes too long to get other information, and user has
// canceled
// remove this rendering.
if (monitor.isCanceled()) {
getMemoryRenderingContainer().removeMemoryRendering(AbstractAsyncTableRendering.this);
return Status.CANCEL_STATUS;
}
// batch update on UI thread
UIJob uiJob = new UIJob("Create Table Viewer UI Job") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor progressMonitor) {
if (fPageBook.isDisposed()) {
return Status.OK_STATUS;
}
fSashForm = new SashForm(parent, SWT.VERTICAL);
fTableViewer = new AsyncTableRenderingViewer(AbstractAsyncTableRendering.this, fSashForm, SWT.VIRTUAL | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.HIDE_SELECTION | SWT.BORDER);
GridData data = new GridData(GridData.FILL_BOTH);
fTableViewer.getControl().setLayoutData(data);
createGoToAddressComposite(fSashForm);
hideGotoAddressComposite();
IMemoryRenderingSite site = getMemoryRenderingContainer().getMemoryRenderingSite();
IMemoryRenderingContainer container = getMemoryRenderingContainer();
fPresentationContext = new MemoryViewPresentationContext(site, container, AbstractAsyncTableRendering.this);
fTableViewer.setContext(fPresentationContext);
// must call this after the context is created as the
// information is stored in the context
getDynamicLoadFromPreference();
getPageSizeFromPreference();
int numberOfLines = getNumLinesToLoad();
fContentDescriptor.setNumLines(numberOfLines);
if (numberOfLines == 0) {
// if the table viewer is not initialized yet, add
// listener
// and load table when we can get the height of the
// table
fTableViewer.getTable().addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
fTableViewer.getTable().removePaintListener(this);
fContentDescriptor.setNumLines(getNumLinesToLoad());
refresh();
}
});
}
BigInteger baseAddress = finalMbBaseAddress;
if (baseAddress == null) {
baseAddress = BigInteger.ZERO;
}
if (!(getMemoryBlock() instanceof IMemoryBlockExtension) || !isDynamicLoad()) {
// If not extended memory block, do not create any
// buffer
// no scrolling
fContentDescriptor.setPreBuffer(0);
fContentDescriptor.setPostBuffer(0);
}
setupInitialFormat();
fTableViewer.setCellModifier(newInternalCellModifier());
fTableViewer.getTable().setHeaderVisible(true);
fTableViewer.getTable().setLinesVisible(true);
fTableViewer.addPresentationErrorListener(fPresentationErrorListener);
fTableViewer.setInput(getMemoryBlock());
fTableViewer.resizeColumnsToPreferredSize();
fTableViewer.setTopIndex(topVisibleAddress);
fTableViewer.setSelection(initialSelectedAddress);
// SET UP FONT
// set to a non-proportional font
fTableViewer.getTable().setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));
if (!fError) {
showTable();
}
fTableViewer.addVirtualContentListener(fViewerListener);
// create context menu
// create pop up menu for the rendering
createActions();
IMenuListener menuListener = AbstractAsyncTableRendering.this::fillContextMenu;
createPopupMenu(fTableViewer.getControl(), menuListener);
createPopupMenu(fTableViewer.getCursor(), menuListener);
fTableViewer.addSelectionChangedListener(fViewerSelectionChangedListener);
fTableViewer.getTable().getVerticalBar().addSelectionListener(fScrollBarSelectionListener);
// add resize listener to figure out number of visible
// lines
// so that the viewer get refreshed with the correct
// number of lines on the
// next refresh request
fTableViewer.getTable().addListener(SWT.Resize, event -> {
if (!fTableViewer.getTable().isDisposed()) {
fContentDescriptor.setNumLines(getNumLinesToLoad());
}
});
createToolTip();
if (isActivated()) {
activatePageActions();
}
// now the rendering is successfully created
fIsCreated = true;
return Status.OK_STATUS;
}
};
uiJob.setSystem(true);
uiJob.schedule();
return Status.OK_STATUS;
}
};
job.schedule();
}
/**
* Create popup menu for this rendering
*
* @param control - control to create the popup menu for
* @param menuListener - listener to notify when popup menu is about to show
*/
private void createPopupMenu(Control control, IMenuListener menuListener) {
IMemoryRenderingContainer container = getMemoryRenderingContainer();
if (fMenuMgr == null) {
fMenuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
fMenuMgr.setRemoveAllWhenShown(true);
IMemoryRenderingSite site = container.getMemoryRenderingSite();
String menuId = container.getId();
ISelectionProvider selProvider = site.getSite().getSelectionProvider();
addMenuListener(menuListener);
site.getSite().registerContextMenu(menuId, fMenuMgr, selProvider);
}
addMenuListener(menuListener);
Menu popupMenu = fMenuMgr.createContextMenu(control);
control.setMenu(popupMenu);
}
private void addMenuListener(IMenuListener menuListener) {
if (fMenuListeners == null) {
fMenuListeners = new ArrayList<>();
}
if (!fMenuListeners.contains(menuListener)) {
fMenuMgr.addMenuListener(menuListener);
fMenuListeners.add(menuListener);
}
}
private BigInteger getInitialSelectedAddress() {
// figure out selected address
BigInteger selectedAddress = (BigInteger) getSynchronizedProperty(AbstractAsyncTableRendering.PROPERTY_SELECTED_ADDRESS);
if (selectedAddress == null) {
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
try {
selectedAddress = ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress();
} catch (DebugException e) {
selectedAddress = BigInteger.ZERO;
}
if (selectedAddress == null) {
selectedAddress = BigInteger.ZERO;
}
} else {
long address = getMemoryBlock().getStartAddress();
selectedAddress = BigInteger.valueOf(address);
}
}
return selectedAddress;
}
private void addListeners() {
DebugUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
addRenderingToSyncService();
JFaceResources.getFontRegistry().addListener(this);
}
private void removeListeners() {
DebugUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
removeRenderingFromSyncService();
JFaceResources.getFontRegistry().removeListener(this);
if (fMenuListeners != null) {
Iterator<IMenuListener> iter = fMenuListeners.iterator();
while (iter.hasNext()) {
fMenuMgr.removeMenuListener(iter.next());
}
fMenuListeners.clear();
}
}
private void addRenderingToSyncService() {
IMemoryRenderingSynchronizationService syncService = getMemoryRenderingContainer().getMemoryRenderingSite().getSynchronizationService();
if (syncService == null) {
return;
}
syncService.addPropertyChangeListener(this, null);
}
private void removeRenderingFromSyncService() {
IMemoryRenderingSynchronizationService syncService = getMemoryRenderingContainer().getMemoryRenderingSite().getSynchronizationService();
if (syncService == null) {
return;
}
syncService.removePropertyChangeListener(this);
}
private void initAddressableSize() {
// set up addressable size and figure out number of bytes required per
// line
fAddressableSize = -1;
try {
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
fAddressableSize = ((IMemoryBlockExtension) getMemoryBlock()).getAddressableSize();
} else {
fAddressableSize = 1;
}
} catch (DebugException e1) {
DebugUIPlugin.log(e1);
// log error and default to 1
fAddressableSize = 1;
return;
}
if (fAddressableSize < 1) {
DebugUIPlugin.logErrorMessage("Invalid addressable size"); //$NON-NLS-1$
fAddressableSize = 1;
}
}
private BigInteger getInitialTopVisibleAddress() {
BigInteger topVisibleAddress = (BigInteger) getSynchronizedProperty(AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS);
if (topVisibleAddress == null) {
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
try {
topVisibleAddress = ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress();
} catch (DebugException e1) {
topVisibleAddress = new BigInteger("0"); //$NON-NLS-1$
}
} else {
topVisibleAddress = BigInteger.valueOf(getMemoryBlock().getStartAddress());
}
}
return topVisibleAddress;
}
private void setupInitialFormat() {
boolean validated = validateInitialFormat();
if (!validated) {
// pop up dialog to ask user for default values
StringBuilder msgBuffer = new StringBuilder(DebugUIMessages.AbstractTableRendering_20);
msgBuffer.append(" "); //$NON-NLS-1$
msgBuffer.append(this.getLabel());
msgBuffer.append("\n\n"); //$NON-NLS-1$
msgBuffer.append(DebugUIMessages.AbstractTableRendering_16);
msgBuffer.append("\n"); //$NON-NLS-1$
msgBuffer.append(DebugUIMessages.AbstractTableRendering_18);
msgBuffer.append("\n\n"); //$NON-NLS-1$
int bytePerLine = fBytePerLine;
int columnSize = fColumnSize;
// initialize this value to populate the dialog properly
fBytePerLine = getDefaultRowSize() / getAddressableSize();
fColumnSize = getDefaultColumnSize() / getAddressableSize();
FormatTableRenderingDialog dialog = new FormatTableRenderingDialog(this, DebugUIPlugin.getShell());
dialog.openError(msgBuffer.toString());
// restore to original value before formatting
fBytePerLine = bytePerLine;
fColumnSize = columnSize;
bytePerLine = dialog.getRowSize() * getAddressableSize();
columnSize = dialog.getColumnSize() * getAddressableSize();
format(bytePerLine, columnSize);
} else {
// Row size is stored as number of addressable units in preference
// store
int bytePerLine = getDefaultRowSize();
// column size is now stored as number of addressable units
int columnSize = getDefaultColumnSize();
// format memory block with specified "bytesPerLine" and
// "columnSize"
boolean ok = format(bytePerLine, columnSize);
if (!ok) {
// this is to ensure that the rest of the rendering can be
// created
// and we can recover from a format error
format(bytePerLine, bytePerLine);
}
}
}
private boolean validateInitialFormat() {
int rowSize = getDefaultRowSize();
int columnSize = getDefaultColumnSize();
if (rowSize < columnSize || rowSize % columnSize != 0 || rowSize == 0 || columnSize == 0) {
return false;
}
return true;
}
@Override
public Control getControl() {
return fPageBook;
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if (!fIsCreated) {
return;
}
// if memory view table font has changed
if (event.getProperty().equals(IInternalDebugUIConstants.FONT_NAME)) {
if (!fIsDisposed) {
Font memoryViewFont = JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME);
setFont(memoryViewFont);
}
return;
}
Object evtSrc = event.getSource();
// always update page size, only refresh if the table is visible
if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE)) {
getPageSizeFromPreference();
}
if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE)) {
getPreBufferSizeFromPreference();
fContentDescriptor.setPreBuffer(getPreBufferSize());
}
if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE)) {
getPostBufferSizeFromPreference();
fContentDescriptor.setPostBuffer(getPostBufferSize());
}
// do not handle event if the rendering is displaying an error
// or if it's not visible
if (isDisplayingError() || !isVisible()) {
handlePropertiesChangeWhenHidden(event);
return;
}
if (event.getProperty().equals(IDebugUIConstants.PREF_PADDED_STR) || event.getProperty().equals(IDebugUIConstants.PREF_CHANGED_DEBUG_ELEMENT_COLOR) || event.getProperty().equals(IDebugUIConstants.PREF_MEMORY_HISTORY_KNOWN_COLOR) || event.getProperty().equals(IDebugUIConstants.PREF_MEMORY_HISTORY_UNKNOWN_COLOR)) {
if (!fIsDisposed) {
fTableViewer.refresh(false);
}
return;
}
if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE) || event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE)) {
if (!fIsDisposed) {
fTableViewer.refresh(true);
}
return;
}
if (event.getProperty().equals(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM)) {
handleDyanicLoadChanged();
return;
}
if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE)) {
if (!isDynamicLoad()) {
int pageSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE);
handlePageSizeChanged(pageSize);
}
return;
}
if (evtSrc == this) {
return;
}
if (evtSrc instanceof IMemoryRendering) {
IMemoryRendering rendering = (IMemoryRendering) evtSrc;
IMemoryBlock memoryBlock = rendering.getMemoryBlock();
// do not handle event from renderings displaying other memory
// blocks
if (memoryBlock != getMemoryBlock()) {
return;
}
}
String propertyName = event.getProperty();
Object value = event.getNewValue();
if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_SELECTED_ADDRESS) && value instanceof BigInteger) {
selectedAddressChanged((BigInteger) value);
} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_COL_SIZE) && value instanceof Integer) {
columnSizeChanged(((Integer) value).intValue());
} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_ROW_SIZE) && value instanceof Integer) {
rowSizeChanged(((Integer) value).intValue());
} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS) && value instanceof BigInteger) {
topVisibleAddressChanged((BigInteger) value);
} else if (propertyName.equals(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS) && value instanceof BigInteger) {
handlePageStartAddressChanged((BigInteger) value);
}
}
/**
* @param pageSize the new page size
*/
private void handlePageSizeChanged(int pageSize) {
fPageSize = pageSize;
// only refresh if in non-auto-load mode
fContentDescriptor.setNumLines(pageSize);
refresh();
}
private void handlePropertiesChangeWhenHidden(PropertyChangeEvent event) {
if (fPendingSyncProperties == null) {
return;
}
String propertyName = event.getProperty();
Object value = event.getNewValue();
if (event.getSource() instanceof IMemoryRendering) {
IMemoryRendering rendering = (IMemoryRendering) event.getSource();
if (rendering == this || rendering.getMemoryBlock() != getMemoryBlock()) {
return;
}
}
if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_COL_SIZE) && value instanceof Integer) {
fPendingSyncProperties.setColumnSize(((Integer) value).intValue());
} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_ROW_SIZE) && value instanceof Integer) {
fPendingSyncProperties.setRowSize(((Integer) value).intValue());
}
else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_SELECTED_ADDRESS) && value instanceof BigInteger) {
fPendingSyncProperties.setSelectedAddress((BigInteger) value);
} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS) && value instanceof BigInteger) {
fPendingSyncProperties.setTopVisibleAddress((BigInteger) value);
} else if (propertyName.equals(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS) && value instanceof BigInteger) {
fPendingSyncProperties.setPageStartAddress((BigInteger) value);
} else if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE)) {
int pageSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE);
fPendingSyncProperties.setPageSize(pageSize);
}
}
private void topVisibleAddressChanged(final BigInteger address) {
final Runnable runnable = () -> {
if (fTableViewer.getTable().isDisposed()) {
return;
}
doTopVisibleAddressChanged(address);
};
runOnUIThread(runnable);
}
/**
* @param address the changed address
*/
private void doTopVisibleAddressChanged(final BigInteger address) {
if (fIsDisposed) {
return;
}
if (!isDynamicLoad()) {
fTableViewer.setTopIndex(address);
fTableViewer.topIndexChanged();
return;
}
if (!isAtTopBuffer(address) && !isAtBottomBuffer(address)) {
fTableViewer.setTopIndex(address);
fTableViewer.topIndexChanged();
} else {
reloadTable(address);
}
}
private boolean isAtBottomBuffer(BigInteger address) {
int idx = fTableViewer.indexOf(address);
if (idx < 0) {
return true;
}
int bottomIdx = idx + getNumberOfVisibleLines();
int elementsCnt = fTableViewer.getVirtualContentModel().getElements().length;
int numLinesLeft = elementsCnt - bottomIdx;
if (numLinesLeft < fViewerListener.getThreshold(IVirtualContentListener.BUFFER_END)) {
return true;
}
return false;
}
private boolean isAtTopBuffer(BigInteger address) {
int topIdx = fTableViewer.indexOf(address);
if (topIdx < fViewerListener.getThreshold(IVirtualContentListener.BUFFER_START)) {
return true;
}
return false;
}
private void runOnUIThread(final Runnable runnable) {
if (Display.getCurrent() != null) {
runnable.run();
} else {
UIJob job = new UIJob("Async Table Rendering UI Job") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
runnable.run();
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
}
private void selectedAddressChanged(final BigInteger address) {
Runnable runnable = () -> {
if (fTableViewer.getTable().isDisposed()) {
return;
}
// call this to make the table viewer to reload when needed
int i = fTableViewer.indexOf(address);
if (i < 0) {
// the model may not have been populated yet,
// try to predict if the address will be in the buffer
boolean contained = isAddressBufferred(address);
if (!contained) {
topVisibleAddressChanged(address);
}
}
fTableViewer.setSelection(address);
};
runOnUIThread(runnable);
}
private boolean isAddressBufferred(BigInteger address) {
// figure out the buffer top address
BigInteger loadAddress = fContentDescriptor.getLoadAddress();
loadAddress = MemoryViewUtil.alignToBoundary(loadAddress, getAddressableUnitPerLine());
int unitPerLine = getAddressableUnitPerLine();
loadAddress = loadAddress.subtract(BigInteger.valueOf(getPreBufferSize() * unitPerLine));
// figure out the buffer end address
int numLines = fContentDescriptor.getNumLines();
BigInteger bufferEnd = loadAddress.add(BigInteger.valueOf(fContentDescriptor.getPostBuffer() * unitPerLine));
bufferEnd = bufferEnd.add(BigInteger.valueOf(numLines * unitPerLine + unitPerLine));
// see if the address is contained based on current content descriptor
if (address.compareTo(loadAddress) >= 0 && address.compareTo(bufferEnd) <= 0) {
return true;
}
return false;
}
private void setFont(Font font) {
// set font
fTableViewer.getTable().setFont(font);
fTableViewer.getCursor().setFont(font);
}
private int getDefaultColumnSize() {
// default to global preference store
IPreferenceStore prefStore = DebugUITools.getPreferenceStore();
int columnSize = prefStore.getInt(IDebugPreferenceConstants.PREF_COLUMN_SIZE);
// actual column size is number of addressable units * size of the
// addressable unit
columnSize = columnSize * getAddressableSize();
// check synchronized col size
Integer colSize = (Integer) getSynchronizedProperty(AbstractAsyncTableRendering.PROPERTY_COL_SIZE);
if (colSize != null) {
// column size is stored as actual number of bytes in synchronizer
int syncColSize = colSize.intValue();
if (syncColSize > 0) {
columnSize = syncColSize;
}
} else {
IPersistableDebugElement elmt = getMemoryBlock().getAdapter(IPersistableDebugElement.class);
int defaultColSize = -1;
if (elmt != null) {
if (elmt.supportsProperty(this, IDebugPreferenceConstants.PREF_COL_SIZE_BY_MODEL)) {
defaultColSize = getDefaultFromPersistableElement(IDebugPreferenceConstants.PREF_COL_SIZE_BY_MODEL);
}
}
if (defaultColSize <= 0) {
// if not provided, get default by model
defaultColSize = getDefaultColumnSizeByModel(getMemoryBlock().getModelIdentifier());
}
if (defaultColSize > 0) {
columnSize = defaultColSize * getAddressableSize();
}
}
return columnSize;
}
private int getDefaultRowSize() {
int rowSize = DebugUITools.getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_ROW_SIZE);
int bytePerLine = rowSize * getAddressableSize();
// check synchronized row size
Integer size = (Integer) getSynchronizedProperty(AbstractAsyncTableRendering.PROPERTY_ROW_SIZE);
if (size != null) {
// row size is stored as actual number of bytes in synchronizer
int syncRowSize = size.intValue();
if (syncRowSize > 0) {
bytePerLine = syncRowSize;
}
} else {
int defaultRowSize = -1;
IPersistableDebugElement elmt = getMemoryBlock().getAdapter(IPersistableDebugElement.class);
if (elmt != null) {
if (elmt.supportsProperty(this, IDebugPreferenceConstants.PREF_ROW_SIZE_BY_MODEL)) {
defaultRowSize = getDefaultFromPersistableElement(IDebugPreferenceConstants.PREF_ROW_SIZE_BY_MODEL);
return defaultRowSize * getAddressableSize();
}
}
if (defaultRowSize <= 0) {
// no synchronized property, ask preference store by id
defaultRowSize = getDefaultRowSizeByModel(getMemoryBlock().getModelIdentifier());
}
if (defaultRowSize > 0) {
bytePerLine = defaultRowSize * getAddressableSize();
}
}
return bytePerLine;
}
/**
* Returns the addressable size of this rendering's memory block in bytes.
*
* @return the addressable size of this rendering's memory block in bytes
*/
@Override
public int getAddressableSize() {
return fAddressableSize;
}
private Object getSynchronizedProperty(String propertyId) {
IMemoryRenderingSynchronizationService syncService = getMemoryRenderingContainer().getMemoryRenderingSite().getSynchronizationService();
if (syncService == null) {
return null;
}
return syncService.getProperty(getMemoryBlock(), propertyId);
}
private int getDefaultFromPersistableElement(String propertyId) {
int defaultValue = -1;
IPersistableDebugElement elmt = getMemoryBlock().getAdapter(IPersistableDebugElement.class);
if (elmt != null) {
try {
Object valueMB = elmt.getProperty(this, propertyId);
if (valueMB != null && !(valueMB instanceof Integer)) {
IStatus status = DebugUIPlugin.newErrorStatus("Model returned invalid type on " + propertyId, null); //$NON-NLS-1$
DebugUIPlugin.log(status);
}
if (valueMB != null) {
Integer value = (Integer) valueMB;
defaultValue = value.intValue();
}
} catch (CoreException e) {
DebugUIPlugin.log(e);
}
}
return defaultValue;
}
/**
* @param modelId the {@link String} model id
* @return default number of addressable units per line for the model
*/
private int getDefaultRowSizeByModel(String modelId) {
int row = DebugUITools.getPreferenceStore().getInt(getRowPrefId(modelId));
if (row == 0) {
DebugUITools.getPreferenceStore().setValue(getRowPrefId(modelId), IDebugPreferenceConstants.PREF_ROW_SIZE_DEFAULT);
}
row = DebugUITools.getPreferenceStore().getInt(getRowPrefId(modelId));
return row;
}
/**
* @param modelId the {@link String} model id
* @return default number of addressable units per column for the model
*/
private int getDefaultColumnSizeByModel(String modelId) {
int col = DebugUITools.getPreferenceStore().getInt(getColumnPrefId(modelId));
if (col == 0) {
DebugUITools.getPreferenceStore().setValue(getColumnPrefId(modelId), IDebugPreferenceConstants.PREF_COLUMN_SIZE_DEFAULT);
}
col = DebugUITools.getPreferenceStore().getInt(getColumnPrefId(modelId));
return col;
}
private String getRowPrefId(String modelId) {
String rowPrefId = IDebugPreferenceConstants.PREF_ROW_SIZE + ":" + modelId; //$NON-NLS-1$
return rowPrefId;
}
private String getColumnPrefId(String modelId) {
String colPrefId = IDebugPreferenceConstants.PREF_COLUMN_SIZE + ":" + modelId; //$NON-NLS-1$
return colPrefId;
}
/**
* Format view tab based on the bytes per line and column.
*
* @param bytesPerLine - number of bytes per line, possible values: (1 / 2 /
* 4 / 8 / 16 / 32 / 64/ 128) * addressableSize
* @param columnSize - number of bytes per column, possible values: (1 / 2 /
* 4 / 8 / 16 / 32/ 64 / 128) * addressableSize
* @return true if format is successful, false, otherwise
*/
@Override
public boolean format(int bytesPerLine, int columnSize) {
// bytes per cell must be divisible to bytesPerLine
if (bytesPerLine % columnSize != 0) {
return false;
}
if (bytesPerLine < columnSize) {
return false;
}
// do not format if the view tab is already in that format
if (fBytePerLine == bytesPerLine && fColumnSize == columnSize) {
return false;
}
fBytePerLine = bytesPerLine;
fColumnSize = columnSize;
formatViewer();
updateSyncRowSize();
updateSyncColSize();
return true;
}
/**
* Returns the number of addressable units per row.
*
* @return number of addressable units per row
*/
@Override
public int getAddressableUnitPerLine() {
return fBytePerLine / getAddressableSize();
}
/**
* Returns the number of addressable units per column.
*
* @return number of addressable units per column
*/
@Override
public int getAddressableUnitPerColumn() {
return fColumnSize / getAddressableSize();
}
/**
* This method estimates the number of visible lines in the rendering table.
*
* @return estimated number of visible lines in the table
*/
private int getNumberOfVisibleLines() {
if (fTableViewer == null) {
return -1;
}
Table table = fTableViewer.getTable();
int height = fTableViewer.getTable().getSize().y;
// when table is not yet created, height is zero
if (height == 0) {
// make use of the table viewer to estimate table size
height = fTableViewer.getTable().getParent().getSize().y;
}
if (height == 0) {
return 0;
}
// height of border
int border = fTableViewer.getTable().getHeaderHeight();
// height of scroll bar
int scroll = fTableViewer.getTable().getHorizontalBar().getSize().y;
// height of table is table's area minus border and scroll bar height
height = height - border - scroll;
// calculate number of visible lines
int lineHeight = getMinTableItemHeight(table);
int numberOfLines = height / lineHeight;
if (numberOfLines <= 0) {
return 0;
}
return numberOfLines;
}
private int getMinTableItemHeight(Table table) {
// Hack to get around Linux GTK problem.
// On Linux GTK, table items have variable item height as
// carriage returns are actually shown in a cell. Some rows will be
// taller than others. When calculating number of visible lines, we
// need to find the smallest table item height. Otherwise, the rendering
// underestimates the number of visible lines. As a result the rendering
// will not be able to get more memory as needed.
if (MemoryViewUtil.isLinuxGTK()) {
// check each of the items and find the minimum
TableItem[] items = table.getItems();
int minHeight = table.getItemHeight();
for (TableItem item : items) {
if (item.getData() != null) {
minHeight = Math.min(item.getBounds(0).height, minHeight);
}
}
return minHeight;
}
return table.getItemHeight();
}
private BigInteger getMemoryBlockBaseAddress() throws DebugException {
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
return ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress();
} else {
return BigInteger.valueOf(getMemoryBlock().getStartAddress());
}
}
/**
* Displays the given message on the error page
*
* @param message - the message to display
*/
protected void showMessage(final String message) {
fSwitchPageJob.setShowMessagePage(true);
fSwitchPageJob.setMessage(message);
fSwitchPageJob.schedule();
}
/**
* Returns the number of bytes displayed in a single column cell.
*
* @return the number of bytes displayed in a single column cell
*/
@Override
public int getBytesPerColumn() {
return fColumnSize;
}
/**
* Returns the number of bytes displayed in a row.
*
* @return the number of bytes displayed in a row
*/
@Override
public int getBytesPerLine() {
return fBytePerLine;
}
/**
* Returns whether the error page is displayed.
*
* @return whether the error page is displayed
*/
public boolean isDisplayingError() {
return fShowMessage;
}
/**
* Displays the content of the table viewer.
*/
public void showTable() {
fSwitchPageJob.setShowMessagePage(false);
fSwitchPageJob.schedule();
}
private BigInteger getTopVisibleAddress() {
if (fTableViewer == null) {
return BigInteger.valueOf(0);
}
Table table = fTableViewer.getTable();
int topIndex = table.getTopIndex();
if (topIndex < 0) {
return null;
}
if (table.getItemCount() > topIndex) {
MemorySegment topItem = (MemorySegment) table.getItem(topIndex).getData();
if (topItem != null) {
return topItem.getAddress();
}
}
return null;
}
private synchronized void reloadTable(final BigInteger topAddress) {
if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) {
DebugUIPlugin.trace(this + " reload at: " + topAddress.toString(16)); //$NON-NLS-1$
}
fContentDescriptor.setLoadAddress(topAddress);
fContentDescriptor.setNumLines(getNumLinesToLoad());
fTableViewer.setTopIndex(topAddress);
fTableViewer.refresh();
}
private boolean isAtTopLimit() {
BigInteger startAddress = fContentDescriptor.getStartAddress();
startAddress = MemoryViewUtil.alignToBoundary(startAddress, getAddressableUnitPerLine());
AbstractVirtualContentTableModel model = fTableViewer.getVirtualContentModel();
if (model != null) {
Object key = model.getKey(0);
if (key instanceof BigInteger) {
BigInteger startBufferAddress = (BigInteger) key;
startBufferAddress = MemoryViewUtil.alignToBoundary(startBufferAddress, getAddressableUnitPerLine());
if (startAddress.compareTo(startBufferAddress) == 0) {
return true;
}
}
}
return false;
}
private boolean isAtBottomLimit() {
BigInteger endAddress = fContentDescriptor.getEndAddress();
endAddress = MemoryViewUtil.alignToBoundary(endAddress, getAddressableUnitPerLine());
AbstractVirtualContentTableModel model = fTableViewer.getVirtualContentModel();
if (model != null) {
int numElements = model.getElements().length;
Object key = model.getKey(numElements - 1);
if (key instanceof BigInteger) {
BigInteger endBufferAddress = (BigInteger) key;
endBufferAddress = MemoryViewUtil.alignToBoundary(endBufferAddress, getAddressableUnitPerLine());
if (endAddress.compareTo(endBufferAddress) == 0) {
return true;
}
}
}
return false;
}
private void formatViewer() {
fTableViewer.disposeColumns();
fTableViewer.disposeCellEditors();
doFormatTable();
fTableViewer.setColumnHeaders(getColumnProperties());
fTableViewer.showColumnHeader(true);
Table table = fTableViewer.getTable();
int colCnt = table.getColumnCount();
CellEditor[] editors = new CellEditor[fTableViewer.getTable().getColumnCount()];
for (int i = 0; i < colCnt; i++) {
editors[i] = createCellEditor(table, i);
}
fTableViewer.setCellEditors(editors);
fTableViewer.formatViewer();
// This resize needs to happen after the viewer has finished
// getting the labels.
// This fix is a hack to delay the resize until the viewer has a chance
// to get
// the setData event from the UI thread. Otherwise, the columns will be
// squeezed together.
UIJob job = new UIJob("resize to fit") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
resizeColumnsToPreferredSize();
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
private void doFormatTable() {
int bytesPerLine = getBytesPerLine();
int columnSize = getBytesPerColumn();
int numColumns = bytesPerLine / columnSize;
Table table = fTableViewer.getTable();
TableColumn column0 = new TableColumn(table, SWT.LEFT, 0);
column0.setText(DebugUIMessages.AbstractTableRendering_2);
// create new byte columns
TableColumn[] byteColumns = new TableColumn[numColumns];
for (int i = 0; i < byteColumns.length; i++) {
TableColumn column = new TableColumn(table, SWT.LEFT, i + 1);
byteColumns[i] = column;
}
// Empty column for cursor navigation
TableColumn emptyCol = new TableColumn(table, SWT.LEFT, byteColumns.length + 1);
emptyCol.setText(" "); //$NON-NLS-1$
emptyCol.setWidth(1);
emptyCol.setResizable(false);
table.setHeaderVisible(true);
// allow clients to override column labels
setColumnHeadings();
}
private String[] getColumnProperties() {
int numColumns = getAddressableUnitPerLine() / getAddressableUnitPerColumn();
// +2 to include properties for address and navigation column
String[] columnProperties = new String[numColumns + 2];
columnProperties[0] = TableRenderingLine.P_ADDRESS;
int addressableUnit = getAddressableUnitPerColumn();
// use column beginning offset to the row address as properties
for (int i = 1; i < columnProperties.length - 1; i++) {
// column properties are stored as number of addressable units from
// the
// the line address
columnProperties[i] = Integer.toHexString((i - 1) * addressableUnit);
}
// Empty column for cursor navigation
columnProperties[columnProperties.length - 1] = " "; //$NON-NLS-1$
return columnProperties;
}
/**
* Create a cell editor from the specified composite and column.
*
* @param composite parent composite that the cell editor is to be created
* from.
* @param column the column where the cell editor is required
* @return the cell editor for editing memory
*
* @since 3.3
*
*/
protected CellEditor createCellEditor(Composite composite, int column) {
return new TextCellEditor(composite);
}
private ICellModifier newInternalCellModifier() {
return new AsyncTableRenderingCellModifier(this, createCellModifier());
}
/**
* Create a custom cell modifier for this rendering. Return null if the
* default cell modifier is to be used.
*
* @return the cell modifier for this rendering, or <code>null</code> if the
* default cell modifier is to be used.
*
* @since 3.3
*
*/
protected ICellModifier createCellModifier() {
return null;
}
@Override
public void dispose() {
if (fIsDisposed) {
return;
}
fIsDisposed = true;
removeListeners();
if (fMenuMgr != null) {
fMenuMgr.removeAll();
fMenuMgr.dispose();
fMenuMgr = null;
}
if (fTableViewer != null) {
if (fViewerListener != null) {
fTableViewer.removeVirtualContentListener(fViewerListener);
}
if (fPresentationErrorListener != null) {
fTableViewer.removePresentationErrorListener(fPresentationErrorListener);
}
fTableViewer.removeSelectionChangedListener(fViewerSelectionChangedListener);
fTableViewer.getTable().getVerticalBar().removeSelectionListener(fScrollBarSelectionListener);
fTableViewer.dispose();
}
if (fPresentationContext != null) {
fPresentationContext.dispose();
}
if (fToolTipShell != null && !fToolTipShell.isDisposed()) {
fToolTipShell.dispose();
fToolTipShell = null;
}
fIsDisposed = true;
super.dispose();
}
/**
* Updates the label of this rendering, optionally displaying the base
* address of this rendering's memory block.
*
* @param showAddress whether to display the base address of this
* rendering's memory block in this rendering's label
*/
protected void updateRenderingLabel(final boolean showAddress) {
Job job = new Job("Update Rendering Label") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
if (fIsDisposed) {
return Status.OK_STATUS;
}
fLabel = buildLabel(showAddress);
firePropertyChangedEvent(new PropertyChangeEvent(AbstractAsyncTableRendering.this, IBasicPropertyConstants.P_TEXT, null, fLabel));
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.setRule(serialByRenderingRule);
job.schedule();
}
private String buildLabel(boolean showAddress) {
String label = IInternalDebugCoreConstants.EMPTY_STRING;
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
label = ((IMemoryBlockExtension) getMemoryBlock()).getExpression();
if (label == null) {
label = DebugUIMessages.AbstractTableRendering_8;
}
if (label.startsWith("&")) { //$NON-NLS-1$
label = "&" + label; //$NON-NLS-1$
}
try {
if (showAddress && ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress() != null) {
label += " : 0x"; //$NON-NLS-1$
label += ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress().toString(16).toUpperCase();
}
} catch (DebugException e) {
// do nothing, the label will not show the address
}
} else {
long address = getMemoryBlock().getStartAddress();
label = Long.toHexString(address).toUpperCase();
}
String preName = DebugUITools.getMemoryRenderingManager().getRenderingType(getRenderingId()).getLabel();
if (preName != null) {
label += " <" + preName + ">"; //$NON-NLS-1$ //$NON-NLS-2$
}
return decorateLabel(label);
}
/*
* Returns the label of this rendering.
* @return label of this rendering
*/
@Override
public String getLabel() {
if (fLabel == null) {
fLabel = DebugUIMessages.AbstractAsyncTableRendering_1;
updateRenderingLabel(isVisible());
}
return fLabel;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == IColorProvider.class) {
return (T) getColorProviderAdapter();
}
if (adapter == ILabelProvider.class) {
return (T) getLabelProviderAdapter();
}
if (adapter == IFontProvider.class) {
return (T) getFontProviderAdapter();
}
if (adapter == IModelChangedListener.class) {
return (T) fModelChangedListener;
}
if (adapter == IWorkbenchAdapter.class) {
// needed workbench adapter to fill the title of property page
if (fWorkbenchAdapter == null) {
fWorkbenchAdapter = new IWorkbenchAdapter() {
@Override
public Object[] getChildren(Object o) {
return new Object[0];
}
@Override
public ImageDescriptor getImageDescriptor(Object object) {
return null;
}
@Override
public String getLabel(Object o) {
return AbstractAsyncTableRendering.this.getLabel();
}
@Override
public Object getParent(Object o) {
return null;
}
};
}
return (T) fWorkbenchAdapter;
}
if (adapter == TableRenderingContentDescriptor.class) {
return (T) getContentDescriptor();
}
return super.getAdapter(adapter);
}
/**
* Returns the number of characters a byte will convert to or -1 if unknown.
*
* @return the number of characters a byte will convert to or -1 if unknown
*/
@Override
public int getNumCharsPerByte() {
return -1;
}
/**
* Create actions for this rendering
*/
protected void createActions() {
fCopyToClipboardAction = new AsyncCopyTableRenderingAction(this, fTableViewer);
fGoToAddressAction = new RenderingGoToAddressAction(getMemoryRenderingContainer(), this);
fResetMemoryBlockAction = new ResetToBaseAddressAction(this);
fPrintViewTabAction = new AsyncPrintTableRenderingAction(this, fTableViewer);
fFormatRenderingAction = new FormatTableRenderingAction(this);
fReformatAction = new ReformatAction(this);
fToggleAddressColumnAction = new ToggleAddressColumnAction();
IMemoryRenderingSite site = getMemoryRenderingContainer().getMemoryRenderingSite();
if (site.getSite().getSelectionProvider() != null) {
fPropertiesDialogAction = new PropertyDialogAction(site.getSite(), site.getSite().getSelectionProvider());
}
fNextAction = new NextPageAction();
fPrevAction = new PrevPageAction();
}
/**
* Returns the currently selected address in this rendering.
*
* @return the currently selected address in this rendering
*/
@Override
public BigInteger getSelectedAddress() {
if (!fIsCreated) {
return null;
}
Object key = fTableViewer.getSelectionKey();
if (key != null && key instanceof BigInteger) {
return (BigInteger) key;
}
return null;
}
/**
* Returns the currently selected content in this rendering as MemoryByte.
*
* @return the currently selected content in array of MemoryByte. Returns an
* empty array if the selected address is out of buffered range.
*/
@Override
public MemoryByte[] getSelectedAsBytes() {
if (getSelectedAddress() == null) {
return new MemoryByte[0];
}
Object key = fTableViewer.getSelectionKey();
AbstractVirtualContentTableModel model = fTableViewer.getVirtualContentModel();
if (model != null) {
model = (AbstractVirtualContentTableModel) fTableViewer.getModel();
int row = model.indexOfKey(key);
Object element = model.getElement(row);
int col = model.columnOf(element, key);
// check precondition
if (col <= 0 || col > getBytesPerLine() / getBytesPerColumn()) {
return new MemoryByte[0];
}
if (!(element instanceof MemorySegment)) {
return new MemoryByte[0];
}
MemorySegment line = (MemorySegment) element;
int offset = (col - 1) * (getAddressableUnitPerColumn() * getAddressableSize());
// make a copy of the bytes to ensure that data cannot be changed
// by caller
MemoryByte[] bytes = line.getBytes(offset, getAddressableUnitPerColumn() * getAddressableSize());
MemoryByte[] retBytes = new MemoryByte[bytes.length];
System.arraycopy(bytes, 0, retBytes, 0, bytes.length);
return retBytes;
}
return new MemoryByte[0];
}
/**
* Returns the currently selected content in this rendering as a String.
*
* @return the currently selected content in this rendering
*/
@Override
public String getSelectedAsString() {
if (getSelectedAddress() == null) {
return IInternalDebugCoreConstants.EMPTY_STRING;
}
MemoryByte[] bytes = getSelectedAsBytes();
if (bytes.length > 0) {
return getString(this.getRenderingId(), getSelectedAddress(), bytes);
} else {
return IInternalDebugCoreConstants.EMPTY_STRING;
}
}
/**
* Moves the cursor to the specified address. Will load more memory if the
* address is not currently visible.
*
* @param address address to position cursor at
* @throws DebugException if an exception occurs
*/
@Override
public void goToAddress(final BigInteger address) throws DebugException {
if (!fIsCreated || fTableViewer.getVirtualContentModel() == null) {
return;
}
final int keyIndex = fTableViewer.getVirtualContentModel().indexOfKey(address);
if (keyIndex < 0) {
// if not extended memory block
// do not allow user to go to an address that's out of range
if (!(getMemoryBlock() instanceof IMemoryBlockExtension)) {
Status stat = new Status(IStatus.ERROR, DebugUIPlugin.getUniqueIdentifier(), DebugException.NOT_SUPPORTED, DebugUIMessages.AbstractTableRendering_11, null);
DebugException e = new DebugException(stat);
throw e;
}
BigInteger startAdd = fContentDescriptor.getStartAddress();
BigInteger endAdd = fContentDescriptor.getEndAddress();
if (address.compareTo(startAdd) < 0 || address.compareTo(endAdd) > 0) {
Status stat = new Status(IStatus.ERROR, DebugUIPlugin.getUniqueIdentifier(), DebugException.NOT_SUPPORTED, DebugUIMessages.AbstractTableRendering_11, null);
DebugException e = new DebugException(stat);
throw e;
}
}
Runnable runnable = () -> {
showTable();
if (keyIndex >= 0) {
// address is within range, set cursor and reveal
fTableViewer.setSelection(address);
updateSyncTopAddress(getTopVisibleAddress());
updateSyncSelectedAddress(address);
} else {
// load at the address
fTableViewer.setSelection(address);
reloadTable(address);
updateSyncSelectedAddress(address);
if (!isDynamicLoad()) {
updateSyncPageStartAddress(address);
}
updateSyncTopAddress(address);
}
};
runOnUIThread(runnable);
}
/**
* Refresh the table viewer with the current top visible address. Update
* labels in the memory rendering.
*/
@Override
public void refresh() {
if (!fIsCreated) {
return;
}
fTableViewer.refresh();
}
/**
* Resize column to the preferred size.
*/
@Override
public void resizeColumnsToPreferredSize() {
if (!fIsCreated) {
return;
}
fTableViewer.resizeColumnsToPreferredSize();
if (!fIsShowAddressColumn) {
final TableColumn column = fTableViewer.getTable().getColumn(0);
column.addControlListener(new ControlListener() {
@Override
public void controlMoved(ControlEvent e) {
}
@Override
public void controlResized(ControlEvent e) {
column.removeControlListener(this);
column.setWidth(0);
}
});
}
}
/**
* Updates labels of this rendering.
*/
@Override
public void updateLabels() {
if (!fIsCreated) {
return;
}
UIJob job = new UIJob("updateLabels") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
// do not handle if the rendering is already disposed
if (fPageBook.isDisposed()) {
return Status.OK_STATUS;
}
// update tab labels
updateRenderingLabel(true);
if (fTableViewer != null) {
// update column labels
setColumnHeadings();
// rebuild cache and force labels to be refreshed
fTableViewer.formatViewer();
}
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
}
/**
* Fills the context menu for this rendering
*
* @param menu menu to fill
*/
protected void fillContextMenu(IMenuManager menu) {
menu.add(new Separator(EMPTY_MEMORY_GROUP));
menu.add(new Separator());
menu.add(fResetMemoryBlockAction);
menu.add(fGoToAddressAction);
menu.add(new Separator(EMPTY_NAVIGATION_GROUP));
menu.add(new Separator());
menu.add(fFormatRenderingAction);
if (!isDynamicLoad() && getMemoryBlock() instanceof IMemoryBlockExtension) {
menu.add(new Separator());
menu.add(fPrevAction);
menu.add(fNextAction);
menu.add(new Separator(EMPTY_NON_AUTO_LOAD_GROUP));
}
menu.add(new Separator());
menu.add(fReformatAction);
menu.add(fToggleAddressColumnAction);
menu.add(new Separator());
menu.add(fCopyToClipboardAction);
menu.add(fPrintViewTabAction);
if (fPropertiesDialogAction != null) {
menu.add(new Separator());
menu.add(fPropertiesDialogAction);
menu.add(new Separator(EMPTY_PROPERTY_GROUP));
}
menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
private int getPageSizeInUnits() {
return fPageSize * getAddressableUnitPerLine();
}
private void getPageSizeFromPreference() {
fPageSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE);
}
private void getPreBufferSizeFromPreference() {
fPreBufferSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE);
}
private void getPostBufferSizeFromPreference() {
fPostBufferSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE);
}
private void updateDynamicLoadProperty() {
boolean value = DebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM);
if (value != isDynamicLoad()) {
setDynamicLoad(value);
if (!fIsDisposed) {
if (isDynamicLoad()) {
fContentDescriptor.setPostBuffer(getPostBufferSize());
fContentDescriptor.setPreBuffer(getPreBufferSize());
fContentDescriptor.setNumLines(getNumberOfVisibleLines());
} else {
fContentDescriptor.setPostBuffer(0);
fContentDescriptor.setPreBuffer(0);
fContentDescriptor.setNumLines(fPageSize);
}
}
}
}
private void getDynamicLoadFromPreference() {
setDynamicLoad(DebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM));
}
private boolean isDynamicLoad() {
return fContentDescriptor.isDynamicLoad();
}
private int getPageSize() {
return fPageSize;
}
private int getNumLinesToLoad() {
int numberOfLines = -1;
if (isDynamicLoad()) {
numberOfLines = getNumberOfVisibleLines();
} else {
numberOfLines = getPageSize();
}
return numberOfLines;
}
private void setDynamicLoad(boolean load) {
fContentDescriptor.setDynamicLoad(load);
}
private void handlePageStartAddressChanged(BigInteger address) {
// do not handle if in dynamic mode
if (isDynamicLoad()) {
return;
}
if (!(getMemoryBlock() instanceof IMemoryBlockExtension)) {
return;
}
// do not handle event if the base address of the memory
// block has changed, wait for debug event to update to
// new location
if (isMemoryBlockBaseAddressChanged()) {
return;
}
Object key = fTableViewer.getKey(0);
if (key != null) {
if (key.equals(address)) {
return;
}
}
BigInteger start = fContentDescriptor.getStartAddress();
BigInteger end = fContentDescriptor.getEndAddress();
// smaller than start address, load at start address
if (address.compareTo(start) < 0) {
if (isAtTopLimit()) {
return;
}
address = start;
}
// bigger than end address, no need to load, already at top
if (address.compareTo(end) > 0) {
if (isAtBottomLimit()) {
return;
}
address = end.subtract(BigInteger.valueOf(getPageSizeInUnits()));
}
fContentDescriptor.setLoadAddress(address);
final BigInteger finaladdress = address;
Runnable runnable = () -> {
if (fTableViewer.getTable().isDisposed()) {
return;
}
fTableViewer.setTopIndex(finaladdress);
refresh();
};
runOnUIThread(runnable);
updateSyncPageStartAddress(address);
updateSyncTopAddress(address);
}
private void handleDyanicLoadChanged() {
// if currently in dynamic load mode, update page
// start address
BigInteger pageStart = getTopVisibleAddress();
updateSyncPageStartAddress(pageStart);
updateDynamicLoadProperty();
if (isDynamicLoad()) {
refresh();
fTableViewer.setTopIndex(pageStart);
} else {
handlePageStartAddressChanged(pageStart);
}
}
@Override
public void becomesHidden() {
// creates new object for storing potential changes in sync properties
fPendingSyncProperties = new PendingPropertyChanges();
super.becomesHidden();
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
updateRenderingLabel(false);
}
}
@Override
public void becomesVisible() {
if (!fIsCreated) {
// label can still be constructed even though the rendering has not
// finished being
// initialized
updateRenderingLabel(true);
super.becomesVisible();
return;
}
// do not do anything if already visible
if (isVisible() == true) {
// super should always be called
super.becomesVisible();
return;
}
super.becomesVisible();
if (fPendingSyncProperties != null) {
// deal with format
boolean format = false;
int rowSize = getBytesPerLine();
if (fPendingSyncProperties.getRowSize() > 0) {
format = true;
rowSize = fPendingSyncProperties.getRowSize();
}
int colSize = getBytesPerColumn();
if (fPendingSyncProperties.getColumnSize() > 0) {
format = true;
colSize = fPendingSyncProperties.getColumnSize();
}
if (format) {
format(rowSize, colSize);
}
BigInteger selectedAddress = fPendingSyncProperties.getSelectedAddress();
if (selectedAddress != null) {
fTableViewer.setSelection(selectedAddress);
}
updateDynamicLoadProperty();
if (isDynamicLoad()) {
BigInteger topVisibleAddress = fPendingSyncProperties.getTopVisibleAddress();
if (topVisibleAddress != null) {
fContentDescriptor.setLoadAddress(topVisibleAddress);
fTableViewer.setTopIndex(topVisibleAddress);
}
} else if (!(getMemoryBlock() instanceof IMemoryBlockExtension)) {
BigInteger topVisibleAddress = fPendingSyncProperties.getTopVisibleAddress();
if (topVisibleAddress != null) {
fTableViewer.setTopIndex(topVisibleAddress);
}
} else {
if (fPendingSyncProperties.getPageSize() > 0) {
fPageSize = fPendingSyncProperties.getPageSize();
fContentDescriptor.setNumLines(fPageSize);
}
BigInteger pageStartAddress = fPendingSyncProperties.getPageStartAddress();
if (pageStartAddress != null) {
fContentDescriptor.setLoadAddress(pageStartAddress);
}
fTableViewer.setTopIndex(pageStartAddress);
}
showTable();
refresh();
}
updateRenderingLabel(true);
Job job = new Job("becomesVisible") //$NON-NLS-1$
{
@Override
protected IStatus run(IProgressMonitor monitor) {
if (fIsDisposed) {
return Status.OK_STATUS;
}
try {
fContentDescriptor.updateContentBaseAddress();
} catch (DebugException e) {
showMessage(e.getMessage());
}
return Status.OK_STATUS;
}
};
job.setSystem(true);
job.schedule();
// discard these properties
fPendingSyncProperties = null;
}
/**
* Handle column size changed event from synchronizer
*
* @param newColumnSize the new column size
*/
private void columnSizeChanged(final int newColumnSize) {
// ignore event if rendering is not visible
if (!isVisible()) {
return;
}
Display.getDefault().asyncExec(() -> {
int rowSize = getBytesPerLine();
if (rowSize < newColumnSize) {
rowSize = newColumnSize;
}
format(rowSize, newColumnSize);
});
}
/**
* @param newRowSize - new row size in number of bytes
*/
private void rowSizeChanged(final int newRowSize) {
// ignore event if rendering is not visible
if (!isVisible()) {
return;
}
Display.getDefault().asyncExec(() -> {
int colSize = getBytesPerColumn();
if (newRowSize < colSize) {
colSize = newRowSize;
}
format(newRowSize, colSize);
});
}
/**
* update selected address in synchronizer if update is true.
*
* @param address the address to update
*/
private void updateSyncSelectedAddress(BigInteger address) {
if (!fIsCreated) {
return;
}
PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractAsyncTableRendering.PROPERTY_SELECTED_ADDRESS, null, address);
firePropertyChangedEvent(event);
}
/**
* update column size in synchronizer
*/
private void updateSyncColSize() {
if (!fIsCreated) {
return;
}
PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractAsyncTableRendering.PROPERTY_COL_SIZE, null, Integer.valueOf(fColumnSize));
firePropertyChangedEvent(event);
}
/**
* update column size in synchronizer
*/
private void updateSyncRowSize() {
if (!fIsCreated) {
return;
}
PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractAsyncTableRendering.PROPERTY_ROW_SIZE, null, Integer.valueOf(fBytePerLine));
firePropertyChangedEvent(event);
}
/**
* update top visible address in synchronizer
*
* @param address the address to update
*/
private void updateSyncTopAddress(BigInteger address) {
if (!fIsCreated) {
return;
}
PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS, null, address);
firePropertyChangedEvent(event);
}
private void updateSyncPageStartAddress(BigInteger address) {
if (!fIsCreated) {
return;
}
if (isMemoryBlockBaseAddressChanged()) {
return;
}
PropertyChangeEvent event = new PropertyChangeEvent(this, IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS, null, address);
firePropertyChangedEvent(event);
}
/**
* Returns the color provider for this rendering's memory block or
* <code>null</code> if none.
* <p>
* By default a color provider is obtained by asking this rendering's memory
* block for its {@link IColorProvider} adapter. When the color provider is
* queried for color information, it is provided with a
* {@link MemoryRenderingElement} as an argument.
* </p>
*
* @return the color provider for this rendering's memory block, or
* <code>null</code>
*/
protected IColorProvider getColorProviderAdapter() {
return getMemoryBlock().getAdapter(IColorProvider.class);
}
/**
* Returns the label provider for this rendering's memory block or
* <code>null</code> if none.
* <p>
* By default a label provider is obtained by asking this rendering's memory
* block for its {@link ILabelProvider} adapter. When the label provider is
* queried for label information, it is provided with a
* {@link MemoryRenderingElement} as an argument.
* </p>
*
* @return the label provider for this rendering's memory block, or
* <code>null</code>
*/
protected ILabelProvider getLabelProviderAdapter() {
return getMemoryBlock().getAdapter(ILabelProvider.class);
}
/**
* Returns the font provider for this rendering's memory block or
* <code>null</code> if none.
* <p>
* By default a font provider is obtained by asking this rendering's memory
* block for its {@link IFontProvider} adapter. When the font provider is
* queried for font information, it is provided with a
* {@link MemoryRenderingElement} as an argument.
* </p>
*
* @return the font provider for this rendering's memory block, or
* <code>null</code>
*/
protected IFontProvider getFontProviderAdapter() {
return getMemoryBlock().getAdapter(IFontProvider.class);
}
/**
* Returns the table presentation for this rendering's memory block or
* <code>null</code> if none.
* <p>
* By default a table presentation is obtained by asking this rendering's
* memory block for its {@link IMemoryBlockTablePresentation} adapter.
* </p>
*
* @return the table presentation for this rendering's memory block, or
* <code>null</code>
*/
protected IMemoryBlockTablePresentation getTablePresentationAdapter() {
return getMemoryBlock().getAdapter(IMemoryBlockTablePresentation.class);
}
/**
* Setup the viewer so it supports hovers to show the offset of each field
*/
private void createToolTip() {
fToolTipShell = new Shell(DebugUIPlugin.getShell(), SWT.ON_TOP | SWT.RESIZE);
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 1;
gridLayout.marginWidth = 2;
gridLayout.marginHeight = 0;
fToolTipShell.setLayout(gridLayout);
fToolTipShell.setBackground(fTableViewer.getTable().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
final Control toolTipControl = createToolTipControl(fToolTipShell);
if (toolTipControl == null) {
// if client decide not to use tooltip support
fToolTipShell.dispose();
return;
}
MouseTrackAdapter listener = new MouseTrackAdapter() {
private TableItem fTooltipItem = null;
private int fCol = -1;
@Override
public void mouseExit(MouseEvent e) {
if (!fToolTipShell.isDisposed()) {
fToolTipShell.setVisible(false);
}
fTooltipItem = null;
}
@Override
public void mouseHover(MouseEvent e) {
Point hoverPoint = new Point(e.x, e.y);
Control control = null;
if (e.widget instanceof Control) {
control = (Control) e.widget;
}
if (control == null) {
return;
}
hoverPoint = control.toDisplay(hoverPoint);
TableItem item = getItem(hoverPoint);
int column = getColumn(hoverPoint);
// Only if there is a change in hover
if (this.fTooltipItem != item || fCol != column) {
// Keep Track of the latest hover
fTooltipItem = item;
fCol = column;
if (item != null) {
toolTipAboutToShow(toolTipControl, fTooltipItem, column);
// Setting location of the tooltip
Rectangle shellBounds = fToolTipShell.getBounds();
shellBounds.x = hoverPoint.x;
shellBounds.y = hoverPoint.y + item.getBounds(0).height;
fToolTipShell.setBounds(shellBounds);
fToolTipShell.pack();
fToolTipShell.setVisible(true);
} else {
fToolTipShell.setVisible(false);
}
}
}
};
fTableViewer.getTable().addMouseTrackListener(listener);
fTableViewer.getCursor().addMouseTrackListener(listener);
}
/**
* Creates the control used to display tool tips for cells in this table. By
* default a label is used to display the address of the cell. Clients may
* override this method to create custom tooltip controls.
* <p>
* Also see the methods <code>getToolTipText(...)</code> and
* <code>toolTipAboutToShow(...)</code>.
* </p>
*
* @param composite parent for the tooltip control
* @return the tooltip control to be displayed
* @since 3.2
*/
protected Control createToolTipControl(Composite composite) {
Control fToolTipLabel = new Label(composite, SWT.NONE);
fToolTipLabel.setForeground(fTableViewer.getTable().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
fToolTipLabel.setBackground(fTableViewer.getTable().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
fToolTipLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_CENTER));
return fToolTipLabel;
}
/**
* Bug with table widget,BUG 113015, the widget is not able to return the
* correct table item if SWT.FULL_SELECTION is not on when the table is
* created. Created the following function to work around the problem. We
* can remove this method when the bug is fixed.
*
* @param point the given {@link Point} to find the {@link TableItem} for
* @return the table item where the point is located, return null if the
* item cannot be located.
*/
private TableItem getItem(Point point) {
TableItem[] items = fTableViewer.getTable().getItems();
for (TableItem item : items) {
if (item.getData() != null) {
Point start = new Point(item.getBounds(0).x, item.getBounds(0).y);
start = fTableViewer.getTable().toDisplay(start);
Point end = new Point(start.x + item.getBounds(0).width, start.y + item.getBounds(0).height);
if (start.y < point.y && point.y < end.y) {
return item;
}
}
}
return null;
}
/**
* Method for figuring out which column the point is located.
*
* @param point the {@link Point} the get the column number for
* @return the column index where the point is located, return -1 if column
* is not found.
*/
private int getColumn(Point point) {
int colCnt = fTableViewer.getTable().getColumnCount();
TableItem item = null;
for (int i = 0; i < fTableViewer.getTable().getItemCount(); i++) {
item = fTableViewer.getTable().getItem(i);
if (item.getData() != null) {
break;
}
}
if (item != null) {
for (int i = 0; i < colCnt; i++) {
Point start = new Point(item.getBounds(i).x, item.getBounds(i).y);
start = fTableViewer.getTable().toDisplay(start);
Point end = new Point(start.x + item.getBounds(i).width, start.y + item.getBounds(i).height);
if (start.x < point.x && end.x > point.x) {
return i;
}
}
}
return -1;
}
/**
* Called when the tool tip is about to show in this rendering. Clients who
* overrides <code>createTooltipControl</code> may need to also override
* this method to ensure that the tooltip shows up properly in their
* customized control.
* <p>
* By default a text tooltip is displayed, and the contents for the tooltip
* are generated by the <code>getToolTipText(...)</code> method.
* </p>
*
* @param toolTipControl - the control for displaying the tooltip
* @param item - the table item where the mouse is pointing.
* @param col - the column at which the mouse is pointing.
* @since 3.2
*/
protected void toolTipAboutToShow(Control toolTipControl, TableItem item, int col) {
if (toolTipControl instanceof Label) {
Object address = fTableViewer.getKey(fTableViewer.getTable().indexOf(item), col);
if (address != null && address instanceof BigInteger) {
Object data = item.getData();
if (data instanceof MemorySegment) {
MemorySegment line = (MemorySegment) data;
if (col > 0) {
int start = (col - 1) * getBytesPerColumn();
int end = start + getBytesPerColumn();
MemoryByte[] bytes = line.getBytes(start, end);
String str = getToolTipText((BigInteger) address, bytes);
if (str != null) {
((Label) toolTipControl).setText(str);
}
} else {
String str = getToolTipText((BigInteger) address, new MemoryByte[] {});
if (str != null) {
((Label) toolTipControl).setText(str);
}
}
}
}
}
}
/**
* Returns the text to display in a tool tip at the specified address for
* the specified bytes. By default the address of the bytes is displayed.
* Subclasses may override.
*
* @param address address of cell that tool tip is displayed for
* @param bytes the bytes in the cell
* @return the tooltip text for the memory bytes located at the specified
* address
* @since 3.2
*/
protected String getToolTipText(BigInteger address, MemoryByte[] bytes) {
StringBuilder buf = new StringBuilder("0x"); //$NON-NLS-1$
buf.append(address.toString(16).toUpperCase());
return buf.toString();
}
private void setColumnHeadings() {
String[] columnLabels = new String[0];
IMemoryBlockTablePresentation presentation = getTablePresentationAdapter();
if (presentation != null) {
columnLabels = presentation.getColumnLabels(getMemoryBlock(), getBytesPerLine(), getBytesPerLine() / getBytesPerColumn());
}
// check that column labels returned are not null
if (columnLabels == null) {
columnLabels = new String[0];
}
int numByteColumns = fBytePerLine / fColumnSize;
TableColumn[] columns = fTableViewer.getTable().getColumns();
int j = 0;
for (int i = 1; i < columns.length - 1; i++) {
// if the number of column labels returned is correct
// use supplied column labels
if (columnLabels.length == numByteColumns) {
columns[i].setText(columnLabels[j]);
j++;
} else {
// otherwise, use default
int addressableUnit = getAddressableUnitPerColumn();
if (addressableUnit >= 4) {
columns[i].setText(Integer.toHexString(j * addressableUnit).toUpperCase() + " - " + Integer.toHexString(j * addressableUnit + addressableUnit - 1).toUpperCase()); //$NON-NLS-1$
} else {
columns[i].setText(Integer.toHexString(j * addressableUnit).toUpperCase());
}
j++;
}
}
}
/**
*
* Return this rendering's viewer
*
* @return this rendering's viewer
*/
public StructuredViewer getViewer() {
return fTableViewer;
}
private boolean isMemoryBlockBaseAddressChanged() {
try {
BigInteger address = getMemoryBlockBaseAddress();
BigInteger oldBaseAddress = fContentDescriptor.getContentBaseAddress();
if (!oldBaseAddress.equals(address)) {
return true;
}
} catch (DebugException e) {
// fail silently
}
return false;
}
/**
* @param topVisibleAddress the address to get the description for
*/
private void createContentDescriptor(final BigInteger topVisibleAddress) {
fContentDescriptor = new TableRenderingContentDescriptor(AbstractAsyncTableRendering.this);
fContentDescriptor.setPostBuffer(getPostBufferSize());
fContentDescriptor.setPreBuffer(getPreBufferSize());
fContentDescriptor.setLoadAddress(topVisibleAddress);
try {
fContentDescriptor.updateContentBaseAddress();
} catch (DebugException e) {
fError = true;
showMessage(e.getMessage());
}
fContentDescriptor.setAddressableSize(getAddressableSize());
try {
int addressSize = 4;
if (getMemoryBlock() instanceof IMemoryBlockExtension) {
IMemoryBlockExtension extMb = (IMemoryBlockExtension) getMemoryBlock();
addressSize = extMb.getAddressSize();
if (addressSize <= 0) {
DebugUIPlugin.logErrorMessage("Invalid address Size: " + addressSize); //$NON-NLS-1$
addressSize = 4;
}
fContentDescriptor.setAddressSize(addressSize);
}
fContentDescriptor.setAddressSize(addressSize);
} catch (DebugException e) {
fError = true;
showMessage(e.getMessage());
} finally {
if (fContentDescriptor.getAddressSize() <= 0) {
fContentDescriptor.setAddressSize(4);
}
}
}
/**
* Return the number of lines to be bufferred before the top visible line of
* the memory rendering
*
* @return number of lines to be buffered before the top visible line in the
* memory rendering
*/
private int getPreBufferSize() {
if (fPreBufferSize < 0) {
getPreBufferSizeFromPreference();
}
return fPreBufferSize;
}
/**
* Returns the number of lines to be bufferred after the last visible line
* in the memory rendering
*
* @return the number of lines to be bufferred after the last visible line
* in the memory rendering
*/
private int getPostBufferSize() {
if (fPostBufferSize < 0) {
getPostBufferSizeFromPreference();
}
return fPostBufferSize;
}
private TableRenderingContentDescriptor getContentDescriptor() {
return fContentDescriptor;
}
private void createGoToAddressComposite(Composite parent) {
fGoToAddressComposite = new GoToAddressComposite();
fGoToAddressComposite.createControl(parent);
Button button = fGoToAddressComposite.getButton(IDialogConstants.OK_ID);
if (button != null) {
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doGoToAddress();
}
});
button = fGoToAddressComposite.getButton(IDialogConstants.CANCEL_ID);
if (button != null) {
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
hideGotoAddressComposite();
}
});
}
}
fGoToAddressComposite.getExpressionWidget().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
doGoToAddress();
}
});
fGoToAddressComposite.getExpressionWidget().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ESC) {
hideGotoAddressComposite();
}
super.keyPressed(e);
}
});
}
private void showGoToAddressComposite() {
String selectedStr = getSelectedAsString();
Text text = fGoToAddressComposite.getExpressionWidget();
text.setText(selectedStr);
text.setSelection(0, text.getCharCount());
double height = fGoToAddressComposite.getHeight();
double canvasHeight = fSashForm.getParent().getClientArea().height;
double tableHeight = canvasHeight - height;
double tableWeight = (tableHeight / canvasHeight) * 100;
double textWeight = (height / canvasHeight) * 100;
fSashForm.setWeights(new int[] { (int) tableWeight, (int) textWeight });
fSashForm.setMaximizedControl(null);
fGoToAddressComposite.getExpressionWidget().setFocus();
}
private void hideGotoAddressComposite() {
fSashForm.setMaximizedControl(fTableViewer.getControl());
if (isActivated()) {
fTableViewer.getControl().setFocus();
}
}
/**
*
*/
private void doGoToAddress() {
try {
BigInteger address = fGoToAddressComposite.getGoToAddress(fContentDescriptor.getContentBaseAddress(), getSelectedAddress());
fGoToAddressAction.doGoToAddress(address.toString(16));
hideGotoAddressComposite();
} catch (DebugException e1) {
MemoryViewUtil.openError(DebugUIMessages.GoToAddressAction_Go_to_address_failed, DebugUIMessages.GoToAddressAction_Go_to_address_failed, e1);
} catch (NumberFormatException e1) {
MemoryViewUtil.openError(DebugUIMessages.GoToAddressAction_Go_to_address_failed, DebugUIMessages.GoToAddressAction_Address_is_invalid, e1);
}
}
@Override
public void activated() {
super.activated();
fActivated = true;
IWorkbench workbench = PlatformUI.getWorkbench();
ICommandService commandSupport = workbench.getAdapter(ICommandService.class);
IContextService contextSupport = workbench.getAdapter(IContextService.class);
if (commandSupport != null && contextSupport != null) {
fContext.add(contextSupport.activateContext(ID_ASYNC_TABLE_RENDERING_CONTEXT));
Command gotoCommand = commandSupport.getCommand(ID_GO_TO_ADDRESS_COMMAND);
if (fGoToAddressHandler == null) {
fGoToAddressHandler = new AbstractHandler() {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
if (fSashForm.getMaximizedControl() != null) {
fGoToAddressAction.run();
} else {
hideGotoAddressComposite();
}
return null;
}
};
}
gotoCommand.setHandler(fGoToAddressHandler);
// The page up and page down actions can only be activated if the
// rendering
// is in manual scrolling mode. We are unable to determine the
// scrolling mode
// until the content descriptor is created. When the rendering is
// activated, the content
// descriptor may not be created yet. In that case, we cannot
// activate the shortcuts here.
// We will activate the shortcut after the rendering is created.
if (fContentDescriptor != null && !isDynamicLoad()) {
activatePageActions();
}
}
}
private void activatePageActions() {
IWorkbench workbench = PlatformUI.getWorkbench();
ICommandService commandSupport = workbench.getAdapter(ICommandService.class);
if (commandSupport != null) {
Command nextPage = commandSupport.getCommand(ID_NEXT_PAGE_COMMAND);
if (fNextPageHandler == null) {
fNextPageHandler = new AbstractHandler() {
@Override
public Object execute(ExecutionEvent arg0) throws ExecutionException {
fNextAction.run();
return null;
}
};
}
nextPage.setHandler(fNextPageHandler);
Command prevPage = commandSupport.getCommand(ID_PREV_PAGE_COMMAND);
if (fPrevPageHandler == null) {
fPrevPageHandler = new AbstractHandler() {
@Override
public Object execute(ExecutionEvent arg0) throws ExecutionException {
fPrevAction.run();
return null;
}
};
}
prevPage.setHandler(fPrevPageHandler);
}
}
@Override
public void deactivated() {
fActivated = false;
IWorkbench workbench = PlatformUI.getWorkbench();
ICommandService commandSupport = workbench.getAdapter(ICommandService.class);
IContextService contextSupport = workbench.getAdapter(IContextService.class);
if (commandSupport != null && contextSupport != null) {
// remove handler
Command command = commandSupport.getCommand(ID_GO_TO_ADDRESS_COMMAND);
command.setHandler(null);
command = commandSupport.getCommand(ID_NEXT_PAGE_COMMAND);
command.setHandler(null);
command = commandSupport.getCommand(ID_PREV_PAGE_COMMAND);
command.setHandler(null);
if (fContext != null) {
contextSupport.deactivateContexts(fContext);
}
}
super.deactivated();
}
private boolean isActivated() {
return fActivated;
}
/**
* Returns text for the given memory bytes at the specified address for the
* specified rendering type. This is called by the label provider for.
* Subclasses must override.
*
* @param renderingTypeId rendering type identifier
* @param address address where the bytes belong to
* @param data the bytes
* @return a string to represent the memory. Cannot not return
* <code>null</code>. Returns a string to pad the cell if the memory
* cannot be converted successfully.
*/
@Override
abstract public String getString(String renderingTypeId, BigInteger address, MemoryByte[] data);
/**
* Returns bytes for the given text corresponding to bytes at the given
* address for the specified rendering type. This is called by the cell
* modifier when modifying bytes in a memory block. Subclasses must convert
* the string value to an array of bytes. The bytes will be passed to the
* debug adapter for memory block modification. Returns <code>null</code> if
* the bytes cannot be formatted properly.
*
* @param renderingTypeId rendering type identifier
* @param address address the bytes begin at
* @param currentValues current values of the data in bytes format
* @param newValue the string to be converted to bytes
* @return the bytes converted from a string
*/
@Override
abstract public byte[] getBytes(String renderingTypeId, BigInteger address, MemoryByte[] currentValues, String newValue);
}