/*******************************************************************************
* Copyright (c) 2007 Michael Krkoska 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:
* Michael Krkoska - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.viewers;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.GlyphMetrics;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
/**
* An OwnerDrawLabelProvider which applies StyleRanges to the labels provided by a wrapped ILabelProvider or
* ITableLabelProvider. Multi line, multi font, multi color labels are supported as well as placing the image anywhere
* in the label text.
*
* To specify the position of the image provided by the label provider, include the IMAGE_PLACEHOLDER char in your
* label.
*
*
* If the wrapped label provider implements IFontProvider or IColorProvider or the corresponding table variants, the
* provided color and font information is used as default style.
*
*
* To enable custom draw for your viewer's control, simply call
* {@link StyleRangeLabelProvider#setUpOwnerDraw(ColumnViewer)} after adding all columns and add the
* StyleRangeLabelProvider to the viewer.
*
*
* @see StyleRange
*
* @author Michael Krkoska
*/
public abstract class StyleRangeLabelProvider extends OwnerDrawLabelProvider {
public static void setUpOwnerDraw(final ColumnViewer viewer) {
OwnerDrawLabelProvider.setUpOwnerDraw(viewer);
Listener l = new Listener() {
public void handleEvent(Event event) {
CellLabelProvider provider = viewer.getViewerColumn(event.index).getLabelProvider();
if (provider instanceof StyleRangeLabelProvider)
((StyleRangeLabelProvider) provider).columnsResized(event);
}
};
if (viewer.getControl() instanceof Table) {
Table table = (Table) viewer.getControl();
TableColumn[] cols = table.getColumns();
for (int i = 0; i < cols.length; i++) {
cols[i].addListener(SWT.Resize, l);
}
} else if (viewer.getControl() instanceof Tree) {
Tree tree = (Tree) viewer.getControl();
TreeColumn[] cols = tree.getColumns();
for (int i = 0; i < cols.length; i++) {
cols[i].addListener(SWT.Resize, l);
}
}
}
/**
* This is the char which can be used as placeholder for an image in the label.
*/
public static final char IMAGE_PLACEHOLDER = '\uFFFC';
/**
* Style constant for centering the viewer's items vertically if their heights are less than the maximum vertical
* height.
*/
public static final int CENTER_ITEMS_VERTICALLY = 1;
/**
* Style constant for centering the text of a viewer's item vertically if its height is less than the height of the
* item's image. StyleRangeLabelProvider only supports this for single line text currently.
*/
public static final int CENTER_TEXT_VERTICALLY = 2;
private static final boolean IS_WIN32 = SWT.getPlatform().equals("win32");
private FontRegistry fontRegistry = JFaceResources.getFontRegistry();
private ILabelProvider labelProvider;
private IFontProvider fontProvider = null;
private IColorProvider colorProvider = null;
private ITableLabelProvider tableLabelProvider = null;
private ITableFontProvider tableFontProvider = null;
private ITableColorProvider tableColorProvider = null;
private boolean centerItemsVertically = false;
private boolean centerTextVertically = false;
private boolean wrapLines = false;
private int measureHeightIndex = -1;
private int maxHeight = 0;
private int[] knownColumnWidths;
/**
* Creates a new StyleRangeLabelProvider using given label provider as underlying provider of labels and, optionally,
* images, default fonts and default colors.
*
* The style bits CENTER_ITEMS_VERTICALLY and CENTER_TEXT_VERTICALLY are set.
*
* @param labelProvider
* the label provider to use which must implement either ILabelProvider or ITableLabelProvider and may
* implement IFontProvider, IColorProvider, ITableFontProvider or ITableColorProvider
*/
public StyleRangeLabelProvider(IBaseLabelProvider labelProvider) {
this(labelProvider, CENTER_ITEMS_VERTICALLY | CENTER_TEXT_VERTICALLY | SWT.WRAP);
}
/**
* Creates a new StyleRangeLabelProvider using given label provider as underlying provider of labels and, optionally,
* images, default fonts and default colors.
*
* @param labelProvider
* the label provider to use which must implement either ILabelProvider or ITableLabelProvider and may
* implement IFontProvider, IColorProvider, ITableFontProvider or ITableColorProvider
* @param style
* the style bits
* @see StyleRangeLabelProvider#CENTER_ITEMS_VERTICALLY
* @see StyleRangeLabelProvider#CENTER_TEXT_VERTICALLY
*/
/* @see SWT#WRAP */
public StyleRangeLabelProvider(IBaseLabelProvider labelProvider, int style) {
setLabelProvider(labelProvider);
if ((CENTER_ITEMS_VERTICALLY & style) != 0) {
centerItemsVertically = true;
}
if ((CENTER_TEXT_VERTICALLY & style) != 0) {
centerTextVertically = true;
}
if ((SWT.WRAP & style) != 0) {
// TODO Wrapping is deactivated as long as either Bug 130024 or Bug 148039 are still existing
// wrapLines = true;
}
}
/**
* Returns an array of StyleRange instances which are to be applied to the label.
*
* @param element
* The element whose label is going to be displayed
* @param columnIndex
* The column index of the label
* @param label
* The label for which to provide the StyleRanges
* @return An array of StyleRange instances, may be null if no styles are to be applied for this label.
*/
public abstract StyleRange[] getStyleRanges(Object element, int columnIndex, String label);
protected void columnsResized(Event event) {
if (!wrapLines)
return;
initKnownWidths(event);
if (event.widget instanceof TableColumn) {
TableColumn tc = (TableColumn) event.widget;
int newWidth = tc.getWidth();
if (newWidth == knownColumnWidths[event.index])
return;
knownColumnWidths[event.index] = newWidth;
int index = tc.getParent().indexOf(tc);
measureHeightIndex = index;
maxHeight = 0;
tc.getParent().redraw();
tc.getParent().update(); // trigger measure events for all visible items -> calculates maxHeight
if(maxHeight>=tc.getParent().getItemHeight())
return;
// TODO This is deactivated as long as either Bug 130024 or Bug 148039 are still existing
// tc.getParent().setItemHeight(maxHeight);
measureHeightIndex = -1;
maxHeight = 0;
tc.getParent().redraw();
tc.getParent().update();
} else if (event.widget instanceof TreeColumn) {
TreeColumn tc = (TreeColumn) event.widget;
int newWidth = tc.getWidth();
if (newWidth == knownColumnWidths[event.index])
return;
knownColumnWidths[event.index] = newWidth;
int index = tc.getParent().indexOf(tc);
measureHeightIndex = index;
maxHeight = 0;
tc.getParent().redraw();
tc.getParent().update(); // trigger measure events for all visible items -> calculates maxHeight
if(maxHeight>=tc.getParent().getItemHeight())
return;
// TODO This is deactivated as long as either Bug 130024 or Bug 148039 are still existing
// tc.getParent().setItemHeight(maxHeight);
measureHeightIndex = -1;
maxHeight = 0;
tc.getParent().redraw();
tc.getParent().update();
}
}
private void initKnownWidths(Event event) {
if (knownColumnWidths == null) {
if (event.widget instanceof TableColumn) {
TableColumn tc = (TableColumn) event.widget;
knownColumnWidths = new int[tc.getParent().getColumnCount()];
} else if (event.widget instanceof TreeColumn) {
TreeColumn tc = (TreeColumn) event.widget;
knownColumnWidths = new int[tc.getParent().getColumnCount()];
}
}
}
/**
* Creates a TextLayout instance for either measuring or painting the item, using the StyleRanges supplied by
* {@link StyleRangeLabelProvider#getStyleRanges(Object, String)}.
*
* Adds an image at the first occurence of {@link StyleRangeLabelProvider#IMAGE_PLACEHOLDER} or at the beginning of
* the label.
*
* @param label
* the label to display.
* @param image
* the image to display. May be null.
* @param defaultFont
* the default font for the label. If null the system default font is used.
* @param defaultBackground
* the default background color. May be null.
* @param display
* the Display of the viewer control.
* @param width
* the column width - currently unused
* @param element
* the model element used to obtain label and styles.
* @return a TextLayout instance which must be disposed after use.
*/
protected TextLayout createTextLayout(String label, Image image, Font defaultFont, Color defaultBackground,
Display display, int width, Object element, int columnIndex) {
TextLayout layout = new TextLayout(display);
if (wrapLines)
layout.setWidth(Math.max(width, 16));
int maxFontHeight = 0;
if (defaultFont != null) {
layout.setFont(defaultFont);
maxFontHeight = getFontHeight(defaultFont);
} else {
maxFontHeight = getFontHeight(display.getSystemFont());
}
int rangeOffset = 0, imageOffset = 0;
GlyphMetrics imageMetrics = null;
TextStyle imageStyle = null;
if (image != null) {
int index = label.indexOf(IMAGE_PLACEHOLDER);
String labelWithImage = label;
if (index < 0) {
labelWithImage = IMAGE_PLACEHOLDER + " " + label;
rangeOffset = 2;
}
layout.setText(labelWithImage);
imageOffset = Math.max(0, index);
Rectangle bounds = image.getBounds();
imageStyle = new TextStyle(null, null, null);
imageMetrics = new GlyphMetrics(bounds.height, 0, bounds.width);
imageStyle.metrics = imageMetrics;
} else {
layout.setText(label);
}
StyleRange[] styleRanges = getStyleRanges(element, columnIndex, label);
if (styleRanges != null) {
for (int i = 0; i < styleRanges.length; i++) {
StyleRange styleRange = styleRanges[i];
checkFont(styleRange);
layout.setStyle(styleRange, styleRange.start + rangeOffset, styleRange.start + rangeOffset
+ styleRange.length - 1);
if (styleRange.font != null) {
maxFontHeight = Math.max(maxFontHeight, getFontHeight(styleRange.font));
}
}
}
if (imageStyle != null) {
layout.setStyle(imageStyle, imageOffset, imageOffset);
}
if (centerTextVertically && imageMetrics != null) {
int lineCount = layout.getLineCount();
if (lineCount == 1 && imageMetrics.ascent > maxFontHeight) {
// vertically center the text if there is an image bigger than the text.
int height = imageMetrics.ascent;
int space = (height - maxFontHeight);
imageMetrics.ascent = (maxFontHeight) + Math.round(space / 2f);
// TODO why does it look better (at least on win xp) with this added pixel?
layout.setDescent(height - imageMetrics.ascent + 1);
}
if (lineCount > 1) {
// do a different (unfortunately inferior) centering for multiline text
centerMultilineVertically(layout, defaultFont, imageMetrics, imageOffset);
}
}
if (defaultBackground != null) {
int[] ranges = layout.getRanges();
int oldEnd = -1;
TextStyle[] styles = layout.getStyles();
for (int i = 0, length = ranges.length; i < length; i += 2) {
int start = ranges[i];
int end = ranges[i + 1];
if (start > oldEnd + 1) {
TextStyle style = new TextStyle(null, null, defaultBackground);
layout.setStyle(style, oldEnd + 1, start - 1);
}
TextStyle style = styles[i >> 1];
if (style.background == null) {
style.background = defaultBackground;
layout.setStyle(style, start, end);
}
oldEnd = end;
}
if (layout.getText().length() > oldEnd + 1) {
TextStyle style = new TextStyle(null, null, defaultBackground);
layout.setStyle(style, oldEnd + 1, layout.getText().length() - 1);
}
}
return layout;
}
private void centerMultilineVertically(TextLayout layout, Font defaultFont, GlyphMetrics imageMetrics,
int imageOffset) {
Font systemFont = layout.getDevice().getSystemFont();
int maxFontHeight = 0;
int lineIndex = layout.getLineIndex(imageOffset);
int[] lineOffsets = layout.getLineOffsets();
int lineStart = lineOffsets[lineIndex];
int lineEnd = lineOffsets[lineIndex + 1] - 1;
int oldEnd = -1;
int[] ranges = layout.getRanges();
TextStyle[] styles = layout.getStyles();
List lineStyles = new ArrayList();
for (int i = 0, length = ranges.length; i < length; i += 2) {
int start = ranges[i];
int end = ranges[i + 1];
if (start > oldEnd + 1) {
TextStyle style = new TextStyle(null, null, null);
layout.setStyle(style, Math.max(oldEnd + 1, lineStart), Math.min(start - 1, lineEnd));
lineStyles.add(style);
Font font = getFirstNotNullFont(style.font, defaultFont, systemFont);
maxFontHeight = Math.max(maxFontHeight, getFontHeight(font));
}
if (start > lineEnd || end < lineStart) {
continue;
}
if (start == imageOffset && end == imageOffset) {
oldEnd = end;
continue;
}
TextStyle style = styles[i >> 1];
if (end > lineEnd) {
TextStyle clonedStyle = cloneStyle(style);
layout.setStyle(clonedStyle, Math.max(start, lineStart), Math.min(end, lineEnd));
lineStyles.add(clonedStyle);
Font font = getFirstNotNullFont(style.font, defaultFont, systemFont);
maxFontHeight = Math.max(maxFontHeight, getFontHeight(font));
} else {
lineStyles.add(style);
}
oldEnd = end;
}
if (layout.getText().length() > oldEnd + 1) {
TextStyle style = new TextStyle(null, null, null);
layout.setStyle(style, Math.max(oldEnd + 1, lineStart), Math.min(layout.getText().length() - 1, lineEnd));
lineStyles.add(style);
Font font = getFirstNotNullFont(style.font, defaultFont, systemFont);
maxFontHeight = Math.max(maxFontHeight, getFontHeight(font));
}
if (imageMetrics.ascent > maxFontHeight) {
int height = imageMetrics.ascent;
int space = (height - maxFontHeight);
int rise = Math.round(space / 2f);
for (Iterator iterator = lineStyles.iterator(); iterator.hasNext();) {
TextStyle style = (TextStyle) iterator.next();
style.rise = rise;
}
}
}
private Font getFirstNotNullFont(Font f1, Font f2, Font f3) {
if (f1 != null)
return f1;
if (f2 != null)
return f2;
return f3;
}
/**
* Handle the erase event. The default implementation fills the item with the label provider's background color, if
* one is set.
*
* @param event
* the erase event
* @param element
* the model object
* @see SWT#EraseItem
*/
protected void erase(Event event, Object element) {
Rectangle bounds = event.getBounds();
Color oldBackground = event.gc.getBackground();
if ((event.detail & SWT.SELECTED) != 0) {
int colorCode = SWT.COLOR_LIST_SELECTION;
if (IS_WIN32) {
// on win32 emulate native behaviour:
// paint selection in widget background color if control has no focus.
boolean hasFocus = ((Control) event.widget).isFocusControl();
colorCode = hasFocus ? colorCode : SWT.COLOR_WIDGET_BACKGROUND;
}
Color bg = event.item.getDisplay().getSystemColor(colorCode);
event.gc.setBackground(bg);
event.gc.fillRectangle(bounds);
/* restore the old GC colors */
event.gc.setBackground(oldBackground);
/* ensure that default selection is not drawn */
event.detail &= ~SWT.SELECTED;
}
/* restore the old GC colors */
event.gc.setBackground(oldBackground);
}
protected void measure(Event event, Object element) {
int columnIndex = event.index;
String label = getLabel(element, columnIndex);
Image image = getImage(element, columnIndex);
Font font = getFont(element, columnIndex);
Color defaultBackground = getBackground(element, columnIndex);
TextLayout textLayout = createTextLayout(label, image, font, defaultBackground, event.widget.getDisplay(),
getItemWidth(event), element, columnIndex);
Rectangle bounds = textLayout.getBounds();
event.setBounds(new Rectangle(0, 0, bounds.width, bounds.height));
textLayout.dispose();
if (measureHeightIndex == event.index)
maxHeight = Math.max(bounds.height, maxHeight);
}
private int getItemWidth(Event event) {
if (!wrapLines)
return -1;
if (event.item instanceof TableItem)
return ((TableItem) event.item).getBounds(event.index).width;
if (event.item instanceof TreeItem)
return ((TreeItem) event.item).getBounds(event.index).width;
return -1;
}
protected void paint(Event event, Object element) {
int columnIndex = event.index;
String label = getLabel(element, columnIndex);
Image image = getImage(element, columnIndex);
Font font = getFont(element, columnIndex);
Color defaultBackground = getBackground(element, event.index);
TextLayout textLayout = createTextLayout(label, image, font, defaultBackground, event.widget.getDisplay(),
getItemWidth(event), element, columnIndex);
GC gc = event.gc;
Color oldForeground = gc.getForeground(), oldBackground = gc.getBackground();
Color foreground = getForeground(element, columnIndex);
if (foreground != null) {
gc.setForeground(foreground);
}
Color background = getBackground(element, columnIndex);
if (background != null) {
gc.setBackground(background);
}
int heightOffset = 0;
if (centerItemsVertically) {
heightOffset = (event.height - textLayout.getBounds().height) / 2;
heightOffset = Math.max(0, heightOffset);
}
textLayout.draw(gc, event.x, event.y + heightOffset);
if (image != null) {
int imageOffset = Math.max(0, label.indexOf(IMAGE_PLACEHOLDER));
int lineIndex = textLayout.getLineIndex(imageOffset);
FontMetrics lineMetrics = textLayout.getLineMetrics(lineIndex);
Point point = textLayout.getLocation(imageOffset, false);
GlyphMetrics glyphMetrics = textLayout.getStyle(imageOffset).metrics;
int y = event.y + point.y + lineMetrics.getAscent() - glyphMetrics.ascent + heightOffset;
// Following line is because of a strange incorrect measurement of 3 pixels under win32 in some cases.
y = Math.max(event.y, y);
gc.drawImage(image, event.x + point.x, y);
}
textLayout.dispose();
gc.setForeground(oldForeground);
gc.setBackground(oldBackground);
}
private void checkFont(StyleRange styleRange) {
int fontStyle = styleRange.fontStyle;
if (fontStyle != SWT.NORMAL) {
Font font = styleRange.font;
if (font == null) {
font = JFaceResources.getDefaultFont();
}
// TODO getting fontData[0] might be wrong on motif
FontData fontData = font.getFontData()[0];
if (fontData.getStyle() != fontStyle) {
if ((fontStyle & SWT.BOLD) != 0) {
font = fontRegistry.getBold(fontData.getName());
} else if ((fontStyle & SWT.ITALIC) != 0) {
font = fontRegistry.getItalic(fontData.getName());
}
// TODO FontRegistry misses a getter for SWT.BOLD | SWT.ITALIC
}
styleRange.font = font;
}
}
private TextStyle cloneStyle(TextStyle style) {
TextStyle clone = new TextStyle(style.font, style.foreground, style.background);
clone.rise = style.rise;
clone.strikeout = style.strikeout;
if (style.metrics != null) {
clone.metrics = new GlyphMetrics(style.metrics.ascent, style.metrics.descent, style.metrics.width);
}
return clone;
}
private Color getBackground(Object element, int columnIndex) {
if (tableColorProvider != null) {
return tableColorProvider.getBackground(element, columnIndex);
}
if (colorProvider != null && columnIndex == 0) {
return colorProvider.getBackground(element);
}
return null;
}
private Font getFont(Object element, int columnIndex) {
if (tableFontProvider != null) {
return tableFontProvider.getFont(element, columnIndex);
}
if (fontProvider != null && columnIndex == 0) {
return fontProvider.getFont(element);
}
return null;
}
private int getFontHeight(Font font) {
int h = 0;
FontData[] fontData = font.getFontData();
for (int i = 0; i < fontData.length; i++) {
h = Math.max(h, (int) fontData[i].height);
}
return h;
}
private Color getForeground(Object element, int columnIndex) {
if (tableColorProvider != null) {
return tableColorProvider.getForeground(element, columnIndex);
}
if (colorProvider != null && columnIndex == 0) {
return colorProvider.getForeground(element);
}
return null;
}
private Image getImage(Object element, int columnIndex) {
if (tableLabelProvider != null) {
return tableLabelProvider.getColumnImage(element, columnIndex);
}
if (columnIndex == 0) {
return labelProvider.getImage(element);
}
return null;
}
private String getLabel(Object element, int columnIndex) {
if (tableLabelProvider != null) {
return tableLabelProvider.getColumnText(element, columnIndex);
}
if (columnIndex == 0) {
return labelProvider.getText(element);
}
return "";
}
private void setLabelProvider(IBaseLabelProvider labelProvider) {
Assert.isTrue(labelProvider instanceof ITableLabelProvider || labelProvider instanceof ILabelProvider);
if (labelProvider instanceof ILabelProvider) {
this.labelProvider = (ILabelProvider) labelProvider;
}
if (labelProvider instanceof IFontProvider) {
fontProvider = (IFontProvider) labelProvider;
}
if (labelProvider instanceof IColorProvider) {
colorProvider = (IColorProvider) labelProvider;
}
if (labelProvider instanceof ITableLabelProvider) {
tableLabelProvider = (ITableLabelProvider) labelProvider;
}
if (labelProvider instanceof ITableFontProvider) {
tableFontProvider = (ITableFontProvider) labelProvider;
}
if (labelProvider instanceof ITableColorProvider) {
tableColorProvider = (ITableColorProvider) labelProvider;
}
}
}