/**
* Copyright (c) 2002-2006 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 - Initial API and implementation
*/
package org.eclipse.emf.common.ui.viewer;
import java.util.LinkedList;
import org.eclipse.jface.viewers.TableTreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableTree;
import org.eclipse.swt.custom.TableTreeItem;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
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.Display;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Widget;
/**
* This class extends a TableTreeViewer to draw images and tree lines in the tree column.
*/
@Deprecated
public class ExtendedTableTreeViewer extends TableTreeViewer
{
public static final String ITEM_ID = "TableTreeItemID";
public ExtendedTableTreeViewer(TableTree tableTree)
{
super(tableTree);
}
public ExtendedTableTreeViewer(Composite parent)
{
super(parent);
}
public ExtendedTableTreeViewer(Composite parent, int style)
{
super(parent, style);
}
@Override
protected Item newItem(Widget parent, int flags, int index)
{
TableTreeItem item =
index >= 0 ?
parent instanceof TableTreeItem ?
new ExtendedTableTreeItem((TableTreeItem) parent, flags, index) :
new ExtendedTableTreeItem((TableTree) parent, flags, index) :
parent instanceof TableTreeItem ?
new ExtendedTableTreeItem((TableTreeItem) parent, flags) :
new ExtendedTableTreeItem((TableTree) parent, flags);
return item;
}
// We cache the dimensions of the expand/collapse icon as soon as we find
// them. They shouldn't ever change, but on GTK, they may be incorrectly reported
// as 0 when the mouse pointer moves down into or up out of a leaf item.
// Also on GTK, they're originally reported as 0, so we need the
// default-value hack.
// See Bugzilla 42434.
//
protected Point interactorSize = new Point(12, 12);
protected boolean interactorFound = false;
@Override
protected void hookControl(Control control)
{
super.hookControl(control);
/*
getTableTree().getTable().addPaintListener
(new PaintListener()
{
public void paintControl(PaintEvent event)
{
if (event.count > 0)
{
Thread.dumpStack();
}
}
});
*/
getTableTree().getTable().addPaintListener
(new PaintListener()
{
protected boolean isStarted;
protected TableTreeItem firstTableTreeItem;
protected TableTreeItem lastTableTreeItem;
protected LinkedList<TableTreeItem> chain;
protected int scrollX;
/**
* On GTK, PaintEvent.gc has its origin below the Table's header,
* instead of above it, as on other platforms. This adjusts for
* that, mutating and returning the given Rectangle.
* See Bugzilla 42416.
*/
protected Rectangle fixForGC(Rectangle bounds)
{
if (isGTK() && bounds != null)
{
bounds.x -= 2;
bounds.y -= getTableTree().getTable().getHeaderHeight();
}
return bounds;
}
public void paintControl(PaintEvent event)
{
// System.out.println("Painting....." + event + " x=" + event.x + " y=" + event.y + " width=" + event.width + " height=" + event.height);
// if (true) return;
Table table = (Table)event.getSource();
TableItem[] items = table.getItems();
firstTableTreeItem = null;
lastTableTreeItem = null;
for (int i = table.getTopIndex(); i < items.length; i++)
{
TableItem tableItem = items[i];
ExtendedTableTreeItem tableTreeItem = (ExtendedTableTreeItem)tableItem.getData(ITEM_ID);
if (!tableTreeItem.isDisposed())
{
if (firstTableTreeItem == null)
{
firstTableTreeItem = tableTreeItem;
}
lastTableTreeItem = tableTreeItem;
if (!interactorFound)
{
Rectangle bounds = tableItem.getImageBounds(0);
if (bounds.width != 0)
{
interactorFound = true;
interactorSize = new Point(bounds.width, bounds.height);
}
}
Rectangle itemBounds = tableTreeItem.getBounds(0);
if (itemBounds != null)
{
Image image = tableTreeItem.getFirstImage();
if (image != null)
{
// Paint over the selected padding spaces with the
// background colour. On GTK, the whole item, not just
// the text, is selected, so we don't do this.
//
if (!isGTK())
{
// On Motif, selection color may be set as background.
//
Display display = tableItem.getDisplay();
event.gc.setBackground(
display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
Rectangle bounds = tableTreeItem.getImageBounds(tableItem, 0);
bounds.x += bounds.width;
bounds.y = itemBounds.y;
bounds.width = imagePaddingWidth - 1;
bounds.height = itemBounds.height;
event.gc.fillRectangle(fixForGC(bounds));
}
// Draw the extra first-column image.
//
Rectangle sourceBounds = image.getBounds();
Rectangle targetBounds =
fixForGC(tableTreeItem.getFirstImageBounds(tableItem));
event.gc.drawImage(image, sourceBounds.x, sourceBounds.y, sourceBounds.width, sourceBounds.height, targetBounds.x, targetBounds.y, targetBounds.width, targetBounds.height);
}
// Stop if the next item will be out the event bounds.
// The event bounds values are also misaligned on GTK.
//
fixForGC(itemBounds);
if (itemBounds.y + itemBounds.height > event.y + event.height)
{
break;
}
}
}
}
// If the table is indenting, draw tree lines.
//
if (firstTableTreeItem != null && isIndenting())
{
isStarted = false;
chain = new LinkedList<TableTreeItem>();
event.gc.setForeground(table.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
scrollX = items[0].getBounds(0).x;
paintLines(event.gc, getTableTree().getItems());
}
}
private boolean indenting = false;
private boolean indentingTested = false;
/**
* Tests whether the table is drawing child items indented compared
* to their parents. This will return false while the tree is still
* totally collapsed.
*/
protected boolean isIndenting()
{
if (!indentingTested)
{
TableItem[] items = getTableTree().getTable().getItems();
if (items.length > 1)
{
int x = items[0].getBounds(0).x;
for (int i = 1, len = items.length; i < len; i++)
{
if (items[i].getBounds(0).x != x)
{
indenting = true;
}
TableTreeItem item = (TableTreeItem)items[i].getData(ITEM_ID);
if (item.getParentItem() != null)
{
indentingTested = true;
break;
}
}
}
}
return indenting;
}
protected boolean paintLines(GC gc, TableTreeItem [] tableTreeItems)
{
int offset = interactorSize.x;
int indent = Math.min(6, (offset - 8) / 2);
if (tableTreeItems != null)
{
for (int i = 0; i < tableTreeItems.length; ++i)
{
TableTreeItem tableTreeItem = tableTreeItems[i];
if (!isStarted && tableTreeItem == firstTableTreeItem)
{
isStarted = true;
}
if (isStarted)
{
Rectangle bounds = tableTreeItem.getBounds(0);
int x = 1 + scrollX;
for (TableTreeItem ancestor : chain)
{
if (ancestor != null)
{
gc.drawLine(x + offset/2, bounds.y, x + offset/2, bounds.y + bounds.height);
}
x += offset;
}
if (i + 1 == tableTreeItems.length)
{
if (i != 0 || !chain.isEmpty())
{
gc.drawLine
(x + offset/2, bounds.y,
x + offset/2, bounds.y + (tableTreeItem.getItemCount() > 0 ? indent - 1 : bounds.height/2));
}
}
else
{
if (tableTreeItem.getItemCount() > 0)
{
gc.drawLine
(x + offset/2, bounds.y, x + offset/2, bounds.y + indent - 1);
gc.drawLine
(x + offset/2, bounds.y + bounds.height - indent + 2, x + offset/2, bounds.y + bounds.height);
}
else
{
gc.drawLine(x + offset/2, bounds.y, x + offset/2, bounds.y + bounds.height);
}
}
gc.drawLine
(x + (tableTreeItem.getItemCount() > 0 ? offset - indent + 1 : offset/2), bounds.y + (bounds.height + 1)/2,
x + offset + 2, bounds.y + (bounds.height + 1)/2);
}
if (tableTreeItem.getExpanded())
{
chain.addLast(i + 1 == tableTreeItems.length ? null : tableTreeItem);
if (!paintLines(gc, tableTreeItem.getItems()))
{
return false;
}
chain.removeLast();
}
if (isStarted && tableTreeItem == lastTableTreeItem)
{
return false;
}
}
}
return true;
}
});
}
protected String imagePadding;
protected int imagePaddingWidth;
protected void createImagePadding(int width)
{
GC gc = new GC(getTableTree().getTable());
imagePadding = " ";
while ((imagePaddingWidth = gc.stringExtent(imagePadding).x) < width + 6)
{
imagePadding += " ";
}
gc.dispose();
TableItem [] tableItems = getTableTree().getTable().getItems();
for (int i = 0; i < tableItems.length; ++i)
{
TableTreeItem tableTreeItem = (TableTreeItem)tableItems[i].getData(ITEM_ID);
tableTreeItem.setText(0, tableTreeItem.getText(0));
}
}
/**
* Returns whether GTK is the current platform. Special treatment is
* needed for GTK in drawing on the table.
*/
protected static boolean isGTK()
{
return "gtk".equals(SWT.getPlatform());
}
/**
* This is a convenient way to get image bound values that are corrected
* on GTK. If the given TableItem underlies an ExtendedTableTreeItem,
* getImageBounds() is called on that ExtendedTableTreeItem. Otherwise,
* it is called directory on the TableItem.
* See Bugzilla 42434.
*/
public static Rectangle getImageBounds(TableItem tableItem, int column)
{
Object item = tableItem.getData(ITEM_ID);
return item instanceof ExtendedTableTreeItem ?
((ExtendedTableTreeItem)item).getImageBounds(tableItem, column) :
tableItem.getImageBounds(column);
}
/**
* Centers the Rectangle vertically, within a surrounding space of
* the given height. The given Rectangle is changed and returned.
*/
protected static Rectangle center(Rectangle bounds, int maxHeight)
{
if (bounds.height < maxHeight)
{
bounds.y += (maxHeight - bounds.height) / 2;
}
return bounds;
}
/**
* Scales the Rectangle, maintaining its aspect, such that it fits within the
* given height. The given Rectangle is changed and returned.
*/
protected static Rectangle scale(Rectangle bounds, int maxHeight)
{
if (bounds.height > maxHeight)
{
float sf = (float)bounds.width / (float)bounds.height;
bounds.width = Math.round(sf * maxHeight);
bounds.height = maxHeight;
}
return bounds;
}
public class ExtendedTableTreeItem extends TableTreeItem
{
protected Image firstImage;
public ExtendedTableTreeItem(TableTree parent, int style)
{
super(parent, style);
}
public ExtendedTableTreeItem(TableTree parent, int style, int index)
{
super(parent, style, index);
}
public ExtendedTableTreeItem(TableTreeItem parent, int style)
{
super(parent, style);
}
public ExtendedTableTreeItem(TableTreeItem parent, int style, int index)
{
super(parent, style, index);
}
@Override
public void setText(int index, String text)
{
// System.out.println("setting the text " + index + " " + text + " " + getImage(index));
if (index == 0 && imagePadding != null)
{
if (text != null && text.indexOf(imagePadding) == 0)
{
super.setText(0, text);
}
else
{
super.setText(0, imagePadding + text);
}
}
else
{
super.setText(index, text);
}
}
@Override
public String getText(int index)
{
String result = super.getText(index);
if (index == 0 && result != null && imagePadding != null && result.indexOf(imagePadding) == 0)
{
result = result.substring(imagePadding.length());
}
return result;
}
@Override
public void setImage(int index, Image image)
{
if (index == 0)
{
firstImage = image;
if (image != null && imagePadding == null)
{
createImagePadding(image.getBounds().width);
}
}
else
{
super.setImage(index, image);
}
}
/**
* Returns the additional first image, which would have been set
* by setImage(..., 0).
*/
public Image getFirstImage()
{
return firstImage;
}
public int getImagePaddingWidth()
{
return imagePaddingWidth;
}
/**
* This is equivalent to TableItem.getImageBounds(), except that it
* gives corrected values on GTK.
* See Bugzilla 42434.
*/
public Rectangle getImageBounds(int column)
{
return getImageBounds(getTableItem(), column);
}
/**
* Because getImageBounds() needs to obtain the underlying TableItem,
* this form is provided for speedy internal use when we've already
* got a handle on it.
*/
private Rectangle getImageBounds(TableItem tableItem, int column)
{
if (isGTK())
{
Rectangle result = tableItem.getBounds(column);
int itemHeight = result.height;
if (column == 0)
{
result.width = interactorSize.x;
result.height = interactorSize.y;
}
else
{
Image image = tableItem.getImage(column);
if (image == null)
{
result.width = 0;
result.height = 0;
}
else
{
Rectangle imageBounds = image.getBounds();
result.width = imageBounds.width;
result.height = imageBounds.height;
}
}
center(result, itemHeight);
result.x += 3;
return result;
}
return tableItem.getImageBounds(column);
}
/**
* Returns the bounds of the additional first image, which would have
* been set by setImage(..., 0).
*/
public Rectangle getFirstImageBounds()
{
return getFirstImageBounds(getTableItem());
}
/**
* Because getFirstImageBounds() needs to obtain the underlying
* TableItem, this form is provided for speedy internal use when we've
* already got a handle on it.
*/
private Rectangle getFirstImageBounds(TableItem tableItem)
{
Rectangle result = new Rectangle(0, 0, 0, 0);
if (tableItem != null)
{
Rectangle itemBounds = tableItem.getBounds(0);
Rectangle interactorBounds = getImageBounds(tableItem, 0);
result.x = interactorBounds.x + interactorBounds.width + 5;
result.y = itemBounds.y;
if (firstImage != null)
{
Rectangle imageBounds = firstImage.getBounds();
result.width = imageBounds.width;
result.height = imageBounds.height;
}
scale(result, itemBounds.height);
center(result, itemBounds.height);
}
return result;
}
/**
* Returns the underlying TableItem.
*/
protected TableItem getTableItem()
{
TableItem[] items = getTableTree().getTable().getItems();
for (int i = 0; i < items.length; i++)
{
if (items[i].getData(ITEM_ID) == this) return items[i];
}
return null;
}
}
}