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