| /******************************************************************************* |
| * Copyright (c) 2006, 2016 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.debug.internal.ui.views.memory.renderings; |
| |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.debug.internal.ui.DebugUIPlugin; |
| import org.eclipse.debug.internal.ui.viewers.AsynchronousModel; |
| import org.eclipse.debug.internal.ui.viewers.AsynchronousTableViewer; |
| import org.eclipse.debug.internal.ui.viewers.model.provisional.IStatusMonitor; |
| import org.eclipse.debug.internal.ui.viewers.provisional.ILabelRequestMonitor; |
| import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil; |
| import org.eclipse.jface.viewers.CellEditor; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.ScrollBar; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableColumn; |
| import org.eclipse.swt.widgets.TableItem; |
| import org.eclipse.ui.progress.UIJob; |
| |
| abstract public class AsyncVirtualContentTableViewer extends AsynchronousTableViewer { |
| |
| private Object fPendingTopIndexKey; |
| private ArrayList<Object> fTopIndexQueue = new ArrayList<Object>(); |
| |
| private boolean fPendingResizeColumns; |
| private ListenerList<IVirtualContentListener> fVirtualContentListeners; |
| private SelectionListener fScrollSelectionListener; |
| private ListenerList<IPresentationErrorListener> fPresentationErrorListeners; |
| private Object fTopIndexKey; |
| |
| public AsyncVirtualContentTableViewer(Composite parent, int style) { |
| super(parent, style); |
| fVirtualContentListeners = new ListenerList<>(); |
| fPresentationErrorListeners = new ListenerList<>(); |
| initScrollBarListener(); |
| } |
| |
| private void initScrollBarListener() { |
| ScrollBar scroll = getTable().getVerticalBar(); |
| fScrollSelectionListener = new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| handleScrollBarSelection(); |
| } |
| }; |
| scroll.addSelectionListener(fScrollSelectionListener); |
| } |
| |
| public void setTopIndex(Object key) { |
| fPendingTopIndexKey = key; |
| attemptSetTopIndex(); |
| } |
| |
| protected Object getPendingSetTopIndexKey() { |
| return fPendingTopIndexKey; |
| } |
| |
| @Override |
| protected void handlePresentationFailure(IStatusMonitor monitor, IStatus status) { |
| notifyPresentationError(monitor, status); |
| } |
| |
| public void disposeColumns() { |
| // clean up old columns |
| TableColumn[] oldColumns = getTable().getColumns(); |
| |
| for (int i = 0; i < oldColumns.length; i++) { |
| oldColumns[i].dispose(); |
| } |
| } |
| |
| public void disposeCellEditors() { |
| // clean up old cell editors |
| CellEditor[] oldCellEditors = getCellEditors(); |
| |
| if (oldCellEditors != null) { |
| for (int i = 0; i < oldCellEditors.length; i++) { |
| oldCellEditors[i].dispose(); |
| } |
| } |
| } |
| |
| /** |
| * Resize column to the preferred size. |
| */ |
| public void resizeColumnsToPreferredSize() { |
| fPendingResizeColumns = true; |
| fPendingResizeColumns = attemptResizeColumnsToPreferredSize(); |
| } |
| |
| private boolean attemptResizeColumnsToPreferredSize() { |
| if (fPendingResizeColumns) { |
| if (!hasPendingUpdates()) { |
| UIJob job = new UIJob("packcolumns") { //$NON-NLS-1$ |
| |
| @Override |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| Table table = getTable(); |
| |
| if (!table.isDisposed()) { |
| // if table size is zero, the rendering has not been |
| // made visible |
| // cannot pack until the rendering is visible |
| if (table.getSize().x > 0) { |
| TableColumn[] columns = table.getColumns(); |
| for (int i = 0; i < columns.length - 1; i++) { |
| columns[i].pack(); |
| } |
| } else { |
| fPendingResizeColumns = true; |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setSystem(true); |
| job.schedule(); |
| return false; |
| } |
| } |
| return fPendingResizeColumns; |
| } |
| |
| /** |
| * Attempts to update any pending setTopIndex |
| */ |
| protected synchronized void attemptSetTopIndex() { |
| if (fPendingTopIndexKey != null) { |
| Object remaining = doAttemptSetTopIndex(fPendingTopIndexKey); |
| if (remaining == null) { |
| fPendingTopIndexKey = remaining; |
| } |
| } |
| } |
| |
| private synchronized Object doAttemptSetTopIndex(final Object topIndexKey) { |
| final int i = getVirtualContentModel().indexOfKey(topIndexKey); |
| if (i >= 0) { |
| UIJob job = new UIJob("set top index") { //$NON-NLS-1$ |
| |
| @Override |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| if (getTable().isDisposed()) { |
| fTopIndexQueue.clear(); |
| return Status.OK_STATUS; |
| } |
| |
| int idx = getVirtualContentModel().indexOfKey(topIndexKey); |
| if (idx >= 0) { |
| if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) { |
| DebugUIPlugin.trace("actual set top index: " + ((BigInteger) topIndexKey).toString(16)); //$NON-NLS-1$ |
| } |
| fPendingTopIndexKey = null; |
| setTopIndexKey(topIndexKey); |
| getTable().setTopIndex(idx); |
| tableTopIndexSetComplete(); |
| |
| if (getTable().getTopIndex() != idx) { |
| if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) { |
| DebugUIPlugin.trace(">>> FAILED set top index : " + ((BigInteger) topIndexKey).toString(16)); //$NON-NLS-1$ |
| } |
| |
| // only retry if we have pending updates |
| if (hasPendingUpdates()) { |
| if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) { |
| DebugUIPlugin.trace(">>> Retry top index: " + ((BigInteger) topIndexKey).toString(16)); //$NON-NLS-1$ |
| } |
| |
| fPendingTopIndexKey = topIndexKey; |
| } |
| } |
| } else { |
| if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) { |
| DebugUIPlugin.trace("cannot find key, put it back to the queue: " + topIndexKey); //$NON-NLS-1$ |
| } |
| fPendingTopIndexKey = topIndexKey; |
| } |
| |
| // remove the top index key from queue when it is processed |
| removeKeyFromQueue(topIndexKey); |
| |
| return Status.OK_STATUS; |
| } |
| }; |
| |
| // set top index does not happen immediately, keep track of |
| // all pending set top index |
| addKeyToQueue(topIndexKey); |
| |
| job.setSystem(true); |
| job.schedule(); |
| return topIndexKey; |
| } |
| return topIndexKey; |
| } |
| |
| protected void tableTopIndexSetComplete() { |
| |
| } |
| |
| public void addVirtualContentListener(IVirtualContentListener listener) { |
| fVirtualContentListeners.add(listener); |
| } |
| |
| public void removeVirtualContentListener(IVirtualContentListener listener) { |
| fVirtualContentListeners.remove(listener); |
| } |
| |
| protected void notifyListenersAtBufferStart() { |
| int topIdx = getTable().getTopIndex(); |
| for (IVirtualContentListener iVirtualContentListener : fVirtualContentListeners) { |
| final IVirtualContentListener listener = iVirtualContentListener; |
| if (topIdx < listener.getThreshold(IVirtualContentListener.BUFFER_START)) { |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void run() throws Exception { |
| listener.handledAtBufferStart(); |
| } |
| |
| @Override |
| public void handleException(Throwable exception) { |
| DebugUIPlugin.log(exception); |
| } |
| }); |
| } |
| } |
| } |
| |
| protected void notifyListenersAtBufferEnd() { |
| int topIdx = getTable().getTopIndex(); |
| int bottomIdx = topIdx + getNumberOfVisibleLines(); |
| int elementsCnt = getVirtualContentModel().getElements().length; |
| int numLinesLeft = elementsCnt - bottomIdx; |
| |
| for (IVirtualContentListener iVirtualContentListener : fVirtualContentListeners) { |
| final IVirtualContentListener listener = iVirtualContentListener; |
| if (numLinesLeft <= listener.getThreshold(IVirtualContentListener.BUFFER_END)) { |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void run() throws Exception { |
| listener.handleAtBufferEnd(); |
| } |
| |
| @Override |
| public void handleException(Throwable exception) { |
| DebugUIPlugin.log(exception); |
| } |
| }); |
| } |
| } |
| } |
| |
| protected void handleScrollBarSelection() { |
| // ignore event if there is pending set top index in the queue |
| if (!fTopIndexQueue.isEmpty()) { |
| return; |
| } |
| topIndexChanged(); |
| } |
| |
| public void topIndexChanged() { |
| if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) { |
| MemorySegment a = (MemorySegment) getTable().getItem(getTable().getTopIndex()).getData(); |
| DebugUIPlugin.trace(Thread.currentThread().getName() + " " + this + " handle scroll bar moved: top index: " + a.getAddress().toString(16)); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| setTopIndexKey(getVirtualContentModel().getKey(getTable().getTopIndex())); |
| |
| notifyListenersAtBufferStart(); |
| notifyListenersAtBufferEnd(); |
| } |
| |
| protected void setTopIndexKey(Object key) { |
| fTopIndexKey = key; |
| } |
| |
| protected Object getTopIndexKey() { |
| return fTopIndexKey; |
| } |
| |
| @Override |
| protected synchronized void preservingSelection(Runnable updateCode) { |
| Object oldTopIndexKey = null; |
| if (fPendingTopIndexKey == null) { |
| // preserve selection |
| oldTopIndexKey = getTopIndexKey(); |
| } else { |
| oldTopIndexKey = fPendingTopIndexKey; |
| } |
| |
| try { |
| |
| // perform the update |
| updateCode.run(); |
| } finally { |
| if (oldTopIndexKey != null) { |
| setTopIndex(oldTopIndexKey); |
| } |
| } |
| |
| } |
| |
| public void addPresentationErrorListener(IPresentationErrorListener errorListener) { |
| fPresentationErrorListeners.add(errorListener); |
| } |
| |
| public void removePresentationErrorListener(IPresentationErrorListener errorListener) { |
| fPresentationErrorListeners.remove(errorListener); |
| } |
| |
| private void notifyPresentationError(final IStatusMonitor monitor, final IStatus status) { |
| for (IPresentationErrorListener iPresentationErrorListener : fPresentationErrorListeners) { |
| final IPresentationErrorListener listener = iPresentationErrorListener; |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void run() throws Exception { |
| listener.handlePresentationFailure(monitor, status); |
| } |
| |
| @Override |
| public void handleException(Throwable exception) { |
| DebugUIPlugin.log(exception); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| protected AsynchronousModel createModel() { |
| return createVirtualContentTableModel(); |
| } |
| |
| abstract protected AbstractVirtualContentTableModel createVirtualContentTableModel(); |
| |
| private void addKeyToQueue(Object topIndexKey) { |
| synchronized (fTopIndexQueue) { |
| if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) { |
| DebugUIPlugin.trace(" >>> add to top index queue: " + ((BigInteger) topIndexKey).toString(16)); //$NON-NLS-1$ |
| } |
| fTopIndexQueue.add(topIndexKey); |
| } |
| } |
| |
| private void removeKeyFromQueue(Object topIndexKey) { |
| synchronized (fTopIndexQueue) { |
| if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) { |
| DebugUIPlugin.trace(" >>> remove frome top index queue: " + ((BigInteger) topIndexKey).toString(16)); //$NON-NLS-1$ |
| } |
| fTopIndexQueue.remove(topIndexKey); |
| } |
| } |
| |
| public AbstractVirtualContentTableModel getVirtualContentModel() { |
| if (getModel() instanceof AbstractVirtualContentTableModel) { |
| return (AbstractVirtualContentTableModel) getModel(); |
| } |
| return null; |
| } |
| |
| private int getNumberOfVisibleLines() { |
| Table table = getTable(); |
| int height = table.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 = table.getParent().getSize().y; |
| } |
| |
| // height of border |
| int border = table.getHeaderHeight(); |
| |
| // height of scroll bar |
| int scroll = table.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 20; |
| } |
| |
| 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 (int i = 0; i < items.length; i++) { |
| if (items[i].getData() != null) { |
| minHeight = Math.min(items[i].getBounds(0).height, minHeight); |
| } |
| } |
| |
| return minHeight; |
| |
| } |
| return table.getItemHeight(); |
| } |
| |
| @Override |
| protected void updateComplete(IStatusMonitor monitor) { |
| super.updateComplete(monitor); |
| attemptSetTopIndex(); |
| if (monitor instanceof ILabelRequestMonitor) { |
| fPendingResizeColumns = attemptResizeColumnsToPreferredSize(); |
| } |
| } |
| |
| protected boolean hasPendingSetTopIndex() { |
| return !fTopIndexQueue.isEmpty(); |
| } |
| |
| } |