001/* 002 * jDTAUS Core Utilities 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.core.text.util; 022 023import java.awt.Component; 024import java.awt.Dimension; 025import java.awt.GraphicsEnvironment; 026import java.awt.GridBagConstraints; 027import java.awt.GridBagLayout; 028import java.awt.HeadlessException; 029import java.awt.Insets; 030import java.beans.PropertyChangeEvent; 031import java.beans.PropertyChangeListener; 032import java.lang.reflect.InvocationTargetException; 033import java.util.Locale; 034import javax.swing.JDialog; 035import javax.swing.JLabel; 036import javax.swing.JOptionPane; 037import javax.swing.JPanel; 038import javax.swing.SwingConstants; 039import javax.swing.SwingUtilities; 040import javax.swing.plaf.basic.BasicHTML; 041import javax.swing.text.View; 042import org.jdtaus.core.container.ContainerFactory; 043import org.jdtaus.core.logging.spi.Logger; 044import org.jdtaus.core.text.MessageEvent; 045import org.jdtaus.core.text.MessageListener; 046 047/** 048 * {@code MessageListener} displaying messages using Swing's {@code JOptionPane}. 049 * <p>This implementation displays a dialog for each {@code MessageEvent} this {@code MessageListener} is notified 050 * about. Since a {@code MessageEvent} can hold multiple messages a maximum number of messages to display per event 051 * can be specified by property {@code maximumMessages} (defaults to 25).</p> 052 * 053 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 054 * @version $JDTAUS: SwingMessagePane.java 8786 2012-12-03 02:09:05Z schulte $ 055 * 056 * @see #onMessage(MessageEvent) 057 */ 058public final class SwingMessagePane implements MessageListener 059{ 060 //--Dependencies------------------------------------------------------------ 061 062// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 063 // This section is managed by jdtaus-container-mojo. 064 065 /** 066 * Gets the configured <code>Logger</code> implementation. 067 * 068 * @return The configured <code>Logger</code> implementation. 069 */ 070 private Logger getLogger() 071 { 072 return (Logger) ContainerFactory.getContainer(). 073 getDependency( this, "Logger" ); 074 075 } 076 077 /** 078 * Gets the configured <code>Locale</code> implementation. 079 * 080 * @return The configured <code>Locale</code> implementation. 081 */ 082 private Locale getLocale() 083 { 084 return (Locale) ContainerFactory.getContainer(). 085 getDependency( this, "Locale" ); 086 087 } 088 089// </editor-fold>//GEN-END:jdtausDependencies 090 091 //------------------------------------------------------------Dependencies-- 092 //--Properties-------------------------------------------------------------- 093 094// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 095 // This section is managed by jdtaus-container-mojo. 096 097 /** 098 * Gets the value of property <code>defaultResizable</code>. 099 * 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 */ 550class 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}