| /****************************************************************************** |
| * Copyright (c) 2007, 2010 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.text; |
| |
| import org.eclipse.draw2d.ColorConstants; |
| import org.eclipse.draw2d.Figure; |
| import org.eclipse.draw2d.Graphics; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| import org.eclipse.draw2d.text.TextFlow; |
| import org.eclipse.draw2d.text.TextFragmentBox; |
| import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Font; |
| |
| /** |
| * A <code>TextFlow</code> with the following additional capabilities: <br> |
| * <UL> |
| * <LI>text can be underlined or have a strike-through |
| * <LI>truncated with ... when the full text doesn't fit vertically |
| * </UL> |
| * |
| * @since 2.1 |
| * @author satif, crevells |
| * |
| */ |
| public class TextFlowEx |
| extends TextFlow { |
| |
| // reserve 1 bit |
| private static int FLAG_UNDERLINED = Figure.MAX_FLAG << 1; |
| |
| private static int FLAG_STRIKEDTHROUGH = Figure.MAX_FLAG << 2; |
| |
| /** |
| * The highest reserved flag used by this class. |
| * |
| * @see Figure#MAX_FLAG |
| */ |
| protected static final int MAX_FLAG = FLAG_STRIKEDTHROUGH; |
| |
| private String truncationString = "..."; //$NON-NLS-1$ |
| |
| /** the FlowUtilities instance that is dependent on the mapmode */ |
| private FlowUtilitiesEx flowUtilities; |
| |
| /** the TextUtilities instance that is dependent on the mapmode */ |
| private TextUtilitiesEx textUtilities; |
| |
| private int nDPtoLP_1 = -1; |
| |
| /** |
| * Constructs a new TextFlow with the empty String. |
| */ |
| public TextFlowEx() { |
| super(); |
| } |
| |
| /** |
| * Constructs a new TextFlow with the specified String. |
| * |
| * @param text |
| * the string |
| */ |
| public TextFlowEx(String text) { |
| super(text); |
| } |
| |
| /** |
| * Gets the truncation string. The default is an ellipsis "...". |
| * |
| * @return the truncation string |
| */ |
| protected String getTruncationString() { |
| return truncationString; |
| } |
| |
| /** |
| * Sets the truncation string. The default is an ellipsis "...". |
| * |
| * @param truncationString |
| * the new truncation string |
| */ |
| public void setTruncationString(String truncationString) { |
| this.truncationString = truncationString; |
| } |
| |
| /** |
| * @return whether the label text is striked-through |
| */ |
| public boolean isTextStrikedThrough() { |
| return (flags & FLAG_STRIKEDTHROUGH) != 0; |
| } |
| |
| /** |
| * Sets whether the label text should be striked-through |
| * |
| * @param strikeThrough |
| * Whether the label text should be striked-through |
| */ |
| public void setTextStrikeThrough(boolean strikeThrough) { |
| if (isTextStrikedThrough() == strikeThrough) |
| return; |
| setFlag(FLAG_STRIKEDTHROUGH, strikeThrough); |
| repaint(); |
| } |
| |
| /** |
| * @return whether the label text is underlined |
| */ |
| public boolean isTextUnderlined() { |
| return (flags & FLAG_UNDERLINED) != 0; |
| } |
| |
| /** |
| * Sets whether the label text should be underlined |
| * |
| * @param underline |
| * Whether the label text should be underlined |
| */ |
| public void setTextUnderline(boolean underline) { |
| if (isTextUnderlined() == underline) |
| return; |
| setFlag(FLAG_UNDERLINED, underline); |
| repaint(); |
| } |
| |
| protected void paintFigure(Graphics g) { |
| TextFragmentBox frag; |
| g.getClip(Rectangle.SINGLETON); |
| int yStart = Rectangle.SINGLETON.y; |
| int yEnd = Rectangle.SINGLETON.bottom(); |
| Rectangle maxBounds = getVisibleBounds(); |
| |
| for (int i = 0; i < getFragments().size(); i++) { |
| frag = (TextFragmentBox) getFragments().get(i); |
| if (frag.offset == -1) |
| continue; |
| // Loop until first visible fragment |
| if (yStart > getVisibleBottom(frag) + 1)// The + 1 |
| // is for |
| // disabled |
| // text |
| continue; |
| // Break loop at first non-visible fragment |
| if (yEnd < getVisibleTop(frag)) |
| break; |
| |
| String draw = getBidiSubstring(frag, i); |
| |
| // /////////////////////////////////////////// |
| // If the next fragment will not be completely visible, then |
| // truncate this fragment. |
| boolean truncate = frag.isTruncated(); |
| if (i + 1 < getFragments().size() |
| && maxBounds.bottom() < getVisibleBottom((TextFragmentBox) getFragments() |
| .get(i + 1))) { |
| |
| draw = truncateText(draw); |
| truncate = true; |
| |
| // increment the counter so no further fragments will be |
| // processed |
| i = getFragments().size(); |
| } |
| |
| if (truncate) |
| draw += getTruncationString(); |
| // /////////////////////////////////////////// |
| |
| if (!isEnabled()) { |
| Color cachedfgColor = g.getForegroundColor(); |
| g.setForegroundColor(ColorConstants.buttonLightest); |
| paintText(g, draw, frag.getX() + getDPtoLP1(), frag |
| .getBaseline() |
| - frag.getAscent() + getDPtoLP1(), frag.getBidiLevel()); |
| g.setForegroundColor(ColorConstants.buttonDarker); |
| paintText(g, draw, frag.getX(), frag.getBaseline() |
| - frag.getAscent(), frag.getBidiLevel()); |
| g.setForegroundColor(cachedfgColor); |
| } else { |
| paintText(g, draw, frag.getX(), frag.getBaseline() |
| - frag.getAscent(), frag.getBidiLevel()); |
| } |
| |
| drawTextAdornments(g, frag); |
| } |
| } |
| |
| private int getDPtoLP1() { |
| if (nDPtoLP_1 == -1) { |
| nDPtoLP_1 = MapModeUtil.getMapMode(this).DPtoLP(1); |
| } |
| return nDPtoLP_1; |
| } |
| |
| private void drawTextAdornments(Graphics g, TextFragmentBox fragment) { |
| int baseline = fragment.getBaseline(); |
| if (isTextUnderlined()) { |
| baseline += getDPtoLP1(); |
| |
| g.drawLine(fragment.getX(), baseline, fragment.getWidth() |
| + fragment.getX(), baseline); |
| } |
| if (isTextStrikedThrough()) { |
| int y = fragment.getBaseline() |
| - fragment.getAscent() |
| + ((fragment.getAscent() + fragment.getDescent() + getDPtoLP1()) / 2); |
| g.drawLine(fragment.getX(), y, fragment.getWidth() |
| + fragment.getX(), y); |
| } |
| } |
| |
| /** |
| * Gets the y-value representing the top of the visible text. |
| * |
| * @param fragment |
| * the text fragment |
| * @return the top value |
| */ |
| private int getVisibleTop(TextFragmentBox fragment) { |
| return fragment.getBaseline() - fragment.getAscent(); |
| } |
| |
| /** |
| * Gets the y-value representing the bottom of the visible text. |
| * |
| * @param fragment |
| * the text fragment |
| * @return the bottom value |
| */ |
| private int getVisibleBottom(TextFragmentBox fragment) { |
| return fragment.getBaseline() + fragment.getDescent(); |
| } |
| |
| /** |
| * Adds an ellipsis to the text passed in if this will fit in the width of |
| * this figure, otherwise first truncates the text as required and then adds |
| * the ellipsis. |
| * |
| * @param text |
| * the full text |
| * @return a new string with the text ending in an ellipsis |
| */ |
| protected String truncateText(String text) { |
| int maxWidth = getVisibleBounds().width; |
| Font currentFont = getFont(); |
| |
| int ellipsisWidth = getTextUtilities().getTextExtents( |
| getTruncationString(), currentFont).width; |
| |
| if (maxWidth < ellipsisWidth) { |
| maxWidth = ellipsisWidth; |
| } |
| |
| int subStringLength = getTextUtilities().getLargestSubstringConfinedTo( |
| text, currentFont, maxWidth - ellipsisWidth); |
| |
| return new String(text.substring(0, subStringLength)); |
| } |
| |
| public FlowUtilitiesEx getFlowUtilities() { |
| if (flowUtilities == null) { |
| flowUtilities = new FlowUtilitiesEx(MapModeUtil.getMapMode(this)); |
| } |
| return flowUtilities; |
| } |
| |
| public TextUtilitiesEx getTextUtilities() { |
| if (textUtilities == null) { |
| textUtilities = new TextUtilitiesEx(MapModeUtil.getMapMode(this)); |
| } |
| return textUtilities; |
| } |
| |
| /** |
| * Gets the area that will be visible to know where to truncate. |
| * |
| * @return the visible bounds |
| */ |
| private Rectangle getVisibleBounds() { |
| // Not the best idea to depend on the parent, but it will have to do for |
| // now. |
| return getParent().getClientArea(); |
| } |
| |
| } |