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