/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.swing; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Toolkit; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JTextPane; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.BoxView; import javax.swing.text.ComponentView; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.GlyphView; import javax.swing.text.IconView; import javax.swing.text.LabelView; import javax.swing.text.MutableAttributeSet; import javax.swing.text.ParagraphView; import javax.swing.text.Position; import javax.swing.text.Segment; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import javax.swing.text.StyledEditorKit; import javax.swing.text.TabExpander; import javax.swing.text.Utilities; import javax.swing.text.View; import javax.swing.text.ViewFactory; /** * Extension to <code>JTextPane</code> that supports 2 tone text. * * @author jgorrell * @version $Revision: 5945 $ $Date: 2013-06-02 21:05:50 +0200 (Sun, 02 Jun 2013) $ $Author: azhrei_fje $ */ public class TwoToneTextPane extends JTextPane { /*--------------------------------------------------------------------------------------------- * Class Variables *-------------------------------------------------------------------------------------------*/ /** * The constant used as the attribute name for two tone colors. */ public static final Object TwoToneColor = new ColorConstants("two-tone-color"); /** * Pattern used to parse text strings for a style */ private static final Pattern TEXT_PATTERN = Pattern.compile("\\$\\{\\s*(\\w*)\\s*\\}"); /*--------------------------------------------------------------------------------------------- * Constructors *-------------------------------------------------------------------------------------------*/ /** * Default constructor */ public TwoToneTextPane() { super(); setEditorKit(new TwoToneStyledEditorKit()); } /** * Create a pane for the styled document. * * @param aDoc The styled document being displayed. */ public TwoToneTextPane(StyledDocument aDoc) { super(aDoc); setEditorKit(new TwoToneStyledEditorKit()); } /*--------------------------------------------------------------------------------------------- * Class Methods *-------------------------------------------------------------------------------------------*/ /** * Gets the background color setting from the attribute list. * * @param a the attribute set * @return the color, Color.black as the default */ public static Color getTwoToneColor(AttributeSet a) { Color ttc = (Color) a.getAttribute(TwoToneColor); return ttc; } /** * Sets the background color. * * @param a the attribute set * @param fg the color */ public static void setTwoToneColor(MutableAttributeSet a, Color fg) { a.addAttribute(TwoToneColor, fg); } /** * Not really support for two tone styles, but a convience method * to set all of the properties for a single font. * * @param style Style being modified * @param font Font to add to the style. */ public static final void setFont(Style style, Font font) { StyleConstants.setFontFamily(style, font.getFamily()); StyleConstants.setFontSize(style, font.getSize()); StyleConstants.setBold(style, font.isBold()); StyleConstants.setItalic(style, font.isItalic()); } /** * A convience method to read a font from a style. * * @param style Style being modified * @return The font inside the passed style */ public static String getFontString(Style style) { String font = StyleConstants.getFontFamily(style) + "-"; if (StyleConstants.isBold(style)) font += "BOLD"; if (StyleConstants.isItalic(style)) font += "ITALIC"; if (!StyleConstants.isBold(style) && !StyleConstants.isItalic(style)) font += "PLAIN"; font += "-" + StyleConstants.getFontSize(style); return font; } /** * Parse the passed text string for style names and add * the styled text to the text pane's document. * * @param text Text to parse * @param pane Pane to modify. The pane also provides the style names */ public static final void parse(String text, JTextPane pane) { try { Matcher match = TEXT_PATTERN.matcher(text); Document doc = pane.getDocument(); int textStart = 0; // Start of current text area Style style = null; // Style for next set of text while (match.find()) { // Save the current text first String styledText = text.substring(textStart, match.start()); textStart = match.end() + 1; if (style != null && styledText != null) { doc.insertString(doc.getLength(), styledText, style); } // endif // Get the next style style = pane.getStyle(match.group(1)); if (style == null) throw new IllegalArgumentException("Unknown style: '" + match.group(1)); } // endwhile // Add the last of the text doc.insertString(doc.getLength(), text.substring(textStart), null); } catch (BadLocationException e) { e.printStackTrace(); throw new IllegalStateException("This should not happen since I always use the document to " + "determine the location to write. It might be due to synchronization problems though"); } } /*--------------------------------------------------------------------------------------------- * TwoToneStyledEditorKit Inner Class *------------------------------------------------------------------------------------------*/ /** * Editor kit that provides the two tone view factory. * * @author jgorrell * @version $Revision: 5945 $ $Date: 2013-06-02 21:05:50 +0200 (Sun, 02 Jun 2013) $ $Author: azhrei_fje $ */ class TwoToneStyledEditorKit extends StyledEditorKit { /** * The view factory used by the editor kit */ private ViewFactory defaultViewFactory = new TwoToneStyledViewFactory(); /** * @see javax.swing.text.StyledEditorKit#getViewFactory() */ @Override public ViewFactory getViewFactory() { return defaultViewFactory; } } /*--------------------------------------------------------------------------------------------- * TwoToneStyledViewFactory Inner Class *------------------------------------------------------------------------------------------*/ /** * This factory is the default view factory extended to return * a view that paints two tone text. * * @author jgorrell * @version $Revision: 5945 $ $Date: 2013-06-02 21:05:50 +0200 (Sun, 02 Jun 2013) $ $Author: azhrei_fje $ */ class TwoToneStyledViewFactory implements ViewFactory { /** * @see javax.swing.text.ViewFactory#create(javax.swing.text.Element) */ @Override public View create(Element elem) { String kind = elem.getName(); if (kind != null) { if (kind.equals(AbstractDocument.ContentElementName)) { return new TwoToneLabelView(elem); } else if (kind.equals(AbstractDocument.ParagraphElementName)) { return new ParagraphView(elem); } else if (kind.equals(AbstractDocument.SectionElementName)) { return new BoxView(elem, View.Y_AXIS); } else if (kind.equals(StyleConstants.ComponentElementName)) { return new ComponentView(elem); } else if (kind.equals(StyleConstants.IconElementName)) { return new IconView(elem); } // endif } // endif // default to text display return new TwoToneLabelView(elem); } } /*--------------------------------------------------------------------------------------------- * TwoToneLabelView Inner Class *-------------------------------------------------------------------------------------------*/ /** * Label view that can paint two tone text. * * @author jgorrell * @version $Revision: 5945 $ $Date: 2013-06-02 21:05:50 +0200 (Sun, 02 Jun 2013) $ $Author: azhrei_fje $ */ public class TwoToneLabelView extends LabelView { /** * Painter used for two tone text. */ private GlyphView.GlyphPainter painter = new TwoToneGlyphPainter(); /** * Create a new TwoToneLabelView * * @param element */ public TwoToneLabelView(Element element) { super(element); setGlyphPainter(painter); } } /*--------------------------------------------------------------------------------------------- * TwoToneLabelView Inner Class *-------------------------------------------------------------------------------------------*/ /** * Paints a black or white background text offest by a pixel both vertically and * horizontally and then paints the normal text. This code is just a copy of * <code>javax.swing.text.GlyphPainter1</code> modified to return an extra pixel for * the width and height and to do the extra painting. * * @author jgorrell * @version $Revision: 5945 $ $Date: 2013-06-02 21:05:50 +0200 (Sun, 02 Jun 2013) $ $Author: azhrei_fje $ */ public class TwoToneGlyphPainter extends GlyphView.GlyphPainter { /*--------------------------------------------------------------------------------------------- * Instance Variables *-------------------------------------------------------------------------------------------*/ /** * The metrics for the font. */ FontMetrics metrics; /*--------------------------------------------------------------------------------------------- * Class Variables *-------------------------------------------------------------------------------------------*/ /** * The amount that the text is offset horizontally */ private static final int HORIZONTAL_OFFSET = 1; /** * The amount that the text is offset vertially */ private static final int VERTICAL_OFFSET = 0; /*--------------------------------------------------------------------------------------------- * Instance Methods *-------------------------------------------------------------------------------------------*/ /** * Fetch a reference to the text that occupies * the given range. This is normally used by * the GlyphPainter to determine what characters * it should render glyphs for. * * @param v Read the text from this glyph view's document. * @param p0 the starting document offset >= 0 * @param p1 the ending document offset >= p0 * @return the <code>Segment</code> containing the text */ public Segment getText(GlyphView v, int p0, int p1) { Segment text = new Segment(); try { Document doc = v.getDocument(); doc.getText(p0, p1 - p0, text); } catch (BadLocationException bl) { throw new IllegalStateException("GlyphView: Stale view: " + bl); } return text; } /*--------------------------------------------------------------------------------------------- * Abstract GlyphPainter Methods *-------------------------------------------------------------------------------------------*/ /** * Determine the span the glyphs given a start location (for tab expansion). * * @see javax.swing.text.GlyphView.GlyphPainter#getSpan(javax.swing.text.GlyphView, int, int, javax.swing.text.TabExpander, float) */ @Override public float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x) { sync(v); Segment text = getText(v, p0, p1); int width = Utilities.getTabbedTextWidth(text, metrics, (int) x, e, p0); // Do not add HORIZONTAL_OFFSET here, it affects the text selection. return width; } /** * @see javax.swing.text.GlyphView.GlyphPainter#getHeight(javax.swing.text.GlyphView) */ @Override public float getHeight(GlyphView v) { sync(v); return metrics.getHeight() + VERTICAL_OFFSET; } /** * Fetches the ascent above the baseline for the glyphs * corresponding to the given range in the model. * * @see javax.swing.text.GlyphView.GlyphPainter#getAscent(javax.swing.text.GlyphView) */ @Override public float getAscent(GlyphView v) { sync(v); return metrics.getAscent(); } /** * Fetches the descent below the baseline for the glyphs * corresponding to the given range in the model. * * @see javax.swing.text.GlyphView.GlyphPainter#getDescent(javax.swing.text.GlyphView) */ @Override public float getDescent(GlyphView v) { sync(v); return metrics.getDescent() + VERTICAL_OFFSET; } /** * @see javax.swing.text.GlyphView.GlyphPainter#modelToView(javax.swing.text.GlyphView, int, javax.swing.text.Position.Bias, java.awt.Shape) */ @Override public Shape modelToView(GlyphView v, int pos, Position.Bias bias, Shape a) throws BadLocationException { sync(v); Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds(); int p0 = v.getStartOffset(); int p1 = v.getEndOffset(); TabExpander expander = v.getTabExpander(); Segment text; Rectangle r = new Rectangle(); if(pos == p1) { // The caller of this is left to right and borders a right to // left view, return our end location. r.setBounds(alloc.x + alloc.width, alloc.y, 0, metrics.getHeight() + VERTICAL_OFFSET); } else if ((pos >= p0) && (pos <= p1)) { // determine range to the left of the position text = getText(v, p0, pos); int width = Utilities.getTabbedTextWidth(text, metrics, alloc.x, expander, p0); r.setBounds(alloc.x + width, alloc.y, 0, metrics.getHeight() + VERTICAL_OFFSET); } else { throw new BadLocationException("modelToView - can't convert", p1); } // endif return r; } /** * Provides a mapping from the view coordinate space to the logical * coordinate space of the model. * * @see View#viewToModel(float, float, java.awt.Shape, javax.swing.text.Position.Bias[]) * @see javax.swing.text.GlyphView.GlyphPainter#viewToModel(javax.swing.text.GlyphView, float, float, java.awt.Shape, javax.swing.text.Position.Bias[]) */ @Override public int viewToModel(GlyphView v, float x, float y, Shape a, Position.Bias[] biasReturn) { sync(v); Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds(); int p0 = v.getStartOffset(); int p1 = v.getEndOffset(); TabExpander expander = v.getTabExpander(); Segment text = getText(v, p0, p1); int offs = Utilities.getTabbedTextOffset(text, metrics, alloc.x, (int) x, expander, p0); int retValue = p0 + offs; if(retValue == p1) { // No need to return backward bias as GlyphPainter1 is used for // ltr text only. retValue--; } biasReturn[0] = Position.Bias.Forward; return retValue; } /** * Determines the best location (in the model) to break * the given view. * This method attempts to break on a whitespace * location. If a whitespace location can't be found, the * nearest character location is returned. * * @see View#breakView * @see javax.swing.text.GlyphView.GlyphPainter#getBoundedPosition(javax.swing.text.GlyphView, int, float, float) */ @Override public int getBoundedPosition(GlyphView v, int p0, float x, float len) { sync(v); TabExpander expander = v.getTabExpander(); Segment s = v.getText(p0, v.getEndOffset()); int index = Utilities.getTabbedTextOffset(s, metrics, (int)x, (int)(x+len), expander, p0, false); int p1 = p0 + index; return p1; } /** * Synchronize this painter with the current state of the view. * * @param v Sync to this view. */ @SuppressWarnings("deprecation") void sync(GlyphView v) { Font f = v.getFont(); if ((metrics == null) || (! f.equals(metrics.getFont()))) { // fetch a new FontMetrics Toolkit kit; Component c = v.getContainer(); if (c != null) { kit = c.getToolkit(); } else { kit = Toolkit.getDefaultToolkit(); } metrics = kit.getFontMetrics(f); } } /** * Much of this code is copied from GlyphPainter1's implementation. * * @see javax.swing.text.GlyphView.GlyphPainter#paint(javax.swing.text.GlyphView, java.awt.Graphics, java.awt.Shape, int, int) */ @Override public void paint(GlyphView v, Graphics g, Shape a, int p0, int p1) { sync(v); Segment text; TabExpander expander = v.getTabExpander(); Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds(); // determine the x coordinate to render the glyphs int x = alloc.x; int p = v.getStartOffset(); if (p != p0) { text = v.getText(p, p0); int width = Utilities.getTabbedTextWidth(text, metrics, x, expander, p); x += width; } // endif // determine the y coordinate to render the glyphs int y = alloc.y + metrics.getHeight() - metrics.getDescent(); // Calculate the background highlight, it gets painted first. Color bg = TwoToneTextPane.getTwoToneColor(v.getElement().getAttributes()); Color fg = g.getColor(); if (bg == null) { // No color set, guess black or white float[] hsb = Color.RGBtoHSB(fg.getRed(), fg.getGreen(), fg.getBlue(), null); bg = hsb[2] > 0.7 ? Color.BLACK : Color.WHITE; } // endif g.setColor(bg); // render the glyphs, first in bg highlight, then in fg text = v.getText(p0, p1); g.setFont(metrics.getFont()); Utilities.drawTabbedText(text, x + HORIZONTAL_OFFSET, y + VERTICAL_OFFSET, g, expander, p0); g.setColor(fg); Utilities.drawTabbedText(text, x, y, g, expander, p0); } } /*--------------------------------------------------------------------------------------------- * ColorConstants Inner Class *-------------------------------------------------------------------------------------------*/ /** * A attribute name class that implements the proper interfaces */ public static class ColorConstants implements AttributeSet.ColorAttribute, AttributeSet.CharacterAttribute { /** * Name of the constant */ String name; /** * Create a new ColorConstants * * @param aName The name of the new color constant. */ protected ColorConstants(String aName) { name = aName; } } }