1 package freeguide.plugins.ui.horizontal.manylabels;
2 
3 import freeguide.common.lib.fgspecific.Application;
4 import freeguide.common.lib.fgspecific.data.TVProgramme;
5 import freeguide.common.lib.general.TemplateParser;
6 
7 import freeguide.common.plugininterfaces.IModuleReminder;
8 
9 import freeguide.plugins.ui.horizontal.manylabels.templates.HandlerProgrammeInfo;
10 
11 import java.awt.Color;
12 import java.awt.Graphics;
13 import java.awt.Graphics2D;
14 import java.awt.Insets;
15 import java.awt.Rectangle;
16 import java.awt.RenderingHints;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.GeneralPath;
20 
21 import java.io.StringWriter;
22 
23 import java.text.DateFormat;
24 
25 import java.util.Date;
26 import java.util.logging.Level;
27 
28 import javax.swing.BorderFactory;
29 import javax.swing.JComponent;
30 import javax.swing.JLabel;
31 import javax.swing.ToolTipManager;
32 import javax.swing.border.Border;
33 
34 /**
35  * Label for display programme info.
36  *
37  * @author Alex Buloichik (alex73 at zaval.org)
38  */
39 public class JLabelProgramme extends JLabel
40 {
41     protected static Border DEFAULT_BORDER;
42     protected static Border DEFAULT_FOCUS;
43     protected static Border MOVIE_BORDER;
44     protected static Border MOVIE_FOCUS;
45     protected static Border NEW_BORDER;
46     protected static Border NEW_FOCUS;
47     protected static Border INGUIDE_DEFAULT_BORDER;
48     protected static Border INGUIDE_DEFAULT_FOCUS;
49     protected static Border INGUIDE_MOVIE_BORDER;
50     protected static Border INGUIDE_MOVIE_FOCUS;
51     protected static Border INGUIDE_NEW_BORDER;
52     protected static Border INGUIDE_NEW_FOCUS;
53 
54     /** Standard reminder. */
55     protected static IModuleReminder REMINDER;
56 
57     /** Heart shape. */
58     protected final static Shape HEART_SHAPE;
59 
60     static
61     {
62         GeneralPath path = new GeneralPath(  );
63 
64         path.moveTo( 300, 200 );
65 
66         path.curveTo( 100, 0, 0, 400, 300, 580 );
67 
68         path.moveTo( 300, 580 );
69 
70         path.curveTo( 600, 400, 500, 0, 300, 200 );
71 
72         HEART_SHAPE = path;
73 
74     }
75 
76     /** Programme for current label. */
77     final protected TVProgramme programme;
78 
79     /** Parent controller. */
80     final protected HorizontalViewer controller;
81 
82     /** Need to draw heart. */
83     protected boolean isDrawHeart;
84 
85     /** Cached tooltip text. */
86     private String tooltip;
87     protected final boolean moveNames;
88     protected final DateFormat timeFormat;
89 
90     /**
91      * Creates a new JLabelProgramme object.
92      *
93      * @param programme DOCUMENT ME!
94      * @param main DOCUMENT ME!
95      * @param moveNames DOCUMENT ME!
96      * @param timeFormat DOCUMENT ME!
97      */
JLabelProgramme( final TVProgramme programme, final HorizontalViewer main, final boolean moveNames, final DateFormat timeFormat )98     public JLabelProgramme(
99         final TVProgramme programme, final HorizontalViewer main,
100         final boolean moveNames, final DateFormat timeFormat )
101     {
102         this.timeFormat = timeFormat;
103         this.programme = programme;
104         this.controller = main;
105         this.moveNames = moveNames;
106         setText( getTitle( programme ) );
107         setupColors( main.getDate() );
108         setupHeart(  );
109         setOpaque( true );
110         setFocusable( true );
111         addMouseListener( main.handlers.labelProgrammeMouseListener );
112         addFocusListener( main.handlers.labelProgrammeFocusListener );
113 
114         setActionMap( main.handlers.labelProgrammeActionMap );
115         setInputMap(
116             JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
117             main.handlers.labelProgrammeInputMap );
118 
119         ToolTipManager tipManager = ToolTipManager.sharedInstance(  );
120 
121         // Register this component for tooltip management.
122         // This is normally done by setting the tooltip text,
123         // but we want to "lazily" evaluate tooltip text--we defer
124         // creation of the tip text until the tip is actually needed.
125         // (see the getToolTipText() method below)
126         tipManager.registerComponent( this );
127     }
128 
129     /**
130      * Generate title for label.
131      *
132      * @param programme
133      *
134      * @return title text
135      */
getTitle( final TVProgramme programme )136     protected String getTitle( final TVProgramme programme )
137     {
138         final StringBuffer toAppendTo = new StringBuffer(  );
139 
140         long programmeStart = programme.getStart(  );
141 
142         String programmeTitle = programme.getTitle(  );
143 
144         String programmeSubTitle = programme.getSubTitle(  );
145 
146         String programmeStarString = programme.getStarString(  );
147 
148         if( controller.config.displayTime && ( timeFormat != null ) )
149         {
150             toAppendTo.append(
151                 timeFormat.format( new Date( programmeStart ) ) ).append( " " );
152         }
153 
154         toAppendTo.append( programmeTitle );
155 
156         if( programmeSubTitle != null )
157         {
158             toAppendTo.append( ": " ).append( programmeSubTitle );
159 
160         }
161 
162         if( programme.getIsMovie(  ) && ( programmeStarString != null ) )
163         {
164             toAppendTo.append( " " ).append( programmeStarString );
165 
166         }
167 
168         if( programme.getPreviouslyShown(  ) )
169         {
170             toAppendTo.append( " " );
171 
172             toAppendTo.append(
173                 Application.getInstance(  ).getLocalizedMessage( "r" ) );
174 
175         }
176 
177         return toAppendTo.toString(  );
178     }
179 
180     /**
181      * Get programme for label.
182      *
183      * @return programme
184      */
getProgramme( )185     public TVProgramme getProgramme(  )
186     {
187         return programme;
188     }
189 
190     /**
191      * Setup colors for current label.
192      */
setupColors( final long theDate )193     public void setupColors( final long theDate )
194     {
195         final boolean isMovie = programme.getIsMovie( );
196         final boolean isNew   = programme.isAirDay( theDate );
197         final boolean isFocus = isFocusOwner(  );
198 
199         if( ( REMINDER != null ) && REMINDER.isSelected( programme ) )
200         {
201             setBackground( controller.config.colorTicked );
202             if ( isNew )
203             {
204                setBorder( isFocus ? INGUIDE_NEW_FOCUS : INGUIDE_NEW_BORDER );
205             }
206             else if ( isMovie )
207             {
208                setBorder( isFocus ? INGUIDE_MOVIE_FOCUS : INGUIDE_MOVIE_BORDER );
209             }
210             else
211             {
212                setBorder( isFocus ? INGUIDE_DEFAULT_FOCUS : INGUIDE_DEFAULT_BORDER );
213             }
214         }
215         else
216         {
217             if ( isNew )
218             {
219                setBackground( controller.config.colorNew );
220                setBorder( isFocus ? NEW_FOCUS : NEW_BORDER );
221             }
222             else if ( isMovie )
223             {
224                setBackground( controller.config.colorMovie );
225                setBorder( isFocus ? MOVIE_FOCUS : MOVIE_BORDER );
226             }
227             else
228             {
229                setBackground( controller.config.colorNonTicked );
230                setBorder( isFocus ? DEFAULT_FOCUS : DEFAULT_BORDER );
231             }
232         }
233     }
234 
235     /**
236      * Setup colors and borders using parent controller's config.
237      *
238      * @param main
239      */
setupLabel( final HorizontalViewer main )240     public static void setupLabel( final HorizontalViewer main )
241     {
242         // TODO change
243         IModuleReminder[] rems = Application.getInstance(  ).getReminders(  );
244 
245         if( rems.length > 0 )
246         {
247             REMINDER = rems[0];
248         }
249         else
250         {
251             REMINDER = null;
252         }
253 
254         DEFAULT_BORDER = BorderFactory.createCompoundBorder(
255                 BorderFactory.createLineBorder( Color.BLACK, 1 ),
256                 BorderFactory.createLineBorder( main.config.colorNonTicked, 2 ) );
257         DEFAULT_FOCUS = BorderFactory.createCompoundBorder(
258                 BorderFactory.createLineBorder( Color.BLUE, 2 ),
259                 BorderFactory.createLineBorder( main.config.colorNonTicked, 1 ) );
260 
261         MOVIE_BORDER = BorderFactory.createCompoundBorder(
262                 BorderFactory.createLineBorder( Color.BLACK, 1 ),
263                 BorderFactory.createLineBorder( main.config.colorMovie, 2 ) );
264         MOVIE_FOCUS = BorderFactory.createCompoundBorder(
265                 BorderFactory.createLineBorder( Color.BLUE, 2 ),
266                 BorderFactory.createLineBorder( main.config.colorMovie, 1 ) );
267 
268         NEW_BORDER = BorderFactory.createCompoundBorder(
269                 BorderFactory.createLineBorder( Color.BLACK, 1 ),
270                 BorderFactory.createLineBorder( main.config.colorNew, 2 ) );
271         NEW_FOCUS = BorderFactory.createCompoundBorder(
272                 BorderFactory.createLineBorder( Color.BLUE, 2 ),
273                 BorderFactory.createLineBorder( main.config.colorNew, 1 ) );
274 
275         INGUIDE_DEFAULT_BORDER = BorderFactory.createCompoundBorder(
276                 BorderFactory.createLineBorder( Color.BLACK, 1 ),
277                 BorderFactory.createLineBorder( main.config.colorTicked, 2 ) );
278         INGUIDE_DEFAULT_FOCUS = BorderFactory.createCompoundBorder(
279                 BorderFactory.createLineBorder( Color.BLUE, 2 ),
280                 BorderFactory.createLineBorder( main.config.colorTicked, 1 ) );
281 
282         INGUIDE_MOVIE_BORDER = BorderFactory.createCompoundBorder(
283                 BorderFactory.createLineBorder( Color.BLACK, 1 ),
284                 BorderFactory.createLineBorder( main.config.colorMovie, 2 ) );
285         INGUIDE_MOVIE_FOCUS = BorderFactory.createCompoundBorder(
286                 BorderFactory.createLineBorder( Color.BLUE, 2 ),
287                 BorderFactory.createLineBorder( main.config.colorMovie, 1 ) );
288 
289         INGUIDE_NEW_BORDER = BorderFactory.createCompoundBorder(
290                 BorderFactory.createLineBorder( Color.BLACK, 1 ),
291                 BorderFactory.createLineBorder( main.config.colorNew, 2 ) );
292         INGUIDE_NEW_FOCUS = BorderFactory.createCompoundBorder(
293                 BorderFactory.createLineBorder( Color.BLUE, 2 ),
294                 BorderFactory.createLineBorder( main.config.colorNew, 1 ) );
295 
296     }
297 
298     /**
299      * Setup need to draw heart for current label.
300      */
setupHeart( )301     protected void setupHeart(  )
302     {
303         if( REMINDER == null )
304         {
305             isDrawHeart = false;
306         }
307         else
308         {
309             freeguide.common.lib.fgspecific.selection.Favourite f =
310                 REMINDER.getFavourite( programme );
311             isDrawHeart = ( f != null );
312         }
313     }
314 
315     /**
316      * Paint component override.
317      *
318      * @param g DOCUMENT ME!
319      */
paintComponent( Graphics g )320     protected void paintComponent( Graphics g )
321     {
322         if( moveNames )
323         {
324             // Paint our own text, aligning to the left of the screen
325             g.setClip( this.getVisibleRect(  ) );
326 
327             // First paint background
328             g.setColor( this.getBackground(  ) );
329 
330             g.fillRect( 0, 0, this.getWidth(  ), this.getHeight(  ) );
331 
332             // Then set everything to draw the text
333             g.setFont( this.getFont(  ) );
334 
335             g.setColor( this.getForeground(  ) );
336 
337             // Compute the right place from the left border
338             int fontX = 0;
339 
340             Insets ins = this.getInsets(  );
341 
342             if( this.getBorder(  ) != null )
343             {
344                 fontX = ins.left;
345 
346             }
347 
348             if(
349                 ( g.getClipBounds(  ) != null )
350                     && ( fontX < g.getClipBounds(  ).x ) )
351             {
352                 fontX = g.getClipBounds(  ).x + ins.left;
353 
354             }
355 
356 	    Graphics2D g2d = (Graphics2D) g;
357 	    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // Anti-alias!
358 				 RenderingHints.VALUE_ANTIALIAS_ON);
359 
360             // now we now where, draw the text
361             g.drawString(
362                 getText(  ), fontX,
363                 ( ins.top
364                 + ( getHeight(  ) - ins.top - ins.bottom
365                 + g.getFontMetrics(  ).getAscent(  ) ) ) >> 1 );
366 
367         }
368         else
369         {
370             super.paintComponent( g );
371         }
372 
373         if( isDrawHeart )
374         {
375             Graphics2D graphics = (Graphics2D)g;
376             AffineTransform originalTransform = graphics.getTransform(  );
377 
378             graphics.setColor( Color.RED );
379 
380             // switch on anti-aliasing
381             graphics.setRenderingHint(
382                 RenderingHints.KEY_ANTIALIASING,
383                 RenderingHints.VALUE_ANTIALIAS_ON );
384 
385             // Scale and position appropriately--taking into account the borders
386             Rectangle bounds = HEART_SHAPE.getBounds(  );
387 
388             double scale = 0.45 * ( getHeight(  ) / bounds.getHeight(  ) );
389 
390             double right = getWidth(  ) - 2 - ( scale * bounds.getWidth(  ) );
391 
392             graphics.translate( right, 2 );
393 
394             graphics.scale( scale, scale );
395 
396             graphics.fill( HEART_SHAPE );
397 
398             graphics.setTransform( originalTransform );
399         }
400     }
401 
402     /**
403      * DOCUMENT_ME!
404      *
405      * @return DOCUMENT_ME!
406      */
getToolTipText( )407     public String getToolTipText(  )
408     {
409         if( !controller.config.displayTooltips )
410         {
411             return null;
412         }
413 
414         String tooltip = super.getToolTipText(  );
415 
416         if( tooltip != null )
417         {
418             return tooltip;
419         }
420 
421         if( ( this.tooltip != null ) )
422         {
423             return this.tooltip;
424         }
425 
426         try
427         {
428             final StringWriter out = new StringWriter(  );
429             TemplateParser parser =
430                 new TemplateParser(
431                     "resources/plugins/ui/horizontal/manylabels/templates/TemplateProgrammeTooltip.html" );
432             parser.process(
433                 new HandlerProgrammeInfo(
434                     controller.getLocalizer(  ), programme, timeFormat ), out );
435             this.tooltip = out.toString(  );
436         }
437         catch( Exception ex )
438         {
439             Application.getInstance(  ).getLogger(  )
440                        .log( Level.WARNING, "Error generate tooltip text", ex );
441         }
442 
443         return this.tooltip;
444     }
445 
446     /**
447      * DOCUMENT_ME!
448      *
449      * @param startMin DOCUMENT_ME!
450      * @param endMax DOCUMENT_ME!
451      *
452      * @return DOCUMENT_ME!
453      */
getMiddle( final long startMin, final long endMax )454     public long getMiddle( final long startMin, final long endMax )
455     {
456         long start = Math.max( getProgramme(  ).getStart(  ), startMin );
457         long end = Math.min( getProgramme(  ).getEnd(  ), endMax );
458 
459         return ( start + end ) / 2;
460     }
461 
462     /**
463      * DOCUMENT_ME!
464      *
465      * @param middleTime DOCUMENT_ME!
466      * @param startMin DOCUMENT_ME!
467      * @param endMax DOCUMENT_ME!
468      *
469      * @return DOCUMENT_ME!
470      */
isOverlap( final long middleTime, final long startMin, final long endMax )471     public boolean isOverlap(
472         final long middleTime, final long startMin, final long endMax )
473     {
474         long start = Math.max( getProgramme(  ).getStart(  ), startMin );
475         long end = Math.min( getProgramme(  ).getEnd(  ), endMax );
476 
477         return ( middleTime >= start ) && ( middleTime < end );
478     }
479 }
480