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    }