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