org.eclipse.debug.internal.ui.views.memory.renderings.AsyncTableRenderingViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.debug.internal.ui.views.memory.renderings.AsyncTableRenderingViewer.java

Source

/*******************************************************************************
 * Copyright (c) 2006, 2011 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 org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
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.provisional.AbstractAsyncTableRendering;
import org.eclipse.debug.internal.ui.viewers.AbstractUpdatePolicy;
import org.eclipse.debug.internal.ui.viewers.AsynchronousModel;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IStatusMonitor;
import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ICellEditorListener;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableCursor;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.progress.UIJob;

public class AsyncTableRenderingViewer extends AsyncVirtualContentTableViewer {

    private AbstractAsyncTableRendering fRendering;

    // selection keys
    private Object fPendingSelection;
    private Object fSelectionKey;

    // cursor and associated listeners
    private TableCursor fTableCursor;
    private KeyAdapter fCursorKeyAdapter;
    private TraverseListener fCursorTraverseListener;
    private MouseAdapter fCursorMouseListener;
    private SelectionAdapter fCursorSelectionListener;

    // cursor editor and associated listeners
    private TableEditor fCursorEditor;
    private KeyAdapter fEditorKeyListener;
    private CellEditorListener fCellEditorListener;

    private class CellEditorListener implements ICellEditorListener {

        private CellEditor fEditor;
        private int fRow;
        private int fCol;

        public CellEditorListener(int row, int col, CellEditor editor) {
            fEditor = editor;
            fRow = row;
            fCol = col;
        }

        public void applyEditorValue() {
            fEditor.removeListener(this);
            modifyValue(fRow, fCol, fEditor.getValue());
        }

        public void cancelEditor() {
            fEditor.removeListener(this);
        }

        public void editorValueChanged(boolean oldValidState, boolean newValidState) {
        }

        public int getRow() {
            return fRow;
        }

        public int getCol() {
            return fCol;
        }
    }

    private boolean fPendingFormatViewer;

    public AsyncTableRenderingViewer(AbstractAsyncTableRendering rendering, Composite parent, int style) {
        super(parent, style);
        fRendering = rendering;

        getTable().addMouseListener(new MouseAdapter() {
            public void mouseDown(MouseEvent e) {
                handleTableMouseEvent(e);
            }
        });

        createCursor(getTable());
    }

    public AbstractUpdatePolicy createUpdatePolicy() {
        return new AsyncTableRenderingUpdatePolicy();
    }

    public AbstractAsyncTableRendering getRendering() {
        return fRendering;
    }

    private void createCursor(Table table) {
        fTableCursor = new TableCursor(table, SWT.NONE);

        Display display = fTableCursor.getDisplay();

        // set up cursor color
        fTableCursor.setBackground(display.getSystemColor(SWT.COLOR_LIST_SELECTION));
        fTableCursor.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));

        fTableCursor.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));

        fCursorKeyAdapter = new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                handleCursorKeyPressed(e);
            }
        };

        fTableCursor.addKeyListener(fCursorKeyAdapter);

        fCursorTraverseListener = new TraverseListener() {
            public void keyTraversed(TraverseEvent e) {
                handleCursorTraverseEvt(e);
            }
        };

        fTableCursor.addTraverseListener(fCursorTraverseListener);

        fCursorMouseListener = new MouseAdapter() {
            public void mouseDown(MouseEvent e) {
                handleCursorMouseEvent(e);
            }
        };
        fTableCursor.addMouseListener(fCursorMouseListener);

        // cursor may be disposed before disposed is called
        // remove listeners whenever the cursor is disposed
        fTableCursor.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                if (fTableCursor == null)
                    return;
                fTableCursor.removeTraverseListener(fCursorTraverseListener);
                fTableCursor.removeKeyListener(fCursorKeyAdapter);
                fTableCursor.removeMouseListener(fCursorMouseListener);
                fTableCursor.removeSelectionListener(fCursorSelectionListener);
            }
        });

        fCursorSelectionListener = new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                handleCursorMoved();
            }
        };
        fTableCursor.addSelectionListener(fCursorSelectionListener);
        fCursorEditor = new TableEditor(getTable());
    }

    private void handleCursorKeyPressed(KeyEvent event) {
        if (event.character == '\r' && event.getSource() instanceof TableCursor) {
            activateCellEditor(null);
            return;
        }

        if (MemoryViewUtil.isValidEditEvent(event.keyCode)) {
            // activate edit as soon as user types something at the cursor
            if (event.getSource() instanceof TableCursor) {
                int col = fTableCursor.getColumn();
                if (getCellEditors()[col] instanceof TextCellEditor) {
                    String initialValue = String.valueOf(event.character);
                    activateCellEditor(initialValue);
                }
            }
        }
    }

    private void handleCursorMouseEvent(MouseEvent e) {
        if (e.button == 1) {
            int col = fTableCursor.getColumn();
            if (col > 0 && col <= (getNumCol()))
                activateCellEditor(null);
        }
    }

    private void handleCursorTraverseEvt(TraverseEvent e) {
        if (fTableCursor.getRow() == null)
            return;

        Table table = (Table) fTableCursor.getParent();
        int row = table.indexOf(fTableCursor.getRow());
        int col = fTableCursor.getColumn();
        if (col == getNumCol() && e.keyCode == SWT.ARROW_RIGHT) {
            if (row + 1 >= table.getItemCount()) {
                return;
            }

            row = row + 1;
            col = 0;
            fTableCursor.setSelection(row, col);
        }
        if (col <= 1 && e.keyCode == SWT.ARROW_LEFT) {
            if (row - 1 < 0) {
                return;
            }

            row = row - 1;
            col = getNumCol() + 1;
            fTableCursor.setSelection(row, col);
        }

        handleCursorMoved();
    }

    /**
     * Update selected address.
     * Load more memory if required.
     */
    private void handleCursorMoved() {
        fSelectionKey = getSelectionKeyFromCursor();
        fPendingSelection = null;

        if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING)
            System.out.println(Thread.currentThread().getName() + " cursor moved selection is: " //$NON-NLS-1$
                    + ((BigInteger) fSelectionKey).toString(16));

        // now check to see if the cursor is approaching buffer limit
        handleScrollBarSelection();
        fireSelectionChanged(fSelectionKey);
    }

    private int getNumCol() {

        int bytesPerLine = fRendering.getBytesPerLine();
        int columnSize = fRendering.getBytesPerColumn();

        return bytesPerLine / columnSize;
    }

    /**
     * Sets the cursor at the specified address
     * @param key selection key
     */
    public void setSelection(Object key) {
        fPendingSelection = key;
        attemptSetKeySelection();
    }

    public Object getSelectionKey() {
        return fSelectionKey;
    }

    private synchronized void attemptSetKeySelection() {
        if (fPendingSelection != null) {
            doAttemptSetKeySelection(fPendingSelection);
        }

    }

    synchronized private Object doAttemptSetKeySelection(final Object key) {
        if (getBufferTopKey() == null || getBufferEndKey() == null)
            return key;

        // calculate selected row address
        int[] location = getCoordinatesFromKey(key);
        if (location.length == 0) {
            return key;
        }

        UIJob uiJob = new UIJob("Set Cursor Selection") { //$NON-NLS-1$

            public IStatus runInUIThread(IProgressMonitor monitor) {
                try {
                    if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING)
                        System.out.println(
                                getRendering() + " set cursor selection " + ((BigInteger) key).toString(16)); //$NON-NLS-1$

                    if (fPendingSelection != null && fPendingSelection != key)
                        return Status.OK_STATUS;

                    if (fTableCursor.isDisposed())
                        return Status.OK_STATUS;

                    // by the time this is called, the location may not be valid anymore
                    int[] newLocation = getCoordinatesFromKey(key);
                    if (newLocation.length == 0) {
                        Object selectionKey = getSelectionKey();
                        fPendingSelection = selectionKey;
                        return Status.OK_STATUS;
                    }

                    fSelectionKey = key;
                    fPendingSelection = null;

                    if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING) {
                        System.out.println(getRendering() + " set cursor selection, row is " //$NON-NLS-1$
                                + getTable().getItem(newLocation[0]).getData());
                        System.out.println(getRendering() + " set cursor selection, model is " //$NON-NLS-1$
                                + getVirtualContentModel().getElement(newLocation[0]));
                    }

                    fTableCursor.setSelection(newLocation[0], newLocation[1]);
                    showTableCursor(true);

                    // show the column for the selection
                    getTable().showColumn(getTable().getColumn(newLocation[1]));

                    int topIndex = getTable().getTopIndex();
                    Object topKey = getVirtualContentModel().getKey(topIndex);
                    setTopIndexKey(topKey);

                } catch (RuntimeException e) {

                    // by the time this is called, the selection may no longer
                    // get the latest selection and try to set selection again
                    Object selectionKey = getSelectionKey();
                    fPendingSelection = selectionKey;
                    doAttemptSetKeySelection(selectionKey);
                }
                return Status.OK_STATUS;
            }
        };

        uiJob.setSystem(true);
        uiJob.schedule();

        return null;
    }

    /**
     * 
     * @param key the element
     * @return the coordinates of the key
     * Element[0] is the row index
     * Element[1] is the column index
     */
    private int[] getCoordinatesFromKey(Object key) {
        final int row = indexOf(key);

        if (row == -1) {
            return new int[0];
        }

        Object element = getVirtualContentModel().getElement(row);
        final int col = columnOf(element, key);

        if (col == -1) {
            return new int[0];
        }
        return new int[] { row, col };
    }

    private Object getSelectionKeyFromCursor() {
        int idx = getTable().indexOf(fTableCursor.getRow());
        int col = fTableCursor.getColumn();

        return getVirtualContentModel().getKey(idx, col);
    }

    private Object getBufferTopKey() {
        return getKey(0);
    }

    private Object getBufferEndKey() {
        AbstractVirtualContentTableModel model = getVirtualContentModel();

        if (model != null)
            return getKey(model.getElements().length - 1);
        return null;
    }

    public int indexOf(Object key) {
        int idx = -1;
        AbstractVirtualContentTableModel model = getVirtualContentModel();
        if (model != null)
            idx = model.indexOfKey(key);
        return idx;
    }

    private int columnOf(Object element, Object key) {
        int idx = -1;
        AbstractVirtualContentTableModel model = getVirtualContentModel();
        if (model != null) {
            idx = model.columnOf(element, key);
        }
        return idx;
    }

    public Object getKey(int index) {
        AbstractVirtualContentTableModel model = getVirtualContentModel();
        if (model != null) {
            Object key = model.getKey(index);
            return key;
        }
        return null;
    }

    public Object getKey(int row, int col) {
        AbstractVirtualContentTableModel model = getVirtualContentModel();
        if (model != null)
            return model.getKey(row, col);
        return null;
    }

    protected synchronized void preservingSelection(Runnable updateCode) {
        Object oldTopIndexKey = null;

        if (getPendingSetTopIndexKey() == null) {
            // preserve selection
            oldTopIndexKey = getTopIndexKey();
        } else {
            oldTopIndexKey = getPendingSetTopIndexKey();
        }
        Object oldSelectionKey = null;
        try {
            if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING) {
                if (oldTopIndexKey != null)
                    System.out.println(
                            getRendering() + " preserve top index: " + ((BigInteger) oldTopIndexKey).toString(16)); //$NON-NLS-1$
                else
                    System.out.println("top index key is null, nothing to preserve"); //$NON-NLS-1$
            }

            if (fPendingSelection != null)
                oldSelectionKey = fPendingSelection;
            else
                oldSelectionKey = getSelectionKey();

            if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING) {
                if (oldTopIndexKey != null)
                    System.out.println(
                            getRendering() + " preserve selection: " + ((BigInteger) oldSelectionKey).toString(16)); //$NON-NLS-1$
                else
                    System.out.println("selection key is null, nothing to preserve"); //$NON-NLS-1$
            }

            // perform the update
            updateCode.run();

        } finally {

            if (oldSelectionKey != null) {
                if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING)
                    System.out.println(
                            getRendering() + " preserved selection " + ((BigInteger) oldSelectionKey).toString(16)); //$NON-NLS-1$
                setSelection(oldSelectionKey);
            }

            if (getPendingSetTopIndexKey() != null) {
                if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING) {
                    if (oldTopIndexKey != null) {
                        System.out.println(getRendering() + " finished top index: " //$NON-NLS-1$
                                + ((BigInteger) oldTopIndexKey).toString(16));
                    }
                }
                setTopIndex(getPendingSetTopIndexKey());
            } else if (oldTopIndexKey != null) {
                setTopIndex(oldTopIndexKey);

                if (AsyncVirtualContentTableViewer.DEBUG_DYNAMIC_LOADING)
                    System.out.println(
                            getRendering() + " finished top index: " + ((BigInteger) oldTopIndexKey).toString(16)); //$NON-NLS-1$
            }
        }
    }

    public void dispose() {
        super.dispose();

        if (fTableCursor != null && !fTableCursor.isDisposed()) {
            fCursorEditor.dispose();
            fCursorEditor = null;

            fTableCursor.removeTraverseListener(fCursorTraverseListener);
            fTableCursor.removeKeyListener(fCursorKeyAdapter);
            fTableCursor.removeMouseListener(fCursorMouseListener);
            fTableCursor.removeSelectionListener(fCursorSelectionListener);

            fTableCursor.dispose();
            fTableCursor = null;
        }
    }

    public void showTableCursor(final boolean show) {

        Display display = DebugUIPlugin.getDefault().getWorkbench().getDisplay();
        if (Thread.currentThread() == display.getThread()) {
            if (!fTableCursor.isDisposed()) {
                if (fTableCursor.isVisible() != show)
                    fTableCursor.setVisible(show);
            }
        } else {
            UIJob job = new UIJob("show table cursor") { //$NON-NLS-1$

                public IStatus runInUIThread(IProgressMonitor monitor) {
                    if (!fTableCursor.isDisposed()) {
                        if (fTableCursor.isVisible() != show)
                            fTableCursor.setVisible(show);
                    }
                    return Status.OK_STATUS;
                }
            };

            job.setSystem(true);
            job.schedule();
        }
    }

    private void handleTableMouseEvent(MouseEvent e) {
        // figure out new cursor position based on here the mouse is pointing
        TableItem[] tableItems = getTable().getItems();
        TableItem selectedRow = null;
        int colNum = -1;
        int numCol = getColumnProperties().length;

        for (int j = 0; j < tableItems.length; j++) {
            TableItem item = tableItems[j];
            if (item.getData() != null) {
                for (int i = 0; i < numCol; i++) {
                    Rectangle bound = item.getBounds(i);
                    if (bound.contains(e.x, e.y)) {
                        colNum = i;
                        selectedRow = item;
                        break;
                    }
                }
            }
            if (colNum >= 0)
                break;
        }

        // if column position cannot be determined, return
        if (colNum < 1)
            return;

        // handle user mouse click onto table
        // move cursor to new position
        if (selectedRow != null) {
            int row = getTable().indexOf(selectedRow);
            showTableCursor(true);
            fTableCursor.setSelection(row, colNum);

            // manually call this since we don't get an event when
            // the table cursor changes selection.
            handleCursorMoved();

            fTableCursor.setFocus();
        }
    }

    /**
     * Activate cell editor and pre-fill it with initial value.
     * If initialValue is null, use cell content as initial value
     * @param initialValue the initial value for the cell editor
     */
    private void activateCellEditor(String initialValue) {

        final int col = fTableCursor.getColumn();
        final int row = indexOf(fSelectionKey);

        if (row < 0)
            return;

        // do not allow user to edit address column
        if (col == 0 || col > getNumCol()) {
            return;
        }

        ICellModifier cellModifier = null;

        cellModifier = getCellModifier();

        TableItem tableItem = getTable().getItem(row);

        Object element = tableItem.getData();

        if (element != null) {
            Object property = getColumnProperties()[col];
            Object value = cellModifier.getValue(element, (String) property);
            boolean canEdit = cellModifier.canModify(element, (String) property);

            if (!canEdit)
                return;

            CellEditor editor = getCellEditors()[col];
            if (editor != null) {
                // The control that will be the editor must be a child of the
                // Table
                Control control = editor.getControl();

                Object cellValue = null;

                if (initialValue != null) {
                    cellValue = initialValue;
                } else {
                    cellValue = value;
                }

                editor.setValue(cellValue);

                fCursorEditor.horizontalAlignment = SWT.LEFT;
                fCursorEditor.grabHorizontal = true;

                // Open the editor editor in selected column of the selected
                // row.
                fCursorEditor.setEditor(control, tableItem, col);

                // Assign focus to the editor control
                editor.setFocus();

                if (initialValue != null && control instanceof Text) {
                    ((Text) control).clearSelection();
                }

                control.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));

                // add listeners for the editor control
                addListeners(control);

                fCellEditorListener = new CellEditorListener(row, col, editor);
                editor.addListener(fCellEditorListener);

                // move cursor below editor control
                fTableCursor.moveBelow(control);
            }
        }
    }

    private void deactivateEditor(CellEditor editor) {
        removeListeners(editor.getControl());
        fTableCursor.moveAbove(editor.getControl());
        fTableCursor.setFocus();
    }

    /*
     * @param editor
     */
    private void addListeners(Control control) {

        fEditorKeyListener = new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                handleKeyEventInEditor(e);
            }
        };

        control.addKeyListener(fEditorKeyListener);
    }

    /**
     * @param event the key event
     */
    private void handleKeyEventInEditor(KeyEvent event) {

        final KeyEvent e = event;
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                Object obj = e.getSource();
                if (obj instanceof Control) {
                    Control control = (Control) obj;
                    int row = fCellEditorListener.getRow();
                    int col = fCellEditorListener.getCol();

                    try {
                        switch (e.keyCode) {
                        case 0:
                            doHandleKeyEvent(row, col);
                            break;
                        case SWT.ESC:
                            cancelEditing(row, col);
                            break;
                        default:
                            doHandleKeyEvent(row, col);
                            break;
                        }
                    } catch (NumberFormatException e1) {
                        MemoryViewUtil.openError(DebugUIMessages.MemoryViewCellModifier_failure_title,
                                DebugUIMessages.MemoryViewCellModifier_data_is_invalid, null);

                        fTableCursor.setSelection(row, col);
                        handleCursorMoved();

                        removeListeners(control);
                    }
                }
            }
        });
    }

    private void doHandleKeyEvent(int row, int col) {
        int numCharsPerByte = fRendering.getNumCharsPerByte();
        if (numCharsPerByte > 0) {
            Object value = getCellEditors()[col].getValue();
            if (getCellEditors()[col] instanceof TextCellEditor && value instanceof String) {
                String str = (String) value;

                if (str.length() > fRendering.getBytesPerColumn() * numCharsPerByte) {
                    String newValue = str;

                    CellEditor editor = getCellEditors()[col];
                    editor.setValue(newValue.substring(0, fRendering.getBytesPerColumn() * numCharsPerByte));

                    // We want to call modify value here to avoid race condition.
                    // Relying on the editor event to modify the cell may introduce a race condition since
                    // we try to activate another cell editor in this method.  If we happen to use same cell
                    // editor in the next activation, the value of the editor may be incorrect when the listener gets the event.
                    // We may write the wrong value in that case.  Calling modify here allows us to capture the value
                    // now and send that to the model.
                    fCellEditorListener.cancelEditor();
                    deactivateEditor(editor);
                    modifyValue(fCellEditorListener.getRow(), fCellEditorListener.getCol(), editor.getValue());

                    // if cursor is at the end of a line, move to next line
                    if (col >= getNumCol()) {
                        col = 1;
                        row++;
                    } else {
                        col++;
                    }

                    fTableCursor.setSelection(row, col);
                    handleCursorMoved();

                    activateCellEditor(newValue.substring(fRendering.getBytesPerColumn() * numCharsPerByte));
                }
            }
        }
    }

    private void cancelEditing(int row, int col) {
        // if user has pressed escape, do not commit the changes
        // remove listener to avoid getting notified on the modify value
        fCellEditorListener.cancelEditor();
        deactivateEditor(getCellEditors()[col]);

        fTableCursor.setSelection(row, col);
        handleCursorMoved();

        // cursor needs to have focus to remove focus from cell editor
        fTableCursor.setFocus();
    }

    /**
     * @param control the control to remove the default key listener from
     */
    private void removeListeners(Control control) {

        control.removeKeyListener(fEditorKeyListener);
    }

    /**
     * Modify value and send new value to debug adapter
     * @param row the row
     * @param col the column
     * @param newValue the new value
     * @throws NumberFormatException if trying to set a number value fails
     */
    private void modifyValue(int row, int col, Object newValue) throws NumberFormatException {
        if (newValue instanceof String && ((String) newValue).length() == 0) {
            // do not do anything if user has not entered anything
            return;
        }

        if (row >= 0 && row < getTable().getItemCount()) {
            TableItem tableItem = getTable().getItem(row);

            Object property = getColumnProperties()[col];
            getCellModifier().modify(tableItem, (String) property, newValue);
        }
    }

    public TableCursor getCursor() {
        return fTableCursor;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.ContentViewer#getLabelProvider()
     * Implemented minimum to work with PrintTableRendering action.
     * This is not a real table labe provider, only goes to the table
     * to get the text at the specified row and column.
     */
    public IBaseLabelProvider getLabelProvider() {
        return new ITableLabelProvider() {

            public Image getColumnImage(Object element, int columnIndex) {
                return null;
            }

            public String getColumnText(Object element, int columnIndex) {
                int idx = getVirtualContentModel().indexOfElement(element);
                if (idx >= 0) {
                    TableItem item = getTable().getItem(idx);
                    return item.getText(columnIndex);
                }
                return IInternalDebugCoreConstants.EMPTY_STRING;
            }

            public void addListener(ILabelProviderListener listener) {
            }

            public void dispose() {
            }

            public boolean isLabelProperty(Object element, String property) {
                return false;
            }

            public void removeListener(ILabelProviderListener listener) {
            }
        };
    }

    public void formatViewer() {
        if (getModel() == null || !hasPendingUpdates())
            doFormatViewer();
        else
            // do not format in the middle of an update
            // set pending update and will format when update is completed
            fPendingFormatViewer = true;
    }

    /**
     * 
     */
    private void doFormatViewer() {
        fPendingFormatViewer = false;
        preservingSelection(new Runnable() {

            public void run() {
                // causes the content of the table viewer to be replaced
                // without asking content adapter for content
                AbstractVirtualContentTableModel model = getVirtualContentModel();
                if (model != null) {
                    model.handleViewerChanged();
                }
            }
        });
    }

    private void fireSelectionChanged(Object selectionKey) {
        if (selectionKey != null) {
            SelectionChangedEvent evt = new SelectionChangedEvent(this, new StructuredSelection(selectionKey));
            fireSelectionChanged(evt);
        }
    }

    public void handlePresentationFailure(IStatusMonitor monitor, IStatus status) {
        super.handlePresentationFailure(monitor, status);
    }

    public void refresh(boolean getContent) {
        if (getContent)
            refresh();
        else {
            preservingSelection(new Runnable() {

                public void run() {
                    AbstractVirtualContentTableModel model = getVirtualContentModel();
                    if (model != null) {
                        Object[] elements = model.getElements();
                        model.remove(elements);
                        model.add(elements);
                    }
                }
            });
        }
    }

    protected void tableTopIndexSetComplete() {

        if (!fTableCursor.isDisposed()) {
            // TODO:  work around swt bug, must force a table cursor redraw after top index is changed
            // BUG 130130
            int[] coordinates = getCoordinatesFromKey(getSelectionKey());
            if (coordinates.length > 0)
                fTableCursor.setVisible(true);
            else
                fTableCursor.setVisible(false);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.debug.internal.ui.viewers.model.provisional.viewers.AsynchronousViewer#getModel()
     */
    public AsynchronousModel getModel() {
        return super.getModel();
    }

    // TODO:  need pluggable model to be truly flexible
    protected AbstractVirtualContentTableModel createVirtualContentTableModel() {
        return new TableRenderingModel(this);
    }

    protected void updateComplete(IStatusMonitor monitor) {
        super.updateComplete(monitor);

        if (!hasPendingUpdates() && !fTableCursor.isDisposed()) {
            attemptSetKeySelection();
            fTableCursor.redraw();

            // if the viewer has pending top index, then more updates will come in
            // and the cursor should not be redrawn yet.
            if (!hasPendingSetTopIndex()) {
                preservingSelection(new Runnable() {

                    public void run() {

                        int[] coordinates = getCoordinatesFromKey(getSelectionKey());
                        if (coordinates.length > 0)
                            fTableCursor.setVisible(true);
                        else
                            fTableCursor.setVisible(false);
                    }
                });
            }
        }

        if (!hasPendingUpdates() && fPendingFormatViewer) {
            formatViewer();
            resizeColumnsToPreferredSize();
        }
    }

    protected void clear(Widget item) {
        super.clear(item);

        // this table viewer assumes that #getData will return null
        // set data to null when clearing an item.
        // only visible item will get SET DATA event again and at that time
        // the viewer would set the data back.
        if (item instanceof TableItem) {
            item.setData(null);
        }
    }
}