View Javadoc

1   /*
2    *  jDTAUS Core Utilities
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.core.text.util;
22  
23  import java.awt.Component;
24  import java.awt.Dimension;
25  import java.awt.GraphicsEnvironment;
26  import java.awt.GridBagConstraints;
27  import java.awt.GridBagLayout;
28  import java.awt.HeadlessException;
29  import java.awt.Insets;
30  import java.beans.PropertyChangeEvent;
31  import java.beans.PropertyChangeListener;
32  import java.lang.reflect.InvocationTargetException;
33  import java.util.Locale;
34  import javax.swing.JDialog;
35  import javax.swing.JLabel;
36  import javax.swing.JOptionPane;
37  import javax.swing.JPanel;
38  import javax.swing.SwingConstants;
39  import javax.swing.SwingUtilities;
40  import javax.swing.plaf.basic.BasicHTML;
41  import javax.swing.text.View;
42  import org.jdtaus.core.container.ContainerFactory;
43  import org.jdtaus.core.logging.spi.Logger;
44  import org.jdtaus.core.text.MessageEvent;
45  import org.jdtaus.core.text.MessageListener;
46  
47  /**
48   * {@code MessageListener} displaying messages using Swing's {@code JOptionPane}.
49   * <p>This implementation displays a dialog for each {@code MessageEvent} this {@code MessageListener} is notified
50   * about. Since a {@code MessageEvent} can hold multiple messages a maximum number of messages to display per event
51   * can be specified by property {@code maximumMessages} (defaults to 25).</p>
52   *
53   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
54   * @version $JDTAUS: SwingMessagePane.java 8786 2012-12-03 02:09:05Z schulte $
55   *
56   * @see #onMessage(MessageEvent)
57   */
58  public final class SwingMessagePane implements MessageListener
59  {
60      //--Dependencies------------------------------------------------------------
61  
62  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
63      // This section is managed by jdtaus-container-mojo.
64  
65      /**
66       * Gets the configured <code>Logger</code> implementation.
67       *
68       * @return The configured <code>Logger</code> implementation.
69       */
70      private Logger getLogger()
71      {
72          return (Logger) ContainerFactory.getContainer().
73              getDependency( this, "Logger" );
74  
75      }
76  
77      /**
78       * Gets the configured <code>Locale</code> implementation.
79       *
80       * @return The configured <code>Locale</code> implementation.
81       */
82      private Locale getLocale()
83      {
84          return (Locale) ContainerFactory.getContainer().
85              getDependency( this, "Locale" );
86  
87      }
88  
89  // </editor-fold>//GEN-END:jdtausDependencies
90  
91      //------------------------------------------------------------Dependencies--
92      //--Properties--------------------------------------------------------------
93  
94  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
95      // This section is managed by jdtaus-container-mojo.
96  
97      /**
98       * Gets the value of property <code>defaultResizable</code>.
99       *
100      * @return Default resizability of the message pane.
101      */
102     private java.lang.Boolean isDefaultResizable()
103     {
104         return (java.lang.Boolean) ContainerFactory.getContainer().
105             getProperty( this, "defaultResizable" );
106 
107     }
108 
109     /**
110      * Gets the value of property <code>defaultMaximumMessages</code>.
111      *
112      * @return Default maximum number of messages displayed per event.
113      */
114     private java.lang.Integer getDefaultMaximumMessages()
115     {
116         return (java.lang.Integer) ContainerFactory.getContainer().
117             getProperty( this, "defaultMaximumMessages" );
118 
119     }
120 
121     /**
122      * Gets the value of property <code>defaultColumns</code>.
123      *
124      * @return Default number of columns the preferred width of the message pane is computed with.
125      */
126     private java.lang.Integer getDefaultColumns()
127     {
128         return (java.lang.Integer) ContainerFactory.getContainer().
129             getProperty( this, "defaultColumns" );
130 
131     }
132 
133 // </editor-fold>//GEN-END:jdtausProperties
134 
135     //--------------------------------------------------------------Properties--
136     //--MessageListener---------------------------------------------------------
137 
138     /**
139      * {@inheritDoc}
140      * <p>This method uses Swing's {@link JOptionPane} to display messages given by the event. It will block the event
141      * dispatch thread until the user confirms the message pane.</p>
142      *
143      * @param event The event holding messages.
144      */
145     public void onMessage( final MessageEvent event )
146     {
147         if ( event != null )
148         {
149             try
150             {
151                 final Runnable r = new Runnable()
152                 {
153 
154                     public void run()
155                     {
156                         final JPanel panel = new JPanel();
157                         panel.setLayout( new GridBagLayout() );
158 
159                         for ( int i = 0, s0 = event.getMessages().length; i < s0 && i < getMaximumMessages(); i++ )
160                         {
161                             String text = event.getMessages()[i].getText( getLocale() );
162 
163                             if ( !BasicHTML.isHTMLString( text ) )
164                             {
165                                 text = "<html>" + HtmlEntities.escapeHtml( text ) + "</html>";
166                             }
167 
168                             final JLabel label = new HtmlLabel( getColumns() );
169                             label.setText( text );
170 
171                             final GridBagConstraints c = new GridBagConstraints();
172                             c.anchor = GridBagConstraints.NORTHWEST;
173                             c.fill = GridBagConstraints.BOTH;
174                             c.gridheight = 1;
175                             c.gridwidth = 1;
176                             c.gridx = 0;
177                             c.gridy = i;
178                             c.weightx = 1.0D;
179 
180                             panel.add( label, c );
181 
182                             if ( !( i + 1 < s0 && i + 1 < getMaximumMessages() ) )
183                             {
184                                 final JPanel p = new JPanel();
185                                 c.anchor = GridBagConstraints.SOUTH;
186                                 c.weighty = 1.0D;
187                                 c.gridy = i + 1;
188                                 panel.add( p, c );
189                             }
190                         }
191 
192                         JOptionPane optionPane = null;
193                         JDialog dialog = null;
194 
195                         switch ( event.getType() )
196                         {
197                             case MessageEvent.INFORMATION:
198                                 optionPane = new JOptionPane();
199                                 optionPane.setMessage( panel );
200                                 optionPane.setMessageType( JOptionPane.INFORMATION_MESSAGE );
201                                 dialog = optionPane.createDialog( getParent(), getInformationMessage( getLocale() ) );
202                                 dialog.setResizable( isResizable() );
203                                 dialog.setVisible( true );
204                                 dialog.dispose();
205                                 break;
206 
207                             case MessageEvent.NOTIFICATION:
208                                 optionPane = new JOptionPane();
209                                 optionPane.setMessage( panel );
210                                 optionPane.setMessageType( JOptionPane.INFORMATION_MESSAGE );
211                                 dialog = optionPane.createDialog( getParent(), getNotificationMessage( getLocale() ) );
212                                 dialog.setResizable( isResizable() );
213                                 dialog.setVisible( true );
214                                 dialog.dispose();
215                                 break;
216 
217                             case MessageEvent.WARNING:
218                                 optionPane = new JOptionPane();
219                                 optionPane.setMessage( panel );
220                                 optionPane.setMessageType( JOptionPane.WARNING_MESSAGE );
221                                 dialog = optionPane.createDialog( getParent(), getWarningMessage( getLocale() ) );
222                                 dialog.setResizable( isResizable() );
223                                 dialog.setVisible( true );
224                                 dialog.dispose();
225                                 break;
226 
227                             case MessageEvent.ERROR:
228                                 optionPane = new JOptionPane();
229                                 optionPane.setMessage( panel );
230                                 optionPane.setMessageType( JOptionPane.ERROR_MESSAGE );
231                                 dialog = optionPane.createDialog( getParent(), getErrorMessage( getLocale() ) );
232                                 dialog.setResizable( isResizable() );
233                                 dialog.setVisible( true );
234                                 dialog.dispose();
235                                 break;
236 
237                             default:
238                                 getLogger().warn( getUnknownMessageEventTypeMessage(
239                                     getLocale(), new Integer( event.getType() ) ) );
240 
241                         }
242                     }
243 
244                 };
245 
246                 if ( SwingUtilities.isEventDispatchThread() )
247                 {
248                     r.run();
249                 }
250                 else
251                 {
252                     SwingUtilities.invokeAndWait( r );
253                 }
254             }
255             catch ( final InterruptedException e )
256             {
257                 this.getLogger().error( e );
258             }
259             catch ( final InvocationTargetException e )
260             {
261                 this.getLogger().error( e );
262             }
263         }
264     }
265 
266     //---------------------------------------------------------MessageListener--
267     //--SwingMessagePane--------------------------------------------------------
268 
269     /** The current parent component to use when displaying messages. */
270     private Component parent;
271 
272     /** Maximum number of messages displayed per event. */
273     private Integer maximumMessages;
274 
275     /** Number of columns the preferred width of the message pane is computed with. */
276     private Integer columns;
277 
278     /** Flag indicating the message pane is resizable. */
279     private Boolean resizable;
280 
281     /**
282      * Creates a new {@code SwingMessagePane} instance taking the parent component to use when displaying messages.
283      *
284      * @param parent The parent component to use when displaying messages.
285      *
286      * @throws NullPointerException if {@code parent} is {@code null}.
287      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
288      */
289     public SwingMessagePane( final Component parent )
290     {
291         this( parent, Integer.MIN_VALUE, Integer.MIN_VALUE, false );
292     }
293 
294     /**
295      * Creates a new {@code SwingMessagePane} instance taking the parent component to use when displaying messages and
296      * the maximum number of messages displayed per event.
297      *
298      * @param parent The parent component to use when displaying messages.
299      * @param maximumMessages Maximum number of messages displayed per event.
300      *
301      * @throws NullPointerException if {@code parent} is {@code null}.
302      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
303      */
304     public SwingMessagePane( final Component parent, final int maximumMessages )
305     {
306         this( parent, maximumMessages, Integer.MIN_VALUE, false );
307     }
308 
309     /**
310      * Creates a new {@code SwingMessagePane} instance taking the parent component to use when displaying messages, the
311      * maximum number of messages displayed per event and a number of columns to compute the preferred width of the
312      * message pane with.
313      *
314      * @param parent The parent component to use when displaying messages.
315      * @param maximumMessages Maximum number of messages displayed per event.
316      * @param columns Number of columns the preferred width of the message pane should be computed with.
317      *
318      * @throws NullPointerException if {@code parent} is {@code null}.
319      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
320      *
321      * @since 1.10
322      */
323     public SwingMessagePane( final Component parent, final int maximumMessages, final int columns )
324     {
325         this( parent, maximumMessages, columns, false );
326     }
327 
328     /**
329      * Creates a new {@code SwingMessagePane} instance taking the parent component to use when displaying messages, the
330      * maximum number of messages displayed per event, a number of columns to compute the preferred width of the
331      * message pane with and a flag indicating the message pane is resizable.
332      *
333      * @param parent The parent component to use when displaying messages.
334      * @param maximumMessages Maximum number of messages displayed per event.
335      * @param columns Number of columns the preferred width of the message pane should be computed with.
336      * @param resizable {@code true}, if the message pane should be resizable; {@code false} if the message pane should
337      * have a fixed size.
338      *
339      * @throws NullPointerException if {@code parent} is {@code null}.
340      * @throws HeadlessException if this class is used in an environment not providing a keyboard, display, or mouse.
341      *
342      * @since 1.11
343      */
344     public SwingMessagePane( final Component parent, final int maximumMessages, final int columns,
345                              final boolean resizable )
346     {
347         if ( parent == null )
348         {
349             throw new NullPointerException( "parent" );
350         }
351 
352         if ( GraphicsEnvironment.isHeadless() )
353         {
354             throw new HeadlessException();
355         }
356 
357         this.parent = parent;
358 
359         if ( maximumMessages > 0 )
360         {
361             this.maximumMessages = new Integer( maximumMessages );
362         }
363         if ( columns > 0 )
364         {
365             this.columns = new Integer( columns );
366         }
367 
368         this.resizable = Boolean.valueOf( resizable );
369     }
370 
371     /**
372      * Gets the parent component for any message displays.
373      *
374      * @return the parent component for any message displays.
375      */
376     public Component getParent()
377     {
378         return this.parent;
379     }
380 
381     /**
382      * Sets the parent component used by any message displays.
383      *
384      * @param parent the parent component used by any message displays.
385      *
386      * @throws NullPointerException if {@code parent} is {@code null}.
387      */
388     public void setParent( final Component parent )
389     {
390         if ( parent == null )
391         {
392             throw new NullPointerException( "parent" );
393         }
394 
395         this.parent = parent;
396     }
397 
398     /**
399      * Gets the maximum number of messages displayed per event.
400      *
401      * @return the maximum number of messages displayed per event.
402      */
403     public int getMaximumMessages()
404     {
405         if ( this.maximumMessages == null )
406         {
407             this.maximumMessages = this.getDefaultMaximumMessages();
408         }
409 
410         return this.maximumMessages.intValue();
411     }
412 
413     /**
414      * Gets the number of columns the preferred width of the message pane is computed with.
415      *
416      * @return The number of columns the preferred width of the message pane is computed with.
417      *
418      * @since 1.10
419      */
420     public int getColumns()
421     {
422         if ( this.columns == null )
423         {
424             this.columns = this.getDefaultColumns();
425         }
426 
427         return this.columns.intValue();
428     }
429 
430     /**
431      * Gets a flag indicating the message pane is resizable.
432      *
433      * @return {@code true}, if the message pane is resizable; {@code false} if the message pane is not resizable.
434      *
435      * @since 1.11
436      */
437     public boolean isResizable()
438     {
439         if ( this.resizable == null )
440         {
441             this.resizable = this.isDefaultResizable();
442         }
443 
444         return this.resizable.booleanValue();
445     }
446 
447     //--------------------------------------------------------SwingMessagePane--
448     //--Messages----------------------------------------------------------------
449 
450 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
451     // This section is managed by jdtaus-container-mojo.
452 
453     /**
454      * Gets the text of message <code>unknownMessageEventType</code>.
455      * <blockquote><pre>Meldung unbekannten Typs {0,number} ignoriert.</pre></blockquote>
456      * <blockquote><pre>Ignored message event of unknown type {0,number}.</pre></blockquote>
457      *
458      * @param locale The locale of the message instance to return.
459      * @param unknownEventType The unknown event type.
460      *
461      * @return Message stating that an unknown message event got ignored.
462      */
463     private String getUnknownMessageEventTypeMessage( final Locale locale,
464             final java.lang.Number unknownEventType )
465     {
466         return ContainerFactory.getContainer().
467             getMessage( this, "unknownMessageEventType", locale,
468                 new Object[]
469                 {
470                     unknownEventType
471                 });
472 
473     }
474 
475     /**
476      * Gets the text of message <code>information</code>.
477      * <blockquote><pre>Information</pre></blockquote>
478      * <blockquote><pre>Information</pre></blockquote>
479      *
480      * @param locale The locale of the message instance to return.
481      *
482      * @return Information title.
483      */
484     private String getInformationMessage( final Locale locale )
485     {
486         return ContainerFactory.getContainer().
487             getMessage( this, "information", locale, null );
488 
489     }
490 
491     /**
492      * Gets the text of message <code>notification</code>.
493      * <blockquote><pre>Hinweis</pre></blockquote>
494      * <blockquote><pre>Notification</pre></blockquote>
495      *
496      * @param locale The locale of the message instance to return.
497      *
498      * @return Notification title.
499      */
500     private String getNotificationMessage( final Locale locale )
501     {
502         return ContainerFactory.getContainer().
503             getMessage( this, "notification", locale, null );
504 
505     }
506 
507     /**
508      * Gets the text of message <code>warning</code>.
509      * <blockquote><pre>Warnung</pre></blockquote>
510      * <blockquote><pre>Warning</pre></blockquote>
511      *
512      * @param locale The locale of the message instance to return.
513      *
514      * @return Warning title.
515      */
516     private String getWarningMessage( final Locale locale )
517     {
518         return ContainerFactory.getContainer().
519             getMessage( this, "warning", locale, null );
520 
521     }
522 
523     /**
524      * Gets the text of message <code>error</code>.
525      * <blockquote><pre>Fehler</pre></blockquote>
526      * <blockquote><pre>Error</pre></blockquote>
527      *
528      * @param locale The locale of the message instance to return.
529      *
530      * @return Error title.
531      */
532     private String getErrorMessage( final Locale locale )
533     {
534         return ContainerFactory.getContainer().
535             getMessage( this, "error", locale, null );
536 
537     }
538 
539 // </editor-fold>//GEN-END:jdtausMessages
540 
541     //----------------------------------------------------------------Messages--
542 }
543 
544 /**
545  * Extension to {@code JLabel} adding support for a preferred width based on a number of columns.
546  *
547  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
548  * @version $JDTAUS: SwingMessagePane.java 8786 2012-12-03 02:09:05Z schulte $
549  */
550 class HtmlLabel extends JLabel
551 {
552 
553     /** Serial version UID for backwards compatibility with 1.10.x classes. */
554     private static final long serialVersionUID = -3022796716512336911L;
555 
556     /** The number of columns the preferred width of the label is computed with. */
557     private int columns;
558 
559     /**
560      * Creates a new {@code HtmlLabel} instance taking a number of columns to compute the preferred width of the label
561      * with.
562      *
563      * @param columns The number of columns to compute the preferred width of the label with.
564      */
565     HtmlLabel( final int columns )
566     {
567         super();
568         this.columns = columns;
569         this.setVerticalAlignment( SwingConstants.TOP );
570         this.setVerticalTextPosition( SwingConstants.TOP );
571 
572         if ( columns > 0 )
573         {
574             this.addPropertyChangeListener( new PropertyChangeListener()
575             {
576 
577                 public void propertyChange( final PropertyChangeEvent evt )
578                 {
579                     if ( ( "text".equals( evt.getPropertyName() )
580                            || "font".equals( evt.getPropertyName() )
581                            || "border".equals( evt.getPropertyName() ) )
582                          && getText() != null
583                          && BasicHTML.isHTMLString( getText() ) )
584                     {
585                         setPreferredSize( computePreferredSize() );
586                     }
587                 }
588 
589             } );
590         }
591     }
592 
593     private Dimension computePreferredSize()
594     {
595         Dimension preferredSize = null;
596         final Insets insets = this.getInsets();
597         final int width = this.columns * this.getFontMetrics( this.getFont() ).charWidth( 'W' )
598                           + ( insets != null ? insets.left + insets.right : 0 );
599 
600         final JLabel label = new JLabel( this.getText() );
601         final Object html = label.getClientProperty( BasicHTML.propertyKey );
602 
603         if ( html instanceof View )
604         {
605             final View view = (View) html;
606             view.setSize( width, 0 );
607             preferredSize = new Dimension( (int) view.getPreferredSpan( View.X_AXIS ),
608                                            (int) view.getPreferredSpan( View.Y_AXIS ) );
609 
610         }
611 
612         return preferredSize;
613     }
614 
615 }