/****************************************************************************** * 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 * Mariot Chauvin <mariot.chauvin@obeo.fr> - bug 245238 ****************************************************************************/ package org.eclipse.gmf.runtime.draw2d.ui.figures; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Map; import java.util.WeakHashMap; import org.eclipse.draw2d.ColorConstants; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.text.FlowContext; import org.eclipse.draw2d.text.FlowPage; import org.eclipse.draw2d.text.ParagraphTextLayout; import org.eclipse.draw2d.text.TextFlow; import org.eclipse.draw2d.text.TextLayout; import org.eclipse.gmf.runtime.draw2d.ui.internal.mapmode.IMapModeHolder; import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; import org.eclipse.gmf.runtime.draw2d.ui.text.TextFlowEx; import org.eclipse.gmf.runtime.draw2d.ui.text.TextUtilitiesEx; import org.eclipse.gmf.runtime.draw2d.ui.text.TruncatedSingleLineTextLayout; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; /** * An extended label that has the following extra features: <br> * 1. Allows selection, focus feedback, underlined and striked-through text.<br> * 2. Enhanced layout functionality for placing icon and text.<br> * 3. Text will be word-wrapped to fit the text in the width available.<br> * 4. Text will be truncated with an ellipsis if the entire text does not fit in * the space available.<br> * <p> * <b>EXPLANATION OF LAYOUTS</b><br> * * This WrappingLabel contains functionality to display an icon alongside text. * The following will describe how the layout of the icon and text are done. * <p> * <br> * * * <u>Using {@link #setTextPlacement(int)}:</u> * <p> * * The position of the text <i>relative</i> to the icon depends on * {@link #setTextPlacement(int)}. If the text placement is set to * {@link PositionConstants#EAST}, then the text would be placed on the right * of the icon. Similarly, if text placement is set to * {@link PositionConstants#WEST}, the text will be placed on the left of the * icon; {@link PositionConstants#NORTH} would put the text above the icon; and * {@link PositionConstants#SOUTH} would place the text below the icon. * <p> * <br> * * <u>Using {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)}:</u> * <p> * * Use {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)} to * align the text and icons <i>relative</i> to each other for more dynamic * control. If the text placement is on the east or west of the icon(s) (i.e. * the text on the right or left of the icon respectively), then only * {@link PositionConstants#TOP}, {@link PositionConstants#CENTER}, and * {@link PositionConstants#BOTTOM} can be used when calling * {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)}. In this * case, setting the text alignment to {@link PositionConstants#TOP} will make * sure that the top of the text is aligned horizontally with the top of the * icon <i>if</i> the height of the icon is greater than the height of the * text. Similarly, setting the text alignment to * {@link PositionConstants#CENTER} will make sure that the top of the text is * aligned horizontally with the vertical center of the size of icon <i>if</i> * the height of the icon is greater than the height of the text. Also, setting * the text alignment to {@link PositionConstants#BOTTOM} will make sure that * the bottom of the text is aligned horizontally with the bottom of the icon * <i>if</i> the height of the icon is greater than the height of the text. * <p> * * The other scenario is when the text placement is on the south or north of the * icon (i.e. the text is below or above the icon respectively). If this is * true, only {@link PositionConstants#LEFT}, {@link PositionConstants#CENTER}, * and {@link PositionConstants#RIGHT} can be used when calling * {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)}. In this * case, setting the text alignment to {@link PositionConstants#LEFT} will make * sure that the left of the text is aligned vertically with the left of the * icon <i>if</i> the width of the icon is greater than the width of the text. * Similarly, setting the text alignment to {@link PositionConstants#CENTER} * will make sure that the left of the text is aligned vertically with the * horizontal center of the icon <i>if</i> the width of the icon is greater * than the width of the text. Also, setting the text alignment to * {@link PositionConstants#RIGHT} will make sure that the right of the text is * aligned vertically with the right of the icon <i>if</i> the width of the * icon is greater than the width of the text. * <p> * * {@link #setIconAlignment(int)} works identically as * {@link #setTextAlignment(int)}, except the roles of text and icon are * switched in the above descriptions. * <p> * <br> * * * <u>Using {@link #setAlignment(int)}:</u> * <p> * * The entire label, text and icon, can be moved into different positions with * the label figure, if the figure is bigger than the icon and text. The * alignment of the label places the text and icon (no matter how they are * arranged relatively to each other) in the top-left, top, top-right, left, * center, right, bottom-left, bottom, or bottom-right of the bounds of this * <code>WrappingLabel</code> figure. * <p> * <br> * * * <u>Using {@link #setTextHorizontalAlignment(int)}:</u> * <p> * * Use {@link #setTextJustification(int)} with {@link PositionConstants#LEFT}, * {@link PositionConstants#CENTER}, or {@link PositionConstants#RIGHT} to * justify the text when wordwrap is turned on. The effect will be noticed in * multi-lined text only. * <p> * <br> * * WARNING: User-nested figures are not expected within this figure. * <p> * * Some code taken from the original <code>WrapLabel</code> in GMF by * melaasar. * <p> * * @since 2.1 * @author satif, crevells */ public class WrappingLabel extends Figure implements PositionConstants { static final String ELLIPSIS = "..."; //$NON-NLS-1$ private static final Dimension EMPTY_DIMENSION = new Dimension(0, 0); private static final Map<IMapMode, MapModeConstants> mapModeConstantsMap = new WeakHashMap<IMapMode, MapModeConstants>(); static class MapModeConstants { private static final int MAX_IMAGE_INFO = 12; public final WeakReference<IMapMode> mapModeRef; public final int nDPtoLP_3; public final int nDPtoLP_2; public final int nDPtoLP_0; public final Dimension dimension_nDPtoLP_0; public final WeakHashMap<Font, Dimension> fontToEllipseTextSize = new WeakHashMap<Font, Dimension>(); public final SingleIconInfo[] singleIconInfos = new SingleIconInfo[MAX_IMAGE_INFO]; public MapModeConstants(IMapMode mapMode) { this.mapModeRef = new WeakReference<IMapMode>(mapMode); nDPtoLP_2 = mapMode.DPtoLP(2); nDPtoLP_3 = mapMode.DPtoLP(3); nDPtoLP_0 = mapMode.DPtoLP(0); dimension_nDPtoLP_0 = new Dimension(nDPtoLP_0, nDPtoLP_0); } public Dimension getEllipseTextSize(Font f) { Dimension d = fontToEllipseTextSize.get(f); if (d == null) { IMapMode mapMode = mapModeRef.get(); d = FigureUtilities.getTextExtents(ELLIPSIS, f); d.height = FigureUtilities.getFontMetrics(f).getHeight(); d = new Dimension(mapMode.DPtoLP(d.width), mapMode .DPtoLP(d.height)); fontToEllipseTextSize.put(f, d); } return d; } public SingleIconInfo getSingleIconInfo(Image image) { if (image == null) { return SingleIconInfo.NULL_INFO; } SingleIconInfo info; for (int i = 0; i < MAX_IMAGE_INFO; ++i) { info = singleIconInfos[i]; if (info == null) { info = new SingleIconInfo(image); singleIconInfos[i] = info; return info; } if (info.icon == image) { return info; } } int index = SingleIconInfo.count % MAX_IMAGE_INFO; info = new SingleIconInfo(image); singleIconInfos[index] = info; return info; } } // reserve 1 bit for these boolean flags private static int FLAG_SELECTED = Figure.MAX_FLAG << 1; private static int FLAG_HASFOCUS = Figure.MAX_FLAG << 2; // reserve 4 bits for these alignment variables /** * @see #setTextAlignment(int) */ private static int FLAG_TEXT_ALIGN = Figure.MAX_FLAG << 3; /** * @see #setIconAlignment(int) */ private static int FLAG_ICON_ALIGN = Figure.MAX_FLAG << 7; /** * @see #setAlignment(int) */ private static int FLAG_LABEL_ALIGN = Figure.MAX_FLAG << 11; /** * @see #setTextPlacement(int) */ private static int FLAG_TEXT_PLACEMENT = Figure.MAX_FLAG << 15; /** * The largest flag defined in this class. If subclasses define flags, they * should declare them as larger than this value and redefine MAX_FLAG to be * their largest flag value. * * @see Figure#MAX_FLAG */ protected static final int MAX_FLAG = FLAG_TEXT_PLACEMENT; private static abstract class IconInfo { /** * Gets the icon at the index location. * * @param i * the index to retrieve the icon of * @return <code>Image</code> that corresponds to the given index. */ public abstract Image getIcon(int i); /** * Gets the icon size of the icon at the given index. * * @param i * @return the <code>Dimension</code> that is the size of the icon at * the given index. */ public abstract Dimension getIconSize(IMapMode mapMode, int i); /** * @return the number of icons */ public abstract int getNumberofIcons(); /** * @return the <code>Dimension</code> that is the total size of all * the icons. */ public abstract Dimension getTotalIconSize(IMapMode mapMode); public abstract void invalidate(); /** * Sets the icon at the index location. * * @param icon * @param i */ public abstract void setIcon(Image icon, int i); /** * */ public abstract int getMaxIcons(); } private static class SingleIconInfo extends IconInfo { static int count; public static final SingleIconInfo NULL_INFO = new SingleIconInfo() { public int getNumberofIcons() { return 0; } }; final Image icon; /** total icon size */ private Dimension totalIconSize; private SingleIconInfo() { icon = null;// don't increment count, used only for NULL_INFO } public SingleIconInfo(Image icon) { this.icon = icon; ++count; } public final int getMaxIcons() { return 1; } public Image getIcon(int i) { if (i == 0) { return icon; } else if (i > 0) { return null; } throw new IndexOutOfBoundsException(); } public void setIcon(Image img, int i) { throw new UnsupportedOperationException(); } public Dimension getIconSize(IMapMode mapMode, int i) { if (i == 0) { return getTotalIconSize(mapMode); } throw new IndexOutOfBoundsException(); } public int getNumberofIcons() { return 1; } public Dimension getTotalIconSize(IMapMode mapMode) { if (totalIconSize != null) return totalIconSize; if (icon != null && !icon.isDisposed()) { org.eclipse.swt.graphics.Rectangle imgBounds = icon.getBounds(); totalIconSize = new Dimension(mapMode.DPtoLP(imgBounds.width), mapMode.DPtoLP(imgBounds.height)); } else { totalIconSize = EMPTY_DIMENSION; } return totalIconSize; } public void invalidate() { totalIconSize = null; } } private static class MultiIconInfo extends IconInfo { /** the label icons */ private ArrayList<Image> icons = new ArrayList<Image>(2); /** total icon size */ private Dimension totalIconSize; public MultiIconInfo() { super(); } public int getMaxIcons() { return -1; } /** * Gets the icon at the index location. * * @param i * the index to retrieve the icon of * @return <code>Image</code> that corresponds to the given index. */ public Image getIcon(int i) { if (i >= icons.size()) return null; return icons.get(i); } /** * Sets the icon at the index location. * * @param icon * @param i */ public void setIcon(Image icon, int i) { int size = icons.size(); if (i >= size) { for (int j = size; j < i; j++) icons.add(null); icons.add(icon); icons.trimToSize(); } else icons.set(i, icon); } /** * Gets the icon size of the icon at the given index. * * @param i * @return the <code>Dimension</code> that is the size of the icon at * the given index. */ public Dimension getIconSize(IMapMode mapMode, int i) { Image img = getIcon(i); if (img != null && !img.isDisposed()) { org.eclipse.swt.graphics.Rectangle imgBounds = img.getBounds(); return new Dimension(mapMode.DPtoLP(imgBounds.width), mapMode .DPtoLP(imgBounds.height)); } return EMPTY_DIMENSION; } /** * @return the number of icons */ public int getNumberofIcons() { return icons.size(); } /** * @return the <code>Dimension</code> that is the total size of all * the icons. */ public Dimension getTotalIconSize(IMapMode mapMode) { if (totalIconSize != null) return totalIconSize; int iconNum = getNumberofIcons(); if (iconNum == 0) { return totalIconSize = EMPTY_DIMENSION; } totalIconSize = new Dimension(); for (int i = 0; i < iconNum; i++) { Dimension iconSize = getIconSize(mapMode, i); totalIconSize.width += iconSize.width; if (iconSize.height > totalIconSize.height) totalIconSize.height = iconSize.height; } return totalIconSize; } /** * */ public void invalidate() { totalIconSize = null; } } private MapModeConstants mapModeConstants; /** * the top-level flow figure */ private FlowPage flowPage; /** * The cached preferred text size that can be used after * {@link #getPreferredSize(int, int)} is called. */ protected Dimension preferredTextSize; /** * The cached truncation string size. */ private Dimension truncationStringSize; private IconInfo iconInfo; /** the cached hint used to calculate text size */ private int cachedPrefSizeHint_width; private int cachedPrefSizeHint_height; /** the icon location */ private Point iconLocation; /** * Construct an empty wrapping label. */ public WrappingLabel() { createTextFigures(); setText("");//$NON-NLS-1$ // default flags... setAlignmentFlags(CENTER, FLAG_TEXT_ALIGN); setAlignmentFlags(CENTER, FLAG_ICON_ALIGN); setAlignmentFlags(TOP | LEFT, FLAG_LABEL_ALIGN); setPlacementFlags(EAST, FLAG_TEXT_PLACEMENT); setTextJustification(LEFT); revalidate(); } /** * Construct a wrapping label with the passed String as its text. * * @param text * the label text */ public WrappingLabel(String text) { this(); if (text != null) { setText(text); } else { setText("");//$NON-NLS-1$ } } /** * Construct a wrapping label with thepassed Image as its icon. * * @param image * the label image */ public WrappingLabel(Image image) { this(); iconInfo = new SingleIconInfo(image); } /** * Construct a wrapping label with passed the String as its text and the * passed Image as its icon. * * @param text * the label text * @param image * the label image */ public WrappingLabel(String text, Image image) { this(text); iconInfo = new SingleIconInfo(image); } /** * Creates the top-level flow figure that will contain and draw the text. * <p> * * @return the new top-level flow figure */ private void createTextFigures() { TextFlowEx textFlow = new TextFlowEx(); flowPage = new FlowPage(); flowPage.add(textFlow); setLayoutManager(textFlow, false); add(flowPage); } /** * Returns the top-level text figure. This is public to accommodate a bug in * the TextDirectEditManager (see WrappingLabelDirectEditManager). It may * not always remain public. * * @return the top-level text figure. */ public IFigure getTextFigure() { return flowPage; } /** * Casts the text figure to a flowpage. * * @return */ private FlowPage getFlowPage() { return (FlowPage) getTextFigure(); } /** * Returns the text flow. * * @return the text flow */ TextFlow getTextFlow() { return (TextFlow) flowPage.getChildren().get(0); } /** * @return <code>IMapMode</code> used by this figure. * <code>IMapMode</code> that allows for the coordinate mapping * from device to logical units. */ IMapMode getFigureMapMode() { return getMapModeConstants().mapModeRef.get(); } MapModeConstants getMapModeConstants() { if (mapModeConstants == null) { IMapMode mapMode = MapModeUtil.getMapMode(this); while (mapMode instanceof IMapModeHolder) { mapMode = ((IMapModeHolder) mapMode).getMapMode(); } mapModeConstants = mapModeConstantsMap.get(mapMode); if (mapModeConstants == null) { mapModeConstants = new MapModeConstants(mapMode); mapModeConstantsMap.put(mapMode, mapModeConstants); } } return mapModeConstants; } private void alignOnHeight(Rectangle area, Rectangle childBounds, int alignment) { switch (alignment) { case TOP: childBounds.y = area.y; childBounds.y = area.y; break; case BOTTOM: childBounds.y = area.getBottom().y - childBounds.height; break; default: childBounds.y = area.y + (area.height - childBounds.height) / 2; } } private void alignOnWidth(Rectangle area, Rectangle childBounds, int alignment) { switch (alignment) { case LEFT: childBounds.x = area.x; break; case RIGHT: childBounds.x = area.getRight().x - childBounds.width; break; default: childBounds.x = area.x + (area.width - childBounds.width) / 2; } } private void calculateAlignment(Rectangle textBounds, Rectangle iconBounds) { Rectangle areaUsed = textBounds.getUnion(iconBounds); areaUsed.x = getInsets().left; areaUsed.y = getInsets().top; switch (getTextPlacement()) { case EAST: case WEST: alignOnHeight(areaUsed, textBounds, getTextAlignment()); alignOnHeight(areaUsed, iconBounds, getIconAlignment()); break; case NORTH: case SOUTH: alignOnWidth(areaUsed, textBounds, getTextAlignment()); alignOnWidth(areaUsed, iconBounds, getIconAlignment()); break; } } /** * Calculates the size of the Label using the passed Dimension as the size * of the Label's text. * * @param txtSize * the precalculated size of the label's text * @return the label's size * @since 2.0 */ protected Dimension calculateLabelSize(Dimension txtSize) { Dimension iconSize = getTotalIconSize(); boolean isEmpty = (iconSize.width == 0 && iconSize.height == 0); int len = getText().length(); if (len == 0 && isEmpty) { return new Dimension(txtSize.width, txtSize.height); } int gap = (len == 0 || isEmpty) ? 0 : getIconTextGap(); int placement = getTextPlacement(); if (placement == WEST || placement == EAST) { return new Dimension(iconSize.width + gap + txtSize.width, Math .max(iconSize.height, txtSize.height)); } else { return new Dimension(Math.max(iconSize.width, txtSize.width), iconSize.height + gap + txtSize.height); } } public void layout() { Rectangle textBounds = new Rectangle(); Rectangle iconBounds = new Rectangle(); calculateSizes(textBounds, iconBounds); calculatePlacement(textBounds, iconBounds); calculateAlignment(textBounds, iconBounds); calculateLabelAlignment(textBounds, iconBounds); if (hasIcons()) { setIconLocation(iconBounds.getLocation()); } getTextFigure().setBounds( textBounds.getTranslated(getBounds().getLocation())); } /** * @param container * @param textBounds * @param iconBounds */ private void calculateSizes(Rectangle textBounds, Rectangle iconBounds) { Rectangle area = getClientArea(); Dimension preferredSize = getPreferredSize(area.width, area.height); Dimension minimumSize = getMinimumSize(area.width, area.height); Dimension shrinkAmount = preferredSize.getDifference(getBounds().getSize() .getUnioned(minimumSize)); Dimension textSize = preferredTextSize.getCopy(); if (shrinkAmount.width > 0) { textSize.shrink(shrinkAmount.width, 0); } if (shrinkAmount.height > 0) { textSize.shrink(0, shrinkAmount.height); } if (getTextFlow().isTextTruncated()) { textBounds.setSize(textSize); } else { // This is needed for label alignment to work. The preferred text // size will extend the entire width, so use the actual text size // instead. textBounds.setSize(getTextFlow().getSize().intersect(textSize)); } iconBounds.setSize(getTotalIconSize()); } private void calculateLabelAlignment(Rectangle textBounds, Rectangle iconBounds) { Dimension offset = getClientArea().getSize().getDifference( textBounds.getUnion(iconBounds).getSize()); switch (getAlignment()) { case TOP | LEFT: offset.height = 0; offset.width = 0; break; case TOP: offset.height = 0; offset.scale(0.5f); break; case TOP | RIGHT: offset.height = 0; case RIGHT: offset.width = offset.width * 2; offset.scale(0.5f); break; case BOTTOM | RIGHT: break; case BOTTOM: offset.height = offset.height * 2; offset.scale(0.5f); break; case BOTTOM | LEFT: offset.width = 0; break; case LEFT: offset.width = 0; offset.scale(0.5f); break; case CENTER: offset.scale(0.5f); break; default: offset.scale(0.5f); break; } textBounds.translate(offset.width, offset.height); iconBounds.translate(offset.width, offset.height); } private void calculatePlacement(Rectangle textBounds, Rectangle iconBounds) { int gap = (textBounds.isEmpty() || iconBounds.isEmpty()) ? 0 : getIconTextGap(); Insets insets = getInsets(); switch (getTextPlacement()) { case EAST: iconBounds.x = insets.left; textBounds.x = iconBounds.width + gap + insets.left; break; case WEST: textBounds.x = insets.left; iconBounds.x = textBounds.width + gap + insets.left; break; case NORTH: textBounds.y = insets.top; iconBounds.y = textBounds.height + gap + insets.top; break; case SOUTH: textBounds.y = iconBounds.height + gap + insets.top; iconBounds.y = insets.top; } } /** * Returns the label's icon. * * @return the label's icon. */ public Image getIcon() { return getIcon(0); } /** * Gets the label's icon at the given index * * @param index * The icon index * @return the <code>Image</code> that is the icon for the given index. */ public Image getIcon(int index) { if (iconInfo == null) return null; return iconInfo.getIcon(index); } /** * Determines if there is any icons by checking if icon size is zeros. * * @return true if icons are present, false otherwise */ protected boolean hasIcons() { return (getNumberofIcons() > 0); } /** * Returns the alignment of the label's icon relative to the label's text * bounds. This is only relevant if the icon's width or height (depending on * the location of the icon relative to the text) is smaller than the text's * width or height. The default is {@link PositionConstants#CENTER}. * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @return the icon alignment relative to the text bounds */ public int getIconAlignment() { return getAlignment(FLAG_ICON_ALIGN); } /** * Returns the location of the Label's icon relative to the Label. * * @return the icon's location * @since 2.0 */ protected Point getIconLocation() { return iconLocation; } protected void setIconLocation(Point location) { iconLocation = location; } /** * Returns the gap in pixels between the Label's icon and its text. * * @return the gap * @since 2.0 */ public int getIconTextGap() { return getMapModeConstants().nDPtoLP_3; } public Dimension getMinimumSize(int w, int h) { if (minSize != null) return minSize; minSize = new Dimension(); Font currentFont = getFont(); Dimension ellipsisSize = getTruncationStringSize(); Dimension textSize = new TextUtilitiesEx(getFigureMapMode()) .getTextExtents(getText(), currentFont); if (textSize.width == 0) { textSize.height = 0; } textSize.intersect(ellipsisSize); Dimension labelSize = calculateLabelSize(textSize); Insets insets = getInsets(); labelSize.expand(insets.getWidth(), insets.getHeight()); minSize.union(labelSize); return minSize; } public Dimension getPreferredSize(int wHint, int hHint) { if (prefSize == null || wHint != cachedPrefSizeHint_width || hHint != cachedPrefSizeHint_height) { cachedPrefSizeHint_width = wHint; cachedPrefSizeHint_height = hHint; int minWHint = 0, minHHint = 0; if (wHint < 0) minWHint = -1; if (hHint < 0) minHHint = -1; if (hasIcons()) { // start with the icon size and then add the text size prefSize = getTotalIconSize().getCopy(); switch (getTextPlacement()) { case EAST: case WEST: wHint = Math.max(minWHint, wHint - (prefSize.width + getIconTextGap())); preferredTextSize = getTextFigure().getPreferredSize( wHint, hHint).getCopy(); prefSize.width += preferredTextSize.width + getIconTextGap(); prefSize.height = Math.max(prefSize.height, preferredTextSize.height); break; case NORTH: case SOUTH: hHint = Math.max(minHHint, hHint - (prefSize.height + getIconTextGap())); preferredTextSize = getTextFigure().getPreferredSize( wHint, hHint).getCopy(); prefSize.width = Math.max(prefSize.width, preferredTextSize.width); prefSize.height += preferredTextSize.height + getIconTextGap(); break; } } else { preferredTextSize = getTextFigure().getPreferredSize(wHint, hHint).getCopy(); if(preferredTextSize.width == 0){ preferredTextSize.height = 0; } prefSize = preferredTextSize.getCopy(); } Insets insets = getInsets(); prefSize.expand(insets.getWidth(), insets.getHeight()); } return prefSize; } /** * Returns the size of the truncation string based on the currently used Map * mode size. * * @return the size of the truncation string * */ Dimension getTruncationStringSize() { if (truncationStringSize == null) { if (getTruncationString().equals(ELLIPSIS)) { truncationStringSize = getMapModeConstants() .getEllipseTextSize(getFont()); } else { Font f = getFont(); IMapMode mapMode = getFigureMapMode(); truncationStringSize = FigureUtilities.getTextExtents( getTruncationString(), f); truncationStringSize.height = FigureUtilities.getFontMetrics(f) .getHeight(); truncationStringSize = new Dimension(mapMode .DPtoLP(truncationStringSize.width), mapMode .DPtoLP(truncationStringSize.height)); } } return truncationStringSize; } /** * Returns the text of the label. Note that this is the complete text of the * label, regardless of whether it is currently being truncated. * * @return the complete text of this label */ public String getText() { return getTextFlow().getText(); } /** * Gets the alignment of the label's text relative to the label's icon * bounds. This is only relevant if the text's width or height (depending on * the location of the text relative to the icon) is smaller than the icon's * width or height. The default text alignment is * {@link PositionConstants#CENTER}. * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @return the text alignment relative to the icon bounds */ public int getTextAlignment() { return getAlignment(FLAG_TEXT_ALIGN); } /** * Returns the alignment of the label (icon and text) within the figure. * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @return the label alignment */ public int getAlignment() { return getAlignment(FLAG_LABEL_ALIGN); } /** * Returns the bounds of the label's text. Note that the bounds are * calculated using the label's complete text regardless of whether the * label's text is currently truncated. * * @return the bounds of this label's complete text relative to this * figure's bounds */ public Rectangle getTextBounds() { return new Rectangle(getFlowPage().getBounds().getLocation(), getTextFlow().getBounds().getSize()); } /** * Returns the current placement of the label's text relative to its icon. * The default text placement is {@link PositionConstants#EAST}. * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @return the text placement */ public int getTextPlacement() { return getPlacement(FLAG_TEXT_PLACEMENT); } public void invalidate() { prefSize = null; minSize = null; iconLocation = null; truncationStringSize = null; if (iconInfo != null) { iconInfo.invalidate(); } super.invalidate(); } public void paintFigure(Graphics graphics) { super.paintFigure(graphics); if (hasIcons()) { paintIcons(graphics); } } protected void paintClientArea(Graphics graphics) { paintSelectionRectangle(graphics); paintFocusRectangle(graphics); super.paintClientArea(graphics); } private void paintSelectionRectangle(Graphics g) { if (isSelected()) { g.pushState(); g.setBackgroundColor(ColorConstants.menuBackgroundSelected); g.fillRectangle(getVisibleTextBounds()); g.popState(); g.setForegroundColor(ColorConstants.menuForegroundSelected); } } private void paintFocusRectangle(Graphics g) { if (hasFocus()) { g.pushState(); g.setXORMode(true); g.setForegroundColor(ColorConstants.menuBackgroundSelected); g.setBackgroundColor(ColorConstants.menuForegroundSelected); g.drawFocus(getVisibleTextBounds()); g.popState(); } } private Rectangle getVisibleTextBounds() { return getTextBounds().getIntersection(getClientArea()); } /** * Paints the icon(s) * * @param graphics * The graphics context */ private void paintIcons(Graphics graphics) { Point p = Point.SINGLETON; if (getIconLocation() != null) { p.setLocation(getIconLocation()); Rectangle figBounds = getBounds(); graphics.translate(figBounds.x, figBounds.y); int num = getNumberofIcons(); for (int i = 0; i < num; i++) { Image icon = getIcon(i); if (icon != null) { graphics.drawImage(icon, p); p.x += getIconSize(i).width; } } graphics.translate(-figBounds.x, -figBounds.y); } } /** * Sets the label's icon to the passed image. * * @param image * the new label image * @since 2.0 */ public void setIcon(Image image) { setIcon(image, 0); } /** * Sets the label's icon at given index * * @param image * The icon image or null to remove the icon * @param index * The icon index */ public void setIcon(Image image, int index) { if (iconInfo == null) { if (index == 0) { iconInfo = getMapModeConstants().getSingleIconInfo(image); } else { iconInfo = new MultiIconInfo(); iconInfo.setIcon(image, index); } revalidate(); repaint();// Call repaint, in case the image dimensions are not // the same. } else if (iconInfo.getIcon(index) != image) { if (iconInfo.getMaxIcons() == 1) { if (index == 0) { iconInfo = getMapModeConstants().getSingleIconInfo(image); revalidate(); repaint();// Call repaint, in case the image dimensions // are not the same. return; } IconInfo oldIconInfo = iconInfo; iconInfo = new MultiIconInfo(); iconInfo.setIcon(oldIconInfo.getIcon(0), 0); } iconInfo.setIcon(image, index); revalidate(); repaint();// Call repaint, in case the image dimensions are not // the same. } } /** * Sets the alignment of the label's icon relative to the label's text * bounds. This is only relevant if the icon's width or height (depending on * the location of the icon relative to the text) is smaller than the text's * width or height. The default value is {@link PositionConstants#CENTER}. * <p> * If the text placement is NORTH/SOUTH, valid values are: * <UL> * <LI><EM>{@link PositionConstants#CENTER}</EM> * <LI>{@link PositionConstants#LEFT} * <LI>{@link PositionConstants#RIGHT} * </UL> * <p> * If the text placement is EAST/WEST, valid values are: * <UL> * <LI><EM>{@link PositionConstants#CENTER}</EM> * <LI>{@link PositionConstants#TOP} * <LI>{@link PositionConstants#BOTTOM} * </UL> * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @param alignment * the icon alignment relative to the text bounds */ public void setIconAlignment(int alignment) { if (getIconAlignment() == alignment) return; setAlignmentFlags(alignment, FLAG_ICON_ALIGN); revalidate(); repaint(); } /** * getIconSize * * @param index * of icon to retrieve size of. * @return Dimension representing the icon size. */ protected Dimension getIconSize(int index) { if (iconInfo == null) return EMPTY_DIMENSION; return iconInfo.getIconSize(getFigureMapMode(), index); } /** * getIconNumber * * @return int number of icons in the wrap label */ protected int getNumberofIcons() { if (iconInfo == null) return 0; return iconInfo.getNumberofIcons(); } /** * getTotalIconSize Calculates the total union of icon sizes * * @return Dimension that is the union of icon sizes */ protected Dimension getTotalIconSize() { if (iconInfo == null) return EMPTY_DIMENSION; return iconInfo.getTotalIconSize(getFigureMapMode()); } /** * Sets the alignment of the label (icon and text) within the figure. If * this figure's bounds are larger than the size needed to display the * label, the label will be aligned accordingly. The default is * {@link PositionConstants#LEFT}. Valid values are: * <UL> * <LI>{@link PositionConstants#TOP} | {@link PositionConstants#LEFT} * <LI>{@link PositionConstants#TOP} * <LI>{@link PositionConstants#TOP} | {@link PositionConstants#RIGHT} * <LI><EM>{@link PositionConstants#LEFT}</EM> * <LI>{@link PositionConstants#CENTER} * <LI>{@link PositionConstants#RIGHT} * <LI>{@link PositionConstants#BOTTOM} | {@link PositionConstants#LEFT} * <LI>{@link PositionConstants#BOTTOM} * <LI>{@link PositionConstants#BOTTOM} | {@link PositionConstants#RIGHT} * </UL> * * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @param alignment * label alignment */ public void setAlignment(int alignment) { if (getAlignment() == alignment) return; setAlignmentFlags(alignment, FLAG_LABEL_ALIGN); revalidate(); repaint(); } /** * Gets the truncation string. The default is an ellipsis "...". Clients may * override, but if the truncation string changes throughout the lifecycle * of this figure, then revalidate() should be called to ensure the cached * <code>truncationStringSize</code> is cleared. * * @return the truncation string */ protected String getTruncationString() { return ELLIPSIS; } /** * Sets the text in this label. * * @param text * the new text to be set */ public void setText(String text) { getTextFlow().setText(text); } /** * Sets the alignment of the label's text relative to the label's icon * bounds. This is only relevant if the text's width or height (depending on * the location of the text relative to the icon) is smaller than the icon's * width or height. The default value is {@link PositionConstants#CENTER}. * <p> * If the text placement is EAST/WEST, valid values are: * <UL> * <LI><EM>{@link PositionConstants#CENTER}</EM> * <LI>{@link PositionConstants#TOP} * <LI>{@link PositionConstants#BOTTOM} * </UL> * <p> * If the text placement is NORTH/SOUTH, and the icon is bigger than the * text, then the location of the text can be controlled by how the text is * justified {@link #setTextJustification(int)}. * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @param alignment * the text alignment relative to the icon bounds */ public void setTextAlignment(int alignment) { if (getTextAlignment() == alignment) return; setAlignmentFlags(alignment, FLAG_TEXT_ALIGN); revalidate(); repaint(); } /** * Sets the text placement of the label relative to its icon. The default is * {@link PositionConstants#EAST}. Valid values are * <UL> * <LI><EM>{@link PositionConstants#EAST}</EM> * <LI>{@link PositionConstants#NORTH} * <LI>{@link PositionConstants#SOUTH} * <LI>{@link PositionConstants#WEST} * </UL> * * e.g. PositionConstants#EAST indicates the text is on the east of the * icon. * <p> * <p> * See the documentation describing the layout of the label in the class * header {@link WrappingLabel} for more detailed information. * </p> * * @param where * the text placement */ public void setTextPlacement(int where) { if (getTextPlacement() == where) return; setPlacementFlags(where, FLAG_TEXT_PLACEMENT); revalidate(); repaint(); } /** * Sets whether the label text should be underlined * * @param underline * Whether the label text should be underlined */ public void setTextUnderline(boolean underline) { ((TextFlowEx) getTextFlow()).setTextUnderline(underline); } /** * @return whether the label text is underlined */ public boolean isTextUnderlined() { return ((TextFlowEx) getTextFlow()).isTextUnderlined(); } /** * Sets whether the label text should be striked-through * * @param strikeThrough * whether the label text should be striked-through */ public void setTextStrikeThrough(boolean strikeThrough) { ((TextFlowEx) getTextFlow()).setTextStrikeThrough(strikeThrough); } /** * @return whether the label text is striked-through */ public boolean isTextStrikedThrough() { return ((TextFlowEx) getTextFlow()).isTextStrikedThrough(); } /** * Sets whether the label text should wrap * * @param b * whether the label text should wrap */ public void setTextWrap(boolean textWrapOn) { if (textWrapOn != isTextWrapOn()) { setLayoutManager(getTextFlow(), textWrapOn); revalidate(); repaint(); } } /** * @return whether the label text wrap is on */ public boolean isTextWrapOn() { return getTextFlow().getLayoutManager() instanceof ParagraphTextLayout; } /** * Sets the text justification of the label text. The default is * {@link PositionConstants#LEFT}. Valid values are * <UL> * <LI><EM>{@link PositionConstants#LEFT}</EM> * <LI>{@link PositionConstants#CENTER} * <LI>{@link PositionConstants#RIGHT} * </UL> * * @param alignment * the text justification. */ public void setTextJustification(int justification) { getFlowPage().setHorizontalAligment(justification); } /** * Gets the text justification of the label text. * * @return the text justification -- {@link PositionConstants#LEFT}, * {@link PositionConstants#CENTER}, or * {@link PositionConstants#RIGHT} */ public int getTextJustification() { return getFlowPage().getHorizontalAligment(); } /** * setPlacementFlags * * @param align * @param flagOffset */ private void setPlacementFlags(int align, int flagOffset) { flags &= ~(0x7 * flagOffset); switch (align) { case EAST: flags |= 0x1 * flagOffset; break; case WEST: flags |= 0x2 * flagOffset; break; case NORTH: flags |= 0x3 * flagOffset; break; case SOUTH: flags |= 0x4 * flagOffset; break; } } /** * getPlacement * * @param flagOffset * @return PositionConstant representing the placement */ private int getPlacement(int flagOffset) { int wrapValue = flags & (0x7 * flagOffset); if (wrapValue == 0x1 * flagOffset) return EAST; else if (wrapValue == 0x2 * flagOffset) return WEST; else if (wrapValue == 0x3 * flagOffset) return NORTH; else if (wrapValue == 0x4 * flagOffset) return SOUTH; return EAST; } /** * setAlignmentFlags * * @param alignment * @param flagOffset */ private void setAlignmentFlags(int alignment, int flagOffset) { flags &= ~(0xF * flagOffset); switch (alignment) { case CENTER: flags |= 0x1 * flagOffset; break; case TOP: flags |= 0x2 * flagOffset; break; case LEFT: flags |= 0x3 * flagOffset; break; case RIGHT: flags |= 0x4 * flagOffset; break; case BOTTOM: flags |= 0x5 * flagOffset; break; case TOP | LEFT: flags |= 0x6 * flagOffset; break; case TOP | RIGHT: flags |= 0x7 * flagOffset; break; case BOTTOM | LEFT: flags |= 0x8 * flagOffset; break; case BOTTOM | RIGHT: flags |= 0x9 * flagOffset; break; } } /** * Retrieves the alignment value from the flags member. * * @param flagOffset * that is the bitwise value representing the offset. * @return PositionConstant representing the alignment */ private int getAlignment(int flagOffset) { int wrapValue = flags & (0xF * flagOffset); if (wrapValue == 0x1 * flagOffset) return CENTER; else if (wrapValue == 0x2 * flagOffset) return TOP; else if (wrapValue == 0x3 * flagOffset) return LEFT; else if (wrapValue == 0x4 * flagOffset) return RIGHT; else if (wrapValue == 0x5 * flagOffset) return BOTTOM; else if (wrapValue == 0x6 * flagOffset) return TOP | LEFT; else if (wrapValue == 0x7 * flagOffset) return TOP | RIGHT; else if (wrapValue == 0x8 * flagOffset) return BOTTOM | LEFT; else if (wrapValue == 0x9 * flagOffset) return BOTTOM | RIGHT; return CENTER; } /** * @return the focus state of this label */ public boolean hasFocus() { return (flags & FLAG_HASFOCUS) != 0; } /** * Sets the focus state of this label * * @param focus * true will cause a focus rectangle to be drawn around the text * of the Label */ public void setFocus(boolean focus) { if (hasFocus() == focus) return; setFlag(FLAG_HASFOCUS, focus); repaint(); } /** * @return the selection state of this label */ public boolean isSelected() { return (flags & FLAG_SELECTED) != 0; } /** * Sets the selection state of this label * * @param selected * true will cause the label to appear selected */ public void setSelected(boolean selected) { if (isSelected() == selected) return; setFlag(FLAG_SELECTED, selected); repaint(); } public void setFont(Font f) { super.setFont(f); // need to trigger a repaint of the textflow getTextFlow().setFont(f); } /** * Set the layout manager, made protected with Bugzilla 460116 * @since 1.9.0 * @param textFlow * @param wrappingOn */ protected void setLayoutManager(TextFlow textFlow, boolean wrappingOn) { TextLayout layout; if (wrappingOn) { layout = new ParagraphTextLayout(textFlow, ParagraphTextLayout.WORD_WRAP_SOFT); } else { layout = new TruncatedSingleLineTextLayout((TextFlowEx) textFlow, getTruncationString()); } layout.setFlowContext((FlowContext) ((FlowPage) textFlow.getParent()) .getLayoutManager()); textFlow.setLayoutManager(layout); } public String toString() { // for debugging purposes return getText(); } /** * Returns <code>true</code> if the label's text is currently truncated and * is displaying an ellipsis, <code>false</code> otherwise. <br> * Note that this code only reports horizontal truncation by delegating to * the GEF TextFlow and ignores that fact that GMF TextFlowEx may be * vertically truncated. * * @return <code>true</code> if the label's text is truncated */ public boolean isTextTruncated() { return getTextFlow().isTextTruncated(); } }