/******************************************************************************* * 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.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.core.runtime.Assert; 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.Device; 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.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; /** * An OwnerDrawLabelProvider which applies StyleRanges to the labels provided by a wrapped ILabelProvider or * ITableLabelProvider. 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. *

*

* Subclasses must implement either ILabelProvider or ITableLabelProvider to provide the items' labels. * * If a subclass implements IFontProvider or IColorProvider or the corresponding table variants, the provided color and * font information is used as default style for the item. *

*

* To enable custom draw for your viewer's control, simply call {@link StyledLabelProvider#setUpOwnerDraw(ColumnViewer)} * after adding all columns and add the StyledLabelProvider to the viewer. *

* * @see StyleRange * @since 3.4 * @author Michael Krkoska */ public abstract class StyledLabelProvider extends BaseLabelProvider { /** * 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. */ public static final int CENTER_TEXT_VERTICALLY = 2; /** * Set up the owner draw callbacks for the viewer. *

* This method must be called after adding all columns to the viewer. *

* @param viewer * the viewer the owner draw is set up */ public static void setUpOwnerDraw( final ColumnViewer viewer ) { /* * Unfortunately we cannot use OwnerDrawLabelProvider because it uses CellLabelProviders: In * ColumnViewer.setLabelProvider(.) the StyledLabelProvider might get wrapped, so that the instanceof operation * fails. */ viewer.getControl().addListener(SWT.MeasureItem, new Listener() { public void handleEvent( Event event ) { IBaseLabelProvider provider = viewer.getLabelProvider(); Object element = event.item.getData(); if ( provider instanceof StyledLabelProvider ) { ((StyledLabelProvider)provider).measure(event, element); } } }); viewer.getControl().addListener(SWT.PaintItem, new Listener() { public void handleEvent( Event event ) { IBaseLabelProvider provider = viewer.getLabelProvider(); Object element = event.item.getData(); if ( provider instanceof StyledLabelProvider ) { ((StyledLabelProvider)provider).paint(event, element); } } }); viewer.getControl().addListener(SWT.EraseItem, new Listener() { public void handleEvent( Event event ) { IBaseLabelProvider provider = viewer.getLabelProvider(); Object element = event.item.getData(); if ( provider instanceof StyledLabelProvider ) { ((StyledLabelProvider)provider).erase(event, element); } } }); CellLabelProvider c = new CellLabelProvider() { public void update( ViewerCell cell ) { // Force a redraw Rectangle cellBounds = cell.getBounds(); cell.getControl().redraw(cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height, true); } }; ViewerColumn column; int i = 0; while ( (column = viewer.getViewerColumn(i++)) != null ) { column.setLabelProvider(c); } } boolean centerItemsVertically = false; boolean centerTextVertically = false; private Map styledFonts = new HashMap(); private ILabelProvider labelProvider; private IFontProvider fontProvider = null; private IColorProvider colorProvider = null; private ITableLabelProvider tableLabelProvider = null; private ITableFontProvider tableFontProvider = null; private ITableColorProvider tableColorProvider = null; /** * Creates a new StyledLabelProvider. * * @param style * the style bits * @see StyledLabelProvider#CENTER_ITEMS_VERTICALLY * @see StyledLabelProvider#CENTER_TEXT_VERTICALLY */ public StyledLabelProvider( int style ) { setLabelProvider(); if ( (CENTER_ITEMS_VERTICALLY & style) != 0 ) { centerItemsVertically = true; } if ( (CENTER_TEXT_VERTICALLY & style) != 0 ) { centerTextVertically = true; } } public void dispose() { super.dispose(); for ( Iterator iterator = styledFonts.values().iterator(); iterator.hasNext(); ) { Font f = (Font)iterator.next(); f.dispose(); } styledFonts.clear(); } /** * 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 ); /** * Creates a TextLayout instance for either measuring or painting the item, using the StyleRanges supplied by * {@link StyledLabelProvider#getStyleRanges(Object, int, String)}. * * Adds an image at the first occurence of {@link StyledLabelProvider#IMAGE_PLACEHOLDER} or at the beginning of the * label. * * @param event * the measure or paint event for which a TextLayout is needed * @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 element * the model element used to obtain label and styles. * @param columnIndex * the column index of the label * @return a TextLayout instance which must be disposed after use. */ protected TextLayout createTextLayout( Event event, String label, Image image, Font defaultFont, Color defaultBackground, Object element, int columnIndex ) { Display display = event.display; TextLayout layout = new TextLayout(display); 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; //$NON-NLS-1$ 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(display, 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 ( 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; } /** * Handle the erase event. The default implementation does nothing to ensure keep native selection highlighting * working. * * @param event * the erase event * @param element * the model object * @see SWT#EraseItem */ protected void erase( Event event, Object element ) { event.detail &= ~SWT.FOREGROUND; } /** * @param event * @param element */ 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(event, label, image, font, defaultBackground, element, columnIndex); Rectangle bounds = textLayout.getBounds(); event.setBounds(new Rectangle(0, 0, bounds.width, bounds.height)); textLayout.dispose(); } /** * @param event * @param element */ 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(event, label, image, font, defaultBackground, 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); } 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 void checkFont( Device device, StyleRange styleRange ) { int fontStyle = styleRange.fontStyle; if ( fontStyle != SWT.NORMAL ) { Font font = styleRange.font; if ( font == null ) { font = JFaceResources.getDefaultFont(); } boolean wrongStyle = false; FontData[] fontData = font.getFontData(); for ( int i = 0, length = fontData.length; i < length && !wrongStyle; i++ ) { if ( fontData[i].getStyle() != fontStyle ) { wrongStyle = true; } } if ( wrongStyle ) { fontData = getFontData(font, fontStyle); // TODO using fontData[0] as cache key might be wrong on motif Font styledFont = (Font)styledFonts.get(fontData[0]); if ( styledFont == null ) { styledFont = new Font(device, fontData); styledFonts.put(fontData[0], styledFont); } } styleRange.font = font; } } 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 FontData[] getFontData( Font font, int style ) { FontData[] fontDatas = font.getFontData(); for ( int i = 0; i < fontDatas.length; i++ ) { fontDatas[i].setStyle(style); } return fontDatas; } 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 ""; //$NON-NLS-1$ } private void setLabelProvider() { Assert.isTrue(this instanceof ITableLabelProvider || this instanceof ILabelProvider); if ( this instanceof ILabelProvider ) { this.labelProvider = (ILabelProvider)this; } if ( this instanceof IFontProvider ) { fontProvider = (IFontProvider)this; } if ( this instanceof IColorProvider ) { colorProvider = (IColorProvider)this; } if ( this instanceof ITableLabelProvider ) { tableLabelProvider = (ITableLabelProvider)this; } if ( this instanceof ITableFontProvider ) { tableFontProvider = (ITableFontProvider)this; } if ( this instanceof ITableColorProvider ) { tableColorProvider = (ITableColorProvider)this; } } }