001 ///////////////////////////////////////////////// 002 //This file is part of Sears project. 003 //Subtitle Editor And Re-Synch 004 //A tool to easily modify and resynch movies subtitles. 005 ///////////////////////////////////////////////// 006 //This program is free software; 007 //you can redistribute it and/or modify it under the terms 008 //of the GNU General Public License 009 //as published by the Free Software Foundation; 010 //either version 2 of the License, or (at your option) any later version. 011 ///////////////////////////////////////////////// 012 //Sears project is available under sourceforge 013 //at adress: http://sourceforge.net/projects/sears/ 014 //Copyright (C) 2005 Booba Skaya 015 //Mail: booba.skaya@gmail.com 016 ///////////////////////////////////////////////// 017 018 // some suggestions about this class: floriaen@gmail.com 019 020 package sears.search.gui; 021 022 import java.awt.AlphaComposite; 023 import java.awt.BasicStroke; 024 import java.awt.Color; 025 import java.awt.Composite; 026 import java.awt.Font; 027 import java.awt.Graphics2D; 028 import java.awt.Point; 029 import java.awt.Rectangle; 030 import java.awt.RenderingHints; 031 import java.awt.Shape; 032 import java.awt.Stroke; 033 import java.awt.geom.Rectangle2D; 034 import java.awt.geom.RoundRectangle2D; 035 036 import sears.gui.glassPane.element.GPShape; 037 038 /** 039 * <br>A <tt>PillShape</tt> is a rounded bordered rectangle with text inside. 040 */ 041 public class PillShape implements GPShape, Cloneable { 042 043 // DEVELOPPER: use for AffineTransform 044 //protected static final int SCALE = 2; 045 046 // CONSTANTS: 047 // ( EXPANDED % 2 ) must be equals to 0 048 protected static final int EXPANDED = 6; 049 /** expanded height factor */ 050 protected static final int EXPANDED_HEIGHT = EXPANDED + 4; 051 /** expanded width factor */ 052 protected static final int EXPANDED_WIDTH = EXPANDED; 053 054 protected static final float DECAY = 3.5f; 055 /** arc factor, use or the rounded rectangle*/ 056 protected static final int ROUNDED_ARC = 12; 057 /** stroke use for line limit */ 058 protected static final float BORDER_STROKE = 2.2f; 059 /** pill foreground color */ 060 protected static final Color PILL_FOREGROUND_COLOR = Color.WHITE; 061 /** pill background color */ 062 protected static final Color PILL_BACKGROUND_COLOR = new Color(255, 165, 0); 063 064 // use to draw ... 065 protected static final Stroke STROKE = new BasicStroke(BORDER_STROKE); 066 protected static final Composite ALPHA_COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.15f ); 067 068 // see TextLayout#getBlackBoxBounds() method: 069 private Rectangle bounds; 070 private String text; 071 072 private Shape pill; 073 private Shape shadow; 074 075 // graphics context coordinates: 076 private int x; 077 private int y; 078 079 private Font font; 080 081 // DEVELOPPER: Carefull this variable could be null, 082 // you must used their accessor instead of its directly 083 private Color backgroundColor = null; 084 private Color foregroundColor = null; 085 086 /** 087 * Creates a new instance of <code>PillShape</code> by passing ... 088 * 089 * <p><b>WARNING:</b> 090 * <br><i>This class is a beta class, so there's some limitation:</i> 091 * <br>The parameter <code>bounds</code> must be the bounds of the <code>text</code> string 092 * <br>There's no control of any sort about this coherence, so use at your own risk. 093 * 094 * @param font the font 095 * @param bounds the text bounds 096 * @param text the text 097 * @throws NullPointerException if at least one of the given parameters is <tt>null</tt> 098 */ 099 public PillShape(Font font, Rectangle bounds, String text) { 100 setData(font, bounds, text); 101 translate(0,0); 102 initPillElement(); 103 } 104 105 /** 106 * Sets the background color for the pill 107 * @param background the background color, if <tt>null</tt> the default background color will be used 108 */ 109 public void setBackground(Color background) { 110 backgroundColor = background; 111 } 112 113 /** 114 * Sets the foreground color for the pill 115 * <br>Foreground color is used for the text inside and pill's border 116 * @param foreground the foreground color, if <tt>null</tt> the default foreground color will be used 117 */ 118 public void setForeground(Color foreground) { 119 foregroundColor = foreground; 120 } 121 122 /** 123 * Gets the foreground color 124 * @return the foreground color 125 */ 126 public Color getForeground() { 127 if( foregroundColor == null ) { 128 foregroundColor = PILL_FOREGROUND_COLOR; 129 } 130 return foregroundColor; 131 } 132 133 /** 134 * Gets the background color 135 * @return the foreground color 136 */ 137 public Color getBackground() { 138 if( backgroundColor == null ) { 139 backgroundColor = PILL_BACKGROUND_COLOR; 140 } 141 return backgroundColor; 142 } 143 144 /** 145 * Sets the data used by this object 146 * @param font the font to rendering the text inside the pill 147 * @param bounds the text's bounds 148 * @param text the text to display in the pill 149 * @throws NullPointerException if at least one of the parameters is <tt>null</tt> 150 */ 151 private void setData(Font font, Rectangle bounds, String text) { 152 if( font == null || bounds == null || text == null ) { 153 throw new NullPointerException("An argument given in parameters is null"); 154 } 155 this.font = font; 156 this.bounds = bounds; 157 this.text = text; 158 } 159 160 /** 161 * Constructs and initializes all the elements that is needed to draw the pill 162 */ 163 private void initPillElement() { 164 // blacBoxBounds isn't null: 165 Rectangle2D baseBounds = bounds; 166 // PILL: rounded bounds: 167 RoundRectangle2D roundedScaledBaseBounds = new RoundRectangle2D.Float(); 168 roundedScaledBaseBounds.setRoundRect( 169 baseBounds.getX() - EXPANDED_WIDTH/2, 170 baseBounds.getY() - baseBounds.getHeight() - EXPANDED_HEIGHT/2, 171 baseBounds.getWidth() + EXPANDED_WIDTH, 172 baseBounds.getHeight() + EXPANDED_HEIGHT, 173 // arc 174 ROUNDED_ARC, ROUNDED_ARC); 175 176 // shadow 177 RoundRectangle2D roundedShadow = (RoundRectangle2D) roundedScaledBaseBounds.clone(); 178 roundedShadow.setRoundRect( 179 roundedShadow.getX() - DECAY/2, 180 roundedShadow.getY() - DECAY/2, 181 roundedShadow.getWidth() + DECAY, 182 roundedShadow.getHeight() + DECAY, 183 roundedShadow.getArcWidth(), 184 roundedShadow.getArcHeight()); 185 186 // INIT VARIABLES: 187 pill = roundedScaledBaseBounds; 188 shadow = roundedShadow; 189 } 190 191 /** 192 * Returns a <code>RoundRectangle2D</code> object, represents the real bounds of this shape 193 * @see sears.gui.glassPane.element.GPShape#getBounds() 194 */ 195 public Shape getBounds() { 196 return pill; 197 } 198 199 /* 200 * (non-Javadoc) 201 * @see sears.gui.glassPane.element.GPShape#paint(java.awt.Graphics2D) 202 */ 203 public void paint(Graphics2D gr) { 204 if( gr != null ) { 205 Color grColor = gr.getColor(); 206 Composite defaultComposite = gr.getComposite(); 207 RenderingHints defaultRendering = gr.getRenderingHints(); 208 Stroke defaultStroke = gr.getStroke(); 209 // move graphics to location: 210 gr.translate(x, y); 211 // PAINT: 212 gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); 213 // paint shadow 214 gr.setComposite(ALPHA_COMPOSITE); 215 gr.setPaint(Color.BLACK); 216 gr.fill(shadow); 217 gr.setComposite(defaultComposite); 218 // paint pill's shape base 219 gr.setColor(getBackground()); 220 gr.fill(pill); 221 // paint inside text: 222 gr.setColor(getForeground()); 223 //gr.draw(blacBoxBounds); 224 gr.setFont(this.font); 225 gr.drawString(text, (int) bounds.x, 226 (int) bounds.y); 227 // paint borders: 228 gr.setStroke(new BasicStroke(BORDER_STROKE)); 229 gr.draw(pill); 230 // return the graphics state: 231 gr.setColor(grColor); 232 gr.setRenderingHints(defaultRendering); 233 gr.setStroke(defaultStroke); 234 } 235 } 236 237 /* 238 * (non-Javadoc) 239 * @see sears.gui.glassPane.element.GPShape#translate(int, int) 240 */ 241 public void translate(int x, int y) { 242 this.x = x; 243 this.y = y; 244 } 245 246 /* 247 * (non-Javadoc) 248 * @see sears.gui.glassPane.element.GPShape#translate(java.awt.Point) 249 */ 250 // UPDATE GPSHAPE Javadoc 251 public void translate(Point point) { 252 if( point != null ) { 253 translate(point.x, point.y); 254 } else { 255 translate(0, 0); 256 } 257 } 258 259 // ------------- 260 // CLONE METHODS 261 // ------------- 262 263 /** 264 * Calls the super method and catch the clone exception if occurs. 265 * <br>The result of catching exception is that a null object could be returned 266 * 267 * @return a clone <code>Object</code> or null if the super method failed to clone the object 268 * @see java.lang.Object#clone() 269 */ 270 protected Object clone() { 271 Object clone = null; 272 try{ 273 clone = super.clone(); 274 } catch(Exception e) { 275 // nothing is done 276 // clone method return null 277 } 278 return clone; 279 } 280 281 /** 282 * Gets a clone of the <code>PillShape</code> object 283 * @return a <code>PillShape</code> object or null if clone() method failed 284 */ 285 protected PillShape getClone() { 286 return (PillShape) clone(); 287 } 288 289 /** 290 * Gets an instance of the object with a new data set 291 * <br>Optimization... 292 * @param newFont the font to associate to the pill 293 * @param newBounds the text bounds 294 * @param newText the text 295 * @param newLocation the pill location 296 * @return a <code>Pill</code> object 297 */ 298 public PillShape getPillInstance(Font newFont, Rectangle newBounds, String newText, Point newLocation) { 299 PillShape clone = null; 300 301 if( newFont.equals(font) && newText.contentEquals(text) && bounds != null ) { 302 clone = this; 303 translate(newLocation.x + (newBounds.x - bounds.x), newLocation.y); 304 } else { 305 // TRY TO CLONE THIS: 306 clone = getClone(); 307 if( clone != null ) { 308 clone.setData(newFont, newBounds, newText); 309 clone.initPillElement(); 310 } else { 311 clone = new PillShape(newFont, newBounds, newText); 312 } 313 clone.translate(newLocation); 314 } 315 return clone; 316 } 317 }