/****************************************************************************** * 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(); } }