/* * (C) 2004 - Geotechnical Software Services * * This code is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ package no.geosoft.cc.graphics; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.lang.ref.WeakReference; import java.awt.BasicStroke; import java.awt.Stroke; import java.awt.Color; import java.awt.Font; import java.awt.Paint; import java.awt.GradientPaint; import java.awt.TexturePaint; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.InputStream; import java.io.BufferedInputStream; import java.io.FileInputStream; import javax.imageio.ImageIO; /** * Graphics object apparence properties. * <p> * GStyle can be set on GObject, GSegment and GPositionals (GText, GImage, * GComponent). It is optional, and when unset the style of the parent * component is applied. This is also the case for the individual properties * of the GStyle. * <p> * Example: * * <pre> * // Define a style with foreground and background color set * GStyle style1 = new GStyle(); * style1.setForegroundColor (Color.RED); * style1.setBackgroundColor (Color.BLUE); * * // Create an object and apply style1. * // It will have foreground color RED and background color BLUE * GObject object1 = new GObject(); * object1.setStyle (style1); * * // Create a sub object without style. style1 is inherited from parent * // It will have foreground color RED and background color BLUE * GObject object2 = new GObject(); * object1.add (object2); * * // Define new style with foreground color set * GStyle style2 = new GStyle() * style2.setForegroundColor (Color.YELLOW); * * // Create a sub object and apply style2. * // It will have foreground color YELLOW and background color BLUE * GObject object3 = new GObject(); * object3.setStyle (style2); * object1.add (object3); * </pre> * * * @author <a href="mailto:info@geosoft.no">GeoSoft</a> */ public class GStyle implements Cloneable { private static final int MASK_FOREGROUNDCOLOR = 1 << 0; private static final int MASK_BACKGROUNDCOLOR = 1 << 1; private static final int MASK_LINEWIDTH = 1 << 2; private static final int MASK_FONT = 1 << 3; private static final int MASK_FILLPATTERN = 1 << 4; private static final int MASK_LINESTYLE = 1 << 5; private static final int MASK_CAPSTYLE = 1 << 6; private static final int MASK_JOINSTYLE = 1 << 7; private static final int MASK_ANTIALIAS = 1 << 8; private static final int MASK_GRADIENT = 1 << 9; public static final int LINESTYLE_SOLID = 1; public static final int LINESTYLE_DASHED = 2; public static final int LINESTYLE_DOTTED = 3; public static final int LINESTYLE_DASHDOT = 4; public static final int LINESTYLE_INVISIBLE = 5; public static final int FILL_NONE = 0; public static final int FILL_SOLID = 1; public static final int FILL_10 = 2; public static final int FILL_25 = 3; public static final int FILL_50 = 4; public static final int FILL_75 = 5; public static final int FILL_HORIZONTAL = 6; public static final int FILL_VERTICAL = 7; public static final int FILL_DIAGONAL = 8; private final Collection listeners_; private int validMask_; private Color foregroundColor_; private Color backgroundColor_; private int lineWidth_; private float dashPattern_[]; private float dashOffset_; private boolean isLineVisible_; private Font font_; private BufferedImage fillPattern_; private int fillWidth_; private int fillHeight_; private int fillData_[]; private Stroke stroke_; // Java2D repr of line width and style private Paint paint_; // Java2D repr of fill pattern private int capStyle_; private int joinStyle_; private float miterLimit_; private boolean isAntialiased_; private Color gradientColor1_; private Color gradientColor2_; /** * Create a new style object. The style is initially empty, individual * settings are only valid if explicitly set. */ public GStyle() { listeners_ = new ArrayList(); // Flag everything setting as invalid validMask_ = 0; // Default settings for all styles foregroundColor_ = Color.black; backgroundColor_ = null; lineWidth_ = 1; font_ = Font.decode ("dialog"); dashPattern_ = null; fillPattern_ = null; fillData_ = null; paint_ = null; capStyle_ = BasicStroke.CAP_ROUND; joinStyle_ = BasicStroke.JOIN_ROUND; miterLimit_ = (float) 0.0; dashOffset_ = (float) 0.0; isLineVisible_ = true; isAntialiased_ = true; gradientColor1_ = null; gradientColor2_ = null; } /** * Create a new style object based on this. * * @return Clone of this style object. */ public Object clone() { GStyle style = new GStyle(); style.validMask_ = validMask_; style.foregroundColor_ = null; if (foregroundColor_ != null) { style.foregroundColor_ = new Color (foregroundColor_.getRed(), foregroundColor_.getGreen(), foregroundColor_.getBlue(), foregroundColor_.getAlpha()); } style.backgroundColor_ = null; if (backgroundColor_ != null) { style.backgroundColor_ = new Color (backgroundColor_.getRed(), backgroundColor_.getGreen(), backgroundColor_.getBlue(), backgroundColor_.getAlpha()); } style.lineWidth_ = lineWidth_; style.font_ = null; if (font_ != null) { style.font_ = new Font (font_.getName(), font_.getStyle(), font_.getSize()); } style.dashPattern_ = null; if (dashPattern_ != null) { style.dashPattern_ = new float[dashPattern_.length]; for (int i = 0; i < dashPattern_.length; i++) style.dashPattern_[i] = dashPattern_[i]; } style.fillPattern_ = null; // Lazy initialized style.fillData_ = null; if (fillData_ != null) { style.fillData_ = new int[fillData_.length]; for (int i = 0; i < fillData_.length; i++) style.fillData_[i] = fillData_[i]; } style.capStyle_ = capStyle_; style.joinStyle_ = joinStyle_; style.miterLimit_ = miterLimit_; style.dashOffset_ = dashOffset_; style.isLineVisible_ = isLineVisible_; style.isAntialiased_ = isAntialiased_; style.gradientColor1_ = gradientColor1_; style.gradientColor2_ = gradientColor2_; return style; } /** * Update this style based on specified style. Style elements * explicitly set in the specified style is copied to this style, * other elements are ignored. * * @param style Style to update with. */ void update (GStyle style) { if (style.isValid (MASK_FOREGROUNDCOLOR)) setForegroundColor (style.foregroundColor_); if (style.isValid (MASK_BACKGROUNDCOLOR)) setBackgroundColor (style.backgroundColor_); if (style.isValid (MASK_LINEWIDTH)) setLineWidth (style.lineWidth_); if (style.isValid (MASK_FONT)) setFont (style.font_); if (style.isValid (MASK_FILLPATTERN)) { if (style.fillData_ != null) setFillPattern (style.fillWidth_, style.fillHeight_, style.fillData_); else setFillPattern (style.fillPattern_); } if (style.isValid (MASK_LINESTYLE)) { setLineStyle (style.dashPattern_); isLineVisible_ = style.isLineVisible_; } if (style.isValid (MASK_CAPSTYLE)) setCapStyle (style.capStyle_); if (style.isValid (MASK_JOINSTYLE)) setJoinStyle (style.joinStyle_); if (style.isValid (MASK_ANTIALIAS)) setAntialiased (style.isAntialiased_); if (style.isValid (MASK_GRADIENT)) setGradient (style.gradientColor1_, style.gradientColor2_); } /** * Set the specified style element to valid. * * @param validMask Identifier of style element to set (MASK_...) */ private void setValid (int validMask) { validMask_ |= validMask; } /** * Invalidate the specified style element. * * @param validMask Identifier of style element to invalidate (MASK_...) */ private void setInvalid (int validMask) { validMask_ &= ~validMask; } /** * Check if a specified style element is valid. * * @param validMask Identifier of style element to check (MASK_...) * @return True if it is valid, false otherwise. */ private boolean isValid (int validMask) { return (validMask_ & validMask) != 0; } /** * Set foreground color of this style. * * @param foregroundColor New foreground color. */ public void setForegroundColor (Color foregroundColor) { if (foregroundColor == null) foregroundColor = Color.black; foregroundColor_ = foregroundColor; setValid (MASK_FOREGROUNDCOLOR); // The fill pattern might have become invalid if (fillData_ != null) fillPattern_ = null; // Notify all owners notifyListeners(); } /** * Unset foreground color of this style. */ public void unsetForegroundColor() { foregroundColor_ = null; setInvalid (MASK_FOREGROUNDCOLOR); // The fill pattern might hav become invalid if (fillData_ != null) fillPattern_ = null; // Notify all owners notifyListeners(); } /** * Return the current foreground color of this style. * The element is not in use if it is invalid. * * @return Current foreground color. */ public Color getForegroundColor() { return foregroundColor_; } /** * Set background color. * * @param backgroundColor New background color. */ public void setBackgroundColor (Color backgroundColor) { backgroundColor_ = backgroundColor; setValid (MASK_BACKGROUNDCOLOR); // The fill pattern might hav become invalid if (fillData_ != null) fillPattern_ = null; // Notify all owners notifyListeners(); } /** * Unset background color. */ public void unsetBackgroundColor() { backgroundColor_ = null; setInvalid (MASK_BACKGROUNDCOLOR); // The fill pattern might hav become invalid if (fillData_ != null) fillPattern_ = null; // Notify all owners notifyListeners(); } /** * Return current background color of this style. * The element is not in use if it is invalid. * * @return Current background color of this style. */ public Color getBackgroundColor() { return backgroundColor_; } /** * Set line width. * * @param lineWidth New line width. */ public void setLineWidth (int lineWidth) { if (lineWidth < 1) lineWidth = 1; lineWidth_ = lineWidth; setValid (MASK_LINEWIDTH); stroke_ = null; // Notify all owners notifyListeners(); } /** * Unset line width. */ public void unsetLineWidth() { setInvalid (MASK_LINEWIDTH); stroke_ = null; // Notify all owners notifyListeners(); } /** * Return current line width of this style. * The element is not in use if it is invalid. * * @return Current line width of this style. */ public int getLineWidth() { return lineWidth_; } /** * Set font of this style. * * @param font New font. */ public void setFont (Font font) { if (font == null) font = Font.decode ("dialog"); font_ = font; setValid (MASK_FONT); // Notify all owners notifyListeners(); } /** * Unset font of this style. */ public void unsetFont() { setInvalid (MASK_FONT); // Notify all owners notifyListeners(); } /** * Return current font of this style. * The element is not in use if it is invalid. * * @return Current font of this style. */ public Font getFont() { return font_; } /** * Set line end cap style of this style. One of BasicStroke.CAP_ROUND * (default), BasicStroke.CAP_BUTT, or BasicStroke.CAP_SQUARE. * * @param capStyle New line end cap style. */ public void setCapStyle (int capStyle) { capStyle_ = capStyle; setValid (MASK_CAPSTYLE); stroke_ = null; // Notify all owners notifyListeners(); } /** * Unset cap style of this style. */ public void unsetCapStyle() { setInvalid (MASK_CAPSTYLE); // Notify all owners notifyListeners(); } /** * Return current line end cap style of this style. * The element is not in use if it is invalid. * * @return Current line end cap style of this style. */ public int getCapStyle() { return capStyle_; } /** * Set line end join style of this style. * One of BasicStroke.JOIN_BEVEL, BasicStroke.JOIN_MITTER or * BasicStroke.JOIN_ROUND (default). * * @param joinStyle New join style. */ public void setJoinStyle (int joinStyle) { joinStyle_ = joinStyle; setValid (MASK_JOINSTYLE); stroke_ = null; // Notify all owners notifyListeners(); } /** * Unset join style of this style. */ public void unsetJoinStyle() { setInvalid (MASK_JOINSTYLE); // Notify all owners notifyListeners(); } /** * Return current join style of this style. * The element is not in use if it is invalid. * * @return Current join style of this style. */ public int getJoinStyle() { return joinStyle_; } /** * Set antialising flag of this style. * * @param isAntialiased Antialiasing on (true) or off (false) (default). */ public void setAntialiased (boolean isAntialiased) { isAntialiased_ = isAntialiased; setValid (MASK_ANTIALIAS); // Notify all owners notifyListeners(); } /** * Unset antialias flag. */ public void unsetAntialias() { setInvalid (MASK_ANTIALIAS); // Notify all owners notifyListeners(); } /** * Return current antialiasing setting of this style. * The element is not in use if it is invalid. * * @return True if antialiasing on, false otherwise. */ public boolean isAntialiased() { return isAntialiased_; } /** * TODO: This code is experimental and should not yet be used. * * @param color1 * @param color2 */ public void setGradient (Color color1, Color color2) { gradientColor1_ = color1; gradientColor2_ = color2; setValid (MASK_GRADIENT); paint_ = null; // Notify all owners notifyListeners(); } public void unsetGradient() { setInvalid (MASK_GRADIENT); // Notify all owners notifyListeners(); } /** * Set predefined fill pattern of this style. * * @param fillType New fill pattern. */ public void setFillPattern (int fillType) { int width; int height; switch (fillType) { case FILL_NONE : width = 0; height = 0; break; case FILL_SOLID : width = 1; height = 1; break; case FILL_10 : width = 3; height = 3; break; case FILL_25 : width = 2; height = 2; break; case FILL_50 : width = 2; height = 2; break; case FILL_75 : width = 2; height = 2; break; case FILL_HORIZONTAL : width = 1; height = 2; break; case FILL_VERTICAL : width = 2; height = 1; break; case FILL_DIAGONAL : width = 3; height = 3; break; default : return; // Unknown fill type } int data[] = new int [width * height]; // Set bits specified switch (fillType) { case FILL_SOLID : case FILL_10 : case FILL_25 : case FILL_HORIZONTAL : case FILL_VERTICAL : data[0] = 1; break; case FILL_50 : data[0] = 1; data[3] = 1; break; case FILL_75 : data[1] = 1; data[2] = 1; data[3] = 1; break; case FILL_DIAGONAL : data[0] = 1; data[4] = 1; data[8] = 1; break; } setFillPattern (width, height, data); } /** * Set custom fill pattern of this style. * * @param width Tile width. * @param height Tile height. * @param data Pattern data (0s and 1s indicating set/unset). */ public void setFillPattern (int width, int height, int data[]) { fillWidth_ = width; fillHeight_ = height; int size = width * height; fillData_ = size > 0 ? new int[size] : null; for (int i = 0; i < size; i++) fillData_[i] = data != null && data.length > i ? data[i] : 0; // Lazily created when needed fillPattern_ = null; paint_ = null; setValid (MASK_FILLPATTERN); // Notify all owners notifyListeners(); } /** * Set image as fill pattern. * * @param image Image to use as fill pattern. */ public void setFillPattern (BufferedImage image) { fillData_ = null; fillPattern_ = image; setValid (MASK_FILLPATTERN); paint_ = null; // Notify all owners notifyListeners(); } /** * Set image as fill pattern. * TODO: Cache the file name and create the image lazy * * @param fileName File name of image. */ public void setFillPattern (String fileName) { try { InputStream stream = new BufferedInputStream (new FileInputStream (fileName)); fillPattern_ = ImageIO.read (stream); paint_ = null; setValid (MASK_FILLPATTERN); // Notify all owners notifyListeners(); } catch (Exception exception) { exception.printStackTrace(); } } /** * Unset fill pattern. */ public void unsetFillPattern() { setInvalid (MASK_FILLPATTERN); // Notify all owners notifyListeners(); } /** * Return current fill pattern. * * @return Current fill pattern. */ public BufferedImage getFillPattern() { return fillPattern_; } /** * Set custom line style. Dash pattern consists of and array of leg * lengths of line on and line off respectively. * * @param dashPattern New dash pattern. */ public void setLineStyle (float dashPattern[]) { // Copy dash pattern locally if (dashPattern != null) { dashPattern_ = new float[dashPattern.length]; System.arraycopy (dashPattern, 0, dashPattern_, 0, dashPattern.length); isLineVisible_ = true; } else dashPattern_ = null; setValid (MASK_LINESTYLE); stroke_ = null; // Create this lazily when needed // Notify all owners notifyListeners(); } /** * Set predefined line style of this style. * * @param lineStyle New line style. */ public void setLineStyle (int lineStyle) { float dashPattern[] = null; switch (lineStyle) { case LINESTYLE_SOLID : isLineVisible_ = true; break; case LINESTYLE_DASHED : dashPattern = new float[2]; dashPattern[0] = (float) 5.0; dashPattern[1] = (float) 5.0; break; case LINESTYLE_DOTTED : dashPattern = new float[2]; dashPattern[0] = (float) 2.0; dashPattern[1] = (float) 5.0; break; case LINESTYLE_DASHDOT : dashPattern = new float[4]; dashPattern[0] = (float) 8.0; dashPattern[1] = (float) 3.0; dashPattern[2] = (float) 2.0; dashPattern[3] = (float) 3.0; break; case LINESTYLE_INVISIBLE : isLineVisible_ = false; break; } setLineStyle (dashPattern); } /** * Unset line style. */ public void unsetLineStyle() { setInvalid (MASK_LINESTYLE); stroke_ = null; // Create this lazily when needed // Notify all owners notifyListeners(); } /** * Return current line style of this style. * The element is not in use if it is invalid. * * @return Current line style of this style. */ public float[] getLineStyle() { return dashPattern_; } /** * Create a Stroke object based on the line width, cap style, join style, * miter limit (0.0), dash pattern and dash offset (0.0) of this style. * * @return Stroke for this style. */ Stroke getStroke() { // Lazy creation if (stroke_ == null) { // Dash pattern is created based on a line width = 1. For wider // lines we increase the dash accordingly float[] dashPattern = null; if (dashPattern_ != null) { dashPattern = new float[dashPattern_.length]; for (int i = 0; i < dashPattern_.length; i++) dashPattern[i] = dashPattern_[i] * lineWidth_; } stroke_ = new BasicStroke (lineWidth_, capStyle_, joinStyle_, miterLimit_, dashPattern, dashOffset_); } return stroke_; } /** * Create a paint object based on the fill pattern of this style. * * @return Paint object for this style. */ Paint getPaint() { // Generate the image if (fillPattern_ == null && fillData_ != null) { // Create the image that represent the pattern fillPattern_ = new BufferedImage (fillWidth_, fillHeight_, BufferedImage.TYPE_INT_ARGB); // Put the data into the image int pointNo = 0; for (int i = 0; i < fillHeight_; i++) { for (int j = 0; j < fillWidth_; j++) { if (fillData_[pointNo] == 0 && backgroundColor_ != null) fillPattern_.setRGB (i, j, backgroundColor_.getRGB()); else if (fillData_[pointNo] != 0) fillPattern_.setRGB (i, j, foregroundColor_.getRGB()); pointNo++; } } paint_ = null; } // Generate the paint if (paint_ == null && fillPattern_ != null) { paint_ = new TexturePaint (fillPattern_, new Rectangle (0, 0, fillWidth_, fillHeight_)); } // TODO: Gradient color is not currently in use else if (paint_ == null && gradientColor1_ != null) { paint_ = new GradientPaint ((float) -100.0, (float) 0.0, gradientColor1_, (float) 100.0, (float) 0.0, gradientColor2_, true); } else if (backgroundColor_ != null) { paint_ = backgroundColor_; } return paint_; } float getMiterLimit() { return miterLimit_; } /** * Check if line is visible. Line is invisible if line style is set * to invisible. * * @return True if line is visible, false otherwise. */ boolean isLineVisible() { return isLineVisible_; } /** * Check if objects using this style appaer as filled? * * @return Return true if objects using this style appear as filled, * false otherwise. */ boolean isDefiningFill() { return fillPattern_ != null || fillData_ != null || backgroundColor_ != null; } /** * Notify all listeners about change in specified style. * * @param style Style that has changed. */ private void notifyListeners() { for (Iterator i = listeners_.iterator(); i.hasNext(); ) { WeakReference reference = (WeakReference) i.next(); GStyleListener listener = (GStyleListener) reference.get(); // If the listener has been GC'd, remove it from the list if (listener == null) i.remove(); else listener.styleChanged (this); } } /** * Add a listener to this style. When the style is changed, a * styleChanged() signal is sent to the listener. * * @param listener Style listener to add. */ void addListener (GStyleListener listener) { // Check if the listener is there already for (Iterator i = listeners_.iterator(); i.hasNext(); ) { WeakReference reference = (WeakReference) i.next(); if (reference.get() == listener) return; } // Add the listener listeners_.add (new WeakReference (listener)); } /** * Remove specified listener from this style. * * @param listener Style listener to remove. */ void removeListener (GStyleListener listener) { for (Iterator i = listeners_.iterator(); i.hasNext(); ) { WeakReference reference = (WeakReference) i.next(); if (reference.get() == listener) { i.remove(); break; } } } }