/******************************************************************************* * 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.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GlyphMetrics; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.graphics.TextStyle; 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; /** * A StyledLabelProvider which supports multiple lines per label. * *

* To set up a MultiLineLabelProvider and for your viewer's control, you must call * {@link MultiLineLabelProvider#setUpOwnerDraw(ColumnViewer)} * after adding all columns and add the MultiLineLabelProvider to the viewer. *

* @since 3.4 * @author Michael Krkoska */ public abstract class MultiLineLabelProvider extends StyledLabelProvider { public static void setUpOwnerDraw( final ColumnViewer viewer ) { StyledLabelProvider.setUpOwnerDraw(viewer); Listener l = new Listener() { public void handleEvent( Event event ) { IBaseLabelProvider provider = viewer.getLabelProvider(); if ( provider instanceof MultiLineLabelProvider ) { ((MultiLineLabelProvider)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); } } } private boolean wrapLines = false; private int measureHeightIndex = -1; private int maxHeight = 0; private int[] knownColumnWidths; /** * Creates a new MultiLineLabelProvider. * * @param style * the style bits * @see StyledLabelProvider#CENTER_ITEMS_VERTICALLY * @see StyledLabelProvider#CENTER_TEXT_VERTICALLY */ /* @see SWT#WRAP */ public MultiLineLabelProvider( int style ) { super(style); if ( (SWT.WRAP & style) != 0 ) { // TODO Wrapping is deactivated as long as either Bug 130024 or Bug 148039 are still existing // wrapLines = true; } } protected TextLayout createTextLayout( Event event, String label, Image image, Font defaultFont, Color defaultBackground, Object element, int columnIndex ) { TextLayout layout = super.createTextLayout(event, label, image, defaultFont, defaultBackground, element, columnIndex); if ( centerTextVertically && image != null ) { int imageOffset = Math.max(0, label.indexOf(IMAGE_PLACEHOLDER)); GlyphMetrics imageMetrics = layout.getStyle(imageOffset).metrics; if ( imageMetrics != null ) { int lineCount = layout.getLineCount(); if ( lineCount > 1 ) { // do a different (unfortunately inferior) centering for multiline text // than in the single line case centerMultilineVertically(layout, defaultFont, imageMetrics, imageOffset); } } } if ( wrapLines ) { layout.setWidth(Math.max(getItemWidth(event), 16)); } return layout; } protected void measure( Event event, Object element ) { super.measure(event, element); if ( measureHeightIndex == event.index ) { maxHeight = Math.max(event.getBounds().height, maxHeight); } } 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 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 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 Font getFirstNotNullFont( Font f1, Font f2, Font f3 ) { if ( f1 != null ) { return f1; } if ( f2 != null ) { return f2; } return f3; } 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; } 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()]; } } } }