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.FontMetrics; 026import java.awt.GraphicsEnvironment; 027import java.awt.GridBagConstraints; 028import java.awt.GridBagLayout; 029import java.awt.HeadlessException; 030import java.awt.Insets; 031import java.lang.reflect.InvocationTargetException; 032import java.util.Locale; 033import javax.swing.JDialog; 034import javax.swing.JLabel; 035import javax.swing.JOptionPane; 036import javax.swing.JPanel; 037import javax.swing.SwingConstants; 038import javax.swing.SwingUtilities; 039import javax.swing.UIManager; 040import javax.swing.plaf.basic.BasicHTML; 041import org.jdtaus.core.container.ContainerFactory; 042import org.jdtaus.core.logging.spi.Logger; 043import org.jdtaus.core.text.MessageEvent; 044import org.jdtaus.core.text.MessageListener; 045 046/** 047 * {@code MessageListener} displaying messages using Swing's {@code JOptionPane}. 048 * <p>This implementation displays a dialog for each {@code MessageEvent} this {@code MessageListener} is notified 049 * about. Since a {@code MessageEvent} can hold multiple messages a maximum number of messages to display per event 050 * can be specified by property {@code maximumMessages} (defaults to 25).</p> 051 * 052 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 053 * @version $JDTAUS: SwingMessagePane.java 8641 2012-09-27 06:45:17Z schulte $ 054 * 055 * @see #onMessage(MessageEvent) 056 */ 057public final class SwingMessagePane implements MessageListener 058{ 059 //--Dependencies------------------------------------------------------------ 060 061// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 062 // This section is managed by jdtaus-container-mojo. 063 064 /** 065 * Gets the configured <code>Logger</code> implementation. 066 * 067 * @return The configured <code>Logger</code> implementation. 068 */ 069 private Logger getLogger() 070 { 071 return (Logger) ContainerFactory.getContainer(). 072 getDependency( this, "Logger" ); 073 074 } 075 076 /** 077 * Gets the configured <code>Locale</code> implementation. 078 * 079 * @return The configured <code>Locale</code> implementation. 080 */ 081 private Locale getLocale() 082 { 083 return (Locale) ContainerFactory.getContainer(). 084 getDependency( this, "Locale" ); 085 086 } 087 088// </editor-fold>//GEN-END:jdtausDependencies 089 090 //------------------------------------------------------------Dependencies-- 091 //--Properties-------------------------------------------------------------- 092 093// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 094 // This section is managed by jdtaus-container-mojo. 095 096 /** 097 * Gets the value of property <code>defaultResizable</code>. 098 * 099 * @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 */ 549class 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}