blob: f31894640288a2ef6065aedd7c1bc3ece4153aff [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2020 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 - various fixes including accessibility
*******************************************************************************/
package org.eclipse.mat.ui.internal.viewer;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.mat.query.Column;
import org.eclipse.mat.query.ContextDerivedData.DerivedOperation;
import org.eclipse.mat.query.ContextProvider;
import org.eclipse.mat.query.IQueryContext;
import org.eclipse.mat.query.IResultTree;
import org.eclipse.mat.query.refined.RefinedTree;
import org.eclipse.mat.query.refined.TotalsRow;
import org.eclipse.mat.query.registry.QueryResult;
import org.eclipse.mat.ui.Messages;
import org.eclipse.mat.ui.accessibility.AccessibleCompositeAdapter;
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.ProgressMonitorWrapper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ControlEditor;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
public class RefinedTreeViewer extends RefinedResultViewer
{
Tree tree;
TreeEditor treeEditor;
public RefinedTreeViewer(IQueryContext context, QueryResult result, RefinedTree tree)
{
super(context, result, tree);
}
@Override
public void init(Composite parent, MultiPaneEditor editor, AbstractEditorPane pane)
{
super.init(new TreeAdapter(), parent, editor, pane);
tree.addListener(SWT.Expand, new Listener()
{
public void handleEvent(Event event)
{
TreeItem parentItem = (TreeItem) event.item;
doExpand(parentItem);
}
});
tree.addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent event)
{
if (event.keyCode == SWT.KEYPAD_DIVIDE)
{
for (TreeItem item : tree.getSelection())
{
collapse(item);
}
}
}
private void collapse(TreeItem parentItem)
{
if (parentItem != null && parentItem.getExpanded())
{
parentItem.setExpanded(false);
for (TreeItem sub : parentItem.getItems())
{
collapse(sub);
}
}
}
});
tree.addListener(SWT.DefaultSelection, new Listener()
{
public void handleEvent(Event event)
{
TreeItem widget = (TreeItem) event.item;
if (widget == null)
return;
if (widget.isDisposed())
return;
Object data = widget.getData();
if (data == null)
return;
if (widget.getExpanded())
{
widget.setExpanded(false);
}
else
{
if (widget.getItemCount() > 0)
{
widget.setExpanded(true);
Object ctrl = widget.getData(Key.CONTROL);
if (ctrl == null)
doExpand(widget);
}
}
}
});
Listener focusBoxListener = new Listener()
{
public void handleEvent(final Event event)
{
if ((event.detail & SWT.FOCUSED) != 0)
{
int w = 0;
for (int i = 0; i < tree.getColumnCount(); ++i)
{
w += tree.getColumn(i).getWidth();
}
event.gc.drawFocus(0, event.y, w - 1, event.height);
}
}
};
// Needed for the focus box on Windows 7, PaintItem doesn't work
tree.addListener(SWT.EraseItem, focusBoxListener);
// Needed for the focus box on Windows XP - perhaps EraseItem is enough
tree.addListener(SWT.PaintItem, focusBoxListener);
}
@Override
protected void configureColumns()
{
/* temporarily removed because of dependency on 3.5, see comments 3-5 in bug 307031 */
// ConfigureColumns.forTree(tree, editor.getEditorSite());
}
private void doExpand(TreeItem parentItem)
{
ControlItem parentCtrl = (ControlItem) parentItem.getData(Key.CONTROL);
if (parentCtrl == null) // initial expansion
{
int level = 1;
TreeItem grandfather = parentItem.getParentItem();
if (grandfather != null)
{
ControlItem pc = (ControlItem) grandfather.getData(Key.CONTROL);
level = pc.level + 1;
}
parentItem.setData(Key.CONTROL, parentCtrl = new ControlItem(false, level));
}
synchronized (parentCtrl)
{
if (parentCtrl.children == null)
{
Thread t = new ReadDataThread(RefinedTreeViewer.this, parentCtrl, parentItem, parentItem.getData(),
false);
t.start();
try
{
parentCtrl.wait(100);
// $JL-WAIT$
if (parentCtrl.children == null || parentCtrl.children.isEmpty())
{
TreeItem[] items = parentItem.getItems();
for (int ii = 0; ii < items.length; ii++)
items[ii].dispose();
applyUpdating(new TreeItem(parentItem, SWT.NONE, 0));
return;
}
else
{
parentCtrl.hasBeenPainted = true;
doUpdateChildren(parentItem, parentCtrl);
}
}
catch (InterruptedException e)
{
// $JL-EXC$
}
}
}
}
static class ReadDataThread extends Thread
{
RefinedResultViewer viewer;
ControlItem ctrl;
TreeItem item;
Object data;
boolean initial;
public ReadDataThread(RefinedResultViewer viewer, ControlItem ctrl, TreeItem item, Object data, boolean initial)
{
this.viewer = viewer;
this.ctrl = ctrl;
this.item = item;
this.data = data;
this.initial = initial;
}
@Override
public void run()
{
List<?> elements = viewer.getElements(data);
TotalsRow totals = viewer.result.buildTotalsRow(elements);
totals.setVisibleItems(Math.min(LIMIT, totals.getNumberOfItems()));
synchronized (ctrl)
{
ctrl.children = elements;
ctrl.totals = totals;
ctrl.hasBeenPainted = false;
ctrl.notifyAll();
}
if (!viewer.control.isDisposed()) viewer.control.getDisplay().asyncExec(new Runnable()
{
public void run()
{
if (viewer.control.isDisposed())
return;
synchronized (ctrl)
{
if (!ctrl.hasBeenPainted)
{
ctrl.hasBeenPainted = true;
viewer.control.getParent().setRedraw(false);
try
{
viewer.doUpdateChildren(item, ctrl);
}
finally
{
viewer.control.getParent().setRedraw(true);
}
}
}
}
});
if (!elements.isEmpty())
calculateTotals(elements);
}
private void calculateTotals(final List<?> elements)
{
new AbstractPaneJob(Messages.RefinedTreeViewer_CalculateTotals, viewer.pane)
{
@Override
protected IStatus doRun(IProgressMonitor monitor)
{
ProgressMonitorWrapper listener = new ProgressMonitorWrapper(monitor);
viewer.result.calculateTotals(elements, ctrl.totals, listener);
if (!viewer.control.isDisposed()) viewer.control.getDisplay().asyncExec(new Runnable()
{
public void run()
{
if (viewer.control.isDisposed())
return;
synchronized (ctrl)
{
viewer.control.getParent().setRedraw(false);
try
{
if (item.isDisposed())
return;
Item i = viewer.adapter.getItem(item, ctrl.totals.getVisibleItems());
viewer.applyTotals(i, ctrl.totals);
}
finally
{
viewer.control.getParent().setRedraw(true);
}
}
}
});
return Status.OK_STATUS;
}
};
}
}
@Override
protected void widgetRevealChildren(Item parent, TotalsRow totalsData)
{
if (parent == null)
{
ControlItem ctrl = (ControlItem) tree.getData(Key.CONTROL);
int nrItems = tree.getItemCount();
// dispose "old" totals line
tree.getItem(nrItems - 1).dispose();
int currentlyVisible = nrItems - 2;
int visible = ctrl.totals.getVisibleItems();
for (int ii = currentlyVisible; ii < visible; ii++)
{
TreeItem item = new TreeItem(tree, SWT.NONE, ii + 1);
doUpdateChild(item, ctrl, ctrl.children.get(ii));
}
boolean isTotalsRowVisible = ctrl.totals.isVisible();
if (isTotalsRowVisible)
applyTotals(new TreeItem(tree, SWT.NONE, visible + 1), ctrl.totals);
}
else
{
ControlItem ctrl = (ControlItem) parent.getData(Key.CONTROL);
TreeItem parentItem = (TreeItem) parent;
int nrItems = parentItem.getItemCount();
// dispose "old" totals line
parentItem.getItem(nrItems - 1).dispose();
int currentlyVisible = nrItems - 1;
int visible = ctrl.totals.getVisibleItems();
for (int ii = currentlyVisible; ii < visible; ii++)
{
TreeItem item = new TreeItem(parentItem, SWT.NONE, ii);
doUpdateChild(item, ctrl, ctrl.children.get(ii));
}
boolean isTotalsRowVisible = ctrl.totals.isVisible();
if (isTotalsRowVisible)
applyTotals(new TreeItem(parentItem, SWT.NONE, visible), ctrl.totals);
}
}
@Override
protected List<?> getElements(Object parent)
{
return parent == null ? ((IResultTree) result).getElements() : ((IResultTree) result).getChildren(parent);
}
@Override
protected void doCalculateDerivedValuesForAll(ContextProvider provider, DerivedOperation operation)
{
super.doCalculateDerivedValuesForAll(provider, operation);
// trigger jobs for all other expanded tree items
LinkedList<TreeItem> children = new LinkedList<TreeItem>();
int count = tree.getItemCount();
for (int index = 1; index < count; index++)
{
TreeItem item = tree.getItem(index);
ControlItem ctrl = (ControlItem) item.getData(Key.CONTROL);
if (ctrl != null && ctrl.children != null)
{
new DerivedDataJob.OnFullList(this, provider, operation, ctrl.children, item, ctrl).schedule();
children.add(item);
}
}
while (!children.isEmpty())
{
TreeItem child = children.removeFirst();
count = child.getItemCount();
for (int index = 0; index < count; index++)
{
TreeItem item = child.getItem(index);
ControlItem ctrl = (ControlItem) item.getData(Key.CONTROL);
if (ctrl != null && ctrl.children != null)
{
new DerivedDataJob.OnFullList(this, provider, operation, ctrl.children, item, ctrl).schedule();
children.add(item);
}
}
}
}
@Override
protected void refresh(boolean expandAndSelect)
{
ControlItem ctrl = new ControlItem(expandAndSelect, 0);
tree.setData(Key.CONTROL, ctrl);
TreeItem[] items = tree.getItems();
for (int ii = 1; ii < items.length; ii++)
items[ii].dispose();
if (tree.getItemCount() == 0)
applyFilterData(new TreeItem(tree, SWT.NONE, 0));
applyUpdating(new TreeItem(tree, SWT.NONE, 1));
new RefinedResultViewer.RetrieveChildrenJob(this, ctrl, null, null).schedule();
}
@Override
protected void doUpdateChildren(Item parentItem, ControlItem ctrl)
{
if (parentItem == null)
{
tree.setData(Key.CONTROL, ctrl);
TreeItem[] items = tree.getItems();
for (int ii = 1; ii < items.length; ii++)
items[ii].dispose();
if (tree.getItemCount() == 0)
applyFilterData(new TreeItem(tree, SWT.NONE, 0));
int visible = ctrl.totals.getVisibleItems();
for (int ii = 0; ii < visible; ii++)
{
TreeItem item = new TreeItem(tree, SWT.NONE, ii + 1);
doUpdateChild(item, ctrl, ctrl.children.get(ii));
}
boolean isTotalsRowVisible = ctrl.totals.isVisible();
if (isTotalsRowVisible)
applyTotals(new TreeItem(tree, SWT.NONE, visible + 1), ctrl.totals);
if (needsPacking)
{
needsPacking = false;
pack();
}
}
else
{
TreeItem parent = (TreeItem) parentItem;
boolean expanded = parent.getExpanded();
parent.setData(Key.CONTROL, ctrl);
TreeItem[] items = parent.getItems();
for (int ii = 0; ii < items.length; ii++)
items[ii].dispose();
int visible = ctrl.totals.getVisibleItems();
for (int ii = 0; ii < visible; ii++)
{
TreeItem item = new TreeItem(parent, SWT.NONE, ii);
doUpdateChild(item, ctrl, ctrl.children.get(ii));
}
parent.setExpanded(expanded);
boolean isTotalsRowVisible = ctrl.totals.isVisible();
if (isTotalsRowVisible)
applyTotals(new TreeItem(parent, SWT.NONE, visible), ctrl.totals);
}
}
private void doUpdateChild(TreeItem item, ControlItem ctrl, Object element)
{
applyTextAndImage(item, element);
if (((RefinedTree) result).hasChildren(element))
{
if (ctrl.expandAndSelect && result.isExpanded(element) && ctrl.level < 25)
{
item.setData(Key.CONTROL, new ControlItem(true, ctrl.level + 1));
doExpand(item);
item.setExpanded(true);
}
else
{
new TreeItem(item, 0);
}
}
if (ctrl.expandAndSelect && result.isSelected(element))
{
// Note that tree.setSelection doesn't work for us
tree.select(item);
}
}
public void refresh(List<?> elementPath)
{
if (elementPath == null || elementPath.isEmpty())
return;
LinkedList<Object> list = new LinkedList<Object>();
list.addAll(elementPath);
Object root = list.removeFirst();
int count = tree.getItemCount();
for (int ii = 0; ii < count; ii++)
{
TreeItem item = tree.getItem(ii);
if (item.getData() == root)
{
doClear(list, item);
return;
}
}
// if we get here, a new root element has appeared
refresh(false);
}
private void doClear(LinkedList<?> path, TreeItem item)
{
PathLoop: while (!path.isEmpty())
{
Object current = path.removeFirst();
int count = item.getItemCount();
for (int index = 0; index < count; index++)
{
TreeItem child = item.getItem(index);
if (child.getData() == current)
{
if (path.isEmpty())
{
child.setData(Key.CONTROL, null);
doExpand(child);
return;
}
item = child;
continue PathLoop;
}
}
break;
}
}
private void pack()
{
tree.getParent().setRedraw(false);
try
{
for (Item item : columns)
{
TreeColumn column = (TreeColumn) item;
column.pack();
if (column.getWidth() > MAX_COLUMN_WIDTH)
column.setWidth(MAX_COLUMN_WIDTH);
if (column.getWidth() < MIN_COLUMN_WIDTH)
column.setWidth(MIN_COLUMN_WIDTH);
}
}
finally
{
tree.getParent().setRedraw(true);
}
}
public class TreeAdapter implements WidgetAdapter
{
public Composite createControl(Composite parent)
{
tree = new Tree(parent, SWT.FULL_SELECTION | SWT.MULTI);
tree.setHeaderVisible(true);
tree.setLinesVisible(true);
AccessibleCompositeAdapter.access(tree);
return tree;
}
public ControlEditor createEditor()
{
return treeEditor = new TreeEditor(tree);
}
public Item getItem(Item item, int index)
{
if (item == null)
return tree.getItem(index);
else
return ((TreeItem) item).getItem(index);
}
public void setExpanded(Item item, boolean expanded)
{
((TreeItem) item).setExpanded(expanded);
}
public void setItemCount(Item item, int count)
{
if (item == null)
tree.setItemCount(count);
else
((TreeItem) item).setItemCount(count);
}
public Item getItem(Point pt)
{
return tree.getItem(pt);
}
public void setEditor(Composite composite, Item item, int columnIndex)
{
tree.showItem((TreeItem) item);
treeEditor.setEditor(composite, (TreeItem) item, columnIndex);
}
public Item getParentItem(Item item)
{
return ((TreeItem) item).getParentItem();
}
public Item[] getSelection()
{
return tree.getSelection();
}
public Item createColumn(Column queryColumn, int index, SelectionListener listener)
{
TreeColumn column = new TreeColumn(tree, queryColumn.getAlign().getSwtCode(), index);
column.setData(queryColumn);
column.setText(queryColumn.getLabel());
column.setMoveable(true);
column.setWidth(MIN_COLUMN_WIDTH);
column.addSelectionListener(listener);
return column;
}
public Font getFont()
{
return tree.getFont();
}
public Item getSortColumn()
{
return tree.getSortColumn();
}
public int getSortDirection()
{
return tree.getSortDirection();
}
public void setSortColumn(Item column)
{
tree.setSortColumn((TreeColumn) column);
}
public void setSortDirection(int direction)
{
tree.setSortDirection(direction);
}
public int getItemCount(Item item)
{
if (item == null)
return tree.getItemCount();
else
return ((TreeItem) item).getItemCount();
}
public int indexOf(Item item)
{
TreeItem treeItem = (TreeItem) item;
TreeItem parent = treeItem.getParentItem();
if (parent == null)
{
return tree.indexOf(treeItem);
}
else
{
return parent.indexOf(treeItem);
}
}
public Rectangle getBounds(Item item, int index)
{
return ((TreeItem) item).getBounds(index);
}
public Rectangle getTextBounds(Widget item, int index)
{
return ((TreeItem) item).getBounds(index);
}
public Rectangle getImageBounds(Item item, int index)
{
return ((TreeItem) item).getImageBounds(index);
}
public void apply(Item item, int index, String label, Color color, Font font)
{
TreeItem treeItem = (TreeItem) item;
treeItem.setFont(index, font);
treeItem.setText(index, label);
treeItem.setForeground(index, color);
}
public void apply(Item item, Font font)
{
((TreeItem) item).setFont(font);
}
public void apply(Item item, int index, String label)
{
((TreeItem) item).setText(index, label);
}
public int getLineHeightEstimation()
{
if (Platform.OS_LINUX.equals(Platform.getOS()))
return 26;
if (Platform.OS_MACOSX.equals(Platform.getOS()))
return 20;
if (System.getProperty("os.name").indexOf("Vista") >= 0)//$NON-NLS-1$//$NON-NLS-2$
return 19;
return 18;
}
public int[] getColumnOrder()
{
return tree.getColumnOrder();
}
public void setColumnOrder(int order[])
{
tree.setColumnOrder(order);
}
public int getColumnWidth(int col)
{
TreeColumn column = (TreeColumn) columns[col];
return column.getWidth();
}
public void setColumnWidth(int col, int width)
{
width = Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, width));
TreeColumn column = (TreeColumn) columns[col];
column.setWidth(width);
}
}
}