/******************************************************************************* * 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; } } }