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