### Eclipse Workspace Patch 1.0 #P org.eclipse.gmf.runtime.draw2d.ui Index: src/org/eclipse/gmf/runtime/draw2d/ui/figures/WrapLabel.java =================================================================== RCS file: /cvsroot/technology/org.eclipse.gmf/plugins/org.eclipse.gmf.runtime.draw2d.ui/src/org/eclipse/gmf/runtime/draw2d/ui/figures/WrapLabel.java,v retrieving revision 1.9.2.3 diff -u -r1.9.2.3 WrapLabel.java --- src/org/eclipse/gmf/runtime/draw2d/ui/figures/WrapLabel.java 19 Sep 2006 19:09:21 -0000 1.9.2.3 +++ src/org/eclipse/gmf/runtime/draw2d/ui/figures/WrapLabel.java 15 Dec 2006 21:34:22 -0000 @@ -8,1707 +8,127 @@ * Contributors: * IBM Corporation - initial API and implementation ****************************************************************************/ - 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.FigureUtilities; -import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.LayoutManager; -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.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.swt.graphics.Font; -import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.Image; -import com.ibm.icu.text.BreakIterator; -import com.ibm.icu.util.StringTokenizer; - /** - * An extended label that has the following extra features: - * - * 1- It is capable of showing selection and focus feedback (primary or - * secondary) 2- It is capable of optionally underlining the label's text 3- It - * is capable of wrapping the label's text at a given width with a given - * alignment 4- It is capable of supporting multiple label icons (temporary - * feature) - * - * This class was originally deriving off Draw2d's Label class - * but with the introduction of the auto-wrapping feature, a copy had to be made - * overriding was not straightforward. Hopefully, this extended version can be - * pushed to opensource + * This is a legacy wrapper of the originial WrapLabel + * for the new LabelWithTextLayout. Since the way the layout + * is done in LabelWithTextLayout is different than the way it was + * in the original WrapLabel, this class tends to obtain the original + * functionality.

* - *

- * Code taken from Eclipse reference bugzilla #98820 - * - * @author melaasar + * @author satif + * */ public class WrapLabel - extends Figure - implements PositionConstants { - - private static final String _ellipse = "..."; //$NON-NLS-1$ - - private static final Dimension EMPTY_DIMENSION = new Dimension(0, 0); - - private static final Map mapModeConstantsMap = new WeakHashMap(); - - private static class MapModeConstants { - - private static final int MAX_IMAGE_INFO = 12; - - public final WeakReference 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 fontToEllipseTextSize = new WeakHashMap(); - - public final SingleIconInfo[] singleIconInfos = new SingleIconInfo[MAX_IMAGE_INFO]; - - public MapModeConstants(IMapMode mapMode) { - this.mapModeRef = new WeakReference(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 = (Dimension) fontToEllipseTextSize.get(f); - if (d == null) { - IMapMode mapMode = (IMapMode) mapModeRef.get(); - d = FigureUtilities.getTextExtents(_ellipse, 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 - private static int FLAG_SELECTED = MAX_FLAG << 1; - - private static int FLAG_HASFOCUS = MAX_FLAG << 2; - - private static int FLAG_UNDERLINED = MAX_FLAG << 3; - - private static int FLAG_STRIKEDTHROUGH = MAX_FLAG << 4; - - private static int FLAG_WRAP = MAX_FLAG << 5; - - // reserve 3 bits - private static int FLAG_TEXT_ALIGN = MAX_FLAG << 6; - - private static int FLAG_WRAP_ALIGN = MAX_FLAG << 9; - - private static int FLAG_ICON_ALIGN = MAX_FLAG << 12; - - private static int FLAG_LABEL_ALIGN = MAX_FLAG << 15; - - private static int FLAG_TEXT_PLACEMENT = MAX_FLAG << 18; - - private MapModeConstants mapModeConstants; - - /** the original label's text */ - private String text; - - /** the label's text used in painting after applying required styles */ - private String subStringText; - - /** the size of text */ - private Dimension textSize; - - private Dimension ellipseTextSize; - - /** the location of text */ - private Point textLocation; - - /** the cached hint used to calculate text size */ - private int cachedPrefSizeHint_width; - - private int cachedPrefSizeHint_height; - - /** the icon location */ - private Point iconLocation; - - private static abstract class IconInfo { - /** - * Gets the icon at the index location. - * - * @param i - * the index to retrieve the icon of - * @return Image 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 Dimension 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 Dimension 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 icons = new ArrayList(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 Image that corresponds to the given index. - */ - public Image getIcon(int i) { - if (i >= icons.size()) - return null; - - return (Image) 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 Dimension 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 Dimension 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 IconInfo iconInfo; - - /** the cached hint used to calculate text size */ - private int cachedTextSizeHint_width; - - private int cachedTextSizeHint_height; - - - - /** - * Construct an empty Label. - * - * @since 2.0 - */ - public WrapLabel() { - text = "";//$NON-NLS-1$ - // set defaults - setAlignmentFlags(CENTER, FLAG_TEXT_ALIGN); - setAlignmentFlags(CENTER, FLAG_ICON_ALIGN); - setAlignmentFlags(CENTER, FLAG_LABEL_ALIGN); - setAlignmentFlags(LEFT, FLAG_WRAP_ALIGN); - setPlacementFlags(EAST, FLAG_TEXT_PLACEMENT); - } - - /** - * Construct a Label with passed String as its text. - * - * @param s the label text - * @since 2.0 - */ - public WrapLabel(String s) { - if (s != null) { - text = s; - } else { - text = "";//$NON-NLS-1$ - } -// setBorder(new LineBorderEx(ColorConstants.red,3)); - } - - /** - * Construct a Label with passed Image as its icon. - * - * @param i the label image - * @since 2.0 - */ - public WrapLabel(Image i) { - text = "";//$NON-NLS-1$ - iconInfo = new SingleIconInfo(i); - } - - /** - * Construct a Label with passed String as text and passed Image as its - * icon. - * - * @param s the label text - * @param i the label image - * @since 2.0 - */ - public WrapLabel(String s, Image i) { - if (s != null) { - text = s; - } else { - text = "";//$NON-NLS-1$ - } - iconInfo = new SingleIconInfo(i); - } - - /** - * @return IMapMode used by this figure. - * IMapMode that allows for the coordinate mapping - * from device to logical units. - */ - private IMapMode getMapMode() { - return (IMapMode) getMapModeConstants().mapModeRef.get(); - } - - private MapModeConstants getMapModeConstants() { - if (mapModeConstants == null) { - IMapMode mapMode = MapModeUtil.getMapMode(this); - while (mapMode instanceof IMapModeHolder) { - mapMode = ((IMapModeHolder) mapMode).getMapMode(); - } - mapModeConstants = (MapModeConstants) mapModeConstantsMap - .get(mapMode); - if (mapModeConstants == null) { - mapModeConstants = new MapModeConstants(mapMode); - mapModeConstantsMap.put(mapMode, mapModeConstants); - } - } - return mapModeConstants; - } - - private void alignOnHeight(Point loc, Dimension size, int alignment) { - switch (alignment) { - case TOP: - loc.y = getInsets().top; - break; - case BOTTOM: - loc.y = bounds.height - size.height - getInsets().bottom; - break; - default: - loc.y = (bounds.height - size.height) / 2; - } - } - - private void alignOnWidth(Point loc, Dimension size, int alignment) { - switch (alignment) { - case LEFT: - loc.x = getInsets().left; - break; - case RIGHT: - loc.x = bounds.width - size.width - getInsets().right; - break; - default: - loc.x = (bounds.width - size.width) / 2; - } - } - - private void calculateAlignment(Dimension iconSize, int textPlacement) { - switch (textPlacement) { - case EAST: - case WEST: - alignOnHeight(textLocation, getTextSize(), getTextAlignment()); - alignOnHeight(getIconLocation(), iconSize, getIconAlignment()); - break; - case NORTH: - case SOUTH: - alignOnWidth(textLocation, getSubStringTextSize(), - getTextAlignment()); - alignOnWidth(getIconLocation(), iconSize, 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); - } - } - - private void calculateLocations() { - textLocation = new Point(); - iconLocation = new Point(); - Dimension iconSize = getTotalIconSize(); - int textPlacement = getTextPlacement(); - calculatePlacement(iconSize, textPlacement); - calculateAlignment(iconSize, textPlacement); - Rectangle r = getBounds(); - Dimension ps = getPreferredSize(r.width, r.height); - int w = (r.width - ps.width) - + (getTextSize().width - getSubStringTextSize().width); - int h = r.height - ps.height; - if (w == 0 && h == 0) { - return; - } - - Dimension offset = new Dimension(w, h); - switch (getLabelAlignment()) { - case CENTER: - offset.scale(0.5f); - break; - case LEFT: - offset.scale(0.0f); - break; - case RIGHT: - offset.scale(1.0f); - break; - case TOP: - offset.height = 0; - offset.scale(0.5f); - break; - case BOTTOM: - offset.height = offset.height * 2; - offset.scale(0.5f); - break; - default: - offset.scale(0.5f); - break; - } - - switch (textPlacement) { - case EAST: - case WEST: - offset.height = 0; - break; - case NORTH: - case SOUTH: - offset.width = 0; - break; - } - - textLocation.translate(offset); - iconLocation.translate(offset); - } - - private void calculatePlacement(Dimension iconSize, int textPlacement) { - int gap = (getText().length() == 0 || (iconSize.width == 0 && iconSize.height == 0)) ? 0 - : getIconTextGap(); - Insets insets = getInsets(); - switch (textPlacement) { - case EAST: - iconLocation.x = insets.left; - textLocation.x = iconSize.width + gap + insets.left; - break; - case WEST: - textLocation.x = insets.left; - iconLocation.x = getSubStringTextSize().width + gap - + insets.left; - break; - case NORTH: - textLocation.y = insets.top; - iconLocation.y = getTextSize().height + gap + insets.top; - break; - case SOUTH: - textLocation.y = iconSize.height + gap + insets.top; - iconLocation.y = insets.top; - } - } - /** - * Calculates the size of the Label's text size. The text size calculated - * takes into consideration if the Label's text is currently truncated. If - * text size without considering current truncation is desired, use - * {@link #calculateTextSize(int, int)}. - * - * @return the size of the label's text, taking into account truncation - * @since 2.0 - */ - protected Dimension calculateSubStringTextSize() { - Font f = getFont(); - return getTextExtents(getSubStringText(), f, getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight())); - } - - /** - * Calculates and returns the size of the Label's text. Note that this - * Dimension is calculated using the Label's full text, regardless of - * whether or not its text is currently truncated. If text size considering - * current truncation is desired, use {@link #calculateSubStringTextSize()}. - * - * @param wHint a width hint - * @param hHint a height hint - * @return the size of the label's text, ignoring truncation - * @since 2.0 - */ - protected Dimension calculateTextSize(int wHint, int hHint) { - Font f = getFont(); - return getTextExtents(getWrappedText(wHint, hHint), f,getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight())); - } - - private void clearLocations() { - iconLocation = textLocation = null; - } - - /** - * Returns the Label's icon. - * - * @return the label icon - * @since 2.0 - */ - public Image getIcon() { - return getIcon(0); - } - - /** - * Gets the label's icon at the given index - * - * @param index The icon index - * @return the Image 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 current alignment of the Label's icon. The default is - * {@link PositionConstants#CENTER}. - * - * @return the icon alignment - * @since 2.0 - */ - public int getIconAlignment() { - return getAlignment(FLAG_ICON_ALIGN); - } - - /** - * Returns the bounds of the Label's icon. - * - * @return the icon's bounds - * @since 2.0 - */ - public Rectangle getIconBounds() { - return new Rectangle(getBounds().getLocation().translate( - getIconLocation()), getTotalIconSize()); - } - - /** - * Returns the location of the Label's icon relative to the Label. - * - * @return the icon's location - * @since 2.0 - */ - protected Point getIconLocation() { - if (iconLocation == null) - calculateLocations(); - return iconLocation; - } - - /** - * 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; - } - - /** - * @see IFigure#getMinimumSize(int, int) - */ - public Dimension getMinimumSize(int w, int h) { - if (minSize != null) - return minSize; - minSize = new Dimension(); - LayoutManager layoutManager = getLayoutManager(); - if (layoutManager != null) - minSize.setSize(layoutManager.getMinimumSize(this, w, h)); - Font f = getFont(); - Dimension d = getEllipseTextSize().getIntersected( - getTextExtents(getText(), f, getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight()))); - - Dimension labelSize = calculateLabelSize(d); - Insets insets = getInsets(); - labelSize.expand(insets.getWidth(), insets.getHeight()); - minSize.union(labelSize); - return minSize; - } - - /* - * (non-Javadoc) - * @see org.eclipse.draw2d.IFigure#getPreferredSize(int, int) - */ - public Dimension getPreferredSize(int wHint, int hHint) { - if (prefSize == null || wHint != cachedPrefSizeHint_width || hHint != cachedPrefSizeHint_height) { - prefSize = calculateLabelSize(getTextSize(wHint, hHint)); - Insets insets = getInsets(); - prefSize.expand(insets.getWidth(), insets.getHeight()); - LayoutManager layoutManager = getLayoutManager(); - if (layoutManager != null) { - prefSize.union(layoutManager.getPreferredSize(this, wHint, - hHint)); - } - prefSize.union(getMinimumSize(wHint, hHint)); - cachedPrefSizeHint_width = wHint; - cachedPrefSizeHint_height = hHint; - } - return prefSize; - } - - /* (non-Javadoc) - * @see org.eclipse.draw2d.IFigure#getMaximumSize() - */ - public Dimension getMaximumSize() { - // this assumes that getPreferredSize(wHint, hHint) is called before - return prefSize; - } - - /** - * Calculates the amount of the Label's current text will fit in the Label, - * including an elipsis "..." if truncation is required. - * - * @return the substring - * @since 2.0 - */ - public String getSubStringText() { - if (subStringText != null) - return subStringText; - - String theText = getText(); - int textLen = theText.length(); - if (textLen == 0) { - return subStringText = "";//$NON-NLS-1$;; - } - Dimension size = getSize(); - Dimension shrink = getPreferredSize(size.width, size.height).getDifference(size); - Dimension effectiveSize = getTextSize().getExpanded(-shrink.width, -shrink.height); - - if (effectiveSize.height == 0) { - return subStringText = "";//$NON-NLS-1$; - } - - Font f = getFont(); - FontMetrics metrics = FigureUtilities.getFontMetrics(f); - IMapMode mm = getMapMode(); - int fontHeight = mm.DPtoLP(metrics.getHeight()); - int charAverageWidth = mm.DPtoLP(metrics.getAverageCharWidth()); - int maxLines = (int) (effectiveSize.height / (double) fontHeight); - if (maxLines == 0) { - return subStringText = "";//$NON-NLS-1$ - } - - StringBuffer accumlatedText = new StringBuffer(); - StringBuffer remainingText = new StringBuffer(theText); - - int effectiveSizeWidth = effectiveSize.width; - int widthHint = Math.max(effectiveSizeWidth - - getEllipseTextSize().width, 0); - int i = 0, j = 0; - while (remainingText.length() > 0 && j++ < maxLines) { - i = getLineWrapPosition(remainingText.toString(), f, effectiveSizeWidth, fontHeight); - - if (accumlatedText.length() > 0) - accumlatedText.append('\n'); - - if (i == 0 || (remainingText.length() > i && j == maxLines)) { - i = getLargestSubstringConfinedTo(remainingText.toString(), f, widthHint, fontHeight, charAverageWidth); - accumlatedText.append(remainingText.substring(0, i)); - accumlatedText.append(getEllipse()); - } else - accumlatedText.append(remainingText.substring(0, i)); - remainingText.delete(0, i); - } - return subStringText = accumlatedText.toString(); - } - - - - - /** - * Creates an equivalent text to that of the label's but with "\n"(s) - * inserted at the wrapping positions. This method assumes unlimited - * bounding box and is used by calculateTextSize() to - * calculate the perfect size of the text with wrapping - * - * @return the wrapped text - */ - private String getWrappedText(int wHint, int hHint) { - String theText = getText(); - if (wHint == -1 || theText.length() == 0 || !isTextWrapped()) - return theText; - - Dimension iconSize = getTotalIconSize(); - if (!(iconSize.width == 0 && iconSize.height == 0)) { - switch(getTextPlacement()) { - case EAST: - case WEST: - wHint -= iconSize.width + getIconTextGap(); - break; - case NORTH: - case SOUTH: - if (hHint != -1) - hHint -= iconSize.height + getIconTextGap(); - break; - } - } - - - if ((hHint == 0)||(wHint == 0)) { - return "";//$NON-NLS-1$; - } - - Font f = getFont(); - int fontHeight = getMapMode().DPtoLP(FigureUtilities.getFontMetrics(f).getHeight()); - int maxLines = Integer.MAX_VALUE; - if (hHint != -1) { - maxLines = (int) (hHint / (double) fontHeight); - if (maxLines == 0) { - return "";//$NON-NLS-1$;; - } - } - - StringBuffer accumlatedText = new StringBuffer(); - StringBuffer remainingText = new StringBuffer(theText); - int i = 0, j = 0; - - while (remainingText.length() > 0 && j++ < maxLines) { - if ((i = getLineWrapPosition(remainingText.toString(), f, wHint, fontHeight)) == 0) - break; - - if (accumlatedText.length() > 0) - accumlatedText.append('\n'); - accumlatedText.append(remainingText.substring(0, i)); - remainingText.delete(0, i); - } - return accumlatedText.toString(); - } - - /** - * Returns the size of the Label's current text. If the text is currently - * truncated, the truncated text with its ellipsis is used to calculate the - * size. - * - * @return the size of this label's text, taking into account truncation - * @since 2.0 - */ - protected Dimension getSubStringTextSize() { - return calculateSubStringTextSize(); - } - - /** - * Returns the size of the String constant "..." the ellipse based on - * the currently used Map mode - * size. - * - * @return the size of ellipse text - * - */ - private Dimension getEllipseTextSize() { - if (ellipseTextSize == null) { - ellipseTextSize = getMapModeConstants().getEllipseTextSize( - getFont()); - } - return ellipseTextSize; - } - - /** - * Returns the text of the label. Note that this is the complete text of the - * label, regardless of whether it is currently being truncated. Call - * {@link #getSubStringText()}to return the label's current text contents - * with truncation considered. - * - * @return the complete text of this label - * @since 2.0 - */ - public String getText() { - return text; - } - - /** - * Returns the current alignment of the Label's text. The default text - * alignment is {@link PositionConstants#CENTER}. - * - * @return the text alignment - */ - public int getTextAlignment() { - return getAlignment(FLAG_TEXT_ALIGN); - } - - /** - * Returns the current alignment of the entire Label. The default label - * alignment is {@link PositionConstants#LEFT}. - * - * @return the label alignment - */ - private int getLabelAlignment() { - 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 - * @since 2.0 - */ - public Rectangle getTextBounds() { - return new Rectangle(getBounds().getLocation().translate( - getTextLocation()), getTextSize()); - } - - /** - * Returns the location of the label's text relative to the label. - * - * @return the text location - * @since 2.0 - */ - protected Point getTextLocation() { - if (textLocation != null) - return textLocation; - calculateLocations(); - return textLocation; - } - - /** - * Returns the current placement of the label's text relative to its icon. - * The default text placement is {@link PositionConstants#EAST}. - * - * @return the text placement - * @since 2.0 - */ - public int getTextPlacement() { - return getPlacement(FLAG_TEXT_PLACEMENT); - } - - /** - * Returns the size of the label's complete text. Note that the text used to - * make this calculation is the label's full text, regardless of whether the - * label's text is currently being truncated and is displaying an ellipsis. - * If the size considering current truncation is desired, call - * {@link #getSubStringTextSize()}. - * - * @param wHint a width hint - * @param hHint a height hint - * @return the size of this label's complete text - * @since 2.0 - */ - protected Dimension getTextSize(int wHint, int hHint) { - if (textSize == null || wHint != cachedTextSizeHint_width || hHint != cachedTextSizeHint_height) { - textSize = calculateTextSize(wHint, hHint); - cachedTextSizeHint_width = wHint; - cachedTextSizeHint_height= hHint; - } - return textSize; - } - - /** - * Gets the text size given the current size as a width hint - */ - private final Dimension getTextSize() { - Rectangle r = getBounds(); - return getTextSize(r.width, r.height); - } - - /** - * @see IFigure#invalidate() - */ - public void invalidate() { - prefSize = null; - minSize = null; - clearLocations(); - ellipseTextSize = null; - textSize = null; - subStringText = null; - if (iconInfo != null) - iconInfo.invalidate(); - super.invalidate(); - } - - /** - * Returns true if the label's text is currently truncated - * and is displaying an ellipsis, false otherwise. - * - * @return true if the label's text is truncated - * @since 2.0 - */ - public boolean isTextTruncated() { - return !getSubStringTextSize().equals(getTextSize()); - } - - /** - * @see org.eclipse.draw2d.Figure#paintFigure(org.eclipse.draw2d.Graphics) - */ - public void paintFigure(Graphics graphics) { - if (isSelected()) { - graphics.pushState(); - graphics.setBackgroundColor(ColorConstants.menuBackgroundSelected); - graphics.fillRectangle(getSelectionRectangle()); - graphics.popState(); - graphics.setForegroundColor(ColorConstants.white); - } - if (hasFocus()) { - graphics.pushState(); - graphics.setXORMode(true); - graphics.setForegroundColor(ColorConstants.menuBackgroundSelected); - graphics.setBackgroundColor(ColorConstants.white); - graphics.drawFocus(getSelectionRectangle().resize(-1, -1)); - graphics.popState(); - } - if (isOpaque()) - super.paintFigure(graphics); - Rectangle figBounds = getBounds(); - - graphics.translate(figBounds.x, figBounds.y); - if (hasIcons()) - paintIcons(graphics); - - String subString = getSubStringText(); - if (subString.length() > 0) { - if (!isEnabled()) { - graphics.translate(1, 1); - graphics.setForegroundColor(ColorConstants.buttonLightest); - paintText(graphics, subString); - graphics.translate(-1, -1); - graphics.setForegroundColor(ColorConstants.buttonDarker); - } else { - paintText(graphics, subString); - } - } - graphics.translate(-figBounds.x, -figBounds.y); - } - - /** - * Paints the text and optioanally underlines it - * - * @param graphics The graphics context - * @param subString The string to draw - */ - private void paintText(Graphics graphics, String subString) { - StringTokenizer tokenizer = new StringTokenizer(subString, "\n"); //$NON-NLS-1$ - Font f = getFont(); - FontMetrics fontMetrics = FigureUtilities.getFontMetrics(f); - int fontHeight = getMapMode().DPtoLP(fontMetrics.getHeight()); - int fontHeightHalf = fontHeight / 2; - int textWidth = getTextExtents(subString, f, fontHeight).width; - Point p = getTextLocation(); - int y = p.y; - int x = p.x; - final int wrapAlignment = getTextWrapAlignment(); - boolean isUnderlined = isTextUnderlined(); - boolean isStrikedThrough = isTextStrikedThrough(); - Rectangle clipRect = new Rectangle(); - graphics.getClip(clipRect); - int clipRectTopRight_x = clipRect.getTopRight().x; - // If the font's leading area is 0 then we need to add an offset to - // avoid truncating at the top (e.g. Korean fonts) - if (0 == fontMetrics.getLeading()) { - y += getMapModeConstants().nDPtoLP_2; // 2 is the leading area for default English - } - - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - int tokenWidth = getTextExtents(token, f, fontHeight).width; - - switch (wrapAlignment) { - case CENTER: - x += (textWidth - tokenWidth) / 2; - break; - case RIGHT: - x += textWidth - tokenWidth; - break; - } - - // increase the clipping rectangle by a small amount to account for font overhang - // from italic / irregular characters etc. - - - if (tokenWidth + x <= clipRectTopRight_x) { - Rectangle newClipRect = new Rectangle(clipRect); - newClipRect.width += (tokenWidth / token.length()) / 2; - graphics.setClip(newClipRect); - } - - graphics.drawText(token, x, y); - graphics.setClip(clipRect); - - y += fontHeight; - - if (isUnderlined) - graphics.drawLine(x, y - 1, x + tokenWidth, y - 1); - if (isStrikedThrough) - graphics.drawLine(x, y - fontHeightHalf + 1, x + tokenWidth, y - - fontHeightHalf + 1); - } - } - - /** - * Paints the icon(s) - * - * @param graphics The graphics context - */ - private void paintIcons(Graphics graphics) { - Point p = Point.SINGLETON; - p.setLocation(getIconLocation()); - - 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; - } - } - } - - /** - * 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 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 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 the same. - } - } - - - /** - * Sets the icon alignment relative to the .abel's alignment to the passed - * value. The default is {@link PositionConstants#CENTER}. Other possible - * values are {@link PositionConstants#TOP}, - * {@link PositionConstants#BOTTOM},{@link PositionConstants#LEFT}and - * {@link PositionConstants#RIGHT}. - * - * @param align the icon alignment - * @since 2.0 - */ - public void setIconAlignment(int align) { - if (getIconAlignment() == align) - return; - setAlignmentFlags(align, FLAG_ICON_ALIGN); - clearLocations(); - 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(getMapMode(), 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(getMapMode()); - } - - /** - * Sets the Label's alignment to the passed value. The default is - * {@link PositionConstants#CENTER}. Other possible values are - * {@link PositionConstants#TOP},{@link PositionConstants#BOTTOM}, - * {@link PositionConstants#LEFT}and {@link PositionConstants#RIGHT}. - * - * @param align label alignment - */ - public void setLabelAlignment(int align) { - if (getLabelAlignment() == align) - return; - setAlignmentFlags(align, FLAG_LABEL_ALIGN); - clearLocations(); - repaint(); - } - - /** - * Return the ellipse string. - * - * @return the String that represents the fact that the - * text has been truncated and that more text is available but hidden. - * Usually this is represented by "...". - */ - protected String getEllipse() { - return _ellipse; - } - - /** - * Sets the label's text. - * - * @param s the new label text - * @since 2.0 - */ - public void setText(String s) { - //"text" will never be null. - if (s == null) - s = "";//$NON-NLS-1$ - if (text.equals(s)) - return; - text = s; - revalidate(); - repaint(); //If the new text does not cause a new size, we still need - // to paint. - } - - /** - * Sets the text alignment of the Label relative to the label alignment. The - * default is {@link PositionConstants#CENTER}. Other possible values are - * {@link PositionConstants#TOP},{@link PositionConstants#BOTTOM}, - * {@link PositionConstants#LEFT}and {@link PositionConstants#RIGHT}. - * - * @param align the text alignment - * @since 2.0 - */ - public void setTextAlignment(int align) { - if (getTextAlignment() == align) - return; - setAlignmentFlags(align, FLAG_TEXT_ALIGN); - clearLocations(); - repaint(); - } - - /** - * Sets the text placement of the label relative to its icon. The default is - * {@link PositionConstants#EAST}. Other possible values are - * {@link PositionConstants#NORTH},{@link PositionConstants#SOUTH}and - * {@link PositionConstants#WEST}. - * - * @param where the text placement - * @since 2.0 - */ - 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 b Wether the label text should be underlined - */ - public void setTextUnderline(boolean b) { - if (isTextUnderlined() == b) - return; - setFlag(FLAG_UNDERLINED, b); - repaint(); - } - - /** - * @return whether the label text is underlined - */ - public boolean isTextUnderlined() { - return (flags & FLAG_UNDERLINED) != 0; - } - - /** - * Sets whether the label text should be striked-through - * - * @param b Wether the label text should be stricked-through - */ - public void setTextStrikeThrough(boolean b) { - if (isTextStrikedThrough() == b) - return; - setFlag(FLAG_STRIKEDTHROUGH, b); - repaint(); - } - - /** - * @return wether the label text is stricked-through - */ - public boolean isTextStrikedThrough() { - return (flags & FLAG_STRIKEDTHROUGH) != 0; - } - - /** - * Sets whether the label text should wrap - * - * @param b whether the label text should wrap - */ - public void setTextWrap(boolean b) { - if (isTextWrapped() == b) - return; - setFlag(FLAG_WRAP, b); - revalidate(); - repaint(); - } - - /** - * @return wether the label text wrap is on - */ - public boolean isTextWrapped() { - return (flags & FLAG_WRAP) != 0; - } - - /** - * Sets the wrapping width of the label text. This is only valid if text - * wrapping is turned on - * - * @param i The label text wrapping width - */ - public void setTextWrapWidth(int i) { - /* - * if (this.wrapWidth == i) return; this.wrapWidth = i; revalidate(); - * repaint(); - */ - } - - /** - * Sets the wrapping width of the label text. This is only valid if text - * wrapping is turned on - * - * @param i The label text wrapping width - */ - public void setTextWrapAlignment(int i) { - if (getTextWrapAlignment() == i) - return; - - setAlignmentFlags(i, FLAG_WRAP_ALIGN); - repaint(); - } - - /** - * @return the label text wrapping width - */ - public int getTextWrapAlignment() { - return getAlignment(FLAG_WRAP_ALIGN); - } - - /** - * 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 align - * @param flagOffset - */ - private void setAlignmentFlags(int align, int flagOffset) { - flags &= ~(0x7 * flagOffset); - switch (align) { - 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; - } - } - - /** - * 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 & (0x7 * 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; - - return CENTER; - } - - - /** - * Sets the selection state of this label - * - * @param b true will cause the label to appear selected - */ - public void setSelected(boolean b) { - if (isSelected() == b) - return; - setFlag(FLAG_SELECTED, b); - repaint(); - } - - /** - * @return the selection state of this label - */ - public boolean isSelected() { - return (flags & FLAG_SELECTED) != 0; - } - - /** - * Sets the focus state of this label - * - * @param b true will cause a focus rectangle to be drawn around the text - * of the Label - */ - public void setFocus(boolean b) { - if (hasFocus() == b) - return; - setFlag(FLAG_HASFOCUS, b); - repaint(); - } - - /** - * @return the focus state of this label - */ - public boolean hasFocus() { - return (flags & FLAG_HASFOCUS) != 0; - } - - /** - * Returns the bounds of the text selection - * - * @return The bounds of the text selection - */ - private Rectangle getSelectionRectangle() { - Rectangle figBounds = getTextBounds(); - int expansion = getMapModeConstants().nDPtoLP_2; - figBounds.resize(expansion, expansion); - translateToParent(figBounds); - figBounds.intersect(getBounds()); - return figBounds; - } - - /** - * returns the position of last character within the supplied text that will - * fit within the supplied width. - * - * @param s a text string - * @param f font used to draw the text string - * @param w width in pixles. - * @param fontHeight int mapped already to logical units. - */ - private int getLineWrapPosition(String s, Font f, int w, int fontHeight) { - if (getTextExtents(s, f, fontHeight).width <= w) { - return s.length(); - } - // create an iterator for line breaking positions - BreakIterator iter = BreakIterator.getLineInstance(); - iter.setText(s); - int start = iter.first(); - int end = iter.next(); - - // if the first line segment does not fit in the width, - // determine the position within it where we need to cut - if (getTextExtents(s.substring(start, end), f, fontHeight).width > w) { - iter = BreakIterator.getCharacterInstance(); - iter.setText(s); - start = iter.first(); - } - - // keep iterating as long as width permits - do - end = iter.next(); - while (end != BreakIterator.DONE - && getTextExtents(s.substring(start, end), f, fontHeight).width <= w); - return (end == BreakIterator.DONE) ? iter.last() - : iter.previous(); - } - - /** - * Returns the largest substring of s in Font f that can be - * confined to the number of pixels in availableWidth . - * - * @param s the original string - * @param f the font - * @param w the available width - * @param fontHeight int mapped already to logical units. - * @param charAverageWidth int mapped already to logical units. - * @return the largest substring that fits in the given width - * @since 2.0 - */ - private int getLargestSubstringConfinedTo(String s, Font f, int w, int fontHeight, int charAverageWidth) { - float avg = charAverageWidth; - int min = 0; - int max = s.length() + 1; - - //The size of the current guess - int guess = 0, guessSize = 0; - while ((max - min) > 1) { - //Pick a new guess size - // New guess is the last guess plus the missing width in pixels - // divided by the average character size in pixels - guess = guess + (int) ((w - guessSize) / avg); - - if (guess >= max) - guess = max - 1; - if (guess <= min) - guess = min + 1; - - //Measure the current guess - guessSize = getTextExtents(s.substring(0, guess), f, fontHeight).width; - - if (guessSize < w) - //We did not use the available width - min = guess; - else - //We exceeded the available width - max = guess; - } - return min; - } - - /** - * Gets the tex extent scaled to the mapping mode - */ - private Dimension getTextExtents(String s, Font f, int fontHeight) { - if (s.length() == 0) { - return getMapModeConstants().dimension_nDPtoLP_0; - } else { - // height should be set using the font height and the number of - // lines in the string - Dimension d = FigureUtilities.getTextExtents(s, f); - IMapMode mapMode = getMapMode(); - d.width = mapMode.DPtoLP(d.width); - d.height = fontHeight * new StringTokenizer(s, "\n").countTokens();//$NON-NLS-1$ - return d; - } - } - + extends LabelWithTextLayout { + + /** + * Construct a Label with no text and no icon. + * + */ + public WrapLabel() { + super(); + } + + /** + * Construct a Label with passed Image as its icon. + * + * @param i + * the label image + */ + public WrapLabel(Image i) { + super(i); + setTextHorizontalAlignment(CENTER); + } + + /** + * Construct a Label with passed String as the Label's text. + * @param s the String to set as the Label's text. + */ + public WrapLabel(String s) { + super(s); + setTextHorizontalAlignment(CENTER); + } + + /** + * Construct a Label with passed String as text and passed Image as its + * icon. + * + * @param s + * the label text + * @param i + * the label image + */ + public WrapLabel(String s, Image i) { + super(s,i); + super.setTextHorizontalAlignment(CENTER); + } + + /* (non-Javadoc) + * @see org.eclipse.gmf.runtime.draw2d.ui.figures.LabelWithTextLayout#setLabelAlignment(int) + */ + public void setLabelAlignment(int align) { + if ((align & (LEFT_CENTER_RIGHT)) != 0) { + super.setLabelAlignment(CENTER); + setTextHorizontalAlignment(align); + } + else + super.setLabelAlignment(align); + } + + /** + * @param i + * The label text horizontal alignment + */ + public void setTextWrapAlignment(int i) { + if ((getLabelAlignment() & (TOP | BOTTOM)) != 0) { + if ((i & (RIGHT | CENTER)) != 0) + setTextHorizontalAlignment(i); + else + setTextHorizontalAlignment(LEFT); + } + else if ((i == CENTER) && + (getLabelAlignment() == CENTER) && + (super.getTextHorizontalAlignment() != CENTER)) { + setTextHorizontalAlignment(i); + } + } + + /** + * The horizontal orientation of the text. + * + * @see WrapLabel#getTextHorizontalAlignment() + */ + public int getTextWrapAlignment() { + return getTextHorizontalAlignment(); + } + + /** + * We do not want to allow anyone else to control the layout of the + * WrapLabel, which is why the manager parameter can only be + * of the type OffsetLayout that is defined within WrapLabel + * + * @see org.eclipse.draw2d.Figure#setLayoutManager(org.eclipse.draw2d.LayoutManager) + */ + public void setLayoutManager(LayoutManager manager) { + // we want to use the OffsetLayout only within WrapLabel. + // This will prevent any client from trying to change + // the layout of the contents within WrapLabel. + if (manager instanceof OffsetLayout) + super.setLayoutManager(manager); + } - -} \ No newline at end of file + /** + * Not supported. + * @deprecated + * + */ + public void setTextWrapWidth() { + // deprecated method. + } +} Index: src/org/eclipse/gmf/runtime/draw2d/ui/figures/LabelWithTextLayout.java =================================================================== RCS file: src/org/eclipse/gmf/runtime/draw2d/ui/figures/LabelWithTextLayout.java diff -N src/org/eclipse/gmf/runtime/draw2d/ui/figures/LabelWithTextLayout.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gmf/runtime/draw2d/ui/figures/LabelWithTextLayout.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,1924 @@ +/****************************************************************************** + * Copyright (c) 2006 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 + ****************************************************************************/ + +package org.eclipse.gmf.runtime.draw2d.ui.figures; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import org.eclipse.draw2d.Figure; +import org.eclipse.draw2d.Graphics; +import org.eclipse.draw2d.IFigure; +import org.eclipse.draw2d.Label; +import org.eclipse.draw2d.PositionConstants; +import org.eclipse.draw2d.StackLayout; +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.BlockFlow; +import org.eclipse.draw2d.text.BlockFlowLayout; +import org.eclipse.draw2d.text.FlowPage; +import org.eclipse.draw2d.text.PageFlowLayout; +import org.eclipse.draw2d.text.TextFragmentBox; +import org.eclipse.gmf.runtime.draw2d.ui.internal.mapmode.IMapModeHolder; +import org.eclipse.gmf.runtime.draw2d.ui.internal.text.FlowUtilitiesEx; +import org.eclipse.gmf.runtime.draw2d.ui.internal.text.ParagraphTextLayoutEx; +import org.eclipse.gmf.runtime.draw2d.ui.internal.text.SelectableTextFlow; +import org.eclipse.gmf.runtime.draw2d.ui.internal.text.SingleLineTextLayoutWithEllipses; +import org.eclipse.gmf.runtime.draw2d.ui.internal.text.TextLayoutManagerWithMapMode; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; + +/** + * An extended label that has the following extra features:
+ * 1. Allows selection, focus feedback, underlined + * and striked-through text. + * 2. Supports multiple icons. + * 3. Enhanced layout functionality for placing icon(s) and text. + * 4. Text can be word-wrapped or be truncated with ellipses.


+ * + * + * EXPLANATION OF LAYOUTS
+ * + * This WrapLabel contains functionality to display many icons alongside text. + * The following will describe how the layout of these icons and text + * are done.


+ * + * + * Using {@link #setTextPlacement(int)}:

+ * + * All icons that are set using {@link #setIcon(Image)} are placed horizontally + * one after the other. The position of the text relative to icons + * depends on {@link #setTextPlacement(int)}. If text placement is set to + * {@link PositionConstants#EAST}, then the icon(s) would be placed on the left + * of the text. Similarly, if text placement is set to + * {@link PositionConstants#WEST}, the icon(s) will be placed on the right of the + * text; {@link PositionConstants#NORTH} would put the icons below the text; and + * {@link PositionConstants#SOUTH} would place the icons above the text.


+ * + * Using {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)}:

+ * + * Use {@link #setTextAlignment(int)} and {@link #setIconAlignment(int)} to + * align the text and icons relative to each other for more dynamic control. + * If the text placement is on the east or west of the icon(s) + * (i.e. the icon(s) on the left or right of the text 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(s) if + * the height of the total size of icon(s) 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 total + * size of icon(s) if the height of the total size of icon(s) 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 total + * size of icon(s) if the height of the total size of icon(s) is greater than + * the height of the text.

+ * + * The other scenario is when the text placement is on the south or north of the icon(s) + * (i.e. the icon(s) above or below the text 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(s) if + * the width of the total size of icon(s) 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 total + * size of icon(s) if the width of the total size of icon(s) 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 total + * size of icon(s) if the width of the total size of icon(s) is greater than + * the width of the text.

+ * + * {@link #setIconAlignment(int)} works identically as {@link #setTextAlignment(int)}, + * except the roles of text and icon(s) are switched in the above descriptions.


+ * + * + * Using {@link #setLabelAlignment(int)}:

+ * + * The entire label, text and icons, can moved into different positions vertically. + * {@link PositionConstants#TOP}, {@link PositionConstants#CENTER}, and + * {@link PositionConstants#BOTTOM} places the text and icons (no matter how they are arranged + * relatively to each other) on the top, center, or bottom of the bounds of + * LabelWithTextLayout.


+ * + * + * Using {@link #setTextHorizontalAlignment(int)}:

+ * + * Use {@link #setTextHorizontalAlignment(int)} with {@link PositionConstants#LEFT}, + * {@link PositionConstants#CENTER}, or {@link PositionConstants#RIGHT} to justify + * the text accordingly. The icon(s) will move with the text, so this can be + * considered a horizontal label alignment equivalent for + * {@link #setLabelAlignment(int)}.


+ * + * + * WARNING: User-nested figures are not expected within this WrapLabel.

+ * + * Some code taken from the original WrapLabel by melaasar + *

+ * + * @author satif

+ */ +public class LabelWithTextLayout + extends Label + implements PositionConstants { + + private static final Map mapModeConstantsMap = new WeakHashMap(); + + private static class MapModeConstants { + + private static final int MAX_IMAGE_INFO = 12; + + public final WeakReference mapModeRef; + + public final int nDPtoLP_3; + + public final int nDPtoLP_2; + + public final SingleIconInfo[] singleIconInfos = new SingleIconInfo[MAX_IMAGE_INFO]; + + public MapModeConstants(IMapMode mapMode) { + this.mapModeRef = new WeakReference(mapMode); + nDPtoLP_2 = mapMode.DPtoLP(2); + nDPtoLP_3 = mapMode.DPtoLP(3); + } + + 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; + } + } + + private static abstract class IconInfo { + /** + * Gets the icon at the index location. + * + * @param i + * the index to retrieve the icon of + * @return Image 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 Dimension 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 Dimension 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 icons = new ArrayList(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 Image that corresponds to the given index. + */ + public Image getIcon(int i) { + if (i >= icons.size()) + return null; + + return (Image) 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 Dimension 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 Dimension 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; + } + } + + + /** + * Lays out children according to the offset bounds provided. + * This offset bounds is added to the container figure's client + * area and applied to all figures. This allows the positioning + * of all child figures in a stack layout but in a specific position + * and with a specific dimension. + * + */ + protected static class OffsetLayout + extends StackLayout { + + // the offset constraint to apply to child figures. + private Rectangle offsetConstraint = null; + + /* (non-Javadoc) + * @see org.eclipse.draw2d.StackLayout#layout(org.eclipse.draw2d.IFigure) + */ + public void layout(IFigure figure) { + List children = figure.getChildren(); + IFigure child; + for (int i = 0; i < children.size(); i++) { + child = (IFigure) children.get(i); + Rectangle r = figure.getClientArea().getCopy(); + if (offsetConstraint != null) { + r.x += offsetConstraint.x; + r.y += offsetConstraint.y; + + r.width += offsetConstraint.width; + r.height += offsetConstraint.height; + + // we don't want the width and height to be + // less than zero. + if (r.width < 0) + r.width = 0; + if (r.height < 0) + r.height = 0; + } + + child.setBounds(r); + } + } + + /** + * Set the offset constraint.

+ * + * The rectangle parameter represents an offset + * that will be added to the container figure's client area + * and this will be applied to all child figures. + * + * @param offsetConstraint the offset constraint rectangle. + */ + public void setOffsetConstraint(Rectangle offsetConstraint) { + this.offsetConstraint = offsetConstraint; + } + } + + private static final Dimension EMPTY_DIMENSION = new Dimension(0, 0); + + /** + * calculateNow is a flag to indicate whether the layout data should be + * recalculated before the Label is painted. This avoids recalculation + * of layout data upon multiple paintFigure calls when the figure has not + * been invalidated. + */ + private static int FLAG_CALCULATE_LOCATIONS = MAX_FLAG << 4; + + /** + * @see #setTextWrap(boolean) + */ + private static int FLAG_WRAP = MAX_FLAG << 5; + + /** + * @see #setTextAlignment(int) + */ + private static int FLAG_TEXT_ALIGN = MAX_FLAG << 6; + + /** + * @see #setIconAlignment(int) + */ + private static int FLAG_ICON_ALIGN = MAX_FLAG << 12; + + /** + * @see #setLabelAlignment(int) + */ + private static int FLAG_LABEL_ALIGN = MAX_FLAG << 15; + + /** + * @see #setTextPlacement(int) + */ + private static int FLAG_TEXT_PLACEMENT = MAX_FLAG << 18; + + + // cache the map mode constants... + private MapModeConstants mapModeConstants; + + private IconInfo iconInfo; + + private SelectableTextFlow textFlow; + + private BlockFlow blockFlow; + + private FlowPage flowPage; + + // cached location of the icon. + private Point iconLocation; + + /** + * textBounds is used as the offsetConstraint in OffsetLayout, + * primarily to position flowPage correctly to align it with the + * icon(s) + */ + private Rectangle textBounds; + + /** + * Cache the preferred width and height parameters. getPreferredSize can get + * called multiple times with the same parameters, so cache these to avoid + * redundant recalculations. + */ + private int cached_PrefWidth = Integer.MIN_VALUE, + cached_PrefHeight = Integer.MIN_VALUE; + + /** + * Construct a Label with no text and no icon. + * + */ + public LabelWithTextLayout() { + super(); + + this.setLayoutManager(new OffsetLayout()); + + setupTextFigures(false); + + // default flags... + setAlignmentFlags(CENTER, FLAG_TEXT_ALIGN); + setAlignmentFlags(CENTER, FLAG_ICON_ALIGN); + setAlignmentFlags(CENTER, FLAG_LABEL_ALIGN); + blockFlow.setHorizontalAligment(LEFT); + + setPlacementFlags(EAST, FLAG_TEXT_PLACEMENT); + setFlag(FLAG_WRAP, false); + } + + /** + * Construct a Label with passed Image as its icon. + * + * @param i + * the label image + */ + public LabelWithTextLayout(Image i) { + this(); + iconInfo = new SingleIconInfo(i); + } + + /** + * Construct a Label with passed String as the Label's text. + * @param s the String to set as the Label's text. + */ + public LabelWithTextLayout(String s) { + this(); + textFlow.setText(s); + } + + /** + * Construct a Label with passed String as text and passed Image as its + * icon. + * + * @param s + * the label text + * @param i + * the label image + */ + public LabelWithTextLayout(String s, Image i) { + this(); + textFlow.setText(s); + iconInfo = new SingleIconInfo(i); + + } + + /** + * Calculates the text alignment relative to the icon.
NOTE: Should be + * called by calculateLocations only. + *

+ * + * @param textPlacement + * the text placement relative to the icon. + * @param totalIconSize + * the total size of all icons. + * @param literalTextSize + * the size of the text. + * + */ + private void calculateIconAlignment(int textPlacement, + Dimension totalIconSize, Rectangle _literalTextBounds) { + switch (textPlacement) { + case NORTH: + case SOUTH: + if (totalIconSize.width < _literalTextBounds.width) { + switch (getIconAlignment()) { + case LEFT: + // nothing to add + break; + case CENTER: + iconLocation.x += _literalTextBounds.x + + (_literalTextBounds.width - totalIconSize.width) / 2; + break; + case RIGHT: + iconLocation.x += _literalTextBounds.x + + (_literalTextBounds.width - totalIconSize.width); + break; + } + } + break; + case WEST: + case EAST: + if (totalIconSize.height < _literalTextBounds.height) { + switch (getIconAlignment()) { + case TOP: + // nothing to add + break; + case CENTER: + iconLocation.y = (_literalTextBounds.height - totalIconSize.height) / 2; + break; + case BOTTOM: + iconLocation.y = (_literalTextBounds.height - totalIconSize.height); + break; + } + } + break; + } + } + + /** + * Calculates the text alignment relative to the icon.
NOTE: Should be + * called by calculateLocations only. + *

+ * + * @param textPlacement + * the text placement relative to the icon. + * @param totalIconSize + * the total size of all icons. + * @param literalTextSize + * the size of the text. + */ + private void calculateTextAlignment(int textPlacement, + Dimension totalIconSize, Dimension literalTextSize) { + switch (textPlacement) { + case NORTH: + case SOUTH: + if (literalTextSize.width < totalIconSize.width) { + switch (getTextAlignment()) { + case LEFT: + // nothing to add + break; + case CENTER: + textBounds.x += (totalIconSize.width - literalTextSize.width) / 2; + break; + case RIGHT: + textBounds.x += (totalIconSize.width - literalTextSize.width); + break; + } + } + break; + case WEST: + case EAST: + if (literalTextSize.height < totalIconSize.height) { + switch (getTextAlignment()) { + case TOP: + // nothing to add + break; + case CENTER: + textBounds.y += (totalIconSize.height - literalTextSize.height) / 2; + break; + case BOTTOM: + textBounds.y += (totalIconSize.height - literalTextSize.height); + break; + } + } + break; + } + } + + /** + * Sets up the layout of text and icons. + * + */ + private void calculateLocations() { + + int textPlacement = getTextPlacement(); + Dimension totalIconSize = getTotalIconSize(); + Rectangle literalSubstringTextSize = textFlow.getLiteralSubstringTextBounds(); + + layoutTextAndIcons(textPlacement, totalIconSize, getPreferredSize( + cached_PrefWidth, -1)); + + calculateIconAlignment(textPlacement, totalIconSize, + literalSubstringTextSize); + calculateTextAlignment(textPlacement, totalIconSize, + literalSubstringTextSize.getSize()); + + if (getLayoutManager() instanceof OffsetLayout) { + ((OffsetLayout)getLayoutManager()).setOffsetConstraint(textBounds); + } + + this.layout(); + flowPage.getLayoutManager().layout(textFlow); + + finalIconAdjustments(textPlacement, literalSubstringTextSize.getSize()); + } + + /** + * Returns the bounds of the label's text. Note that the bounds are calculated using the + * label's text AFTER it has been laid out by the TextLayoutManager.

+ * + * If you want to know the size of the text without any layout style applied, + * use {@link #getTextSize()} + * + * @return the bounds of this label's laid-out text + */ + public Rectangle getTextBounds() { + calculateLocations(); + + return calculateTextBounds(false); + } + + /* (non-Javadoc) + * @see org.eclipse.draw2d.Label#getIconBounds() + */ + public Rectangle getIconBounds() { + if (iconLocation == null) + calculateLocations(); + + Point icon_Location = this.iconLocation.getCopy(); + translateToParent(icon_Location); + Rectangle iconBounds = new Rectangle(icon_Location, getTotalIconSize()); + return iconBounds; + } + + /** + * Returns the gap between the text and the icon(s). + * + * @return the gap, or 0 if there is no icon or text. + */ + public int getIconTextGap() { + if (iconInfo == null || getText().equals("") || getIcon() == null) //$NON-NLS-1$ + return 0; + return getMapModeConstants().nDPtoLP_3; + } + + /** + * Get the text of only the visible area. + */ + protected Dimension getSubStringTextSize() { + return textFlow.getLiteralSubstringTextBounds().getSize(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Label#getTextLocation() + */ + protected Point getTextLocation() { + return getTextBounds().getLocation(); + } + + /** + * not yet supported. Default is to return the entire text. + * + * @see WrapLabel#getText() + */ + public String getSubStringText() { + return getText(); + } + + /** + * Returns the visible-only or total text size from whatever + * the current state of the fragments in textFlow are.

+ * + * These bounds represents the bounds of the text and text + * only. However, note that since flowPage can be translated by + * the OffsetLayout, these will not be the same as the one given + * by {@link SelectableTextFlow#getLiteralSubstringTextBounds()}.

+ * + * These bounds exclude any area which the text does not occupy. + * + * @param subStringText + * true to stop calculating at non-visible text area. + */ + private Rectangle calculateTextBounds(boolean subStringText) { + + Rectangle refBounds = getBounds(), calRect = new Rectangle( + Integer.MAX_VALUE, Integer.MAX_VALUE, 0, 0); + + if (textFlow == null || blockFlow == null || flowPage == null) + return calRect; + + List fragments = textFlow.getFragments(); + + int locY = 0; + + if (textBounds != null) + locY = textBounds.y; + + for (int i = 0; i < fragments.size(); i++) { + TextFragmentBox fragment = (TextFragmentBox) fragments.get(i); + + if (fragment.getLineRoot() == null) + continue; + + int y = fragment.getLineRoot().getVisibleTop(); + + if (fragment.getX() < calRect.x) + calRect.x = fragment.getX(); + if (y < calRect.y) + calRect.y = y; + + if (calRect.width < fragment.getWidth()) + calRect.width = fragment.getWidth(); + + y = fragment.getAscent() + fragment.getDescent(); + calRect.height += y; + + // we don't want to continue to non-visible fragments, + // resulting in performance improvement + if (subStringText && calRect.height + locY > refBounds.height) { + if (subStringText) // calculate only substring text. + break; + } + } + + if (!subStringText) { + calRect.resize(getMapModeConstants().nDPtoLP_2, + getMapModeConstants().nDPtoLP_2); + + blockFlow.translateToParent(calRect); + flowPage.translateToParent(calRect); + translateToParent(calRect); + } + + return calRect; + } + + /** + * Calculates the placement of the text relative to the icon.
+ * NOTE: to be used by calculateLocations only.
+ * @see #getTextPlacement()
+ * + *

+ * + * @param insertionPoint + * the Point where the text and icon have to be + * inserted. + * @param textPlacement + * the placement of text relative to the icon. + * @param totalIconSize + * the total size of all icons. + */ + private void calculateTextPlacement(Point insertionPoint, + int textPlacement, Dimension totalIconSize) { + switch (textPlacement) { + case NORTH: + textBounds = new Rectangle(new Point(insertionPoint.x, + insertionPoint.y), new Dimension(-insertionPoint.x, + -insertionPoint.y)); + iconLocation = new Point(insertionPoint.x, + textFlow.getSize().height + getIconTextGap() + + textFlow.getDescent()); + + if (textBounds.x < 0) + textBounds.x = 0; + if (textBounds.y < 0) + textBounds.y = 0; + + break; + case SOUTH: + textBounds = new Rectangle(new Point(insertionPoint.x, + totalIconSize.height + getIconTextGap()), new Dimension( + -insertionPoint.x, + -(totalIconSize.height + getIconTextGap()))); + iconLocation = new Point(insertionPoint.x, insertionPoint.y); + + if (textBounds.x < 0) + textBounds.x = 0; + + break; + case WEST: + textBounds = new Rectangle(new Point(insertionPoint.x, + insertionPoint.y), new Dimension( + -(totalIconSize.width + getIconTextGap() + insertionPoint.x), + -insertionPoint.y)); + iconLocation = new Point(getSize().width - totalIconSize.width, + insertionPoint.y); + + if (textBounds.x < 0) + textBounds.x = 0; + if (textBounds.y < 0) + textBounds.y = 0; + + break; + case EAST: + iconLocation = new Point(insertionPoint.x, insertionPoint.y); + textBounds = new Rectangle( + new Point(insertionPoint.x + totalIconSize.width + + getIconTextGap(), insertionPoint.y), + new Dimension( + -(totalIconSize.width + getIconTextGap() + insertionPoint.x), + -insertionPoint.y)); + + if ((textBounds.y < 0) || (textBounds.y > getBounds().height)) + textBounds.y = 0; + + break; + } + } + + /** + * Clears all cached layout data. Will enable layout recalculation next time + * anything is validated or when paintFigure is called. + * + */ + private void clearLocations() { + prefSize = null; + minSize = null; + iconLocation = null; + textBounds = null; + setFlag(FLAG_CALCULATE_LOCATIONS, true); + } + + /** + * Executes the case exceptions/constraints for icon alignment.
+ * NOTE: to be used by calculateLocations only. + *

+ * + * @param textPlacement + * the text placement (N/S/E/W) + * @param literalTextSize + * the substring text size. + */ + private void finalIconAdjustments(int textPlacement, Dimension literalTextSize) { + // the range checks below are to make sure we haven't calculated + // anything that can't be displayed... + int textHorizontalAlignment = getTextHorizontalAlignment(); + + switch (textPlacement) { + case EAST: + // we want the icon to be right next to the text all the time... + if (textHorizontalAlignment == CENTER) { + iconLocation.x = (blockFlow.getBounds().width / 2) + - (literalTextSize.width / 2) - getIconTextGap(); + } else if (textHorizontalAlignment == RIGHT) { + iconLocation.x = blockFlow.getBounds().width + - literalTextSize.width - getIconTextGap(); + } + + if (iconLocation.x < 0) + iconLocation.x = 0; + if (iconLocation.y < 0) + iconLocation.y = 0; + break; + case WEST: + // we want the icon to be right next to the text all the time... + if (textHorizontalAlignment == LEFT) { + iconLocation.x = literalTextSize.width + getIconTextGap(); + } else if (textHorizontalAlignment == CENTER) { + iconLocation.x = (flowPage.getBounds().width / 2) + + (literalTextSize.width / 2) + getIconTextGap(); + } + + if (iconLocation.y < 0) + iconLocation.y = 0; + break; + case NORTH: + if (iconLocation.x < 0) + iconLocation.x = 0; + break; + case SOUTH: + if (iconLocation.x < 0) + iconLocation.x = 0; + if (iconLocation.y < 0) + iconLocation.y = 0; + 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 & (0x7 * 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; + + return CENTER; + } + + /** + * Return the ellipse string. + * + * @return the String that represents the fact that the text + * has been truncated and that more text is available but hidden. + * Usually this is represented by "...". + */ + protected String getEllipse() { + return FlowUtilitiesEx.ELLIPSIS; + } + + /** + * Returns the Label's icon. + * + * @return the label icon + */ + public Image getIcon() { + return getIcon(0); + } + + /** + * Returns the label's icon at the given index (provided an icon exists at + * this index). + *

+ * + * @param index + * @return the icon at the given index. + */ + public Image getIcon(int index) { + if (iconInfo == null) + return null; + return iconInfo.getIcon(index); + } + + /** + * Returns the current alignment of the Label's icon. The default is + * {@link PositionConstants#CENTER}. + *

+ * + * @return the icon alignment + */ + public int getIconAlignment() { + return getAlignment(FLAG_ICON_ALIGN); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Label#getIconLocation() + */ + protected Point getIconLocation() { + if (iconLocation == null) + calculateLocations(); + else + finalIconAdjustments(getTextPlacement(), + textFlow.getLiteralSubstringTextBounds().getSize()); + return iconLocation; + } + + /** + * Returns the size of the icon at a given index. + * + * @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); + } + + /** + * Returns the current alignment of the entire Label. The default label + * alignment is {@link PositionConstants#LEFT}. + * + * @return the label alignment + */ + public int getLabelAlignment() { + return getAlignment(FLAG_LABEL_ALIGN); + } + + /** + * @return IMapMode used by this figure. + * IMapMode that allows for the coordinate mapping + * from device to logical units. + */ + private IMapMode getFigureMapMode() { + return (IMapMode) getMapModeConstants().mapModeRef.get(); + } + + /** + * Returns the stored map mode constants. + * @return MapModeConstants that are cached. + */ + private MapModeConstants getMapModeConstants() { + if (mapModeConstants == null) { + IMapMode mapMode = MapModeUtil.getMapMode(this); + while (mapMode instanceof IMapModeHolder) { + mapMode = ((IMapModeHolder) mapMode).getMapMode(); + } + mapModeConstants = (MapModeConstants) mapModeConstantsMap + .get(mapMode); + if (mapModeConstants == null) { + mapModeConstants = new MapModeConstants(mapMode); + mapModeConstantsMap.put(mapMode, mapModeConstants); + } + if (textFlow != null) { + ((TextLayoutManagerWithMapMode) + textFlow.getLayoutManager()).setMapMode(mapMode); + textFlow.setMapMode(mapMode); + } + } + return mapModeConstants; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Label#calculateLabelSize(org.eclipse.draw2d.geometry.Dimension) + */ + protected Dimension calculateLabelSize(Dimension txtSize) { + if (txtSize.equals(EMPTY_DIMENSION)) + return txtSize; // return 0,0 dimension. + + int gap = getIconTextGap(); + Dimension d = new Dimension(0, 0); + switch (getTextPlacement()) { + case WEST: + case EAST: + d.width = getTotalIconSize().width + gap + txtSize.width; + d.height = Math.max(getTotalIconSize().height, txtSize.height); + break; + default: // NORTH or SOUTH + d.width = Math.max(getTotalIconSize().width, txtSize.width); + d.height = getTotalIconSize().height + gap + txtSize.height; + break; + } + + return d; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Label#calculateTextSize() + */ + protected Dimension calculateTextSize() { + /* getTextExtent calls GC#stringExtent and this adds + * a blank character before calculating the text size + * if the text's length is zero. However, we do want an + * empty dimension if there is not text. + */ + + if (getText().length() == 0) { + return EMPTY_DIMENSION.getCopy(); + } + else { + Dimension textExtent = (Dimension) getFigureMapMode().DPtoLP( + FigureUtilities.getTextExtents(getText(), getFont())); + textExtent.height += getMapModeConstants().nDPtoLP_2 / 2; + return textExtent; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.IFigure#getPreferredSize(int, int) + */ + public Dimension getPreferredSize(int wHint, int hHint) { + if (prefSize == null || wHint != cached_PrefWidth + || hHint != cached_PrefHeight) { + + if (getText().length() == 0) + return prefSize = EMPTY_DIMENSION.getCopy(); + + if (wHint < 0) { + prefSize = calculateLabelSize(getTextSize()); + + Insets insets = getInsets(); + prefSize.expand(insets.getWidth(), insets.getHeight()); + + } else { + int _wHint = wHint; + switch (getTextPlacement()) { + case EAST: + case WEST: + _wHint -= (getTotalIconSize().width + getIconTextGap()); + break; + } + + prefSize = calculateLabelSize(getLayoutManager().getPreferredSize(this, + _wHint, hHint)); + + minSize = null; + prefSize.union(getMinimumSize(wHint, hHint)); + cached_PrefWidth = wHint; + cached_PrefHeight = hHint; + } + + + } + + return prefSize; + } + + /** + * @see IFigure#getMinimumSize(int, int) + */ + public Dimension getMinimumSize(int w, int h) { + if (minSize != null) + return minSize; + + if (getText().length() == 0) + return minSize = EMPTY_DIMENSION.getCopy(); + + if (w < 0) { + minSize = calculateLabelSize(getTextSize()); + + Insets insets = getInsets(); + minSize.expand(insets.getWidth(), insets.getHeight()); + } else { + minSize = new Dimension(); + + int _wHint = w; + switch (getTextPlacement()) { + case EAST: + case WEST: + _wHint -= (getTotalIconSize().width + getIconTextGap()); + break; + } + + if (getLayoutManager() != null) + minSize.setSize(getLayoutManager().getMinimumSize(this, _wHint, h)); + + Dimension d = FlowUtilitiesEx.getEllipsisSize(textFlow.getFont(), getFigureMapMode()). + getIntersected(getTextSize()); + + Dimension labelSize = calculateLabelSize(d); + + minSize.width = labelSize.width; + + Insets insets = getInsets(); + minSize.expand(insets.getWidth(), insets.getHeight()); + } + + return minSize; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Figure#getMaximumSize() + */ + public Dimension getMaximumSize() { + return prefSize; + } + + /** + * Get the maximum size, given width and height hints.
+ * (Note: defaults to getMaximumSize()) + *

+ * + * @param w + * the width hint + * @param h + * the height hint + * @return the maximum size. + */ + public Dimension getMaximumSize(int w, int h) { + return getMaximumSize(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Label#calculateSubStringTextSize() + */ + protected Dimension calculateSubStringTextSize() { + return getSubStringTextSize(); + } + + /** + * Returns the total number of icons being displayed. + * + * @return int number of icons in the wrap label + */ + protected int getNumberofIcons() { + if (iconInfo == null) + return 0; + return iconInfo.getNumberofIcons(); + } + + /** + * 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; + } + + /* (non-Javadoc) + * @see org.eclipse.draw2d.Label#getText() + */ + public String getText() { + return textFlow.getText(); + } + + /** + * Returns the current alignment of the Label's text. The default text + * alignment is {@link PositionConstants#CENTER}. + * + * @return the text alignment + */ + public int getTextAlignment() { + return getAlignment(FLAG_TEXT_ALIGN); + } + + /** + * Returns the current placement of the label's text relative to its icon. + * The default text placement is {@link PositionConstants#EAST}. + * + * @return the text placement + */ + public int getTextPlacement() { + return getPlacement(FLAG_TEXT_PLACEMENT); + } + + /** + * Gets the horizontal alignment of the text (Left, Center or Right + * justified). + *

+ * Default is {@link PositionConstants#CENTER}. Can be either + * {@link PositionConstants#CENTER}, {@link PositionConstants#LEFT}, or + * {@link PositionConstants#RIGHT}. + * + * @return the horizontal alignment. + */ + public int getTextHorizontalAlignment() { + return blockFlow.getHorizontalAligment(); + } + + /** + * 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()); + } + + /** + * Returns true if the WrapLabel has focus, false otherwise. + * + * @return the focus state of this label + */ + public boolean hasFocus() { + return textFlow.hasFocus(); + } + + /** + * Returns true if the WrapLabel has icons, false otherwise. + * + * @return + */ + protected boolean hasIcons() { + return (getNumberofIcons() > 0); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Label#invalidate() + */ + public void invalidate() { + clearLocations(); + if (iconInfo != null) + iconInfo.invalidate(); + super.invalidate(); + } + + public void validate() { + super.validate(); + // calculateLocations(); + } + + /** + * @return the selection state of this label + */ + public boolean isSelected() { + return textFlow.isSelected(); + } + + /** + * @return whether the label text is striked-through + */ + public boolean isTextStrikedThrough() { + return textFlow.isTextStrikeThrough(); + } + + /** + * Returns true if the label's text is currently truncated, + * false otherwise. + * + * @return true if the label's text is truncated + */ + public boolean isTextTruncated() { + return (getBounds().width < textFlow.getBounds().width) + || (getBounds().height < textFlow.getBounds().height); + } + + /** + * @return whether the label text is underlined + */ + public boolean isTextUnderlined() { + return textFlow.isTextUnderlined(); + } + + /** + * @return wether the label text wrap is on + */ + public boolean isTextWrapped() { + return (flags & FLAG_WRAP) != 0; + } + + /** + * Determines the insertion points based on the Label Alignment.
+ * Note: should be called by calculateLocations only. + *

+ * + * @param textPlacement + * the placement of the text relative to the icon. + * @param totalIconSize + * the total size of all icons. + * @param totalPossibleSize + * the total possible size of icon(s) and text. + */ + private void layoutTextAndIcons(int textPlacement, Dimension totalIconSize, + Dimension totalPossibleSize) { + switch (getLabelAlignment()) { + case TOP: + calculateTextPlacement(new Point(0, 0), textPlacement, + totalIconSize); + break; + case BOTTOM: + calculateTextPlacement(new Point(0, getBounds().height + - totalPossibleSize.height), textPlacement, totalIconSize); + break; + case CENTER: // RIGHT and LEFT not supported. + case RIGHT: + case LEFT: + calculateTextPlacement(new Point(0, + (getBounds().height - totalPossibleSize.height) / 2), + textPlacement, totalIconSize); + break; + + /* the following commented lines of code obtain original functionality + * of label alignment but the code is buggy. To use, remove CENTER and RIGHT + * cases above and uncomment the lines below. + */ +// case RIGHT: +// calculateTextBounds(false); +// if (literalTextBounds == null || literalTextBounds.width == 0) { +// calculateTextPlacement(new Point( +// getBounds().width - totalPossibleSize.width, +// (getBounds().height - totalPossibleSize.height) / 2), +// textPlacement, totalIconSize); +// } else { +// Dimension _totalLabelSize = calculateLabelSize(literalTextBounds +// .getSize()); +// calculateTextPlacement(new Point( +// (getBounds().width - _totalLabelSize.width), +// (getBounds().height - _totalLabelSize.height) / 2), +// textPlacement, totalIconSize); +// } +// break; +// default: // CENTER +// calculateTextBounds(false); +// if (literalTextBounds == null || literalTextBounds.width == 0) { +// calculateTextPlacement(new Point( +// (getBounds().width - totalPossibleSize.width) / 2, +// (getBounds().height - totalPossibleSize.height) / 2), +// textPlacement, totalIconSize); +// } else { +// Dimension _totalLabelSize = calculateLabelSize(literalTextBounds +// .getSize()); +// calculateTextPlacement(new Point( +// (getBounds().width - _totalLabelSize.width) / 2, +// (getBounds().height - _totalLabelSize.height) / 2), +// textPlacement, totalIconSize); +// } + } + } + + /** + * @see Figure#paintFigure(Graphics) + */ + protected void paintFigure(Graphics graphics) { + if (isOpaque()) + super.paintFigure(graphics); + + if ((flags & FLAG_CALCULATE_LOCATIONS) != 0) { + calculateLocations(); + setFlag(FLAG_CALCULATE_LOCATIONS, false); + } + + if (hasIcons()) + paintIcons(graphics); + } + + /** + * Paints the icon(s) + * + * @param graphics + * The graphics context + */ + private void paintIcons(Graphics graphics) { + Rectangle figBounds = getBounds(); + graphics.translate(figBounds.x, figBounds.y); + + Point p = Point.SINGLETON; + p.setLocation(getIconLocation()); + + int num = getNumberofIcons(); + for (int i = 0; i < num; i++) { + Image tempIcon = getIcon(i); + if (tempIcon != null) { + graphics.drawImage(tempIcon, p); + p.x += getIconSize(i).width; + } + } + + graphics.translate(-figBounds.x, -figBounds.y); + } + + /** + * setAlignmentFlags + * + * @param align + * @param flagOffset + */ + private void setAlignmentFlags(int align, int flagOffset) { + flags &= ~(0x7 * flagOffset); + switch (align) { + 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; + } + } + + /** + * Sets the focus state of this label + * + * @param b + * true will cause a focus rectangle to be drawn around the text + * of the Label + */ + public void setFocus(boolean b) { + textFlow.setFocus(b); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Figure#setFont(org.eclipse.swt.graphics.Font) + */ + public void setFont(Font f) { + super.setFont(f); + + /* + * Simply calling revalidate + * may not make textFlow an invalid figure and revalidate it. + * Therefore, when font is changed (e.g. size change, bold, italic), + * the layout manager for textFlow may not get invoked. + */ + textFlow.revalidate(); + } + + /** + * Sets the label's icon to the passed image. + * + * @param image + * the new label image + */ + 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 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 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 the same. + } + } + + /** + * Sets the icon alignment relative to the text. The default is + * {@link PositionConstants#CENTER}. + *

+ * + * If the text placement is NORTH/SOUTH, icon alignment should be either + * LEFT, CENTER, or RIGHT to the icon. + *

+ * + * If the text placement is EAST/WEST, icon alignment should be either TOP, + * CENTER, or BOTTOM to the icon. + *

+ * + * @param align + * the icon alignment + */ + public void setIconAlignment(int align) { + if (getIconAlignment() == align) + return; + setAlignmentFlags(align, FLAG_ICON_ALIGN); + revalidate(); + repaint(); + } + + /** + * Sets the Label's alignment to the passed value. The default is + * {@link PositionConstants#TOP}. Other possible values are + * {@link PositionConstants#CENTER},{@link PositionConstants#BOTTOM}. + *

+ * Note:{@link PositionConstants#LEFT} and {@link PositionConstants#RIGHT} + * are not supported as wrap label occupies the entire client area. Use + * horizontal alignment instead (see WrapLabel#setHorizontalAlignment(int)). + *

+ * + * @param align + * label alignment + */ + public void setLabelAlignment(int align) { + if (getLabelAlignment() == align) + return; + + setAlignmentFlags(align, FLAG_LABEL_ALIGN); + + revalidate(); + repaint(); + } + + /** + * 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; + } + } + + /** + * Sets the selection state of this label + * + * @param b + * true will cause the label to appear selected + */ + public void setSelected(boolean b) { + textFlow.setSelected(b); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Label#setText(java.lang.String) + */ + public void setText(String s) { + if (s == null) + s = "";//$NON-NLS-1$ + if (getText().equals(s)) + return; + textFlow.setText(s); + revalidate(); + repaint(); + } + + /** + * Sets the text alignment of the text relative to the icon. The default is + * {@link PositionConstants#CENTER}. + *

+ * + * If the text placement is NORTH/SOUTH, text alignment should be either + * LEFT, CENTER, or RIGHT to the icon. + *

+ * + * If the text placement is EAST/WEST, text alignment should be either TOP, + * CENTER, or BOTTOM to the icon. + * + * @param align + * the text alignment + */ + public void setTextAlignment(int align) { + if (getTextAlignment() == align) + return; + setAlignmentFlags(align, FLAG_TEXT_ALIGN); + revalidate(); + repaint(); + } + + /** + * Sets the text placement of the label relative to its icon. The default is + * {@link PositionConstants#EAST}. Other possible values are + * {@link PositionConstants#NORTH},{@link PositionConstants#SOUTH}and + * {@link PositionConstants#WEST}.

+ * + * e.g. PositionConstants#EAST indicates the text is on the east of the icon.

+ * + * @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 striked-through + * + * @param b + * Whether the label text should be striked-through + */ + public void setTextStrikeThrough(boolean b) { + textFlow.setStrikeThrough(b); + } + + /** + * Sets whether the label text should be underlined + * + * @param b + * Whether the label text should be underlined + */ + public void setTextUnderline(boolean b) { + textFlow.setTextUnderline(b); + } + + /** + * Sets up the flow page, block flow and text flow figures + * with appropriate wrapping layouts.

+ * + * @param enableWrapping set to true if wrapping should be enabled. + */ + private void setupTextFigures(boolean enableWrapping) { + + String oldText = ""; //$NON-NLS-1$ + + int oldTextHorizontalAlignment = CENTER; + + if (textFlow != null) { + oldText = textFlow.getText(); + oldTextHorizontalAlignment = blockFlow.getHorizontalAligment(); + remove(flowPage); + } + + flowPage = new FlowPage(); + blockFlow = new BlockFlow(); + textFlow = new SelectableTextFlow(); + + flowPage.setLayoutManager(new PageFlowLayout(flowPage)); + blockFlow.setLayoutManager(new BlockFlowLayout(blockFlow)); + + if (enableWrapping) { + textFlow.setLayoutManager(new ParagraphTextLayoutEx(textFlow, + ParagraphTextLayoutEx.WORD_WRAP_SOFT)); + } else { + textFlow.setLayoutManager(new SingleLineTextLayoutWithEllipses(textFlow)); + } + + blockFlow.add(textFlow); + flowPage.add(blockFlow); + add(flowPage); + + textFlow.setText(oldText); + setTextHorizontalAlignment(oldTextHorizontalAlignment); + } + + /** + * Sets whether the label text should wrap + * + * @param b + * whether the label text should wrap + */ + public void setTextWrap(boolean b) { + if (isTextWrapped() == b) + return; + setFlag(FLAG_WRAP, b); + + setupTextFigures(b); + + revalidate(); + repaint(); + } + + /** + * Sets the horizontal alignment of the label text. The default is + * {@link PositionConstants#CENTER}. + *

+ * + * It can be either {@link PositionConstants#LEFT}, + * {@link PositionConstants#CENTER}, or {@link PositionConstants#RIGHT} + *

+ * + * @param i + * the horizontal alignment. + */ + public void setTextHorizontalAlignment(int i) { + blockFlow.setHorizontalAligment(i); + + revalidate(); + repaint(); + } + + /** + * Obtains the text offset the caret should be placed given + * the location in absolute coordinates. + * @param location where the caret should be placed in + * absolute pixel coordinates + * @return text offset + */ + public int getCaretOffset(Point location) { + Rectangle _textBounds = getTextBounds().getCopy(); + translateToAbsolute(_textBounds); + + if (!_textBounds.contains(location)) + return -1; + + return textFlow.getCaretOffset(location); + } +} Index: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SingleLineTextLayoutWithEllipses.java =================================================================== RCS file: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SingleLineTextLayoutWithEllipses.java diff -N src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SingleLineTextLayoutWithEllipses.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SingleLineTextLayoutWithEllipses.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,160 @@ +/****************************************************************************** + * Copyright (c) 2006 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 + ****************************************************************************/ + +package org.eclipse.gmf.runtime.draw2d.ui.internal.text; + +import java.util.List; + +import org.eclipse.draw2d.text.TextFlow; +import org.eclipse.draw2d.text.TextFragmentBox; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; +import org.eclipse.swt.graphics.Font; + +/** + * A text layout that does not wrap words but inserts ellipses to + * truncated text. + * @author satif + * + */ +public class SingleLineTextLayoutWithEllipses + extends org.eclipse.draw2d.text.TextLayout + implements TextLayoutManagerWithMapMode { + + private static final String[] DELIMITERS = {"\r\n", //$NON-NLS-1$ + "\n", //$NON-NLS-1$ + "\r"};//$NON-NLS-1$ + + private static int result; + + private static int delimeterLength; + + private IMapMode mm = MapModeUtil.getMapMode(); + + /* (non-Javadoc) + * @see org.eclipse.gmf.runtime.draw2d.ui.internal.text.TextLayoutManagerWithMapMode#setMapMode(org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode) + */ + public void setMapMode(IMapMode mm) { + this.mm = mm; + } + + /** + * Creates a new SimpleTextLayout with the given TextFlow + * + * @param flow + * the TextFlow + */ + public SingleLineTextLayoutWithEllipses(TextFlow flow) { + super(flow); + } + + private int nextLineBreak(String text, int offset) { + result = text.length(); + delimeterLength = 0; + int current; + for (int i = 0; i < DELIMITERS.length; i++) { + current = text.indexOf(DELIMITERS[i], offset); + if (current != -1 && current < result) { + result = current; + delimeterLength = DELIMITERS[i].length(); + } + } + return result; + } + + /** + * @see org.eclipse.draw2d.text.FlowFigureLayout#layout() + */ + protected void layout() { + TextFlow textFlow = (TextFlow) getFlowFigure(); + String text = textFlow.getText(); + List fragments = textFlow.getFragments(); + Font font = textFlow.getFont(); + TextFragmentBox fragment; + int i = 0; + int offset = 0; + int ellipsesSize = FlowUtilitiesEx.getEllipsisSize(font, mm).width; + + do { + nextLineBreak(text, offset); + fragment = getFragment(i++, fragments); + fragment.length = result - offset; + fragment.offset = offset; + fragment.setWidth(-1); + fragment.setTruncated(false); + FlowUtilitiesEx.setupFragment(fragment, font, text, mm); + + int remainingLineWidth = getContext().getRemainingLineWidth(); + + // if the text is truncated... + + if (remainingLineWidth > 0 + && remainingLineWidth < fragment.getWidth()) { + + // estimate the length of the string just before we add the + // ellipses... + + remainingLineWidth -= ellipsesSize; + + if (remainingLineWidth <= 0) + break; + + fragment.setTruncated(true); + + // we estimate the length of the fragment by using average + // character + // width then +/- as required. This drastically increases + // the speed. + + int min = (int) (remainingLineWidth / FlowUtilitiesEx.getAverageCharWidth( + fragment, font, mm)); + + fragment.length = min; + fragment.setWidth(-1); + FlowUtilitiesEx.setupFragment(fragment, font, text, mm); + int dimensionX = fragment.getWidth(); + + int maxLength = text.length() - offset; + while (dimensionX < remainingLineWidth) { + min++; + if (min > maxLength) + break; + + fragment.length = min; + fragment.setWidth(-1); + FlowUtilitiesEx.setupFragment(fragment, font, text, + mm); + dimensionX = fragment.getWidth(); + } + + while (dimensionX > remainingLineWidth) { + min--; + if (min <= 0) + break; + + fragment.length = min; + fragment.setWidth(-1); + FlowUtilitiesEx.setupFragment(fragment, font, text, + mm); + dimensionX = fragment.getWidth(); + } + } else + fragment.setTruncated(false); + + getContext().addToCurrentLine(fragment); + getContext().endLine(); + offset = result + delimeterLength; + } while (offset < text.length()); + // Remove the remaining unused fragments. + while (i < fragments.size()) + fragments.remove(i++); + } +} Index: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/TextLayoutManagerWithMapMode.java =================================================================== RCS file: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/TextLayoutManagerWithMapMode.java diff -N src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/TextLayoutManagerWithMapMode.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/TextLayoutManagerWithMapMode.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,36 @@ +/****************************************************************************** + * Copyright (c) 2006 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 + ****************************************************************************/ + +package org.eclipse.gmf.runtime.draw2d.ui.internal.text; + +import org.eclipse.draw2d.LayoutManager; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; + +/** + * Interface for text layout managers to use map mode. + * @author satif + * + */ +public interface TextLayoutManagerWithMapMode + extends LayoutManager { + + /** Set the map mode to use within this text layout + * manager.

+ * + * Default map mode should be the one given by + * MapModeUtil.getMapMode() but may also depend on + * the specific implementation of the text layout manager. + * + * @param mm the map mode to set. + */ + public void setMapMode(IMapMode mm); +} Index: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/FlowUtilitiesEx.java =================================================================== RCS file: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/FlowUtilitiesEx.java diff -N src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/FlowUtilitiesEx.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/FlowUtilitiesEx.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,360 @@ +/****************************************************************************** + * Copyright (c) 2006 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 + ****************************************************************************/ + +package org.eclipse.gmf.runtime.draw2d.ui.internal.text; + + +import org.eclipse.draw2d.FigureUtilities; +import org.eclipse.draw2d.geometry.Dimension; +import org.eclipse.draw2d.text.FlowContext; +import org.eclipse.draw2d.text.FlowUtilities; +import org.eclipse.draw2d.text.ParagraphTextLayout; +import org.eclipse.draw2d.text.TextFragmentBox; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.TextLayout; + +import com.ibm.icu.text.BreakIterator; + +/** + * This class is a direct copy from Draw2D's FlowUtilities but with map mode + * functionality. + * @author satif + */ +public class FlowUtilitiesEx + extends FlowUtilities { + + public static final String ELLIPSIS = "..."; //$NON-NLS-1$ + + private static final BreakIterator INTERNAL_LINE_BREAK = BreakIterator + .getLineInstance(); + + + /** + * Get the ellipses text size given the font and the map mode. + * @param font the font to measure the ellipses size. + * @param mm the map mode the fragment will be in. + * @return the width of the ellipses + */ + public static Dimension getEllipsisSize(Font font, IMapMode mm) { + return (Dimension)mm + .DPtoLP(FigureUtilities.getStringExtents(ELLIPSIS, font)); + } + + /** + * Gets the average character width. + * + * @param fragment the supplied TextFragmentBox to use for calculation. + * if the length is 0 or if the width is or below 0, + * the average character width is taken from standard + * font metrics. + * @param font the font to use incase the TextFragmentBox conditions + * above are true. + * @param mm the map mode the fragment will be in. + * @return the average character width + */ + public static float getAverageCharWidth(TextFragmentBox fragment, + Font font, IMapMode mm) { + if (fragment.getWidth() > 0 && fragment.length != 0) + return fragment.getWidth() / (float) fragment.length; + return mm.DPtoLP(getFontMetrics(font).getAverageCharWidth()); + } + + /** + * Measures the width of the supplied (sub)string.

+ * + * @param frag the supplied TextFragmentBox (needed to check + * if it uses BiDi). + * @param string the String to measure. + * @param guess the length of the supplied String to measure. + * @param font the font the fragment will use. + * @param mm the map mode the fragment will be in. + * @return the width of the supplied String + */ + public static int measureString(TextFragmentBox frag, String string, + int guess, Font font, IMapMode mm) { + if (frag.requiresBidi()) { + // The text and/or could have changed if the lookAhead was invoked. + // This will + // happen at most once. + TextLayout _layout = getTextLayout(); + _layout.setText(string); + _layout.setFont(font); + return mm.DPtoLP(_layout.getBounds(0, guess - 1).width); + } else + return mm.DPtoLP(getStringDimension(string.substring(0, guess), + font).x); + } + + /** + * Sets up the fragment's width according to BiDi and truncation + * given all the parameters and the fields in the supplied fragment.

+ * + * @param frag the supplied TextFragmentBox + * @param f the font the fragment will use. + * @param s the string the fragment will use. + * @param mm the map mode the fragment will be in. + */ + public static void setupFragment(TextFragmentBox frag, Font f, String s, + IMapMode mm) { + if (frag.getWidth() == -1 || frag.isTruncated()) { + int width; + if (s.length() == 0 || frag.length == 0) + width = 0; + else if (frag.requiresBidi()) { + TextLayout textLayout = getTextLayout(); + textLayout.setFont(f); + textLayout.setText(s); + width = mm + .DPtoLP(textLayout.getBounds(0, frag.length - 1).width); + } else + width = mm.DPtoLP(getStringDimension(s + .substring(0, frag.length), f).x); + if (frag.isTruncated()) + width += getEllipsisSize(f, mm).width; + frag.setWidth(width); + } + } + + /** + * Sets up a fragment and returns the number of characters consumed from the + * given String. An average character width can be provided as a hint for + * faster calculation. If a fragment's bidi level is set, a TextLayout will + * be used to calculate the width. + * + * @param frag + * the TextFragmentBox + * @param string + * the String + * @param font + * the Font used for measuring + * @param context + * the flow context + * @param wrapping + * the word wrap style + * @return the number of characters that will fit in the given space; can be + * 0 (eg., when the first character of the given string is a + * newline) + */ + public static int wrapFragmentInContext(TextFragmentBox frag, + String string, FlowContext context, LookAhead lookahead, Font font, + int wrapping, IMapMode mm) { + frag.setTruncated(false); + int strLen = string.length(); + if (strLen == 0) { + frag.setWidth(-1); + frag.length = 0; + setupFragment(frag, font, string, mm); + context.addToCurrentLine(frag); + return 0; + } + + INTERNAL_LINE_BREAK.setText(string); + + initBidi(frag, string, font); + float avgCharWidth = getAverageCharWidth(frag, font, mm); + frag.setWidth(-1); + + /* + * Setup initial boundaries within the string. + */ + int absoluteMin = 0; + int max, min = 1; + if (wrapping == ParagraphTextLayout.WORD_WRAP_HARD) { + absoluteMin = INTERNAL_LINE_BREAK.next(); + while (absoluteMin > 0 + && Character.isWhitespace(string.charAt(absoluteMin - 1))) + absoluteMin--; + min = Math.max(absoluteMin, 1); + } + int firstDelimiter = findFirstDelimeter(string); + if (firstDelimiter == 0) + min = max = 0; + else + max = Math.min(strLen, firstDelimiter) + 1; + + int availableWidth = context.getRemainingLineWidth(); + int guess = 0, guessSize = 0; + + while (true) { + if ((max - min) <= 1) { + if (min == absoluteMin + && context.isCurrentLineOccupied() + && !context.getContinueOnSameLine() + && availableWidth < measureString(frag, string, min, font, + mm) + + ((min == strLen && lookahead != null) ? lookahead + .getWidth() + : 0)) { + context.endLine(); + availableWidth = context.getRemainingLineWidth(); + max = Math.min(strLen, firstDelimiter) + 1; + if ((max - min) <= 1) + break; + } else + break; + } + // Pick a new guess size + // New guess is the last guess plus the missing width in pixels + // divided by the average character size in pixels + guess += 0.5f + (availableWidth - guessSize) / avgCharWidth; + + if (guess >= max) + guess = max - 1; + if (guess <= min) + guess = min + 1; + + guessSize = measureString(frag, string, guess, font, mm); + + if (guess == strLen && lookahead != null + && !canBreakAfter(string.charAt(strLen - 1)) + && guessSize + lookahead.getWidth() > availableWidth) { + max = guess; + continue; + } + + if (guessSize <= availableWidth) { + min = guess; + frag.setWidth(guessSize); + if (guessSize == availableWidth) + max = guess + 1; + } else + max = guess; + } + + int result = min; + boolean continueOnLine = false; + if (min == strLen) { + // Everything fits + if (string.charAt(strLen - 1) == ' ') { + if (frag.getWidth() == -1) { + frag.length = result; + frag + .setWidth(measureString(frag, string, result, font, mm)); + } + if (lookahead.getWidth() > availableWidth - frag.getWidth()) { + frag.length = result - 1; + frag.setWidth(-1); + } else + frag.length = result; + } else { + continueOnLine = !canBreakAfter(string.charAt(strLen - 1)); + frag.length = result; + } + } else if (min == firstDelimiter) { + // move result past the delimiter + frag.length = result; + if (string.charAt(min) == '\r') { + result++; + if (++min < strLen && string.charAt(min) == '\n') + result++; + } else if (string.charAt(min) == '\n') + result++; + } else if (string.charAt(min) == ' ' + || canBreakAfter(string.charAt(min - 1)) + || INTERNAL_LINE_BREAK.isBoundary(min)) { + frag.length = min; + if (string.charAt(min) == ' ') + result++; + else if (string.charAt(min - 1) == ' ') { + frag.length--; + frag.setWidth(-1); + } + } else + out: { + // In the middle of an unbreakable offset + result = INTERNAL_LINE_BREAK.preceding(min); + if (result == 0) { + switch (wrapping) { + case ParagraphTextLayout.WORD_WRAP_TRUNCATE: + int truncatedWidth = availableWidth - getEllipsisSize(font, mm).width; + if (truncatedWidth > 0) { + // $TODO this is very slow. It should be using + // avgCharWidth to go faster + while (min > 0) { + guessSize = measureString(frag, string, + min, font, mm); + if (guessSize <= truncatedWidth) + break; + min--; + } + frag.length = min; + } else + frag.length = 0; + frag.setTruncated(true); + result = INTERNAL_LINE_BREAK.following(max - 1); + break out; + + default: + result = min; + break; + } + } + frag.length = result; + if (string.charAt(result - 1) == ' ') + frag.length--; + frag.setWidth(-1); + } + + setupFragment(frag, font, string, mm); + context.addToCurrentLine(frag); + context.setContinueOnSameLine(continueOnLine); + return result; + } + + public static void setupEllipses(TextFragmentBox fragment, String text, + int remainingLineWidth, Font font, IMapMode mm) { + remainingLineWidth -= getEllipsisSize(font, mm).width; + + if (remainingLineWidth <= 0) + return; + + fragment.setTruncated(true); + + // we estimate the length of the fragment by using average + // character + // width then +/- as required. This drastically increases + // the speed. + + int min = (int) (remainingLineWidth / FlowUtilitiesEx.getAverageCharWidth( + fragment, font, mm)); + + fragment.length = min; + fragment.setWidth(-1); + FlowUtilitiesEx.setupFragment(fragment, font, text, mm); + int dimensionX = fragment.getWidth(); + + int maxLength = text.length() - fragment.offset; + while (dimensionX < remainingLineWidth) { + min++; + if (min > maxLength) + break; + + fragment.length = min; + fragment.setWidth(-1); + FlowUtilitiesEx.setupFragment(fragment, font, text, + mm); + dimensionX = fragment.getWidth(); + } + + while (dimensionX > remainingLineWidth) { + min--; + if (min <= 0) + break; + + fragment.length = min; + fragment.setWidth(-1); + FlowUtilitiesEx.setupFragment(fragment, font, text, + mm); + dimensionX = fragment.getWidth(); + } + } +} Index: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/ParagraphTextLayoutEx.java =================================================================== RCS file: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/ParagraphTextLayoutEx.java diff -N src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/ParagraphTextLayoutEx.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/ParagraphTextLayoutEx.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,240 @@ +/****************************************************************************** + * Copyright (c) 2006 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 + *******************************************************************************/ +package org.eclipse.gmf.runtime.draw2d.ui.internal.text; + +import java.util.List; + +import org.eclipse.draw2d.text.FlowBorder; +import org.eclipse.draw2d.text.FlowContext; +import org.eclipse.draw2d.text.FlowFigure; +import org.eclipse.draw2d.text.ParagraphTextLayout; +import org.eclipse.draw2d.text.TextFlow; +import org.eclipse.draw2d.text.TextFragmentBox; +import org.eclipse.draw2d.text.FlowUtilities.LookAhead; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; +import org.eclipse.swt.graphics.Font; + +/** + * An extended Paragraph Text layout that considers map mode. + * @author satif + * + */ +public class ParagraphTextLayoutEx + extends ParagraphTextLayout + implements TextLayoutManagerWithMapMode { + + private IMapMode mm = MapModeUtil.getMapMode(); + + + /** + * Note: This method is a direct copy from + * org.eclipse.draw2d.text.ParagraphTextLayout

+ * + * Given the Bidi levels of the given text, this method breaks the given + * text up by its level runs. + * + * @param text + * the String that needs to be broken up into its level runs + * @param levelInfo + * the Bidi levels + * @return the requested segment + */ + private String[] getSegments(String text, int levelInfo[]) { + if (levelInfo.length == 1) + return new String[] {text}; + + String result[] = new String[levelInfo.length / 2 + 1]; + + int i; + int endOffset; + int beginOffset = 0; + + for (i = 0; i < result.length - 1; i++) { + endOffset = levelInfo[i * 2 + 1]; + result[i] = text.substring(beginOffset, endOffset); + beginOffset = endOffset; + } + endOffset = text.length(); + result[i] = text.substring(beginOffset, endOffset); + return result; + } + + /** + * Note: This class is a direct copy from ParagraphTextLayout + */ + class SegmentLookahead + implements LookAhead { + + private int seg = -1; + + private String segs[]; + + private int[] width; + + private final int trailingBorderSize; + + private FlowContext context; + + private FlowFigure flowFigure; + + public SegmentLookahead(String segs[], int trailingBorderSize, + FlowContext context, FlowFigure flowFigure) { + this.segs = segs; + this.trailingBorderSize = trailingBorderSize; + this.context = context; + this.flowFigure = flowFigure; + } + + public int getWidth() { + if (width == null) { + width = new int[1]; + int startingIndex = seg + 1; + TextFlow textFlow = (TextFlow) flowFigure; + + if (startingIndex == segs.length) { + width[0] += trailingBorderSize; + context.getWidthLookahead(textFlow, width); + } else { + String rest = segs[startingIndex]; + for (int k = startingIndex + 1; k < segs.length; k++) + rest += segs[k]; + if (!textFlow.addLeadingWordWidth(rest, width)) { + width[0] += trailingBorderSize; + context.getWidthLookahead(textFlow, width); + } + } + } + return width[0]; + } + + public void setIndex(int value) { + this.seg = value; + width = null; + } + } + + /** + * Note: This method is a direct copy from + * org.eclipse.draw2d.text.ParagraphTextLayout but takes into account + * the map mode and uses methods from FlowUtilitiesEx

+ */ + protected void layout() { + TextFlow textFlow = (TextFlow) getFlowFigure(); + int offset = 0; + + FlowContext context = getContext(); + List fragments = textFlow.getFragments(); + Font font = textFlow.getFont(); + int fragIndex = 0; + int advance = 0; + + TextFragmentBox fragment; + int levelInfo[] = (textFlow.getBidiInfo() == null) ? new int[] {-1} + : textFlow.getBidiInfo().levelInfo; + + String segment, segments[] = getSegments(textFlow.getText(), + levelInfo); + FlowBorder border = null; + if (textFlow.getBorder() instanceof FlowBorder) + border = (FlowBorder) textFlow.getBorder(); + + SegmentLookahead lookahead = new SegmentLookahead( + segments, border == null ? 0 + : mm.DPtoLP(border.getRightMargin()), getContext(), + getFlowFigure()); + int seg; + + if (border != null) { + fragment = getFragment(fragIndex++, fragments); + fragment.setBidiLevel(levelInfo[0]); + fragment.setTruncated(false); + fragment.offset = fragment.length = -1; + fragment.setWidth(mm.DPtoLP(border.getLeftMargin()) + + mm.DPtoLP(border.getInsets(textFlow).left)); + if (context.getRemainingLineWidth() < fragment.getWidth() + + lookahead.getWidth()) + context.endLine(); + context.addToCurrentLine(fragment); + } + + for (seg = 0; seg < segments.length; seg++) { + segment = segments[seg]; + lookahead.setIndex(seg); + + do { + fragment = getFragment(fragIndex++, fragments); + + fragment.offset = offset; + fragment.setBidiLevel(levelInfo[seg * 2]); + + advance = FlowUtilitiesEx.wrapFragmentInContext(fragment, + segment, context, lookahead, font, getWrappingStyle(), mm); + segment = segment.substring(advance); + offset += advance; + if ((segment.length() > 0 || fragment.length < advance) + || fragment.isTruncated()) + context.endLine(); + } while (segment.length() > 0 + || (!fragment.isTruncated() && fragment.length < advance)); + } + + if (border != null) { + fragment = getFragment(fragIndex++, fragments); + fragment.setBidiLevel(levelInfo[0]); + fragment.setTruncated(false); + fragment.offset = fragment.length = -1; + fragment.setWidth(mm.DPtoLP(border.getRightMargin()) + + mm.DPtoLP(border.getInsets(textFlow).right)); + context.addToCurrentLine(fragment); + } + + // Remove the remaining unused fragments. + while (fragIndex < fragments.size()) + fragments.remove(fragments.size() - 1); + } + + /* (non-Javadoc) + * @see org.eclipse.gmf.runtime.draw2d.ui.internal.text.TextLayoutManagerWithMapMode#setMapMode(org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode) + */ + public void setMapMode(IMapMode mm) { + this.mm = mm; + } + + /** + * Constructs a new ParagraphTextLayout on the specified TextFlow. + * + * @param flow + * the TextFlow + */ + public ParagraphTextLayoutEx(TextFlow flow) { + super(flow); + } + + /** + * Constructs the layout with the specified TextFlow and wrapping style. + * The wrapping style must be one of: + *

+ * + * @param flow + * the textflow + * @param style + * the style of wrapping + */ + public ParagraphTextLayoutEx(TextFlow flow, int style) { + super(flow, style); + } + +} Index: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SelectableTextFlow.java =================================================================== RCS file: src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SelectableTextFlow.java diff -N src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SelectableTextFlow.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/gmf/runtime/draw2d/ui/internal/text/SelectableTextFlow.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,536 @@ +/****************************************************************************** + * Copyright (c) 2006 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 + ****************************************************************************/ + +package org.eclipse.gmf.runtime.draw2d.ui.internal.text; + +import java.util.List; + +import org.eclipse.draw2d.ColorConstants; +import org.eclipse.draw2d.FigureUtilities; +import org.eclipse.draw2d.Graphics; +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.draw2d.text.BidiChars; +import org.eclipse.draw2d.text.CaretInfo; +import org.eclipse.draw2d.text.TextFlow; +import org.eclipse.draw2d.text.TextFragmentBox; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; +import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.TextLayout; + +/** + * Text flow with the following capabilities:
+ * 1. Selectable, focusable,
+ * 2. text can have underline, strikethrough
+ * 3. return total height or visible height of text. + * + * @author satif + * + */ +public class SelectableTextFlow + extends TextFlow { + + // flag to indicate text selection + private static int FLAG_SELECTED = MAX_FLAG << 1; + + // flag to indicate if the text has a focus rectangle + // drawn around it + private static int FLAG_HASFOCUS = MAX_FLAG << 2; + + // flag to indicate if text should be underlined + private static int FLAG_UNDERLINED = MAX_FLAG << 3; + + // flag to indicate if text should be striked through + private static int FLAG_STRIKEDTHROUGH = MAX_FLAG << 4; + + // map mode constants cached... + private int nDPtoLP_1 = 1, nDPtoLP_2 = 2; + + private IMapMode mm = MapModeUtil.getMapMode(); + + // the literal bounds of the text excluding any extra space occupied + // by the TextFlow. These bounds are like the union of all fragments. + private Rectangle literal_SubstringTextBounds; + + + /** + * Note: this method is overridden to allow the use of FlowUtilitiesEx methods + * with map mode.

+ * + * @see org.eclipse.draw2d.text.TextFlow#addLeadingWordWidth(java.lang.String, int[]) + */ + public boolean addLeadingWordWidth(String text, int[] width) { + if (text.length() == 0) + return false; + if (Character.isWhitespace(text.charAt(0))) + return true; + + text = 'a' + text + 'a'; + FlowUtilitiesEx.LINE_BREAK.setText(text); + int index = FlowUtilitiesEx.LINE_BREAK.next() - 1; + if (index == 0) + return true; + while (Character.isWhitespace(text.charAt(index))) + index--; + boolean result = index < text.length() - 1; + // index should point to the end of the actual text (not including the + // 'a' that was + // appended), if there were no breaks + if (index == text.length() - 1) + index--; + text = text.substring(1, index + 1); + + if (getBidiInfo() == null) + width[0] += FlowUtilitiesEx.getStringExtents(text, getFont()).width; + else { + TextLayout textLayout = FlowUtilitiesEx.getTextLayout(); + textLayout.setFont(getFont()); + textLayout.setText(text); + width[0] += textLayout.getBounds().width; + } + return result; + } + + /** + * Calculates literal_SubstringTextBounds. + * This is the literal bounds of the text excluding any extra space occupied + * by the TextFlow. These bounds are like the union of all fragments. + */ + private void calculateBounds() { + if (literal_SubstringTextBounds != null) + return; + + List list = getFragments(); + TextFragmentBox box; + int left = Integer.MAX_VALUE, top = left; + int right = Integer.MIN_VALUE, bottom = right; + + for (int i = 0; i < list.size(); i++) { + box = (TextFragmentBox) list.get(i); + left = Math.min(left, box.getX()); + + // The +2 is for leading characters... + right = Math.max(right, box.getX() + box.getWidth() + nDPtoLP_2); + + top = Math.min(top, box.getLineRoot().getVisibleTop()); + bottom = Math.max(bottom, box.getLineRoot().getVisibleBottom()); + } + if (literal_SubstringTextBounds == null) + literal_SubstringTextBounds = new Rectangle(left, top, + right - left, bottom - top); + else { + literal_SubstringTextBounds.x = left; + literal_SubstringTextBounds.y = top; + literal_SubstringTextBounds.width = right - left; + literal_SubstringTextBounds.height = bottom - top; + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.text.TextFlow#getAscent() + */ + public int getAscent() { + return mm.DPtoLP(super.getAscent()); + } + + /** + * Note: this method is directly copied from + * org.eclipse.draw2d.text.TextFlow

+ * + * @param box + * which fragment + * @param index + * the fragment index + * @return the bidi string for that fragment + */ + private String getBidiSubstring(TextFragmentBox box, int index) { + if (box.getBidiLevel() < 1) + return getText().substring(box.offset, box.offset + box.length); + + StringBuffer buffer = new StringBuffer(box.length + 3); + buffer.append(box.isRightToLeft() ? BidiChars.RLO + : BidiChars.LRO); + if (index == 0 && getBidiInfo().leadingJoiner) + buffer.append(BidiChars.ZWJ); + buffer.append(getText().substring(box.offset, box.offset + box.length)); + if (index == getFragments().size() - 1 && getBidiInfo().trailingJoiner) + buffer.append(BidiChars.ZWJ); + return buffer.toString(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.text.TextFlow#getDescent() + */ + public int getDescent() { + return mm.DPtoLP(super.getDescent()); + } + + /** + * Returns the bounds of visible text. + * + * @return Rectangle bounds of visible text. + */ + public Rectangle getLiteralSubstringTextBounds() { + calculateBounds(); + return literal_SubstringTextBounds; + } + + /** + * Note: this method is directly copied from + * org.eclipse.draw2d.text.TextFlow + */ + private int getBidiPrefixLength(TextFragmentBox box, int index) { + if (box.getBidiLevel() < 1) + return 0; + if (index > 0 || !getBidiInfo().leadingJoiner) + return 1; + return 2; + } + + /** + * Note: this method is directly copied from + * org.eclipse.draw2d.text.TextFlow but takes into account + * the map mode and uses methods from FlowUtilitiesEx

+ * + * @see org.eclipse.draw2d.text.TextFlow#getPointInBox(org.eclipse.draw2d.text.TextFragmentBox, + * int, int, boolean) + */ + protected Point getPointInBox(TextFragmentBox box, int offset, int index, + boolean trailing) { + offset -= box.offset; + offset = Math.min(box.length, offset); + Point result = new Point(0, box.getBaseline() - box.getAscent()); + if (getBidiInfo() == null) { + if (trailing && offset < box.length) + offset++; + String substring = getText().substring(box.offset, box.offset + offset); + result.x = mm.DPtoLP(FigureUtilities.getStringExtents( + substring, getFont()).width); + } else { + TextLayout layout = FlowUtilitiesEx.getTextLayout(); + layout.setFont(getFont()); + String fragString = getBidiSubstring(box, index); + layout.setText(fragString); + offset += getBidiPrefixLength(box, index); + result.x = layout.getLocation(offset, trailing).x; + if (isMirrored()) + result.x = box.getWidth() - result.x; + } + result.x += box.getX(); + return result; + } + + /** + * @return the focus state of this label + */ + public boolean hasFocus() { + return (flags & FLAG_HASFOCUS) != 0; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.Figure#invalidate() + */ + public void invalidate() { + super.invalidate(); + literal_SubstringTextBounds = null; + } + + /** + * @return the selection state of this label + */ + public boolean isSelected() { + return (flags & FLAG_SELECTED) != 0; + } + + /** + * @return whether the label text is striked-through + */ + public boolean isTextStrikeThrough() { + return (flags & FLAG_STRIKEDTHROUGH) != 0; + } + + /** + * @return whether the label text is underlined + */ + public boolean isTextUnderlined() { + return (flags & FLAG_UNDERLINED) != 0; + } + + /** + * @see org.eclipse.draw2d.Figure#paintFigure(Graphics) + */ + protected void paintFigure(Graphics g) { + TextFragmentBox frag; + g.getClip(Rectangle.SINGLETON); + int yStart = Rectangle.SINGLETON.y; + int yEnd = Rectangle.SINGLETON.bottom(); + + int i; + + // first we calculate the literal size of the visible text and do the + // selection painting (note we have to do this loop, cannot merge + // it in the next one). + calculateBounds(); + + // draw the selection background, if applicable... + if (isSelected()) { + g.pushState(); + g.setBackgroundColor(ColorConstants.menuBackgroundSelected); + g.fillRectangle(literal_SubstringTextBounds); + g.popState(); + g.setForegroundColor(ColorConstants.white); + } else + g.setForegroundColor(getForegroundColor()); + + // draw the focus rectangle, if applicable... + if (hasFocus()) { + // focus drawing is done manually, as g.drawFocus gives + // incorrect results in this case. + g.pushState(); + g.setXORMode(true); + g.setLineWidth(0); + g.setLineStyle(SWT.LINE_DOT); + g.setLineDash(new int[] {1, 2}); + g.drawRectangle(literal_SubstringTextBounds.getCopy() + .resize(-1, -1)); + g.popState(); + + // restore line dash as it is not restored on pop + g.setLineDash(new int[] {}); + } + + ///////////////////////////////////////////////////////////////////////// + // START: Copy from org.eclipse.draw2d.text.TextFlow#paintFigure //////// + + for (i = 0; i < getFragments().size(); i++) { + frag = (TextFragmentBox) getFragments().get(i); + // g.drawLine(frag.getX(), frag.getLineRoot().getVisibleTop(), + // frag.getWidth() + frag.getX(), frag.getLineRoot().getVisibleTop()); + // g.drawLine(frag.getX(), frag.getBaseline(), frag.getWidth() + frag.getX(), frag.getBaseline()); + if (frag.offset == -1) + continue; + // Loop until first visible fragment + if (yStart > frag.getLineRoot().getVisibleBottom() + nDPtoLP_1)// The + 1 is for + // disabled text + continue; + // Break loop at first non-visible fragment + if (yEnd < frag.getLineRoot().getVisibleTop()) + break; + + String draw = getBidiSubstring(frag, i); + + if (frag.isTruncated()) + draw += FlowUtilitiesEx.ELLIPSIS; + + if (!isEnabled()) { + g.setForegroundColor(ColorConstants.buttonLightest); + paintText(g, draw, + frag.getX() + nDPtoLP_1, + frag.getBaseline() - getAscent() + nDPtoLP_1, + frag.getBidiLevel()); + g.setForegroundColor(ColorConstants.buttonDarker); + paintText(g, draw, + frag.getX(), + frag.getBaseline() - getAscent(), + frag.getBidiLevel()); + g.setForegroundColor(fgColor); + } else { + paintText(g, draw, + frag.getX(), + frag.getBaseline() - getAscent(), + frag.getBidiLevel()); + } + + // END: Copy from org.eclipse.draw2d.text.TextFlow#paintFigure ////////// + ///////////////////////////////////////////////////////////////////////// + + + // underline the text, if applicable... + if (isTextUnderlined()) { + g.drawLine(frag.getX(), + frag.getBaseline() + nDPtoLP_1, + frag.getWidth() + frag.getX(), + frag.getBaseline() + nDPtoLP_1); + } + + // strike through the text, if applicable... + if (isTextStrikeThrough()) { + int visibleTop = frag.getLineRoot().getVisibleTop() + + (frag.getBaseline() + getDescent() + + nDPtoLP_2 - + frag.getLineRoot().getVisibleTop()) / 2; + g.drawLine(frag.getX(), + visibleTop, + frag.getWidth() + frag.getX(), + visibleTop); + } + } + } + + /** + * Note: this method is directly copied from + * org.eclipse.draw2d.text.TextFlow but uses methods from FlowUtilitiesEx

+ * + * Paints the String draw at the given x and y location. + */ + private void paintText(Graphics g, String draw, int x, int y, int bidiLevel) { + if (bidiLevel == -1) { + g.drawString(draw, x, y); + } else { + TextLayout tl = FlowUtilitiesEx.getTextLayout(); + if (isMirrored()) + tl.setOrientation(SWT.RIGHT_TO_LEFT); + tl.setFont(g.getFont()); + tl.setText(draw); + g.drawTextLayout(tl, x, y); + } + } + + /** + * Sets the focus state of the text + * + * @param b + * true will cause a focus rectangle to be drawn around the text + * of the Label + */ + public void setFocus(boolean b) { + if (hasFocus() == b) + return; + setFlag(FLAG_HASFOCUS, b); + repaint(); + } + + /** + * Sets the map mode. + * @param mm the map mode to set. + */ + public void setMapMode(IMapMode mm) { + this.mm = mm; + nDPtoLP_1 = mm.DPtoLP(1); + nDPtoLP_2 = mm.DPtoLP(2); + } + + /** + * Sets the selection state of the text + * + * @param b + * true will cause the label to appear selected + */ + public void setSelected(boolean b) { + if (isSelected() == b) + return; + setFlag(FLAG_SELECTED, b); + repaint(); + } + + /** + * Sets whether the text should be striked-through + * + * @param b + * Whether the label text should be striked-through + */ + public void setStrikeThrough(boolean strikeThrough) { + if (isTextStrikeThrough() == strikeThrough) + return; + setFlag(FLAG_STRIKEDTHROUGH, strikeThrough); + repaint(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.draw2d.text.TextFlow#setText(java.lang.String) + */ + public void setText(String s) { + super.setText(s); + literal_SubstringTextBounds = null; + } + + /** + * Sets whether the text should be underlined + * + * @param b + * Whether the label text should be underlined + */ + public void setTextUnderline(boolean underline) { + if (isTextUnderlined() == underline) + return; + setFlag(FLAG_UNDERLINED, underline); + repaint(); + } + + /** + * Maps a location (in absolute coordinates) to an + * offset within the text.

+ * + * @param location (in absolute coordinates) to map into offset + * @return the offset within the text. + */ + public int getCaretOffset(Point location) { + // do a binary search for the caret location... + + String text = getText(); + int min = 0, + max = text.length(), + mid = -2, + previousMids[] = {-1,-1}; // cache previous results to detect infinite loops + + char midChar = ' '; + + while (max - min > 1) { + + mid = (max + min) / 2; + + midChar = text.charAt(mid); + + CaretInfo caretInfo = getCaretPlacement(mid, false); + + if (caretInfo.getY() + caretInfo.getLineHeight() < location.y) + min = mid; + else if (caretInfo.getY() > location.y) + max = mid; + else { + if (caretInfo.getX() + FigureUtilities. + getStringExtents(String.valueOf(midChar), + getFont()).width < location.x) + min = mid; + else if (caretInfo.getX() > location.x) + max = mid; + else // we have found the correct match + return ++mid; // we want a trailing selection. + } + + // detect and prevent infinite loops... + // this works because we don't expect to calculate a mid + // which we have calculated before. + if (mid == previousMids[0] || mid == previousMids[1]) + break; + + previousMids[1] = previousMids[0]; + previousMids[0] = mid; + } + + + if (midChar == '\n') // last character when the loop ended + --mid; // this happens when the user clicks past the end of line. + else + ++mid; // we want a trailing selection. + + return mid; + } +} #P org.eclipse.gmf.runtime.diagram.ui Index: src/org/eclipse/gmf/runtime/diagram/ui/tools/TextDirectEditManager.java =================================================================== RCS file: /cvsroot/technology/org.eclipse.gmf/plugins/org.eclipse.gmf.runtime.diagram.ui/src/org/eclipse/gmf/runtime/diagram/ui/tools/TextDirectEditManager.java,v retrieving revision 1.13.2.10 diff -u -r1.13.2.10 TextDirectEditManager.java --- src/org/eclipse/gmf/runtime/diagram/ui/tools/TextDirectEditManager.java 29 Nov 2006 18:47:16 -0000 1.13.2.10 +++ src/org/eclipse/gmf/runtime/diagram/ui/tools/TextDirectEditManager.java 15 Dec 2006 21:34:23 -0000 @@ -12,6 +12,7 @@ package org.eclipse.gmf.runtime.diagram.ui.tools; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -35,6 +36,7 @@ import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIPlugin; import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIStatusCodes; import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer; +import org.eclipse.gmf.runtime.draw2d.ui.figures.LabelWithTextLayout; import org.eclipse.gmf.runtime.draw2d.ui.figures.WrapLabel; import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode; import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; @@ -55,6 +57,7 @@ import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; @@ -129,6 +132,28 @@ } public void relocate(CellEditor celleditor) { +// Text text = (Text) celleditor.getControl(); +// WrapLabel fig = getWrapLabel(); +// +// Rectangle rect = fig.getTextBounds().getCopy(); +// fig.translateToAbsolute(rect); +// +// int avrWidth = FigureUtilities.getFontMetrics(text.getFont()) +// .getAverageCharWidth(); +// +// if (fig.isTextWrapped() && fig.getText().length() > 0) +// rect.setSize(new Dimension(rect.width + avrWidth * 2, rect.height)); +// else +// rect.setSize(new Dimension(text.computeSize(SWT.DEFAULT, +// SWT.DEFAULT)).expand(avrWidth * 2, 0)); +// +// org.eclipse.swt.graphics.Rectangle newRect = +// text.computeTrim(rect.x, rect.y, rect.width, rect.height); +// text.setBounds(newRect); + + + // TODO: find a common relocate method for all labels. + Text text = (Text) celleditor.getControl(); WrapLabel fig = getWrapLabel(); @@ -234,6 +259,46 @@ return TextCellEditorEx.class; } + protected CellEditor createCellEditorOn(Composite composite) { + + Class ceClass = getTextCellEditorClass(getEditPart()); + + if (ceClass == TextCellEditorEx.class || + ceClass == WrapTextCellEditor.class) { + + try { + + Constructor constructor = ceClass.getConstructor(new Class[]{Composite.class, + int.class}); + + int style; + + if (ceClass == TextCellEditorEx.class) + style = SWT.SINGLE; + else + style = SWT.MULTI | SWT.WRAP; + + IFigure figure = getEditPart().getFigure(); + + if (figure instanceof WrapLabel) { + if (((WrapLabel)figure).getTextWrapAlignment() == PositionConstants.CENTER) + style |= SWT.CENTER; + else if (((WrapLabel)figure).getTextWrapAlignment() == PositionConstants.RIGHT) + style |= SWT.RIGHT; + } + + Integer param1 = new Integer(style); + + return (CellEditor)constructor.newInstance(new Object[]{composite, param1}); + + } catch (Exception e) { + return null; + } + } + else { + return super.createCellEditorOn(composite); + } + } /** * Given a WrapLabel object, this will calculate the @@ -535,15 +600,26 @@ String text; IFigure fig = getEditPart().getFigure(); - if (fig instanceof WrapLabel) { - WrapLabel label = (WrapLabel) fig; + if (fig instanceof LabelWithTextLayout) { + int caretPos = ((LabelWithTextLayout) fig) + .getCaretOffset(new org.eclipse.draw2d.geometry.Point( + location.x, location.y)); + Text textControl = (Text) getCellEditor().getControl(); + if (caretPos == -1) + textControl.setSelection(0, textControl.getText().length()); + else + textControl.setSelection(caretPos); + + return; + } else if (fig instanceof Label) { + Label label = (Label) fig; iconBounds = label.getIconBounds().getCopy(); icon = label.getIcon(); textPlacement = label.getTextPlacement(); subStringText = label.getSubStringText(); text = label.getText(); - } else if (fig instanceof Label) { - Label label = (Label) fig; + } else if (fig instanceof WrapLabel) { + WrapLabel label = (WrapLabel) fig; iconBounds = label.getIconBounds().getCopy(); icon = label.getIcon(); textPlacement = label.getTextPlacement();