/******************************************************************************* * Copyright (c) 2007, 2015 IBM Corporation 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: * IBM Corporation - initial API and implementation * Michael Krkoska - initial API and implementation (bug 188333) * Pawel Piech - Bug 291245 - [Viewers] StyledCellLabelProvider.paint(...) does not respect column alignment *******************************************************************************/ package org.eclipse.jface.viewers; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.viewers.StyledString.Styler; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; /** * A {@link StyledCellLabelProvider} supports styled labels by using owner * draw. * Besides the styles in labels, the label provider preserves native viewer behavior: * <ul> * <li>similar image and label positioning</li> * <li>native drawing of focus and selection</li> * </ul> * <p> * For providing the label's styles, create a subclass and overwrite * {@link StyledCellLabelProvider#update(ViewerCell)} to * return set all information needed to render a element. Use * {@link ViewerCell#setStyleRanges(StyleRange[])} to set style ranges * on the label. * </p> * * @since 3.4 */ public abstract class StyledCellLabelProvider extends OwnerDrawLabelProvider { /** * Style constant for indicating that the styled colors are to be applied * even it the viewer's item is selected. Default is not to apply colors. */ public static final int COLORS_ON_SELECTION = 1 << 0; /** * Style constant for indicating to draw the focus if requested by the owner * draw event. Default is to draw the focus. */ public static final int NO_FOCUS = 1 << 1; /** * Private constant to indicate if owner draw is enabled for the * label provider's column. */ private static final int OWNER_DRAW_ENABLED = 1 << 4; private int style; // reused text layout private TextLayout cachedTextLayout; private ColumnViewer viewer; private ViewerColumn column; private int deltaOfLastMeasure; /** * Creates a new StyledCellLabelProvider. By default, owner draw is enabled, focus is drawn and no * colors are painted on selected elements. */ public StyledCellLabelProvider() { this(0); } /** * Creates a new StyledCellLabelProvider. By default, owner draw is enabled. * * @param style * the style bits * @see StyledCellLabelProvider#COLORS_ON_SELECTION * @see StyledCellLabelProvider#NO_FOCUS */ public StyledCellLabelProvider(int style) { this.style = style & (COLORS_ON_SELECTION | NO_FOCUS) | OWNER_DRAW_ENABLED; } /** * Returns <code>true</code> is the owner draw rendering is enabled for this label provider. * By default owner draw rendering is enabled. If owner draw rendering is disabled, rending is * done by the viewer and no styled ranges (see {@link ViewerCell#getStyleRanges()}) * are drawn. * * @return <code>true</code> is the rendering of styles is enabled. */ public boolean isOwnerDrawEnabled() { return (this.style & OWNER_DRAW_ENABLED) != 0; } /** * Specifies whether owner draw rendering is enabled for this label * provider. By default owner draw rendering is enabled. If owner draw * rendering is disabled, rendering is done by the viewer and no styled * ranges (see {@link ViewerCell#getStyleRanges()}) are drawn. * It is the caller's responsibility to also call * {@link StructuredViewer#refresh()} or similar methods to update the * underlying widget. * * @param enabled * specifies if owner draw rendering is enabled */ public void setOwnerDrawEnabled(boolean enabled) { boolean isEnabled= isOwnerDrawEnabled(); if (isEnabled != enabled) { if (enabled) { this.style |= OWNER_DRAW_ENABLED; } else { this.style &= ~OWNER_DRAW_ENABLED; } if (this.viewer != null) { setOwnerDrawEnabled(this.viewer, this.column, enabled); } } } /** * Returns the viewer on which this label provider is installed on or <code>null</code> if the * label provider is not installed. * * @return the viewer on which this label provider is installed on or <code>null</code> if the * label provider is not installed. */ protected final ColumnViewer getViewer() { return this.viewer; } /** * Returns the column on which this label provider is installed on or <code>null</code> if the * label provider is not installed. * * @return the column on which this label provider is installed on or <code>null</code> if the * label provider is not installed. */ protected final ViewerColumn getColumn() { return this.column; } @Override public void initialize(ColumnViewer viewer, ViewerColumn column) { Assert.isTrue(this.viewer == null && this.column == null, "Label provider instance already in use"); //$NON-NLS-1$ this.viewer= viewer; this.column= column; super.initialize(viewer, column, isOwnerDrawEnabled()); } @Override public void dispose() { if (this.cachedTextLayout != null) { cachedTextLayout.dispose(); cachedTextLayout = null; } this.viewer= null; this.column= null; super.dispose(); } @Override public void update(ViewerCell cell) { // clients must override and configure the cell and call super super.update(cell); // calls 'repaint' to trigger the paint listener } private TextLayout getSharedTextLayout(Display display) { if (cachedTextLayout == null) { int orientation = viewer.getControl().getStyle() & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT); cachedTextLayout = new TextLayout(display); cachedTextLayout.setOrientation(orientation); } return cachedTextLayout; } private boolean useColors(Event event) { return (event.detail & SWT.SELECTED) == 0 || (this.style & COLORS_ON_SELECTION) != 0; } private boolean drawFocus(Event event) { return (event.detail & SWT.FOCUSED) != 0 && (this.style & NO_FOCUS) == 0; } /** * Prepares the given style range before it is applied to the label. This method makes sure that * no colors are drawn when the element is selected. * The current version of the {@link StyledCellLabelProvider} will also ignore all font settings on the * style range. Clients can override. * * @param styleRange * the style range to prepare. the style range element must not be modified * @param applyColors * specifies if colors should be applied. * @return * returns the style range to use on the label */ protected StyleRange prepareStyleRange(StyleRange styleRange, boolean applyColors) { // if no colors apply or font is set, create a clone and clear the // colors and font if (!applyColors && (styleRange.foreground != null || styleRange.background != null)) { styleRange = (StyleRange) styleRange.clone(); if (!applyColors) { styleRange.foreground = null; styleRange.background = null; } } return styleRange; } private ViewerCell getViewerCell(Event event, Object element) { ViewerRow row= viewer.getViewerRowFromItem(event.item); return new ViewerCell(row, event.index, element); } /** * Handle the erase event. The default implementation does nothing to ensure * keep native selection highlighting working. * * @param event * the erase event * @param element * the model object * @see SWT#EraseItem */ @Override protected void erase(Event event, Object element) { // use native erase if (isOwnerDrawEnabled()) { // info has been set by 'update': announce that we paint ourselves event.detail &= ~SWT.FOREGROUND; } } @Override protected void measure(Event event, Object element) { if (!isOwnerDrawEnabled()) return; ViewerCell cell= getViewerCell(event, element); boolean applyColors = useColors(event); TextLayout layout = getSharedTextLayout(event.display); int textWidthDelta = deltaOfLastMeasure = updateTextLayout(layout, cell, applyColors); event.width += textWidthDelta; } /** * @param layout * @param cell * @param applyColors * @return the text width delta (0 if the text layout contains no other font) */ private int updateTextLayout(TextLayout layout, ViewerCell cell, boolean applyColors) { layout.setStyle(null, 0, Integer.MAX_VALUE); // clear old styles layout.setText(cell.getText()); layout.setFont(cell.getFont()); // set also if null to clear previous usages int originalTextWidth = layout.getBounds().width; // text width without any styles boolean containsOtherFont= false; StyleRange[] styleRanges = cell.getStyleRanges(); if (styleRanges != null) { // user didn't fill styled ranges for (StyleRange styleRange : styleRanges) { StyleRange curr = prepareStyleRange(styleRange, applyColors); layout.setStyle(curr, curr.start, curr.start + curr.length - 1); if (curr.font != null) { containsOtherFont= true; } } } int textWidthDelta = 0; if (containsOtherFont) { textWidthDelta = layout.getBounds().width - originalTextWidth; } return textWidthDelta; } @Override protected void paint(Event event, Object element) { if (!isOwnerDrawEnabled()) return; ViewerCell cell= getViewerCell(event, element); boolean applyColors= useColors(event); GC gc = event.gc; // remember colors to restore the GC later Color oldForeground = gc.getForeground(); Color oldBackground = gc.getBackground(); if (applyColors) { Color foreground= cell.getForeground(); if (foreground != null) { gc.setForeground(foreground); } Color background= cell.getBackground(); if (background != null) { gc.setBackground(background); } } Image image = cell.getImage(); if (image != null) { Rectangle imageBounds = cell.getImageBounds(); if (imageBounds != null) { Rectangle bounds = image.getBounds(); // center the image in the given space int x = imageBounds.x + Math.max(0, (imageBounds.width - bounds.width) / 2); int y = imageBounds.y + Math.max(0, (imageBounds.height - bounds.height) / 2); gc.drawImage(image, x, y); } } Rectangle textBounds = cell.getTextBounds(); if (textBounds != null) { TextLayout textLayout= getSharedTextLayout(event.display); // text layout already configured in measure(Event, Object) Rectangle layoutBounds = textLayout.getBounds(); int style = viewer.getColumnViewerOwner(cell.getColumnIndex()).getStyle(); int x = textBounds.x; if ((style & SWT.RIGHT) != 0) { x = textBounds.x + textBounds.width - textLayout.getBounds().width; } else if ((style & SWT.CENTER) != 0) { x = textBounds.x + (textBounds.width - textLayout.getBounds().width)/2; } int y = textBounds.y + Math.max(0, (textBounds.height - layoutBounds.height) / 2); Rectangle saveClipping = gc.getClipping(); gc.setClipping(textBounds); textLayout.draw(gc, x, y); gc.setClipping(saveClipping); } if (drawFocus(event)) { Rectangle focusBounds = cell.getViewerRow().getBounds(); gc.drawFocus(focusBounds.x, focusBounds.y, focusBounds.width + deltaOfLastMeasure, focusBounds.height); } if (applyColors) { gc.setForeground(oldForeground); gc.setBackground(oldBackground); } } /** * Applies decoration styles to the decorated string and adds the styles of the previously * undecorated string. * <p> * If the <code>decoratedString</code> contains the <code>styledString</code>, then the result * keeps the styles of the <code>styledString</code> and styles the decorations with the * <code>decorationStyler</code>. Otherwise, the decorated string is returned without any * styles. * * @param decoratedString the decorated string * @param decorationStyler the styler to use for the decoration or <code>null</code> for no * styles * @param styledString the original styled string * * @return the styled decorated string (can be the given <code>styledString</code>) * @since 3.5 */ public static StyledString styleDecoratedString(String decoratedString, Styler decorationStyler, StyledString styledString) { String label= styledString.getString(); int originalStart= decoratedString.indexOf(label); if (originalStart == -1) { return new StyledString(decoratedString); // the decorator did something wild } if (decoratedString.length() == label.length()) return styledString; if (originalStart > 0) { StyledString newString= new StyledString(decoratedString.substring(0, originalStart), decorationStyler); newString.append(styledString); styledString= newString; } if (decoratedString.length() > originalStart + label.length()) { // decorator appended something return styledString.append(decoratedString.substring(originalStart + label.length()), decorationStyler); } return styledString; // no change } }