Index: Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet042StyledMultiLineLabelSupport.java =================================================================== RCS file: Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet042StyledMultiLineLabelSupport.java diff -N Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet042StyledMultiLineLabelSupport.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet042StyledMultiLineLabelSupport.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,264 @@ +/******************************************************************************* + * 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.snippets.viewers; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ColumnPixelData; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.OwnerDrawLabelProvider; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledLabelSupport; +import org.eclipse.jface.viewers.StyledMultiLineLabelSupport; +import org.eclipse.jface.viewers.TableLayout; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * A TableViewer using a StyledMultiLineLabelSupport to show some of the possibilities: + * + * + * @author Michael Krkoska + */ +public class Snippet042StyledMultiLineLabelSupport { + + private static final String[] TEXT = new String[] { // + // + "TextStyle defines a set of styles that can be\n" + // + "applied to a range of text. The hashCode() method\n" + // + "in this class uses the values of the public fields to",// + "compute the hash value.\uFFFC When storing instances\n" + // + "of the class in hashed collections do not modify\n" + // + "these fields after the object has been inserted.",// + "Application code does not need to explicitly\n" + // + "release the resources managed and thus no\n" + // + "dispose() method is provided.\uFFFC", }; + private static final Pattern METHODS = Pattern.compile("\\w+\\(\\)"); + private static final int SHELL_WIDTH = 640; + private static final Display DISPLAY = Display.getDefault(); + private static int IMAGE_SIZE = 16; + private static final Image IMAGE1 = new Image(DISPLAY, DISPLAY.getSystemImage(SWT.ICON_WARNING).getImageData() + .scaledTo(IMAGE_SIZE, IMAGE_SIZE)); + private static final Image IMAGE2 = new Image(DISPLAY, DISPLAY.getSystemImage(SWT.ICON_ERROR).getImageData() + .scaledTo(IMAGE_SIZE, IMAGE_SIZE)); + + public static void main(String[] args) { + + Shell shell = new Shell(DISPLAY, SWT.CLOSE); + shell.setSize(SHELL_WIDTH, 300); + shell.setLayout(new GridLayout(2, false)); + + Snippet042StyledMultiLineLabelSupport example = new Snippet042StyledMultiLineLabelSupport(); + example.createPartControl(shell); + + shell.open(); + + while (!shell.isDisposed()) { + if (!DISPLAY.readAndDispatch()) { + DISPLAY.sleep(); + } + } + DISPLAY.dispose(); + } + + private TableViewer viewer; + + private Text text; + + public Snippet042StyledMultiLineLabelSupport() { + int defaultSize = JFaceResources.getDefaultFontDescriptor().getFontData()[0].getHeight(); + JFaceResources.getFontRegistry().put("Courier", + new FontData[] { new FontData("Courier New", defaultSize, SWT.NORMAL) }); + FontData[] fontData = JFaceResources.getFontRegistry().defaultFontDescriptor().getFontData(); + fontData[0].setHeight(fontData[0].getHeight() + 2); + JFaceResources.getFontRegistry().put("BiggerDefault", fontData); + } + + public void createPartControl(Composite parent) { + Label label = new Label(parent, SWT.NONE); + label.setText("type something here:"); + + text = new Text(parent, SWT.NONE); + text.setText("hash"); + text.setSelection(0, 4); + text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + text.addListener(SWT.Modify, new Listener() { + + public void handleEvent(Event event) { + viewer.refresh(); + } + }); + + // SWT.FULL_SELECTION is needed on win32 as long as Bug 168807 exists + viewer = new TableViewer(parent, SWT.FULL_SELECTION); + + GridData data = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.FILL_BOTH); + data.horizontalSpan = 2; + viewer.getControl().setLayoutData(data); + + viewer.setContentProvider(new ArrayContentProvider()); + createColumns(); + + OwnerDrawLabelProvider.setUpOwnerDraw(viewer); + viewer.setInput(TEXT); + } + + private void createColumns() { + TableLayout layout = new TableLayout(); + viewer.getTable().setLayout(layout); + viewer.getTable().setHeaderVisible(true); + viewer.getTable().setLinesVisible(true); + + int width = SHELL_WIDTH - 2 * 5 - viewer.getTable().getShell().computeTrim(0, 0, 0, 0).width; + int width1 = width / 2; + int width2 = width - width1; + + TableViewerColumn tc = new TableViewerColumn(viewer, SWT.NONE); + tc.getColumn().setText("col1"); + layout.addColumnData(new ColumnPixelData(width1)); + tc.setLabelProvider(new MyStyleRangeLabelProviderCol1(viewer, new MyStyledFontSupport())); + + tc = new TableViewerColumn(viewer, SWT.NONE); + tc.getColumn().setText("col2"); + layout.addColumnData(new ColumnPixelData(width2)); + tc.setLabelProvider(new MyStyleRangeLabelProviderCol2(viewer, new MyStyledFontSupport())); + } + + private class MyStyledFontSupport extends StyledMultiLineLabelSupport { + + public MyStyledFontSupport() { + super(StyledLabelSupport.CENTER_ITEMS_VERTICALLY|StyledLabelSupport.CENTER_TEXT_VERTICALLY|SWT.WRAP); + } + + public StyleRange[] getStyleRanges(Object element, String label) { + String searchText = text.getText(); + TextPresentation tp = new TextPresentation(); + if (searchText.length() > 0) { + Color yellow = text.getDisplay().getSystemColor(SWT.COLOR_YELLOW); + int index = -1; + while ((index = label.indexOf(searchText, index + 1)) >= 0) { + StyleRange styleRange = new StyleRange(index, searchText.length(), null, yellow); + tp.mergeStyleRange(styleRange); + } + } + + Font courier = JFaceResources.getFont("Courier"); + Matcher matcher = METHODS.matcher(label); + while (matcher.find()) { + StyleRange styleRange = new StyleRange(matcher.start(), matcher.end() - matcher.start(), null, null); + styleRange.font = courier; + tp.mergeStyleRange(styleRange); + } + + List ranges = new ArrayList(); + for (Iterator iterator = tp.getAllStyleRangeIterator(); iterator.hasNext();) { + ranges.add(iterator.next()); + } + return (StyleRange[]) ranges.toArray(new StyleRange[ranges.size()]); + } + + } + + private final class MyStyleRangeLabelProviderCol1 extends StyledCellLabelProvider { + + private MyStyleRangeLabelProviderCol1(ColumnViewer viewer, StyledLabelSupport fontSupport) { + super(viewer, fontSupport); + } + + public Color getBackground(Object element) { + return null; + } + + public Font getFont(Object element) { + if (element == TEXT[0]) { + return JFaceResources.getFont("BiggerDefault"); + } + return null; + } + + public Color getForeground(Object element) { + if (element == TEXT[1]) { + return DISPLAY.getSystemColor(SWT.COLOR_DARK_RED); + } + return null; + } + + public Image getImage(Object element) { + return IMAGE1; + } + + public String getText(Object element) { + return element.toString(); + } + } + + private final class MyStyleRangeLabelProviderCol2 extends StyledCellLabelProvider { + + private MyStyleRangeLabelProviderCol2(ColumnViewer viewer, StyledLabelSupport fontSupport) { + super(viewer, fontSupport); + } + + public Color getBackground(Object element) { + return null; + } + + public Font getFont(Object element) { + if (element == TEXT[1]) { + return JFaceResources.getFont("BiggerDefault"); + } + return null; + } + + public Color getForeground(Object element) { + if (element == TEXT[2]) { + return DISPLAY.getSystemColor(SWT.COLOR_DARK_RED); + } + return null; + } + + public Image getImage(Object element) { + return IMAGE2; + } + + public String getText(Object element) { + return element.toString(); + } + } + +} Index: src/org/eclipse/jface/viewers/StyledMultiLineLabelSupport.java =================================================================== RCS file: src/org/eclipse/jface/viewers/StyledMultiLineLabelSupport.java diff -N src/org/eclipse/jface/viewers/StyledMultiLineLabelSupport.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/viewers/StyledMultiLineLabelSupport.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,296 @@ +/******************************************************************************* + * 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.Control; +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; +import org.eclipse.swt.widgets.Widget; + +/** + * A StyledLabelSupport which supports multiple lines per label. + * + * @since 3.4 + * @author Michael Krkoska + */ +public abstract class StyledMultiLineLabelSupport extends StyledLabelSupport { + + private static final String KEY_HEIGHTWATCHER = "org.eclipse.jface.viewers.StyledMultiLineLabelSupport.Heightwatcher"; //$NON-NLS-1$ + private static final String KEY_COLUMNRESIZER = "org.eclipse.jface.viewers.StyledMultiLineLabelSupport.ColumnResizer"; //$NON-NLS-1$ + + private HeightWatcher heightWatcher = new HeightWatcher(); + private ColumnResizer columnResizer = new ColumnResizer(); + private boolean wrapLines = false; + private boolean shrinkPending = false; + + /** + * Creates a new StyledMultiLineLabelSupport. + * + * @param style + * the style bits + * @see StyledLabelSupport#CENTER_ITEMS_VERTICALLY + * @see StyledLabelSupport#CENTER_TEXT_VERTICALLY + */ + /* @see SWT#WRAP */ + public StyledMultiLineLabelSupport(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) { + + TextLayout layout = super.createTextLayout(event, label, image, defaultFont, defaultBackground, element); + + 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, ViewerLabel viewerLabel) { + super.measure(event, element, viewerLabel); + + addColumnResizer(event.widget); + + HeightWatcher heightWatcher = (HeightWatcher) event.widget.getData(KEY_HEIGHTWATCHER); + if (heightWatcher == null) { + heightWatcher = this.heightWatcher; + event.widget.setData(KEY_HEIGHTWATCHER, heightWatcher); + event.widget.getDisplay().asyncExec(heightWatcher); + heightWatcher.widget = event.widget; + } + heightWatcher.maxHeight = Math.max(event.getBounds().height, heightWatcher.maxHeight); + } + + private void addColumnResizer(Widget w) { + if (w instanceof Table) { + Table table = (Table) w; + TableColumn[] cols = table.getColumns(); + for (int i = 0; i < cols.length; i++) { + if (cols[i].getData(KEY_COLUMNRESIZER) != columnResizer) { + cols[i].setData(KEY_COLUMNRESIZER, columnResizer); + cols[i].addListener(SWT.Resize, columnResizer); + } + } + } else if (w instanceof Tree) { + Tree tree = (Tree) w; + TreeColumn[] cols = tree.getColumns(); + for (int i = 0; i < cols.length; i++) { + if (cols[i].getData(KEY_COLUMNRESIZER) != columnResizer) { + cols[i].setData(KEY_COLUMNRESIZER, columnResizer); + cols[i].addListener(SWT.Resize, columnResizer); + } + } + } + } + + 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 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; + } + + /** + * Try to shrink the item height of the viewer's control. Only performed if the SWT.WRAP style is set. + * + * @param control + * The control of the viewer, either Table or Tree. + */ + protected void shrinkHeight(final Control control) { + if (!wrapLines || shrinkPending) + return; + shrinkPending = true; + control.getDisplay().asyncExec(new Runnable() { + public void run() { + /* This is executed in an asyncExec because table init doesn't work on Vista + with multicolumned tables otherwise */ + heightWatcher.shrink = true; + heightWatcher.maxHeight = 0; + control.redraw(); + shrinkPending = false; + } + }); + } + + private class ColumnResizer implements Listener { + + public void handleEvent(Event event) { + Control c = null; + if (event.widget instanceof TableColumn) + c = ((TableColumn) event.widget).getParent(); + else if (event.widget instanceof TreeColumn) + c = ((TreeColumn) event.widget).getParent(); + shrinkHeight(c); + } + } + + private class HeightWatcher implements Runnable { + + Widget widget; + int maxHeight = 0; + boolean expand = SWT.getPlatform().equals("carbon"); //$NON-NLS-1$ + boolean shrink = false; + + public void run() { + if (!wrapLines || widget.isDisposed()) + return; + if (widget instanceof Table) { + Table t = (Table) widget; + int currentHeight = t.getItemHeight(); + if ((currentHeight > maxHeight && shrink) || (currentHeight < maxHeight && expand)) { + // TODO Wrapping is deactivated as long as either Bug 130024 or Bug 148039 are still existing + // t.setItemHeight(maxHeight); + } + } else if (widget instanceof Tree) { + Tree t = (Tree) widget; + int currentHeight = t.getItemHeight(); + if ((currentHeight > maxHeight && shrink) || (currentHeight < maxHeight && expand)) { + // TODO Wrapping is deactivated as long as either Bug 130024 or Bug 148039 are still existing + // t.setItemHeight(maxHeight); + } + } + widget.setData(KEY_HEIGHTWATCHER, null); + maxHeight = 0; + shrink = false; + } + } +}