001/* 002 * $Id: BasicErrorPaneUI.java 3927 2011-02-22 16:34:11Z kleopatra $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021package org.jdesktop.swingx.plaf.basic; 022 023import java.awt.BorderLayout; 024import java.awt.Component; 025import java.awt.Container; 026import java.awt.Dialog; 027import java.awt.Dimension; 028import java.awt.Frame; 029import java.awt.GridBagConstraints; 030import java.awt.GridBagLayout; 031import java.awt.Insets; 032import java.awt.LayoutManager; 033import java.awt.Point; 034import java.awt.Window; 035import java.awt.datatransfer.StringSelection; 036import java.awt.datatransfer.Transferable; 037import java.awt.event.ActionEvent; 038import java.awt.event.ActionListener; 039import java.awt.event.ComponentAdapter; 040import java.awt.event.ComponentEvent; 041import java.awt.event.KeyEvent; 042import java.awt.event.WindowAdapter; 043import java.awt.event.WindowEvent; 044import java.beans.PropertyChangeEvent; 045import java.beans.PropertyChangeListener; 046import java.util.logging.Level; 047 048import javax.swing.AbstractAction; 049import javax.swing.AbstractButton; 050import javax.swing.Action; 051import javax.swing.BorderFactory; 052import javax.swing.Icon; 053import javax.swing.JButton; 054import javax.swing.JComponent; 055import javax.swing.JDialog; 056import javax.swing.JEditorPane; 057import javax.swing.JFrame; 058import javax.swing.JInternalFrame; 059import javax.swing.JLabel; 060import javax.swing.JOptionPane; 061import javax.swing.JPanel; 062import javax.swing.JScrollPane; 063import javax.swing.KeyStroke; 064import javax.swing.LookAndFeel; 065import javax.swing.SwingUtilities; 066import javax.swing.TransferHandler; 067import javax.swing.UIManager; 068import javax.swing.border.EmptyBorder; 069import javax.swing.plaf.ComponentUI; 070import javax.swing.plaf.UIResource; 071import javax.swing.plaf.basic.BasicHTML; 072import javax.swing.text.JTextComponent; 073import javax.swing.text.StyledEditorKit; 074import javax.swing.text.html.HTMLEditorKit; 075 076import org.jdesktop.swingx.JXEditorPane; 077import org.jdesktop.swingx.JXErrorPane; 078import org.jdesktop.swingx.action.AbstractActionExt; 079import org.jdesktop.swingx.error.ErrorInfo; 080import org.jdesktop.swingx.error.ErrorLevel; 081import org.jdesktop.swingx.error.ErrorReporter; 082import org.jdesktop.swingx.plaf.ErrorPaneUI; 083import org.jdesktop.swingx.plaf.UIManagerExt; 084import org.jdesktop.swingx.util.WindowUtils; 085 086/** 087 * Base implementation of the <code>JXErrorPane</code> UI. 088 * 089 * @author rbair 090 * @author rah003 091 */ 092public class BasicErrorPaneUI extends ErrorPaneUI { 093 /** 094 * Used as a prefix when pulling data out of UIManager for i18n 095 */ 096 protected static final String CLASS_NAME = "JXErrorPane"; 097 098 /** 099 * The error pane this UI is for 100 */ 101 protected JXErrorPane pane; 102 /** 103 * Error message text area 104 */ 105 protected JEditorPane errorMessage; 106 107 /** 108 * Error message text scroll pane wrapper. 109 */ 110 protected JScrollPane errorScrollPane; 111 /** 112 * details text area 113 */ 114 protected JXEditorPane details; 115 /** 116 * detail button 117 */ 118 protected AbstractButton detailButton; 119 /** 120 * ok/close button 121 */ 122 protected JButton closeButton; 123 /** 124 * label used to display the warning/error icon 125 */ 126 protected JLabel iconLabel; 127 /** 128 * report an error button 129 */ 130 protected AbstractButton reportButton; 131 /** 132 * details panel 133 */ 134 protected JPanel detailsPanel; 135 protected JScrollPane detailsScrollPane; 136 protected JButton copyToClipboardButton; 137 138 /** 139 * Property change listener for the error pane ensures that the pane's UI 140 * is reinitialized. 141 */ 142 protected PropertyChangeListener errorPaneListener; 143 144 /** 145 * Action listener for the detail button. 146 */ 147 protected ActionListener detailListener; 148 149 /** 150 * Action listener for the copy to clipboard button. 151 */ 152 protected ActionListener copyToClipboardListener; 153 154 //------------------------------------------------------ private helpers 155 /** 156 * The height of the window when collapsed. This value is stashed when the 157 * dialog is expanded 158 */ 159 private int collapsedHeight = 0; 160 /** 161 * The height of the window when last expanded. This value is stashed when 162 * the dialog is collapsed 163 */ 164 private int expandedHeight = 0; 165 166 //---------------------------------------------------------- constructor 167 168 /** 169 * {@inheritDoc} 170 */ 171 public static ComponentUI createUI(JComponent c) { 172 return new BasicErrorPaneUI(); 173 } 174 175 /** 176 * {@inheritDoc} 177 */ 178 @Override 179 public void installUI(JComponent c) { 180 super.installUI(c); 181 182 this.pane = (JXErrorPane)c; 183 184 installDefaults(); 185 installComponents(); 186 installListeners(); 187 188 //if the report action needs to be defined, do so 189 Action a = c.getActionMap().get(JXErrorPane.REPORT_ACTION_KEY); 190 if (a == null) { 191 final JXErrorPane pane = (JXErrorPane)c; 192 AbstractActionExt reportAction = new AbstractActionExt() { 193 @Override 194 public void actionPerformed(ActionEvent e) { 195 ErrorReporter reporter = pane.getErrorReporter(); 196 if (reporter != null) { 197 reporter.reportError(pane.getErrorInfo()); 198 } 199 } 200 }; 201 configureReportAction(reportAction); 202 c.getActionMap().put(JXErrorPane.REPORT_ACTION_KEY, reportAction); 203 } 204 } 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override 210 public void uninstallUI(JComponent c) { 211 super.uninstallUI(c); 212 213 uninstallListeners(); 214 uninstallComponents(); 215 uninstallDefaults(); 216 } 217 218 /** 219 * Installs the default colors, and default font into the Error Pane 220 */ 221 protected void installDefaults() { 222 } 223 224 225 /** 226 * Uninstalls the default colors, and default font into the Error Pane. 227 */ 228 protected void uninstallDefaults() { 229 LookAndFeel.uninstallBorder(pane); 230 } 231 232 233 /** 234 * Create and install the listeners for the Error Pane. 235 * This method is called when the UI is installed. 236 */ 237 protected void installListeners() { 238 //add a listener to the pane so I can reinit() whenever the 239 //bean properties change (particularly error info) 240 errorPaneListener = new ErrorPaneListener(); 241 pane.addPropertyChangeListener(errorPaneListener); 242 } 243 244 245 /** 246 * Remove the installed listeners from the Error Pane. 247 * The number and types of listeners removed and in this method should be 248 * the same that was added in <code>installListeners</code> 249 */ 250 protected void uninstallListeners() { 251 //remove the property change listener from the pane 252 pane.removePropertyChangeListener(errorPaneListener); 253 } 254 255 256 // =============================== 257 // begin Sub-Component Management 258 // 259 260 /** 261 * Creates and initializes the components which make up the 262 * aggregate combo box. This method is called as part of the UI 263 * installation process. 264 */ 265 protected void installComponents() { 266 iconLabel = new JLabel(pane.getIcon()); 267 268 errorMessage = new JEditorPane(); 269 errorMessage.setEditable(false); 270 errorMessage.setContentType("text/html"); 271 errorMessage.setEditorKitForContentType("text/plain", new StyledEditorKit()); 272 errorMessage.setEditorKitForContentType("text/html", new HTMLEditorKit()); 273 274 errorMessage.setOpaque(false); 275 errorMessage.putClientProperty(JXEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); 276 277 closeButton = new JButton(UIManagerExt.getString( 278 CLASS_NAME + ".ok_button_text", errorMessage.getLocale())); 279 280 reportButton = new EqualSizeJButton(pane.getActionMap().get(JXErrorPane.REPORT_ACTION_KEY)); 281 282 detailButton = new EqualSizeJButton(UIManagerExt.getString( 283 CLASS_NAME + ".details_expand_text", errorMessage.getLocale())); 284 285 details = new JXEditorPane(); 286 details.setContentType("text/html"); 287 details.putClientProperty(JXEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); 288 details.setTransferHandler(createDetailsTransferHandler(details)); 289 detailsScrollPane = new JScrollPane(details); 290 detailsScrollPane.setPreferredSize(new Dimension(10, 250)); 291 details.setEditable(false); 292 detailsPanel = new JPanel(); 293 detailsPanel.setVisible(false); 294 copyToClipboardButton = new JButton(UIManagerExt.getString( 295 CLASS_NAME + ".copy_to_clipboard_button_text", errorMessage.getLocale())); 296 copyToClipboardListener = new ActionListener() { 297 @Override 298 public void actionPerformed(ActionEvent ae) { 299 details.copy(); 300 } 301 }; 302 copyToClipboardButton.addActionListener(copyToClipboardListener); 303 304 detailsPanel.setLayout(createDetailPanelLayout()); 305 detailsPanel.add(detailsScrollPane); 306 detailsPanel.add(copyToClipboardButton); 307 308 // Create error scroll pane. Make sure this happens before call to createErrorPaneLayout() in case any extending 309 // class wants to manipulate the component there. 310 errorScrollPane = new JScrollPane(errorMessage); 311 errorScrollPane.setBorder(new EmptyBorder(0,0,5,0)); 312 errorScrollPane.setOpaque(false); 313 errorScrollPane.getViewport().setOpaque(false); 314 315 //initialize the gui. Most of this code is similar between Mac and PC, but 316 //where they differ protected methods have been written allowing the 317 //mac implementation to alter the layout of the dialog. 318 pane.setLayout(createErrorPaneLayout()); 319 320 //An empty border which constitutes the padding from the edge of the 321 //dialog to the content. All content that butts against this border should 322 //not be padded. 323 Insets borderInsets = new Insets(16, 24, 16, 17); 324 pane.setBorder(BorderFactory.createEmptyBorder(borderInsets.top, borderInsets.left, borderInsets.bottom, borderInsets.right)); 325 326 //add the JLabel responsible for displaying the icon. 327 //TODO: in the future, replace this usage of a JLabel with a JXImagePane, 328 //which may add additional "coolness" such as allowing the user to drag 329 //the image off the dialog onto the desktop. This kind of coolness is common 330 //in the mac world. 331 pane.add(iconLabel); 332 pane.add(errorScrollPane); 333 pane.add(closeButton); 334 pane.add(reportButton); 335 reportButton.setVisible(false); // not visible by default 336 pane.add(detailButton); 337 pane.add(detailsPanel); 338 339 //make the buttons the same size 340 EqualSizeJButton[] buttons = new EqualSizeJButton[] { 341 (EqualSizeJButton)detailButton, (EqualSizeJButton)reportButton }; 342 ((EqualSizeJButton)reportButton).setGroup(buttons); 343 ((EqualSizeJButton)detailButton).setGroup(buttons); 344 345 reportButton.setMinimumSize(reportButton.getPreferredSize()); 346 detailButton.setMinimumSize(detailButton.getPreferredSize()); 347 348 //set the event handling 349 detailListener = new DetailsClickEvent(); 350 detailButton.addActionListener(detailListener); 351 } 352 353 /** 354 * The aggregate components which compise the combo box are 355 * unregistered and uninitialized. This method is called as part of the 356 * UI uninstallation process. 357 */ 358 protected void uninstallComponents() { 359 iconLabel = null; 360 errorMessage = null; 361 closeButton = null; 362 reportButton = null; 363 364 detailButton.removeActionListener(detailListener); 365 detailButton = null; 366 367 details.setTransferHandler(null); 368 details = null; 369 370 detailsScrollPane.removeAll(); 371 detailsScrollPane = null; 372 373 detailsPanel.setLayout(null); 374 detailsPanel.removeAll(); 375 detailsPanel = null; 376 377 copyToClipboardButton.removeActionListener(copyToClipboardListener); 378 copyToClipboardButton = null; 379 380 pane.removeAll(); 381 pane.setLayout(null); 382 pane.setBorder(null); 383 } 384 385 // 386 // end Sub-Component Management 387 // =============================== 388 389 /** 390 * @inheritDoc 391 */ 392 @Override 393 public JFrame getErrorFrame(Component owner) { 394 reinit(); 395 expandedHeight = 0; 396 collapsedHeight = 0; 397 JXErrorFrame frame = new JXErrorFrame(pane); 398 centerWindow(frame, owner); 399 return frame; 400 } 401 402 /** 403 * @inheritDoc 404 */ 405 @Override 406 public JDialog getErrorDialog(Component owner) { 407 reinit(); 408 expandedHeight = 0; 409 collapsedHeight = 0; 410 Window w = WindowUtils.findWindow(owner); 411 JXErrorDialog dlg = null; 412 if (w instanceof Dialog) { 413 dlg = new JXErrorDialog((Dialog)w, pane); 414 } else if (w instanceof Frame) { 415 dlg = new JXErrorDialog((Frame)w, pane); 416 } else { 417 // default fallback to null 418 dlg = new JXErrorDialog(JOptionPane.getRootFrame(), pane); 419 } 420 centerWindow(dlg, owner); 421 return dlg; 422 } 423 424 /** 425 * @inheritDoc 426 */ 427 @Override 428 public JInternalFrame getErrorInternalFrame(Component owner) { 429 reinit(); 430 expandedHeight = 0; 431 collapsedHeight = 0; 432 JXInternalErrorFrame frame = new JXInternalErrorFrame(pane); 433 centerWindow(frame, owner); 434 return frame; 435 } 436 437 /** 438 * Create and return the LayoutManager to use with the error pane. 439 */ 440 protected LayoutManager createErrorPaneLayout() { 441 return new ErrorPaneLayout(); 442 } 443 444 protected LayoutManager createDetailPanelLayout() { 445 GridBagLayout layout = new GridBagLayout(); 446 layout.addLayoutComponent(detailsScrollPane, new GridBagConstraints(0,0,1,1,1.0,1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(6,0,0,0),0,0)); 447 GridBagConstraints gbc = new GridBagConstraints(); 448 gbc.anchor = GridBagConstraints.LINE_END; 449 gbc.fill = GridBagConstraints.NONE; 450 gbc.gridwidth = 1; 451 gbc.gridx = 0; 452 gbc.gridy = 1; 453 gbc.weighty = 0.0; 454 gbc.weightx = 1.0; 455 gbc.insets = new Insets(6, 0, 6, 0); 456 layout.addLayoutComponent(copyToClipboardButton, gbc); 457 return layout; 458 } 459 460 @Override 461 public Dimension calculatePreferredSize() { 462 //TODO returns a Dimension that is either X wide, or as wide as necessary 463 //to show the title. It is Y high. 464 return new Dimension(iconLabel.getPreferredSize().width + errorMessage.getPreferredSize().width, 206); 465 } 466 467 protected int getDetailsHeight() { 468 return 300; 469 } 470 471 protected void configureReportAction(AbstractActionExt reportAction) { 472 reportAction.setName(UIManagerExt.getString(CLASS_NAME + ".report_button_text", pane.getLocale())); 473 } 474 475 //----------------------------------------------- private helper methods 476 477 /** 478 * Creates and returns a TransferHandler which can be used to copy the details 479 * from the details component. It also disallows pasting into the component, or 480 * cutting from the component. 481 * 482 * @return a TransferHandler for the details area 483 */ 484 private TransferHandler createDetailsTransferHandler(JTextComponent detailComponent) { 485 return new DetailsTransferHandler(detailComponent); 486 } 487 488 /** 489 * @return the default error icon 490 */ 491 protected Icon getDefaultErrorIcon() { 492 try { 493 Icon icon = UIManager.getIcon(CLASS_NAME + ".errorIcon"); 494 return icon == null ? UIManager.getIcon("OptionPane.errorIcon") : icon; 495 } catch (Exception e) { 496 return null; 497 } 498 } 499 500 /** 501 * @return the default warning icon 502 */ 503 protected Icon getDefaultWarningIcon() { 504 try { 505 Icon icon = UIManager.getIcon(CLASS_NAME + ".warningIcon"); 506 return icon == null ? UIManager.getIcon("OptionPane.warningIcon") : icon; 507 } catch (Exception e) { 508 return null; 509 } 510 } 511 512 /** 513 * Set the details section of the error dialog. If the details are either 514 * null or an empty string, then hide the details button and hide the detail 515 * scroll pane. Otherwise, just set the details section. 516 * 517 * @param details Details to be shown in the detail section of the dialog. 518 * This can be null if you do not want to display the details section of the 519 * dialog. 520 */ 521 private void setDetails(String details) { 522 if (details == null || details.equals("")) { 523 detailButton.setVisible(false); 524 } else { 525 this.details.setText(details); 526 detailButton.setVisible(true); 527 } 528 } 529 530 protected void configureDetailsButton(boolean expanded) { 531 if (expanded) { 532 detailButton.setText(UIManagerExt.getString( 533 CLASS_NAME + ".details_contract_text", detailButton.getLocale())); 534 } else { 535 detailButton.setText(UIManagerExt.getString( 536 CLASS_NAME + ".details_expand_text", detailButton.getLocale())); 537 } 538 } 539 540 /** 541 * Set the details section to be either visible or invisible. Set the 542 * text of the Details button accordingly. 543 * @param b if true details section will be visible 544 */ 545 private void setDetailsVisible(boolean b) { 546 if (b) { 547 collapsedHeight = pane.getHeight(); 548 pane.setSize(pane.getWidth(), expandedHeight == 0 ? collapsedHeight + getDetailsHeight() : expandedHeight); 549 detailsPanel.setVisible(true); 550 configureDetailsButton(true); 551 detailsPanel.applyComponentOrientation(detailButton.getComponentOrientation()); 552 553 // workaround for bidi bug, if the text is not set "again" and the component orientation has changed 554 // then the text won't be aligned correctly. To reproduce this (in JDK 1.5) show two dialogs in one 555 // use LTOR orientation and in the second use RTOL orientation and press "details" in both. 556 // Text in the text box should be aligned to right/left respectively, without this line this doesn't 557 // occure I assume because bidi properties are tested when the text is set and are not updated later 558 // on when setComponentOrientation is invoked. 559 details.setText(details.getText()); 560 details.setCaretPosition(0); 561 } else if (collapsedHeight != 0) { //only collapse if the dialog has been expanded 562 expandedHeight = pane.getHeight(); 563 detailsPanel.setVisible(false); 564 configureDetailsButton(false); 565 // Trick to force errorMessage JTextArea to resize according 566 // to its columns property. 567 errorMessage.setSize(0, 0); 568 errorMessage.setSize(errorMessage.getPreferredSize()); 569 pane.setSize(pane.getWidth(), collapsedHeight); 570 } else { 571 detailsPanel.setVisible(false); 572 } 573 574 pane.doLayout(); 575 } 576 577 /** 578 * Set the error message for the dialog box 579 * @param errorMessage Message for the error dialog 580 */ 581 private void setErrorMessage(String errorMessage) { 582 if(BasicHTML.isHTMLString(errorMessage)) { 583 this.errorMessage.setContentType("text/html"); 584 } else { 585 this.errorMessage.setContentType("text/plain"); 586 } 587 this.errorMessage.setText(errorMessage); 588 this.errorMessage.setCaretPosition(0); 589 } 590 591 /** 592 * Reconfigures the dialog if settings have changed, such as the 593 * errorInfo, errorIcon, warningIcon, etc 594 */ 595 protected void reinit() { 596 setDetailsVisible(false); 597 Action reportAction = pane.getActionMap().get(JXErrorPane.REPORT_ACTION_KEY); 598 reportButton.setAction(reportAction); 599 reportButton.setVisible(reportAction != null && reportAction.isEnabled() && pane.getErrorReporter() != null); 600 reportButton.setEnabled(reportButton.isVisible()); 601 ErrorInfo errorInfo = pane.getErrorInfo(); 602 if (errorInfo == null) { 603 iconLabel.setIcon(pane.getIcon()); 604 setErrorMessage(""); 605 closeButton.setText(UIManagerExt.getString( 606 CLASS_NAME + ".ok_button_text", closeButton.getLocale())); 607 setDetails(""); 608 //TODO Does this ever happen? It seems like if errorInfo is null and 609 //this is called, it would be an IllegalStateException. 610 } else { 611 //change the "closeButton"'s text to either the default "ok"/"close" text 612 //or to the "fatal" text depending on the error level of the incident info 613 if (errorInfo.getErrorLevel() == ErrorLevel.FATAL) { 614 closeButton.setText(UIManagerExt.getString( 615 CLASS_NAME + ".fatal_button_text", closeButton.getLocale())); 616 } else { 617 closeButton.setText(UIManagerExt.getString( 618 CLASS_NAME + ".ok_button_text", closeButton.getLocale())); 619 } 620 621 //if the icon for the pane has not been specified by the developer, 622 //then set it to the default icon based on the error level 623 Icon icon = pane.getIcon(); 624 if (icon == null || icon instanceof UIResource) { 625 if (errorInfo.getErrorLevel().intValue() <= Level.WARNING.intValue()) { 626 icon = getDefaultWarningIcon(); 627 } else { 628 icon = getDefaultErrorIcon(); 629 } 630 } 631 iconLabel.setIcon(icon); 632 setErrorMessage(errorInfo.getBasicErrorMessage()); 633 String details = errorInfo.getDetailedErrorMessage(); 634 if(details == null) { 635 details = getDetailsAsHTML(errorInfo); 636 } 637 setDetails(details); 638 } 639 } 640 641 /** 642 * Creates and returns HTML representing the details of this incident info. This 643 * method is only called if the details needs to be generated: ie: the detailed 644 * error message property of the incident info is null. 645 */ 646 protected String getDetailsAsHTML(ErrorInfo errorInfo) { 647 if(errorInfo.getErrorException() != null) { 648 //convert the stacktrace into a more pleasent bit of HTML 649 StringBuffer html = new StringBuffer("<html>"); 650 html.append("<h2>" + escapeXml(errorInfo.getTitle()) + "</h2>"); 651 html.append("<HR size='1' noshade>"); 652 html.append("<div></div>"); 653 html.append("<b>Message:</b>"); 654 html.append("<pre>"); 655 html.append(" " + escapeXml(errorInfo.getErrorException().toString())); 656 html.append("</pre>"); 657 html.append("<b>Level:</b>"); 658 html.append("<pre>"); 659 html.append(" " + errorInfo.getErrorLevel()); 660 html.append("</pre>"); 661 html.append("<b>Stack Trace:</b>"); 662 Throwable ex = errorInfo.getErrorException(); 663 while(ex != null) { 664 html.append("<h4>"+ex.getMessage()+"</h4>"); 665 html.append("<pre>"); 666 for (StackTraceElement el : ex.getStackTrace()) { 667 html.append(" " + el.toString().replace("<init>", "<init>") + "\n"); 668 } 669 html.append("</pre>"); 670 ex = ex.getCause(); 671 } 672 html.append("</html>"); 673 return html.toString(); 674 } else { 675 return null; 676 } 677 } 678 679 //------------------------------------------------ actions/inner classes 680 681 /** 682 * Default action for closing the JXErrorPane's enclosing window 683 * (JDialog, JFrame, or JInternalFrame) 684 */ 685 private static final class CloseAction extends AbstractAction { 686 private Window w; 687 688 /** 689 * @param w cannot be null 690 */ 691 private CloseAction(Window w) { 692 if (w == null) { 693 throw new NullPointerException("Window cannot be null"); 694 } 695 this.w = w; 696 } 697 698 /** 699 * @inheritDoc 700 */ 701 @Override 702 public void actionPerformed(ActionEvent e) { 703 w.setVisible(false); 704 w.dispose(); 705 } 706 } 707 708 709 /** 710 * Listener for Details click events. Alternates whether the details section 711 * is visible or not. 712 * 713 * @author rbair 714 */ 715 private final class DetailsClickEvent implements ActionListener { 716 717 /* (non-Javadoc) 718 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) 719 */ 720 @Override 721 public void actionPerformed(ActionEvent e) { 722 setDetailsVisible(!detailsPanel.isVisible()); 723 } 724 } 725 726 private final class ResizeWindow implements ActionListener { 727 private Window w; 728 private ResizeWindow(Window w) { 729 if (w == null) { 730 throw new NullPointerException(); 731 } 732 this.w = w; 733 } 734 735 @Override 736 public void actionPerformed(ActionEvent ae) { 737 Dimension contentSize = null; 738 if (w instanceof JDialog) { 739 contentSize = ((JDialog)w).getContentPane().getSize(); 740 } else { 741 contentSize = ((JFrame)w).getContentPane().getSize(); 742 } 743 744 Dimension dialogSize = w.getSize(); 745 int ydiff = dialogSize.height - contentSize.height; 746 Dimension paneSize = pane.getSize(); 747 w.setSize(new Dimension(dialogSize.width, paneSize.height + ydiff)); 748 w.validate(); 749 w.repaint(); 750 } 751 } 752 753 /** 754 * This is a button that maintains the size of the largest button in the button 755 * group by returning the largest size from the getPreferredSize method. 756 * This is better than using setPreferredSize since this will work regardless 757 * of changes to the text of the button and its language. 758 */ 759 private static final class EqualSizeJButton extends JButton { 760 761 public EqualSizeJButton(String text) { 762 super(text); 763 } 764 765 public EqualSizeJButton(Action a) { 766 super(a); 767 } 768 769 /** 770 * Buttons whose size should be taken into consideration 771 */ 772 private EqualSizeJButton[] group; 773 774 public void setGroup(EqualSizeJButton[] group) { 775 this.group = group; 776 } 777 778 /** 779 * Returns the actual preferred size on a different instance of this button 780 */ 781 private Dimension getRealPreferredSize() { 782 return super.getPreferredSize(); 783 } 784 785 /** 786 * If the <code>preferredSize</code> has been set to a 787 * non-<code>null</code> value just returns it. 788 * If the UI delegate's <code>getPreferredSize</code> 789 * method returns a non <code>null</code> value then return that; 790 * otherwise defer to the component's layout manager. 791 * 792 * @return the value of the <code>preferredSize</code> property 793 * @see #setPreferredSize 794 * @see ComponentUI 795 */ 796 @Override 797 public Dimension getPreferredSize() { 798 int width = 0; 799 int height = 0; 800 for(int iter = 0 ; iter < group.length ; iter++) { 801 Dimension size = group[iter].getRealPreferredSize(); 802 width = Math.max(size.width, width); 803 height = Math.max(size.height, height); 804 } 805 806 return new Dimension(width, height); 807 } 808 809 } 810 811 /** 812 * Returns the text as non-HTML in a COPY operation, and disabled CUT/PASTE 813 * operations for the Details pane. 814 */ 815 private static final class DetailsTransferHandler extends TransferHandler { 816 private JTextComponent details; 817 private DetailsTransferHandler(JTextComponent detailComponent) { 818 if (detailComponent == null) { 819 throw new NullPointerException("detail component cannot be null"); 820 } 821 this.details = detailComponent; 822 } 823 824 @Override 825 protected Transferable createTransferable(JComponent c) { 826 String text = details.getSelectedText(); 827 if (text == null || text.equals("")) { 828 details.selectAll(); 829 text = details.getSelectedText(); 830 details.select(-1, -1); 831 } 832 return new StringSelection(text); 833 } 834 835 @Override 836 public int getSourceActions(JComponent c) { 837 return TransferHandler.COPY; 838 } 839 840 } 841 842 private final class JXErrorDialog extends JDialog { 843 public JXErrorDialog(Frame parent, JXErrorPane p) { 844 super(parent, true); 845 init(p); 846 } 847 848 public JXErrorDialog(Dialog parent, JXErrorPane p) { 849 super(parent, true); 850 init(p); 851 } 852 853 protected void init(JXErrorPane p) { 854 // FYI: info can be null 855 setTitle(p.getErrorInfo() == null ? null : p.getErrorInfo().getTitle()); 856 initWindow(this, p); 857 } 858 } 859 860 private final class JXErrorFrame extends JFrame { 861 public JXErrorFrame(JXErrorPane p) { 862 setTitle(p.getErrorInfo().getTitle()); 863 initWindow(this, p); 864 } 865 } 866 867 private final class JXInternalErrorFrame extends JInternalFrame { 868 public JXInternalErrorFrame(JXErrorPane p) { 869 setTitle(p.getErrorInfo().getTitle()); 870 871 setLayout(new BorderLayout()); 872 add(p, BorderLayout.CENTER); 873 final Action closeAction = new AbstractAction() { 874 @Override 875 public void actionPerformed(ActionEvent evt) { 876 setVisible(false); 877 dispose(); 878 } 879 }; 880 closeButton.addActionListener(closeAction); 881 addComponentListener(new ComponentAdapter() { 882 @Override 883 public void componentHidden(ComponentEvent e) { 884 //remove the action listener 885 closeButton.removeActionListener(closeAction); 886 exitIfFatal(); 887 } 888 }); 889 890 getRootPane().setDefaultButton(closeButton); 891 setResizable(false); 892 setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE); 893 KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); 894 getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW); 895 //setPreferredSize(calculatePreferredDialogSize()); 896 } 897 } 898 899 /** 900 * Utility method for initializing a Window for displaying a JXErrorPane. 901 * This is particularly useful because the differences between JFrame and 902 * JDialog are so minor. 903 * removed. 904 */ 905 private void initWindow(final Window w, final JXErrorPane pane) { 906 w.setLayout(new BorderLayout()); 907 w.add(pane, BorderLayout.CENTER); 908 final Action closeAction = new CloseAction(w); 909 closeButton.addActionListener(closeAction); 910 final ResizeWindow resizeListener = new ResizeWindow(w); 911 //make sure this action listener is last (or, oddly, the first in the list) 912 ActionListener[] list = detailButton.getActionListeners(); 913 for (ActionListener a : list) { 914 detailButton.removeActionListener(a); 915 } 916 detailButton.addActionListener(resizeListener); 917 for (ActionListener a : list) { 918 detailButton.addActionListener(a); 919 } 920 921 if (w instanceof JFrame) { 922 final JFrame f = (JFrame)w; 923 f.getRootPane().setDefaultButton(closeButton); 924 f.setResizable(true); 925 f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 926 KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); 927 f.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW); 928 } else if (w instanceof JDialog) { 929 final JDialog d = (JDialog)w; 930 d.getRootPane().setDefaultButton(closeButton); 931 d.setResizable(true); 932 d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); 933 KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); 934 d.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW); 935 } 936 937 w.addWindowListener(new WindowAdapter() { 938 @Override 939 public void windowClosing(WindowEvent e) { 940 //remove the action listener 941 closeButton.removeActionListener(closeAction); 942 detailButton.removeActionListener(resizeListener); 943 exitIfFatal(); 944 } 945 }); 946 w.pack(); 947 } 948 949 private void exitIfFatal() { 950 ErrorInfo info = pane.getErrorInfo(); 951 // FYI: info can be null 952 if (info != null && info.getErrorLevel() == ErrorLevel.FATAL) { 953 Action fatalAction = pane.getActionMap().get(JXErrorPane.FATAL_ACTION_KEY); 954 if (fatalAction == null) { 955 System.exit(1); 956 } else { 957 ActionEvent ae = new ActionEvent(closeButton, -1, "fatal"); 958 fatalAction.actionPerformed(ae); 959 } 960 } 961 } 962 963 private final class ErrorPaneListener implements PropertyChangeListener { 964 @Override 965 public void propertyChange(PropertyChangeEvent evt) { 966 reinit(); 967 } 968 } 969 970 /** 971 * Lays out the BasicErrorPaneUI components. 972 */ 973 private final class ErrorPaneLayout implements LayoutManager { 974 private JEditorPane dummy = new JEditorPane(); 975 976 @Override 977 public void addLayoutComponent(String name, Component comp) {} 978 @Override 979 public void removeLayoutComponent(Component comp) {} 980 981 /** 982 * The preferred size is: 983 * The width of the parent container 984 * The height necessary to show the entire message text 985 * (as long as said height does not go off the screen) 986 * plus the buttons 987 * 988 * The preferred height changes depending on whether the details 989 * are visible, or not. 990 */ 991 @Override 992 public Dimension preferredLayoutSize(Container parent) { 993 int prefWidth = parent.getWidth(); 994 int prefHeight = parent.getHeight(); 995 final Insets insets = parent.getInsets(); 996 int pw = detailButton.isVisible() ? detailButton.getPreferredSize().width : 0; 997 pw += detailButton.isVisible() ? detailButton.getPreferredSize().width : 0; 998 pw += reportButton.isVisible() ? (5 + reportButton.getPreferredSize().width) : 0; 999 pw += closeButton.isVisible() ? (5 + closeButton.getPreferredSize().width) : 0; 1000 prefWidth = Math.max(prefWidth, pw) + insets.left + insets.right; 1001 if (errorMessage != null) { 1002 //set a temp editor to a certain size, just to determine what its 1003 //pref height is 1004 dummy.setContentType(errorMessage.getContentType()); 1005 dummy.setEditorKit(errorMessage.getEditorKit()); 1006 dummy.setText(errorMessage.getText()); 1007 dummy.setSize(prefWidth, 20); 1008 int errorMessagePrefHeight = dummy.getPreferredSize().height; 1009 1010 prefHeight = 1011 //the greater of the error message height or the icon height 1012 Math.max(errorMessagePrefHeight, iconLabel.getPreferredSize().height) + 1013 //the space between the error message and the button 1014 10 + 1015 //the button preferred height 1016 closeButton.getPreferredSize().height; 1017 1018 if (detailsPanel.isVisible()) { 1019 prefHeight += getDetailsHeight(); 1020 } 1021 1022 } 1023 1024 if (iconLabel != null && iconLabel.getIcon() != null) { 1025 prefWidth += iconLabel.getIcon().getIconWidth(); 1026 prefHeight += 10; // top of icon is positioned 10px above the text 1027 } 1028 1029 return new Dimension( 1030 prefWidth + insets.left + insets.right, 1031 prefHeight + insets.top + insets.bottom); 1032 } 1033 1034 @Override 1035 public Dimension minimumLayoutSize(Container parent) { 1036 return preferredLayoutSize(parent); 1037 } 1038 1039 @Override 1040 public void layoutContainer(Container parent) { 1041 final Insets insets = parent.getInsets(); 1042 int x = insets.left; 1043 int y = insets.top; 1044 1045 //place the icon 1046 if (iconLabel != null) { 1047 Dimension dim = iconLabel.getPreferredSize(); 1048 iconLabel.setBounds(x, y, dim.width, dim.height); 1049 x += dim.width + 17; 1050 int leftEdge = x; 1051 1052 //place the error message 1053 dummy.setContentType(errorMessage.getContentType()); 1054 dummy.setText(errorMessage.getText()); 1055 dummy.setSize(parent.getWidth() - leftEdge - insets.right, 20); 1056 dim = dummy.getPreferredSize(); 1057 int spx = x; 1058 int spy = y; 1059 Dimension spDim = new Dimension (parent.getWidth() - leftEdge - insets.right, dim.height); 1060 y += dim.height + 10; 1061 int rightEdge = parent.getWidth() - insets.right; 1062 x = rightEdge; 1063 dim = detailButton.getPreferredSize(); //all buttons should be the same height! 1064 int buttonY = y + 5; 1065 if (detailButton.isVisible()) { 1066 dim = detailButton.getPreferredSize(); 1067 x -= dim.width; 1068 detailButton.setBounds(x, buttonY, dim.width, dim.height); 1069 } 1070 if (detailButton.isVisible()) { 1071 detailButton.setBounds(x, buttonY, dim.width, dim.height); 1072 } 1073 errorScrollPane.setBounds(spx, spy, spDim.width, buttonY - spy); 1074 if (reportButton.isVisible()) { 1075 dim = reportButton.getPreferredSize(); 1076 x -= dim.width; 1077 x -= 5; 1078 reportButton.setBounds(x, buttonY, dim.width, dim.height); 1079 } 1080 1081 dim = closeButton.getPreferredSize(); 1082 x -= dim.width; 1083 x -= 5; 1084 closeButton.setBounds(x, buttonY, dim.width, dim.height); 1085 1086 //if the dialog is expanded... 1087 if (detailsPanel.isVisible()) { 1088 //layout the details 1089 y = buttonY + dim.height + 6; 1090 x = leftEdge; 1091 int width = rightEdge - x; 1092 detailsPanel.setBounds(x, y, width, parent.getHeight() - (y + insets.bottom) ); 1093 } 1094 } 1095 } 1096 } 1097 1098 private static void centerWindow(Window w, Component owner) { 1099 //center based on the owner component, if it is not null 1100 //otherwise, center based on the center of the screen 1101 if (owner != null) { 1102 Point p = owner.getLocation(); 1103 p.x += owner.getWidth()/2; 1104 p.y += owner.getHeight()/2; 1105 SwingUtilities.convertPointToScreen(p, owner); 1106 w.setLocation(p); 1107 } else { 1108 w.setLocation(WindowUtils.getPointForCentering(w)); 1109 } 1110 } 1111 1112 private static void centerWindow(JInternalFrame w, Component owner) { 1113 //center based on the owner component, if it is not null 1114 //otherwise, center based on the center of the screen 1115 if (owner != null) { 1116 Point p = owner.getLocation(); 1117 p.x += owner.getWidth()/2; 1118 p.y += owner.getHeight()/2; 1119 SwingUtilities.convertPointToScreen(p, owner); 1120 w.setLocation(p); 1121 } else { 1122 w.setLocation(WindowUtils.getPointForCentering(w)); 1123 } 1124 } 1125 1126 /** 1127 * Converts the incoming string to an escaped output string. This method 1128 * is far from perfect, only escaping <, > and & characters 1129 */ 1130 private static String escapeXml(String input) { 1131 String s = input == null ? "" : input.replace("&", "&"); 1132 s = s.replace("<", "<"); 1133 return s = s.replace(">", ">"); 1134 } 1135}