org.eclipse.mat.ui.internal.viewer.RefinedResultViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.mat.ui.internal.viewer.RefinedResultViewer.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2011 SAP AG and IBM Corporation
 * 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:
 *    SAP AG - initial API and implementation
 *    IBM Corporation - fixes and selection of undisplayed items
 *******************************************************************************/
package org.eclipse.mat.ui.internal.viewer;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

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.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.mat.query.Column;
import org.eclipse.mat.query.ContextDerivedData;
import org.eclipse.mat.query.ContextDerivedData.DerivedColumn;
import org.eclipse.mat.query.ContextDerivedData.DerivedOperation;
import org.eclipse.mat.query.ContextProvider;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.query.IQueryContext;
import org.eclipse.mat.query.refined.Filter;
import org.eclipse.mat.query.refined.RefinedStructuredResult;
import org.eclipse.mat.query.refined.RefinedStructuredResult.DerivedDataJobDefinition;
import org.eclipse.mat.query.refined.TotalsRow;
import org.eclipse.mat.query.registry.QueryResult;
import org.eclipse.mat.ui.MemoryAnalyserPlugin;
import org.eclipse.mat.ui.Messages;
import org.eclipse.mat.ui.actions.OpenHelpPageAction;
import org.eclipse.mat.ui.editor.AbstractEditorPane;
import org.eclipse.mat.ui.editor.AbstractPaneJob;
import org.eclipse.mat.ui.editor.MultiPaneEditor;
import org.eclipse.mat.ui.util.Copy;
import org.eclipse.mat.ui.util.EasyToolBarDropDown;
import org.eclipse.mat.ui.util.ErrorHelper;
import org.eclipse.mat.ui.util.PopupMenu;
import org.eclipse.mat.ui.util.ProgressMonitorWrapper;
import org.eclipse.mat.ui.util.QueryContextMenu;
import org.eclipse.mat.ui.util.SearchOnTyping;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ControlEditor;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.themes.ColorUtil;

public abstract class RefinedResultViewer {
    protected static final int LIMIT = 25;
    protected static final int MAX_COLUMN_WIDTH = 500;
    protected static final int MIN_COLUMN_WIDTH = 90;

    /* package */interface Key {
        String CONTROL = "$control";//$NON-NLS-1$
    }

    /* package */static class ControlItem {
        public ControlItem(boolean expandAndSelect, int level) {
            this.expandAndSelect = expandAndSelect;
            this.level = level;
        }

        boolean expandAndSelect;
        int level;

        List<?> children;
        TotalsRow totals;
        boolean hasBeenPainted;

        @Override
        public String toString() {
            return level + " " + hashCode() + " " + totals;//$NON-NLS-1$//$NON-NLS-2$
        }

        public TotalsRow getTotals() {
            return totals;
        }

    }

    /* package */interface WidgetAdapter {
        Composite createControl(Composite parent);

        Item createColumn(Column column, int ii, SelectionListener selectionListener);

        ControlEditor createEditor();

        void setEditor(Composite composite, Item item, int columnIndex);

        Item[] getSelection();

        int indexOf(Item item);

        Item getItem(Item item, int index);

        Item getParentItem(Item item);

        Item getItem(Point pt);

        Rectangle getBounds(Item item, int columnIndex);

        Rectangle getImageBounds(Item item, int columnIndex);

        void apply(Item item, int index, String label, Color color, Font font);

        void apply(Item item, int index, String label);

        void apply(Item item, Font font);

        Font getFont();

        Item getSortColumn();

        int getSortDirection();

        void setSortColumn(Item column);

        void setSortDirection(int direction);

        void setItemCount(Item item, int count);

        int getItemCount(Item object);

        void setExpanded(Item parentItem, boolean b);

        Rectangle getTextBounds(Widget item, int index);

        int getLineHeightEstimation();

        int[] getColumnOrder();

        void setColumnOrder(int order[]);

        int getColumnWidth(int column);

        void setColumnWidth(int column, int width);
    }

    /** pane in which the viewer is embedded */
    protected MultiPaneEditor editor;

    /** the editor pane */
    protected AbstractEditorPane pane;

    /** adapter hiding specifics of table or tree */
    protected WidgetAdapter adapter;

    /** load SWT resources */
    protected LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources());

    /** the control (either tree or table) */
    protected Composite control;

    /** the columns (either tree or table) */
    protected Item[] columns;

    /** the editor (either tree or table) */
    protected ControlEditor controlEditor;

    /** number of items visible in the window */
    protected int visibleItemsEstimate;

    /** fonts & colors for filter & total row */
    protected Font boldFont;
    protected Color grayColor;
    protected Color greenColor;

    /** fonts used for decorated columns */
    protected Font[] fonts;
    /** colors used for decorated columns */
    protected Color[] colors;

    protected IQueryContext context;
    protected QueryResult queryResult;
    protected RefinedStructuredResult result;

    /** details a/b retained size calculation */
    protected List<DerivedDataJobDefinition> jobs;

    protected QueryContextMenu contextMenu;
    protected TotalsRow rootTotalsRow;
    protected boolean needsPacking = true;

    // //////////////////////////////////////////////////////////////
    // initialization
    // //////////////////////////////////////////////////////////////

    /* package */ RefinedResultViewer(IQueryContext context, QueryResult result,
            RefinedStructuredResult refinedResult) {
        this.context = context;
        this.queryResult = result;
        this.result = refinedResult;
        this.jobs = new ArrayList<DerivedDataJobDefinition>(refinedResult.getJobs());
    }

    public abstract void init(Composite parent, MultiPaneEditor editor, AbstractEditorPane pane);

    protected void init(WidgetAdapter viewer, Composite parent, MultiPaneEditor editor, AbstractEditorPane pane) {
        this.adapter = viewer;
        this.editor = editor;
        this.pane = pane;

        parent.setRedraw(false);
        try {
            control = adapter.createControl(parent);
            control.addFocusListener(new FocusListener() {

                public void focusGained(FocusEvent e) {
                    RefinedResultViewer.this.editor.getEditorSite().getActionBars()
                            .setGlobalActionHandler(ActionFactory.COPY.getId(), new Action() {
                                @Override
                                public void run() {
                                    Copy.copyToClipboard(control);
                                }
                            });
                    RefinedResultViewer.this.editor.getEditorSite().getActionBars().updateActionBars();
                }

                public void focusLost(FocusEvent e) {
                }
            });

            boldFont = resourceManager.createFont(FontDescriptor.createFrom(adapter.getFont()).setStyle(SWT.BOLD));
            grayColor = resourceManager.createColor( //
                    ColorUtil.blend(control.getBackground().getRGB(), control.getForeground().getRGB()));
            greenColor = control.getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN);

            createColumns();
            addPaintListener();
            addDoubleClickListener();
            createContextMenu();

            if (result.getSortColumn() >= 0) {
                adapter.setSortColumn(columns[result.getSortColumn()]);
                adapter.setSortDirection(result.getSortDirection().getSwtCode());
            }

            SearchOnTyping.attachTo(control, 0);

            // estimate the number of lines to render
            // (the control itself is not visible, possibly even the parent is
            // not visible)
            Rectangle bounds = parent.getParent().getBounds();
            if (bounds.height == 0 && bounds.width == 0)
                visibleItemsEstimate = 10;
            else
                visibleItemsEstimate = Math.max(((bounds.height - 10) / adapter.getLineHeightEstimation()) - 2, 1);

            refresh(true);

            addTextEditors();
        } finally {
            parent.setRedraw(true);
        }
    }

    private void createColumns() {
        Column[] queryColumns = result.getColumns();
        int nrOfColumns = queryColumns.length;

        columns = new Item[nrOfColumns];
        for (int ii = 0; ii < nrOfColumns; ++ii) {
            Column queryColumn = queryColumns[ii];
            columns[ii] = adapter.createColumn(queryColumn, ii, new ColumnSelectionListener());

            if (ii == 0) {
                columns[ii].addListener(SWT.Resize, new Listener() {
                    public void handleEvent(Event event) {
                        control.redraw();
                    }
                });
            }
        }
    }

    private void addDoubleClickListener() {
        control.addListener(SWT.DefaultSelection, new Listener() {
            public void handleEvent(Event event) {
                Item widget = (Item) event.item;
                if (widget == null)
                    return;
                Object data = widget.getData();
                if (data != null)
                    return;

                ControlItem ctrl = null;

                Item parent = adapter.getParentItem(widget);
                if (parent == null) {
                    if (adapter.indexOf(widget) != 0)
                        ctrl = (ControlItem) control.getData(Key.CONTROL);
                    else {
                        // Select the filter row and open the filter editor
                        int columnIndex = 0;
                        Filter filter = RefinedResultViewer.this.result.getFilter()[columnIndex];
                        activateEditor(widget, filter, columnIndex);
                    }
                } else {
                    ctrl = (ControlItem) parent.getData(Key.CONTROL);
                }

                if (ctrl == null || ctrl.totals == null)
                    return;

                if (ctrl.totals.getVisibleItems() >= ctrl.totals.getNumberOfItems())
                    return;

                doRevealChildren(parent, LIMIT);

                event.doit = false;
            }
        });
    }

    protected abstract List<?> getElements(Object parent);

    protected abstract void configureColumns();

    protected void applyTextAndImage(Item item, Object element) {
        if (element == null) {
            /*
             * Possibly a virtual table which is not yet updated, so wait for
             * the update.
             */
            return;
        }
        item.setData(element);
        adapter.apply(item, adapter.getFont());

        URL image = result.getIcon(element);
        if (image != null)
            item.setImage(MemoryAnalyserPlugin.getDefault().getImage(image));

        for (int ii = 0; ii < columns.length; ii++) {
            if (!result.isDecorated(ii)) {
                adapter.apply(item, ii, result.getFormattedColumnValue(element, ii));
            } else {
                String[] texts = new String[3];
                texts[0] = result.getColumns()[ii].getDecorator().prefix(element);
                texts[1] = result.getFormattedColumnValue(element, ii);
                texts[2] = result.getColumns()[ii].getDecorator().suffix(element);
                item.setData(String.valueOf(ii), texts);
                adapter.apply(item, ii, asString(texts));
            }
        }
    }

    protected void applyTotals(Item item, TotalsRow totalsRow) {
        item.setImage(MemoryAnalyserPlugin.getDefault().getImage(totalsRow.getIcon()));

        for (int ii = 0; ii < columns.length; ii++)
            adapter.apply(item, ii, totalsRow.getLabel(ii));

        adapter.apply(item, boldFont);

        item.setData(null);
        item.setData(Key.CONTROL, null);
    }

    protected void applyUpdating(Item item) {
        item.setText(Messages.RefinedResultViewer_updating);
        item.setImage(MemoryAnalyserPlugin.getImage(MemoryAnalyserPlugin.ISharedImages.REFRESHING));
        item.setData(null);
        item.setData(Key.CONTROL, null);
    }

    protected void applyFilterData(Item item) {
        Filter[] filter = result.getFilter();
        for (int ii = 0; ii < filter.length; ii++)
            applyFilterData(item, ii, filter[ii]);
    }

    protected void applyFilterData(Item item, int columnIndex, Filter filter) {
        if (columnIndex == 0)
            item.setImage(MemoryAnalyserPlugin.getImage(MemoryAnalyserPlugin.ISharedImages.FILTER));

        String label = filter.isActive() ? filter.getCriteria() : filter.getLabel();
        Color color = filter.isActive() ? greenColor : grayColor;
        Font font = filter.isActive() ? boldFont : adapter.getFont();
        adapter.apply(item, columnIndex, label, color, font);
    }

    protected String asString(String[] texts) {
        StringBuilder buf = new StringBuilder();
        for (int ii = 0; ii < texts.length; ii++) {
            if (texts[ii] != null) {
                if (buf.length() > 0)
                    buf.append(" ");//$NON-NLS-1$
                buf.append(texts[ii]);
            }
        }
        return buf.toString();
    }

    private void addPaintListener() {
        boolean isDecorated = false;
        for (int ii = 0; !isDecorated && ii < result.getColumns().length; ii++)
            isDecorated = result.isDecorated(ii);

        if (!isDecorated)
            return;

        // fonts
        fonts = new Font[3];
        fonts[0] = boldFont; // prefix
        fonts[1] = adapter.getFont(); // normal
        fonts[2] = boldFont; // suffix

        // colors
        colors = new Color[3];
        colors[0] = null;
        colors[1] = null;
        colors[2] = grayColor;

        control.addListener(SWT.MeasureItem, new Listener() {
            public void handleEvent(final Event event) {
                if (!result.isDecorated(event.index))
                    return;

                Object element = event.item.getData();
                if (element == null)
                    return;

                String[] texts = (String[]) event.item.getData(String.valueOf(event.index));
                if (texts == null)
                    return;

                if (texts.length > 0) {
                    int width = 0;
                    int height = 0;

                    Image image = ((Item) event.item).getImage();
                    if (image != null)
                        width += image.getBounds().width + 4;

                    for (int ii = 0; ii < texts.length; ii++) {
                        if (texts[ii] != null) {
                            event.gc.setFont(fonts[ii]);
                            Point size = event.gc.textExtent(texts[ii]);
                            width += size.x + 4;
                            height = Math.max(event.height, size.y + 2);
                        }
                    }

                    event.width = width;
                    event.height = height;
                } else {
                    event.height = Math.max(event.height, 16 + 1);
                    event.width = MIN_COLUMN_WIDTH;
                }

                event.doit = false;
            }
        });

        control.addListener(SWT.EraseItem, new Listener() {
            public void handleEvent(final Event event) {
                if (!result.isDecorated(event.index))
                    return;

                Object element = event.item.getData();
                if (element == null)
                    return;

                String[] texts = (String[]) event.item.getData(String.valueOf(event.index));
                if (texts != null) {
                    event.detail &= ~SWT.FOREGROUND;
                }
            }
        });

        control.addListener(SWT.PaintItem, new Listener() {
            public void handleEvent(final Event event) {
                if (!result.isDecorated(event.index))
                    return;

                Object element = event.item.getData();
                if (element == null)
                    return;

                String[] texts = (String[]) event.item.getData(String.valueOf(event.index));
                if (texts != null) {
                    boolean isSelected = (event.detail & SWT.SELECTED) != 0;

                    if (isSelected) {
                        Rectangle r = event.gc.getClipping();
                        event.gc.fillRectangle(event.x, event.y, r.width, event.height - 1);
                    }

                    int x = event.x;
                    Image image = ((Item) event.item).getImage();
                    if (image != null) {
                        event.gc.drawImage(image, event.x + 1, event.y);
                        x += image.getBounds().width + 4;
                    }

                    Color fg = event.gc.getForeground();

                    for (int ii = 0; ii < texts.length; ii++) {
                        if (texts[ii] != null) {
                            event.gc.setFont(fonts[ii]);
                            if (!isSelected)
                                event.gc.setForeground(colors[ii] != null ? colors[ii] : fg);

                            Point size = event.gc.textExtent(texts[ii]);
                            event.gc.drawText(texts[ii], x + 1, event.y + Math.max(0, (event.height - size.y) / 2),
                                    true);
                            x += size.x + 4;
                        }
                    }

                    event.gc.setForeground(fg);

                    event.doit = false;
                }

            }

        });

    }

    private void createContextMenu() {
        contextMenu = new QueryContextMenu(editor, queryResult) {
            @Override
            protected void customMenu(PopupMenu menu, List<IContextObject> menuContext,
                    final ContextProvider provider, String label) {
                menu.addSeparator();

                for (DerivedColumn derivedColumn : context.getContextDerivedData().getDerivedColumns()) {
                    for (final DerivedOperation derivedOperation : derivedColumn.getOperations()) {
                        Action action = new Action(derivedOperation.getLabel()) {
                            @Override
                            public void run() {
                                doCalculateDerivedValuesForSelection(provider, derivedOperation);
                            }
                        };

                        action.setImageDescriptor(MemoryAnalyserPlugin
                                .getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.CALCULATOR));
                        menu.add(action);
                    }
                }
            }
        };
    }

    private void addTextEditors() {
        controlEditor = adapter.createEditor();

        control.addListener(SWT.MouseDown, new Listener() {
            public void handleEvent(Event event) {
                Point pt = new Point(event.x, event.y);

                Item item = adapter.getItem(pt);
                if (item != adapter.getItem(null, 0))
                    return;

                int columnIndex = getColumnIndex(item, pt);
                if (columnIndex < 0)
                    return;

                Filter filter = result.getFilter()[columnIndex];

                activateEditor(item, filter, columnIndex);
            }

            private int getColumnIndex(Item item, Point pt) {
                for (int ii = 0; ii < columns.length; ii++) {
                    Rectangle bounds = adapter.getBounds(item, ii);
                    if (bounds.contains(pt))
                        return ii;
                }
                return -1;
            }
        });
    }

    // //////////////////////////////////////////////////////////////
    // tool bar / context menu handling
    // //////////////////////////////////////////////////////////////

    public void contributeToToolBar(IToolBarManager manager) {
        Action calculateRetainedSizeMenu = new EasyToolBarDropDown(
                Messages.RefinedResultViewer_CalculateRetainedSize, //
                MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.CALCULATOR), //
                editor) {

            @Override
            public void contribute(PopupMenu menu) {
                List<ContextProvider> providers = new ArrayList<ContextProvider>();

                List<ContextProvider> p = result.getResultMetaData().getContextProviders();
                if (p != null && !p.isEmpty())
                    providers.addAll(p);
                else
                    providers.add(queryResult.getDefaultContextProvider());

                if (!providers.isEmpty()) {
                    for (ContextProvider cp : providers) {
                        PopupMenu toThisMenu = menu;
                        if (providers.size() > 1) {
                            PopupMenu subMenu = new PopupMenu(cp.getLabel());
                            menu.add(subMenu);
                            toThisMenu = subMenu;
                        }

                        addRetainedSizeActions(toThisMenu, cp);
                    }
                }
            }

            private void addRetainedSizeActions(PopupMenu toThisMenu, final ContextProvider cp) {
                ContextDerivedData derivedData = context.getContextDerivedData();
                if (derivedData == null)
                    return;

                for (DerivedColumn derivedColumn : derivedData.getDerivedColumns()) {
                    for (final DerivedOperation derivedOperation : derivedColumn.getOperations()) {
                        Action action = new Action(derivedOperation.getLabel()) {
                            @Override
                            public void run() {
                                doCalculateDerivedValuesForAll(cp, derivedOperation);
                            }
                        };

                        action.setImageDescriptor(MemoryAnalyserPlugin
                                .getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.CALCULATOR));
                        toThisMenu.add(action);
                    }
                }
            }

        };

        Action exportMenu = new EasyToolBarDropDown(Messages.RefinedResultViewer_Export,
                MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.EXPORT_MENU), //
                editor) {

            @Override
            public void contribute(PopupMenu menu) {
                menu.add(new ExportActions.HtmlExport(control, result, context));
                menu.add(new ExportActions.CsvExport(control, result, context));
                menu.add(new ExportActions.TxtExport(control));
            }
        };

        manager.add(calculateRetainedSizeMenu);
        manager.add(exportMenu);

        if (queryResult.getQuery() != null && queryResult.getQuery().getHelpUrl() != null)
            manager.appendToGroup("help", new OpenHelpPageAction(queryResult.getQuery().getHelpUrl()));//$NON-NLS-1$

    }

    public void addContextMenu(PopupMenu menu) {
        contextMenu.addContextActions(menu, getSelection(), getControl());
        addColumnsMenu(menu);
        addMoreMenu(menu);
    }

    private void addColumnsMenu(PopupMenu menu) {
        menu.addSeparator();
        PopupMenu columnsMenu = new PopupMenu(Messages.RefinedResultViewer_Columns);
        menu.add(columnsMenu);

        addFilterMenu(columnsMenu);
        addSortByMenu(columnsMenu);
        /* temporarily removed because of dependency on 3.5, see comments 3-5 in bug 307031 */
        //        addConfigureColumnsMenu(columnsMenu); 

    }

    private void addFilterMenu(PopupMenu menu) {

        PopupMenu filterMenu = new PopupMenu(Messages.RefinedResultViewer_EditFilter);
        menu.add(filterMenu);

        for (int ii = 0; ii < columns.length; ii++) {
            final int columnIndex = ii;

            Action action = new Action(columns[ii].getText()) {
                @Override
                public void run() {
                    Item item = adapter.getItem(null, 0);
                    Filter filter = result.getFilter()[columnIndex];
                    activateEditor(item, filter, columnIndex);
                }
            };
            filterMenu.add(action);
        }
    }

    /* temporarily removed because of dependency on 3.5, see comments 3-5 in bug 307031 */
    private void addConfigureColumnsMenu(PopupMenu menu) {
        Action columnsAction = new Action(Messages.RefinedResultViewer_ConfigureColumns) {
            @Override
            public void run() {
                configureColumns();
            }
        };

        menu.add(columnsAction);
    }

    private void addSortByMenu(PopupMenu menu) {
        PopupMenu sortByMenu = new PopupMenu(Messages.RefinedResultViewer_Sort_By);
        menu.add(sortByMenu);

        for (int ii = 0; ii < columns.length; ii++) {
            final int columnIndex = ii;

            Action action = new Action(columns[ii].getText()) {
                @Override
                public void run() {
                    resort(columns[columnIndex]);
                }
            };
            sortByMenu.add(action);
        }
    }

    private void addMoreMenu(PopupMenu menu) {
        Item[] selection = adapter.getSelection();
        if (selection.length != 1)
            return;

        if (selection[0].getData() != null)
            return;

        ControlItem ctrl = null;

        final Item parent = adapter.getParentItem(selection[0]);
        if (parent == null) {
            if (adapter.indexOf(selection[0]) != 0)
                ctrl = (ControlItem) control.getData(Key.CONTROL);
        } else {
            ctrl = (ControlItem) parent.getData(Key.CONTROL);
        }

        if (ctrl != null && ctrl.totals != null //
                && ctrl.totals.getVisibleItems() < ctrl.totals.getNumberOfItems()) {
            menu.addSeparator();

            boolean isRest = ctrl.totals.getNumberOfItems() - ctrl.totals.getVisibleItems() <= LIMIT;

            if (!isRest) {
                Action action = new Action(Messages.RefinedResultViewer_Next25) {
                    @Override
                    public void run() {
                        doRevealChildren(parent, LIMIT);
                    }

                };
                action.setImageDescriptor(
                        MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.PLUS));
                menu.add(action);
            }

            Action action = new Action(Messages.RefinedResultViewer_CustomExpand) {
                @Override
                public void run() {
                    IInputValidator inputValidator = new IInputValidator() {

                        public String isValid(String newText) {
                            if (newText == null || newText.length() == 0)
                                return " "; //$NON-NLS-1$
                            try {
                                if (Integer.parseInt(newText) > 0)
                                    return null;
                            } catch (NumberFormatException e) {
                            }
                            return Messages.RefinedResultViewer_notValidNumber;

                        }

                    };

                    InputDialog inputDialog = new InputDialog(
                            PlatformUI.getWorkbench().getDisplay().getActiveShell(),
                            Messages.RefinedResultViewer_ExpandToLimit, //
                            Messages.RefinedResultViewer_EnterNumber, null, inputValidator);

                    if (inputDialog.open() == 1) // if canceled
                        return;
                    int number = new Integer(inputDialog.getValue()).intValue();
                    doRevealChildren(parent, number);
                }

            };
            action.setImageDescriptor(
                    MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.PLUS));
            menu.add(action);

            action = new Action(Messages.RefinedResultViewer_ExpandAll) {
                @Override
                public void run() {
                    doRevealChildren(parent, Integer.MAX_VALUE);
                }

            };
            action.setImageDescriptor(
                    MemoryAnalyserPlugin.getImageDescriptor(MemoryAnalyserPlugin.ISharedImages.PLUS));
            menu.add(action);
        }
    }

    private final void doRevealChildren(Item parent, int number) {
        if (parent != null && parent.isDisposed())
            return;

        ControlItem ctrl = (ControlItem) (parent == null ? control.getData(Key.CONTROL)
                : parent.getData(Key.CONTROL));
        if (ctrl == null || ctrl.totals == null)
            return;
        int visible = number == Integer.MAX_VALUE ? ctrl.totals.getNumberOfItems() : //
                Math.min(ctrl.totals.getVisibleItems() + number, ctrl.totals.getNumberOfItems());

        if (visible - ctrl.totals.getVisibleItems() > 5000) {
            MessageBox box = new MessageBox(control.getShell(), SWT.ICON_QUESTION | SWT.OK | SWT.CANCEL);
            box.setMessage(MessageUtil.format(Messages.RefinedResultViewer_BlockingWarning, //
                    (visible - ctrl.totals.getVisibleItems())));
            if (box.open() != SWT.OK)
                return;
        }

        ctrl.totals.setVisibleItems(visible);
        control.getParent().setRedraw(false);

        try {
            widgetRevealChildren(parent, ctrl.totals);
        } finally {
            control.getParent().setRedraw(true);
        }
    }

    protected abstract void widgetRevealChildren(Item parent, TotalsRow totalsData);

    private void activateEditor(final Item item, final Filter filter, final int columnIndex) {
        boolean showBorder = false;
        final Composite composite = new Composite(control, SWT.NONE);
        final Text text = new Text(composite, SWT.NONE);
        // Make text editor appear on Linux
        text.setText(" "); //$NON-NLS-1$
        final int inset = showBorder ? 1 : 0;

        // Once the item goes e.g. on sort, remove the filter to avoid operating
        // on the disposed item.
        final DisposeListener disposeListener = new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                composite.dispose();
            }
        };
        item.addDisposeListener(disposeListener);

        composite.addListener(SWT.Resize, new Listener() {
            public void handleEvent(Event e) {
                Rectangle rect = composite.getClientArea();
                text.setBounds(rect.x + inset, rect.y + inset, rect.width - inset * 2, rect.height - inset * 2);
            }
        });

        Listener textListener = new Listener() {
            public void handleEvent(final Event e) {
                switch (e.type) {
                case SWT.FocusOut:
                    updateCriteria(filter, columnIndex, text.getText());
                    // Updating the criteria can dispose the TreeItem!
                    if (!item.isDisposed()) {
                        item.removeDisposeListener(disposeListener);
                        composite.dispose();
                    }
                    break;

                case SWT.Verify:
                    Rectangle cell = adapter.getBounds(item, columnIndex);
                    Rectangle image = adapter.getImageBounds(item, columnIndex);

                    controlEditor.minimumHeight = cell.height;
                    controlEditor.minimumWidth = cell.width - image.width;
                    controlEditor.layout();
                    break;

                case SWT.Traverse:
                    int newIndex = -1;
                    switch (e.detail) {
                    case SWT.TRAVERSE_ARROW_NEXT:
                        if ((e.stateMask & SWT.MOD2) == SWT.MOD2 && (e.keyCode == SWT.ARROW_DOWN)) {
                            // Reorder columns
                            int order[] = adapter.getColumnOrder();
                            for (int i = 0; i < order.length - 1; ++i) {
                                if (order[i] == columnIndex) {
                                    order[i] = order[i + 1];
                                    order[i + 1] = columnIndex;
                                    adapter.setColumnOrder(order);
                                    break;
                                }
                            }
                            e.doit = true;
                            e.detail = SWT.TRAVERSE_NONE;
                        } else if ((e.stateMask & SWT.MOD1) == SWT.MOD1 && (e.keyCode == SWT.ARROW_DOWN)) {
                            // Resize column
                            // Updating the criteria can dispose the TreeItem!
                            updateCriteria(filter, columnIndex, text.getText());
                            if (!item.isDisposed()) {
                                item.removeDisposeListener(disposeListener);
                                composite.dispose();
                            }
                            newIndex = columnIndex;
                            int width = adapter.getColumnWidth(columnIndex);
                            adapter.setColumnWidth(columnIndex, width + 1);
                            e.doit = false;
                        } else if (e.keyCode == SWT.ARROW_DOWN) {
                            // Sort column in descending order
                            updateCriteria(filter, columnIndex, text.getText());
                            resort(columns[columnIndex], SWT.DOWN);
                            e.doit = true;
                            e.detail = SWT.TRAVERSE_NONE;
                        }
                        break;
                    case SWT.TRAVERSE_TAB_NEXT:
                        if ((e.stateMask & SWT.MOD1) == 0) {
                            // Move to next column
                            // Updating the criteria can dispose the TreeItem!
                            updateCriteria(filter, columnIndex, text.getText());
                            if (!item.isDisposed()) {
                                item.removeDisposeListener(disposeListener);
                                composite.dispose();
                            }
                            // Columns can be reordered, so tab in the display order
                            int order[] = adapter.getColumnOrder();
                            newIndex = order[0];
                            for (int i = 1; i < order.length; ++i) {
                                if (order[i - 1] == columnIndex) {
                                    newIndex = order[i];
                                    break;
                                }
                            }
                            e.doit = false;
                        }
                        break;
                    case SWT.TRAVERSE_ARROW_PREVIOUS:
                        if ((e.stateMask & SWT.MOD2) == SWT.MOD2 && (e.keyCode == SWT.ARROW_UP)) {
                            // Reorder columns
                            int order[] = adapter.getColumnOrder();
                            for (int i = 1; i < order.length; ++i) {
                                if (order[i] == columnIndex) {
                                    order[i] = order[i - 1];
                                    order[i - 1] = columnIndex;
                                    adapter.setColumnOrder(order);
                                    break;
                                }
                            }
                            e.doit = true;
                            e.detail = SWT.TRAVERSE_NONE;
                        } else if ((e.stateMask & SWT.MOD1) == SWT.MOD1 && (e.keyCode == SWT.ARROW_UP)) {
                            // Resize column
                            updateCriteria(filter, columnIndex, text.getText());
                            if (!item.isDisposed()) {
                                item.removeDisposeListener(disposeListener);
                                composite.dispose();
                            }
                            newIndex = columnIndex;
                            int width = adapter.getColumnWidth(columnIndex);
                            adapter.setColumnWidth(columnIndex, width - 1);
                            e.doit = false;
                        } else if (e.keyCode == SWT.ARROW_UP) {
                            // Sort column in ascending order
                            updateCriteria(filter, columnIndex, text.getText());
                            resort(columns[columnIndex], SWT.UP);
                            e.doit = true;
                            e.detail = SWT.TRAVERSE_NONE;
                        }
                        break;
                    case SWT.TRAVERSE_TAB_PREVIOUS:
                        if ((e.stateMask & SWT.MOD1) == 0) {
                            // Move to previous column
                            // Updating the criteria can dispose the TreeItem!
                            if (!item.isDisposed()) {
                                item.removeDisposeListener(disposeListener);
                                composite.dispose();
                            }
                            // Columns can be reordered, so tab in the display order
                            int order[] = adapter.getColumnOrder();
                            newIndex = order[order.length - 1];
                            for (int i = 1; i < order.length; ++i) {
                                if (order[i] == columnIndex) {
                                    newIndex = order[i - 1];
                                    break;
                                }
                            }
                            e.doit = false;
                        }
                        break;
                    case SWT.TRAVERSE_RETURN:
                        updateCriteria(filter, columnIndex, text.getText());
                        //$FALL-THROUGH$
                    case SWT.TRAVERSE_ESCAPE:
                        // Updating the criteria can dispose the TreeItem!
                        if (!item.isDisposed()) {
                            item.removeDisposeListener(disposeListener);
                            composite.dispose();
                        }
                        e.doit = false;
                        break;
                    default:
                        break;
                    }
                    if (newIndex >= 0) {
                        Filter f[] = result.getFilter();
                        // Need a new version of the item
                        activateEditor(adapter.getItem(null, 0), f[newIndex], newIndex);
                    }
                    break;
                }
            }

            private void updateCriteria(final Filter filter, final int columnIndex, String text) {
                boolean changed = false;
                try {
                    changed = filter.setCriteria(text);

                } catch (IllegalArgumentException e) {
                    ErrorHelper.showErrorMessage(e);
                }

                if (changed) {
                    applyFilterData(item, columnIndex, filter);
                    refresh(false);
                }
            }
        };

        text.addListener(SWT.FocusOut, textListener);
        text.addListener(SWT.Traverse, textListener);
        text.addListener(SWT.Verify, textListener);

        adapter.setEditor(composite, item, columnIndex);

        text.setText(filter.getCriteria() != null ? filter.getCriteria() : "");//$NON-NLS-1$
        text.selectAll();
        text.setFocus();

    }

    // //////////////////////////////////////////////////////////////
    // retained size calculation for all/selection
    // //////////////////////////////////////////////////////////////

    public void showDerivedDataColumn(ContextProvider provider, DerivedOperation operation) {
        prepareColumns(provider, operation);
    }

    protected void prepareColumns(ContextProvider provider, DerivedOperation operation) {
        DerivedColumn derivedColumn = context.getContextDerivedData().lookup(operation);

        Column queryColumn = result.getColumnFor(provider, derivedColumn);
        if (queryColumn == null) {
            queryColumn = result.addDerivedDataColumn(provider, derivedColumn);

            Item column = adapter.createColumn(queryColumn, this.columns.length, new ColumnSelectionListener());

            Item[] copy = new Item[columns.length + 1];
            System.arraycopy(columns, 0, copy, 0, columns.length);
            copy[columns.length] = column;
            columns = copy;

            applyFilterData(adapter.getItem(null, 0));
        }
    }

    protected void doCalculateDerivedValuesForAll(ContextProvider provider, DerivedOperation operation) {
        prepareColumns(provider, operation);

        boolean jobFound = false;
        for (DerivedDataJobDefinition job : jobs) {
            if (job.getContextProvider().hasSameTarget(provider)) {
                jobFound = true;
                job.setOperation(operation);
                break;
            }
        }

        if (!jobFound)
            jobs.add(new DerivedDataJobDefinition(provider, operation));

        ControlItem ctrl = (ControlItem) control.getData(Key.CONTROL);
        new DerivedDataJob.OnFullList(this, provider, operation, ctrl.children, null, ctrl).schedule();
    }

    protected void doCalculateDerivedValuesForSelection(ContextProvider provider, DerivedOperation operation) {
        prepareColumns(provider, operation);

        Item[] items = adapter.getSelection();
        if (items.length == 0)
            return;

        List<Item> widgetItems = new ArrayList<Item>();
        List<Object> subjectItems = new ArrayList<Object>();

        for (Item tItem : items) {
            Object subject = tItem.getData();
            if (subject != null) {
                widgetItems.add(tItem);
                subjectItems.add(subject);
            }
        }

        if (widgetItems.size() > 0) {
            new DerivedDataJob.OnSelection(this, provider, operation, subjectItems, widgetItems).schedule();
        }
    }

    // //////////////////////////////////////////////////////////////
    // manipulation
    // //////////////////////////////////////////////////////////////

    public RefinedStructuredResult getResult() {
        return result;
    }

    public QueryResult getQueryResult() {
        return queryResult;
    }

    public final Control getControl() {
        return control;
    }

    public final IStructuredSelection getSelection() {
        Item[] items = adapter.getSelection();
        if (items.length == 0)
            return StructuredSelection.EMPTY;

        List<Object> selection = new ArrayList<Object>(items.length);
        for (int ii = 0; ii < items.length; ii++) {
            items[ii].getText(); // Force virtual Item data to be populated
            Object row = items[ii].getData();
            if (row != null)
                selection.add(row);
            else {
                // Expand a totals row into all remaining objects
                final Item parent = adapter.getParentItem(items[ii]);
                ControlItem ctrl = null;
                // Used to confirm this is the last row (totals row)
                int ix = adapter.indexOf(items[ii]);
                int nx = adapter.getItemCount(parent);
                if (parent == null) {
                    // Exclude the filter row
                    if (ix != 0) {
                        ctrl = (ControlItem) control.getData(Key.CONTROL);
                    }
                } else {
                    // Subtree, so no filter row
                    ctrl = (ControlItem) parent.getData(Key.CONTROL);
                }
                if (ctrl != null && ctrl.getTotals() != null && ix + 1 == nx) {
                    // Add all the undisplayed items to the list
                    for (int jj = ctrl.getTotals().getVisibleItems(); jj < ctrl.children.size(); ++jj) {
                        selection.add(ctrl.children.get(jj));
                    }
                }
            }
        }

        return new StructuredSelection(selection);
    }

    public final void setFocus() {
        control.setFocus();
    }

    protected final void resort() {
        List<?> children = ((ControlItem) control.getData(Key.CONTROL)).children;
        if (children != null) {
            new SortingJob(this, children).schedule();
        }
    }

    protected final void resort(Item column) {
        Column queryColumn = (Column) column.getData();

        boolean isSorted = column == adapter.getSortColumn();

        int direction = SWT.UP;

        if (isSorted)
            direction = adapter.getSortDirection() == SWT.UP ? SWT.DOWN : SWT.UP;
        else
            direction = queryColumn.isNumeric() ? SWT.DOWN : SWT.UP;

        control.getParent().setRedraw(false);
        resort(column, direction);
    }

    private final void resort(Item column, int direction) {
        Column queryColumn = (Column) column.getData();
        try {
            adapter.setSortColumn(column);
            adapter.setSortDirection(direction);

            result.setSortOrder(queryColumn, Column.SortDirection.of(direction));

            resort();
        } finally {
            control.getParent().setRedraw(true);
        }
    }

    protected abstract void refresh(boolean expandAndSelect);

    protected abstract void doUpdateChildren(Item parentItem, ControlItem ctrl);

    public void dispose() {
        control.dispose();
        resourceManager.dispose();
    }

    // //////////////////////////////////////////////////////////////
    // inner classes
    // //////////////////////////////////////////////////////////////

    private final class ColumnSelectionListener implements SelectionListener {
        public void widgetDefaultSelected(SelectionEvent e) {
        }

        public void widgetSelected(SelectionEvent e) {
            Item treeColumn = (Item) e.widget;
            resort(treeColumn);
        }
    }

    // //////////////////////////////////////////////////////////////
    // jobs
    // //////////////////////////////////////////////////////////////

    protected static class RetrieveChildrenJob extends AbstractPaneJob implements ISchedulingRule {
        private RefinedResultViewer viewer;
        private ControlItem ctrl;
        private Item parentItem;
        private Object parent;

        protected RetrieveChildrenJob(RefinedResultViewer viewer, ControlItem ctrl, Item parentItem,
                Object parent) {
            super(Messages.RefinedResultViewer_RetrieveViewElements, viewer.pane);

            this.viewer = viewer;
            this.ctrl = ctrl;
            this.parentItem = parentItem;
            this.parent = parent;

            setUser(true);
            setRule(this);
        }

        @Override
        protected IStatus doRun(IProgressMonitor monitor) {
            try {
                loadElements();
                updateDisplay();
                calculateTotals(monitor);

                for (RefinedStructuredResult.DerivedDataJobDefinition job : viewer.jobs) {
                    new DerivedDataJob.OnFullList(viewer, job.getContextProvider(), job.getOperation(),
                            ctrl.children, parentItem, ctrl).schedule();
                }

                return Status.OK_STATUS;
            } catch (RuntimeException e) {
                if (!viewer.control.isDisposed())
                    viewer.control.getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            if (viewer.control.isDisposed())
                                return;

                            viewer.control.getParent().setRedraw(false);
                            try {
                                if (parentItem != null) {
                                    parentItem.setData(Key.CONTROL, null);
                                    viewer.adapter.setItemCount(parentItem, 1);
                                    viewer.adapter.setExpanded(parentItem, false);
                                } else {
                                    viewer.refresh(false);
                                }
                            } finally {
                                viewer.control.getParent().setRedraw(true);
                            }
                        }
                    });

                if (e instanceof IProgressListener.OperationCanceledException)
                    return Status.CANCEL_STATUS;
                else
                    return ErrorHelper.createErrorStatus(e);
            }
        }

        private void calculateTotals(IProgressMonitor monitor) {
            if (monitor.isCanceled())
                return;

            boolean hasChildren = ctrl.totals.getNumberOfItems() > 0 || ctrl.totals.getFilteredItems() > 0;
            if (hasChildren && ctrl.children.size() > 1) {
                viewer.result.calculateTotals(ctrl.children, ctrl.totals, new ProgressMonitorWrapper(monitor));

                if (!viewer.control.isDisposed())
                    viewer.control.getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            if (viewer.control.isDisposed())
                                return;

                            viewer.control.getParent().setRedraw(false);
                            try {
                                if (parentItem == null) // root elements
                                {
                                    int index = viewer.adapter.getItemCount(null) - 1;
                                    Item item = viewer.adapter.getItem(null, index);
                                    updateItem(item, (ControlItem) viewer.control.getData(Key.CONTROL));
                                } else {
                                    if (parentItem.isDisposed())
                                        return;

                                    int index = viewer.adapter.getItemCount(parentItem) - 1;
                                    Item item = viewer.adapter.getItem(parentItem, index);
                                    updateItem(item, (ControlItem) parentItem.getData(Key.CONTROL));
                                }
                            } finally {
                                viewer.control.getParent().setRedraw(true);
                            }
                        }

                        private void updateItem(Item item, ControlItem ctrl) {
                            if (item.isDisposed())
                                return;

                            viewer.applyTotals(item, ctrl.totals);
                        }
                    });
            }
        }

        private void updateDisplay() {
            if (!viewer.control.isDisposed())
                viewer.control.getDisplay().syncExec(new Runnable() {
                    public void run() {
                        if (viewer.control.isDisposed())
                            return;

                        viewer.control.getParent().setRedraw(false);
                        try {
                            viewer.doUpdateChildren(parentItem, ctrl);
                        } finally {
                            viewer.control.getParent().setRedraw(true);
                        }
                    }
                });
        }

        private void loadElements() {
            if (ctrl == null)
                ctrl = new ControlItem(false, 0);

            ctrl.children = viewer.getElements(parent);
            ctrl.totals = viewer.result.buildTotalsRow(ctrl.children);

            if (parent != null) {
                ctrl.totals.setVisibleItems(Math.min(LIMIT, ctrl.totals.getNumberOfItems()));
            } else {
                if (viewer.rootTotalsRow != null
                        && viewer.rootTotalsRow.getVisibleItems() > ctrl.totals.getVisibleItems()) {
                    ctrl.totals.setVisibleItems(Math.min(ctrl.totals.getNumberOfItems(), //
                            Math.max(viewer.rootTotalsRow.getVisibleItems(), viewer.visibleItemsEstimate)));
                } else {
                    ctrl.totals
                            .setVisibleItems(Math.min(viewer.visibleItemsEstimate, ctrl.totals.getNumberOfItems()));
                }
            }

            if (parent == null)
                viewer.rootTotalsRow = ctrl.totals;
        }

        public boolean contains(ISchedulingRule rule) {
            return rule.getClass() == getClass();
        }

        public boolean isConflicting(ISchedulingRule rule) {
            return rule.getClass() == getClass();
        }
    }

    private static class SortingJob extends AbstractPaneJob {
        RefinedResultViewer viewer;
        List<?> list;

        private SortingJob(RefinedResultViewer viewer, List<?> list) {
            super(Messages.RefinedResultViewer_Sorting, viewer.pane);
            this.viewer = viewer;
            this.list = list;

            setUser(true);
        }

        @Override
        protected IStatus doRun(IProgressMonitor monitor) {
            viewer.result.sort(list);

            if (!viewer.control.isDisposed())
                viewer.control.getDisplay().asyncExec(new Runnable() {
                    public void run() {
                        if (viewer.control.isDisposed())
                            return;

                        try {
                            viewer.control.getParent().setRedraw(false);
                            viewer.doUpdateChildren(null, (ControlItem) viewer.control.getData(Key.CONTROL));
                        } finally {
                            viewer.control.getParent().setRedraw(true);
                        }
                    }
                });

            return Status.OK_STATUS;
        }

    }

}