001/** 002 * Please feel free to use any fragment of the code in this file that you need in your own 003 * work. As far as I am concerned, it's in the public domain. No permission is necessary 004 * or required. Credit is always appreciated if you use a large chunk or base a 005 * significant product on one of my examples, but that's not required either. 006 * 007 * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 008 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 009 * PURPOSE. 010 * 011 * --- Joseph A. Huwaldt 012 */ 013package jahuwaldt.swing; 014 015import java.awt.*; 016import java.awt.desktop.AboutEvent; 017import java.awt.desktop.AboutHandler; 018import java.awt.event.ActionEvent; 019import java.awt.event.ActionListener; 020import java.awt.image.ImageObserver; 021import java.io.*; 022import java.lang.reflect.Method; 023import java.net.MalformedURLException; 024import java.net.URI; 025import java.net.URISyntaxException; 026import java.net.URL; 027import java.text.MessageFormat; 028import java.util.ArrayList; 029import java.util.Calendar; 030import java.util.List; 031import java.util.ResourceBundle; 032import java.util.logging.Level; 033import java.util.logging.Logger; 034import javax.swing.*; 035import javax.swing.event.HyperlinkEvent; 036import javax.swing.event.HyperlinkListener; 037 038/** 039 * A set of generic utilities that I have found useful and that are used by most of my 040 * Java applications. 041 * 042 * <p> Modified by: Joseph A. Huwaldt </p> 043 * 044 * @author Joseph A. Huwaldt, Date: February 16, 2000 045 * @version February 23, 2025 046 */ 047public class AppUtilities { 048 049 // Used to neatly locate new windows on the display. 050 private static int nthWindow = 0; 051 052 // OS flags. 053 private static final boolean IS_WINDOWS = System.getProperty("os.name").startsWith("Windows"); 054 055 /** 056 * Prevent anyone from instantiating this utility class. 057 */ 058 private AppUtilities() { } 059 060 /** 061 * Returns true if this program is running in any MacOS environment, false is 062 * returned otherwise. 063 * 064 * @return true if this program is running in any MacOS environment, false is 065 * returned otherwise. 066 */ 067 public static boolean isMacOS() { 068 return MacOSUtilities.isMacOS(); 069 } 070 071 /** 072 * Returns true if this program is running in a MS Windows environment, false is 073 * returned otherwise. 074 * 075 * @return true if this program is running in a MS Windows environment. 076 */ 077 public static boolean isWindows() { 078 return IS_WINDOWS; 079 } 080 081 /** 082 * Method that displays a dialog with a scrollable text field that contains the text 083 * of a Java exception message. This allows caught exceptions to be displayed in a GUI 084 * rather than being hidden at a console window or not displayed at all. 085 * 086 * @param parent The component that the exception dialog should be associated with 087 * (<code>null</code> is fine). 088 * @param title The title of the dialog window. 089 * @param message An optional message to display above the text pane 090 * (<code>null</code> is fine). 091 * @param th The exception to be displayed in the text pane of the dialog. 092 */ 093 public static void showException(Component parent, String title, String message, Throwable th) { 094 StringWriter tracer = new StringWriter(); 095 th.printStackTrace(new PrintWriter(tracer, true)); 096 String trace = tracer.toString(); 097 JPanel view = new JPanel(new BorderLayout()); 098 if (message != null) 099 view.add(new JLabel(message), BorderLayout.NORTH); 100 view.add(new JScrollPane(new JTextArea(trace, 10, 40)), BorderLayout.CENTER); 101 JOptionPane.showMessageDialog(parent, view, title, 0); 102 } 103 104 /** 105 * Center the "inside" component inside of the bounding rectangle of the "outside" 106 * component. 107 * 108 * @param outside The component that the "inside" component should be center on. 109 * @param inside The component that is to be center on the "outside" component. 110 * @return A point representing the upper left corner location required to center the 111 * inside component in the outside one. 112 */ 113 public static Point centerIt(Component outside, Component inside) { 114 if (outside == null || !outside.isVisible() || !outside.isDisplayable()) 115 return centerIt(inside); 116 Dimension outerSize = outside.getSize(); 117 Dimension innerSize = inside.getSize(); 118 Point outerLoc = outside.getLocationOnScreen(); 119 Point innerLoc = new Point(); 120 innerLoc.x = (outerSize.width - innerSize.width) / 2 + outerLoc.x; 121 innerLoc.y = (outerSize.height - innerSize.height) / 2 + outerLoc.y; 122 return innerLoc; 123 } 124 125 /** 126 * Center the specified component on the screen. 127 * 128 * @param comp The component to be centered on the display screen. 129 * @return A point representing the upper left corner location required to center the 130 * specified component on the screen. 131 */ 132 public static Point centerIt(Component comp) { 133 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 134 Dimension componentSize = comp.getSize(); 135 Point componentLoc = new Point(0, 0); 136 componentLoc.x = (screenSize.width - componentSize.width) / 2; 137 componentLoc.y = screenSize.height / 2 - componentSize.height / 2; 138 return componentLoc; 139 } 140 141 /** 142 * Returns a point that can be used to locate a component in the "dialog" position 143 * (1/3 of the way from the top to the bottom of the screen). 144 * 145 * @param comp The component to be located in a "dialog" position. 146 * @return A point representing the upper left corner location required to locate a 147 * component in the "dialog" position on the screen. 148 */ 149 public static Point dialogPosition(Component comp) { 150 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 151 Dimension componentSize = comp.getSize(); 152 Point componentLoc = new Point(0, 0); 153 componentLoc.x = (screenSize.width - componentSize.width) / 2; 154 componentLoc.y = screenSize.height / 3 - componentSize.height / 2; 155 156 // Top of screen check 157 componentLoc.y = (componentLoc.y > 0) ? componentLoc.y : 20; 158 return componentLoc; 159 } 160 161 /** 162 * Positions the specified window neatly on the screen such that document windows are 163 * staggered one after the other. The window is set to the input width and height. 164 * Care is also taken to make sure the window will fit on the screen. 165 * 166 * @param inWindow The window to be positioned neatly on the screen. 167 * @param width the width to set the window to. 168 * @param height the height to set the window to. 169 */ 170 public static void positionWindow(Window inWindow, int width, int height) { 171 final int margin = 30; 172 173 // Window location: staggered 174 int xOffset = (nthWindow * 20) + margin; 175 int yOffset = (nthWindow * 20) + margin; 176 Point windowLoc = new Point(xOffset, yOffset); 177 178 // Window size: minimum of either the input dimensions or the display size. 179 Dimension windowSize = inWindow.getToolkit().getScreenSize(); 180 windowSize.width -= xOffset + margin; 181 windowSize.height -= yOffset + margin; 182 if ((width > 0) && (width < windowSize.width)) 183 windowSize.width = width; 184 185 if ((height > 0) && (height < windowSize.height)) 186 windowSize.height = height; 187 188 // Set final size and location 189 inWindow.setLocation(windowLoc); 190 inWindow.setSize(windowSize); 191 192 // Next window position 193 nthWindow = (nthWindow < 5) ? nthWindow + 1 : 0; 194 } 195 196 /** 197 * Positions the specified window neatly on the screen such that document windows are 198 * staggered one after the other. The size of the window is also set to the input 199 * values. This version assumes that windows are a fixed size and can not be shrunk. 200 * 201 * @param inWindow The window to be placed neatly on the screen. 202 * @param width the width to set the window to. 203 * @param height the height to set the window to. 204 */ 205 public static void positionWindowFixedSize(Window inWindow, int width, int height) { 206 final int margin = 30; 207 208 // Window location 209 int xOffset = (nthWindow * 20) + margin; 210 int yOffset = (nthWindow * 20) + margin; 211 Point windowLoc = new Point(xOffset, yOffset); 212 213 // Window size 214 Dimension screenSize = inWindow.getToolkit().getScreenSize(); 215 216 // Make sure fixed size window fits. 217 int sum = xOffset + margin + width; 218 if (sum > screenSize.width) 219 windowLoc.x -= sum - screenSize.width; 220 221 sum = yOffset + margin + height; 222 if (sum > screenSize.height) 223 windowLoc.y -= sum - screenSize.height; 224 225 // Set final size and location 226 inWindow.setLocation(windowLoc); 227 inWindow.setSize(width, height); 228 229 // Next window position 230 nthWindow = (nthWindow < 5) ? nthWindow + 1 : 0; 231 } 232 233 /** 234 * Fills in the GridBagConstraints record for a given component with input items. 235 * 236 * @param gbc The GridBagConstraints record to be filled in. 237 * @param gx Grid (cell) index for this component in the x direction (1 == 2nd 238 * column). 239 * @param gy Grid (cell) index for this component in the y direction (3 == 4th row). 240 * @param gw The number of cells this component spans in width. 241 * @param gh The number of cells this component spans in height. 242 * @param wx Proportional width of this grid cell compared to others. 243 * @param wy Proportional height of this grid cell compared to others. 244 */ 245 public static void buildConstraints(GridBagConstraints gbc, int gx, int gy, 246 int gw, int gh, int wx, int wy) { 247 gbc.gridx = gx; 248 gbc.gridy = gy; 249 250 gbc.gridwidth = gw; 251 gbc.gridheight = gh; 252 253 gbc.weightx = wx; 254 gbc.weighty = wy; 255 256 } 257 258 /** 259 * Try and determine the directory to where the program is installed and return that 260 * as a URL. Has essentially the same function as Applet.getDocumentBase(), but works 261 * on local file systems in applications. 262 * 263 * @return A URL pointing to the directory where the program is installed. 264 */ 265 public static URL getDocumentBase() { 266 267 // Object is to try and figure out where the program is installed. 268 // First see if the program was installed using ZeroG's Install Anywhere installer. 269 String dir = System.getProperty("lax.root.install.dir"); 270 271 if (dir == null || dir.equals("")) { 272 try { 273 // Try this class's code source location as a file. 274 URL location = AppUtilities.class.getProtectionDomain().getCodeSource().getLocation(); 275 File file = new File(location.toURI()); 276 if (file.exists()) { 277 file = file.getParentFile(); 278 return file.toURI().toURL(); 279 } 280 } catch (MalformedURLException | URISyntaxException e) { 281 /* Just move on. */ 282 } 283 /* Just move on. */ 284 285 // Fall back on "user.dir" if all else fails. 286 // However, on some systems this will return the directory where the program is executed, 287 // not where it is installed. 288 dir = System.getProperty("user.dir"); 289 } 290 291 // Deal with different file separator characters. 292 String urlDir = dir.replace(File.separatorChar, '/'); 293 if (!urlDir.endsWith("/")) 294 urlDir = urlDir + "/"; 295 296 URL output = null; 297 try { 298 output = new URI("file", urlDir, null).toURL(); 299 300 } catch (MalformedURLException | URISyntaxException e) { 301 e.printStackTrace(); 302 } 303 304 return output; 305 } 306 307 /** 308 * Return the root component of the given component. 309 * 310 * @param source The Component or MenuComponent to find the root Component for. 311 * @return the root component of the given component. 312 */ 313 public static Component getRootComponent(Object source) { 314 Component root = null; 315 316 if (source instanceof Component) 317 root = SwingUtilities.getRoot((Component)source); 318 319 else if (source instanceof MenuComponent) { 320 MenuContainer mParent = ((MenuComponent)source).getParent(); 321 return getRootComponent(mParent); 322 } 323 324 return root; 325 } 326 327 /** 328 * Returns the specified component's top-level <code>Frame</code>. 329 * 330 * @param parentComponent the <code>Component</code> to check for a <code>Frame</code> 331 * @return the <code>Frame</code> that contains the component, or <code>null</code> if 332 * the component is <code>null</code>, or does not have a valid 333 * <code>Frame</code> parent 334 */ 335 public static Frame getFrameForComponent(Component parentComponent) { 336 if (parentComponent == null) 337 return null; 338 if (parentComponent instanceof Frame) 339 return (Frame)parentComponent; 340 return getFrameForComponent(parentComponent.getParent()); 341 } 342 343 /** 344 * Method that loads an image from a URL and creates a custom mouse cursor from it. 345 * 346 * @param url A URL to the image to be loaded as a cursor. 347 * @param hsx The x-coordinate of the point on the image that represents the 348 * cursor hot spot. 349 * @param hsy The y coordinate of the point on the image that represents the 350 * cursor hot spot. 351 * @param name The name to assign to this cursor for Java Accessibility. 352 * @param observer The component to use to observe the loading of the image. Pass 353 * <code>null</code> if none. 354 * @return The custom cursor generated from the specified image. If any error occurs 355 * <code>null</code> will be returned. 356 */ 357 public static Cursor getImageCursor(URL url, int hsx, int hsy, String name, ImageObserver observer) { 358 Toolkit tk = Toolkit.getDefaultToolkit(); 359 Image img = tk.getImage(url); 360 return makeImageCursor(img, hsx, hsy, name, observer); 361 } 362 363 /** 364 * Method that loads an image from a file and creates a custom mouse cursor from it. 365 * 366 * @param path The path to the image to be loaded as a cursor. 367 * @param hsx The x-coordinate of the point on the image that represents the 368 * cursor hot spot. 369 * @param hsy The y coordinate of the point on the image that represents the 370 * cursor hot spot. 371 * @param name The name to assign to this cursor for Java Accessibility. 372 * @param observer The component to use to observe the loading of the image. Pass 373 * <code>null</code> if none. 374 * @return The custom cursor generated from the specified image. If any error occurs 375 * <code>null</code> will be returned. 376 */ 377 public static Cursor getImageCursor(String path, int hsx, int hsy, String name, ImageObserver observer) { 378 Toolkit tk = Toolkit.getDefaultToolkit(); 379 Image img = tk.getImage(path); 380 return makeImageCursor(img, hsx, hsy, name, observer); 381 } 382 383 /** 384 * Method that creates a custom mouse cursor from the specified image. 385 * 386 * @param img The image to be loaded as a cursor. 387 * @param hsx The x-coordinate of the point on the image that represents the 388 * cursor hot spot. 389 * @param hsy The y coordinate of the point on the image that represents the 390 * cursor hot spot. 391 * @param name The name to assign to this cursor for Java Accessibility. 392 * @param observer The component to use to observe the loading of the image. Pass 393 * <code>null</code> if none. 394 * @return The custom cursor generated from the specified image. If any error occurs 395 * <code>null</code> will be returned. 396 */ 397 public static Cursor makeImageCursor(Image img, int hsx, int hsy, String name, ImageObserver observer) { 398 Cursor cursor = null; 399 400 if (img == null) 401 return null; 402 403 try { 404 Toolkit tk = Toolkit.getDefaultToolkit(); 405 406 // Wait for the image to load. 407 int width = 0, height = 0; 408 int count = 0; 409 while ((width < 1 || height < 1) && count < 10000) { 410 width = img.getWidth(observer); 411 height = img.getHeight(observer); 412 ++count; 413 } 414 415 if (width > 0 && height > 0) { 416 // Scale hot spot to the best cursor size. 417 Dimension bestSize = tk.getBestCursorSize(width, height); 418 419 if (bestSize.width > 0 && bestSize.height > 0) { 420 Point hotSpot = new Point(hsx * bestSize.width / width, hsy * bestSize.height / height); 421 422 // Create the cursor. 423 cursor = tk.createCustomCursor(img, hotSpot, name); 424 } 425 } 426 427 } catch (IndexOutOfBoundsException e) { 428 // Just return null. 429 } 430 431 return cursor; 432 } 433 434 /** 435 * Sets the Swing look and feel to hide that hideous default Java LAF. 436 */ 437 public static void setSystemLAF() { 438 439 try { 440 // Set the system look and feel to hide that hideous Java LAF. 441 String laf = UIManager.getSystemLookAndFeelClassName(); 442 UIManager.setLookAndFeel(laf); 443 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { 444 Logger.getLogger(AppUtilities.class.getName()).log(Level.SEVERE, null, ex); 445 } 446 447 } 448 449 /** 450 * Returns true if the current look and feel is the same as the system look and feel. 451 * Otherwise, returns false. 452 * 453 * @return true if the current look and feel is the system look and feel. 454 */ 455 public static boolean isSystemLAF() { 456 return UIManager.getSystemLookAndFeelClassName().equals(UIManager.getLookAndFeel().getClass().getName()); 457 } 458 459 /** 460 * Build up a JMenu from a description stored in a list of String arrays. The 461 * description contains the text of each menu item, accelerator key, and the name of a 462 * public method in the specified "eventTarget" object that handles the menu item 463 * action event. 464 * 465 * @param eventTarget This is the object that must contain the methods used to handle 466 * action events for this menu's items. 467 * @param name Name of the menu to create. 468 * @param menuDesc List of String arrays that describes each of the menu items that 469 * will be built into this menu. For each String array, item 0 is 470 * the menu item text string. Item 1 is the accelerator key for the 471 * menu item. Item 2 is the name of the public method in 472 * "eventTarget" that will handle the user choosing the menu item. 473 * This method must take an ActionEvent as it's only parameter. If 474 * this is <code>null</code>, the menu item will be shown as 475 * disabled. Items 3 is optional and is the tool-tip text to show 476 * for the menu item if not <code>null</code>. 477 * @return A menu built up from the menu description supplied. 478 * @throws NoSuchMethodException If one of the action event methods in parent could 479 * not be found. 480 */ 481 public static JMenu buildMenu(Object eventTarget, String name, List<String[]> menuDesc) 482 throws NoSuchMethodException { 483 return buildMenu(eventTarget, name, menuDesc.toArray(new String[0][])); 484 } 485 486 /** 487 * Build up a JMenu from a description stored in a String array. The description 488 * contains the text of each menu item, accelerator key, and the name of a public 489 * method in the specified "eventTarget" object that handles the menu item action 490 * event. 491 * 492 * @param eventTarget This is the object that must contain the public methods used to 493 * handle action events for this menu's items. 494 * @param name Name of the menu to create. 495 * @param menuDesc String array that describes each of the menu items that will be 496 * built into this menu. For each String array, item 0 is the menu 497 * item text string. Item 1 is the accelerator key for the menu 498 * item. Item 2 is the name of the public method in "eventTarget" 499 * that will handle the user choosing the menu item. This method 500 * must take an ActionEvent as it's only parameter. If this is 501 * <code>null</code>, the menu item will be shown as disabled. 502 * Items 3 is optional and is the tool-tip text to show for the 503 * menu item if not <code>null</code>. 504 * @return A menu built up from the menu description supplied. 505 * @throws NoSuchMethodException If one of the action event methods in parent could 506 * not be found. 507 */ 508 public static JMenu buildMenu(Object eventTarget, String name, String[][] menuDesc) 509 throws NoSuchMethodException { 510 if (menuDesc == null || eventTarget == null) { 511 throw new NullPointerException("menuDesc or eventTarget are null!"); 512 } 513 514 // Create a menu to add items to. 515 JMenu aMenu = new JMenu(name); 516 517 // Add a list of items to a specified menu. 518 int numItems = menuDesc.length; 519 for (int i = 0; i < numItems; ++i) { 520 521 // Is this a menu separator? 522 if (menuDesc[i][0] == null) { 523 JSeparator separator = new JSeparator(); 524 aMenu.add(separator); 525 526 } else { 527 528 // Create the menu item. 529 JMenuItem menuItem = buildMenuItem(eventTarget, menuDesc[i]); 530 531 // Add this new menu item to the specified menu. 532 aMenu.add(menuItem); 533 } 534 } 535 536 return aMenu; 537 } 538 539 /** 540 * Build up a JMenuItem from a description stored in a String array. The description 541 * contains the text of each menu item, accelerator key, and the name of a public 542 * method in the specified "eventTarget" object that handles the menu item action 543 * event. 544 * 545 * @param eventTarget This is the object that must contain the public methods used to 546 * handle action events for this item. 547 * @param menuDesc A 3 or 4-element String array that describes the menu item that 548 * will be built. Item 0 is the menu item text string. Item 1 is 549 * the accelerator key for the menu item. Item 2 is the name of the 550 * public method in "eventTarget" that will handle the user 551 * choosing this menu item. This method must take an ActionEvent as 552 * it's only parameter. If this is <code>null</code>, the menu item 553 * will be shown as disabled. Items 3 is optional and is the 554 * tool-tip text to show for the menu item if not 555 * <code>null</code>. 556 * @return A new JMenuItem built from the description in menuDesc with action 557 * listeners that call methods in parent. 558 * @throws NoSuchMethodException If one of the action event methods in parent could 559 * not be found. 560 */ 561 public static JMenuItem buildMenuItem(Object eventTarget, String[] menuDesc) throws NoSuchMethodException { 562 if (eventTarget == null) 563 throw new NullPointerException("eventTarget is null"); 564 565 // Create the menu item. 566 String menuString = menuDesc[0]; 567 JMenuItem menuItem = new JMenuItem(menuString); 568 569 // Does this menu item have an accelerator key? 570 String accelerator = menuDesc[1]; 571 if (accelerator != null && !accelerator.equals("")) { 572 // Add the accelerator key. 573 Toolkit tk = Toolkit.getDefaultToolkit(); 574 int accCharMask = tk.getMenuShortcutKeyMaskEx(); 575 menuItem.setAccelerator(KeyStroke.getKeyStroke(accelerator.charAt(0), accCharMask, false)); 576 } 577 578 // Does this menu item have an action method? 579 String methodStr = menuDesc[2]; 580 if (methodStr != null && !methodStr.equals("")) { 581 // Create an action listener by reflection that refers back to 582 // the specified method in the specified frame. 583 ActionListener listener = getActionListenerForMethod(eventTarget, methodStr); 584 menuItem.addActionListener(listener); 585 menuItem.setActionCommand(menuString); 586 menuItem.setEnabled(true); 587 588 } else { 589 // If no method, disable this menu item. 590 menuItem.setEnabled(false); 591 } 592 593 if (menuDesc.length > 3) { 594 String tooltip = menuDesc[3]; 595 if (tooltip != null) 596 menuItem.setToolTipText(tooltip); 597 } 598 599 return menuItem; 600 } 601 602 /** 603 * Build up a ButtonGroup containing toggle buttons from a description stored in a 604 * String array. The description contains, for each button, the text to appear in the 605 * button (<code>null</code> if no text), the name of the method in the parent object 606 * that will be called when a button is clicked on (if <code>null</code> is passed, 607 * the button is disabled), and the tool tip text to display for the button; 608 * (<code>null</code> for no tool tip). 609 * 610 * @param parent The parent object that will contain handle the user clicking on one 611 * of the buttons in this group. This object is also used to observe 612 * the reading in of the button icon images. 613 * @param defs String array that describes each of the text string that will be 614 * displayed in the button. Item 0 is the text to be displayed in each 615 * button. If this is <code>null</code> no text is displayed. Item 1 is 616 * the name of the method in "parent" that will handle the user 617 * clicking on this button item. If this is <code>null</code>, the menu 618 * button will be shown as disabled. Item 2 is the tool tip to be shown 619 * above this button item. If <code>null</code> is passed no tool tip 620 * is shown. 621 * @param imgURLs An array of URLs for the images to be displayed in the buttons. If 622 * <code>null</code> is passed in, then there will be no button images. 623 * If any element is <code>null</code>, that button will have no image. 624 * @return A button group built up from the button description supplied. 625 * @throws java.lang.NoSuchMethodException if a specified method in the parent object 626 * doesn't exist. 627 */ 628 public static ButtonGroup buildButtonGroup(ImageObserver parent, String[][] defs, URL[] imgURLs) throws NoSuchMethodException { 629 630 ImageIcon[] icons = null; 631 632 if (imgURLs != null) { 633 int length = imgURLs.length; 634 if (defs.length != length) 635 throw new IllegalArgumentException("The defs & imgURLs arrays have different lengths."); 636 icons = new ImageIcon[length]; 637 for (int i = 0; i < length; ++i) { 638 if (imgURLs[i] != null) 639 icons[i] = new ImageIcon(imgURLs[i]); 640 } 641 } 642 643 return buildButtonGroup(parent, defs, icons); 644 } 645 646 /** 647 * Build up a ButtonGroup containing toggle buttons from a description stored in a 648 * String array. The description contains, for each button, the text to appear in the 649 * button (<code>null</code> if no text), the name of the method in the parent object 650 * that will be called when a button is clicked on (if <code>null</code> is passed, 651 * the button is disabled), and the tool tip text to display for the button 652 * (<code>null</code> for no tool tip). 653 * 654 * @param parent The parent object that will contain handle the user clicking on one 655 * of the buttons in this group. This object is also used to observe 656 * the reading in of the button icon images. 657 * @param defs String array that describes each of the text string that will be 658 * displayed in the button. Item 0 is the text to be displayed in each 659 * button. If this is <code>null</code> no text is displayed. Item 1 660 * is the name of the method in "parent" that will handle the user 661 * clicking on this button item. If this is <code>null</code>, the 662 * menu button will be shown as disabled. Item 2 is the tool tip to be 663 * shown above this button item. If <code>null</code> is passed no 664 * tool tip is shown. 665 * @param imgPaths An array of image file paths for the images to be displayed in the 666 * buttons. If <code>null</code> is passed in, then there will be no 667 * button images. If any element is <code>null</code>, that button 668 * will have no image. 669 * @return A button group built up from the button description supplied. 670 * @throws java.lang.NoSuchMethodException if a specified method in the parent object 671 * doesn't exist. 672 */ 673 public static ButtonGroup buildButtonGroup(ImageObserver parent, String[][] defs, String[] imgPaths) throws NoSuchMethodException { 674 675 ImageIcon[] icons = null; 676 677 if (imgPaths != null) { 678 int length = imgPaths.length; 679 if (defs.length != length) 680 throw new IllegalArgumentException("The defs & imgPaths arrays have different lengths."); 681 icons = new ImageIcon[length]; 682 for (int i = 0; i < length; ++i) { 683 if (imgPaths[i] != null) 684 icons[i] = new ImageIcon(imgPaths[i]); 685 } 686 } 687 688 return buildButtonGroup(parent, defs, icons); 689 } 690 691 /** 692 * Build up a ButtonGroup containing toggle buttons from a description stored in a 693 * String array. The description contains, for each button, the text to appear in the 694 * button (<code>null</code> if no text), the path to an image file to display in the 695 * button (<code>null</code> for no image), the name of the method in the parent 696 * object that will be called when a button is clicked on (if <code>null</code> is 697 * passed, the button is disabled), and the tool tip text to display for the button 698 * (<code>null</code> for no tool tip). 699 * 700 * @param parent The parent object that will contain handle the user clicking on one 701 * of the buttons in this group. This object is also used to observe the 702 * reading in of the button icon images. 703 * @param defs String array that describes each of the text string that will be 704 * displayed in the button. Item 0 is the text to be displayed in each 705 * button. If this is <code>null</code> no text is displayed. Item 1 is 706 * the name of the method in "parent" that will handle the user clicking 707 * on this button item. If this is <code>null</code>, the menu button 708 * will be shown as disabled. Item 2 is the tool tip to be shown above 709 * this button item. If <code>null</code> is passed no tool tip is 710 * shown. 711 * @param icons An array of icon images to be displayed in the buttons. If this array 712 * is <code>null</code>, there will be no images displayed in the 713 * buttons. If one of the elements of the array is <code>null</code>, 714 * that button will have no image. 715 * @return A button group built up from the button description supplied. 716 * @throws java.lang.NoSuchMethodException if a specified method in the parent object 717 * doesn't exist. 718 */ 719 public static ButtonGroup buildButtonGroup(ImageObserver parent, String[][] defs, ImageIcon[] icons) throws NoSuchMethodException { 720 721 if (icons != null && defs.length != icons.length) 722 throw new IllegalArgumentException("The defs & icons arrays have different lengths."); 723 724 // Create a group for these buttons so that they toggle. 725 ButtonGroup bGroup = new ButtonGroup(); 726 727 // Create buttons. 728 int numBtns = defs.length; 729 for (int i = 0; i < numBtns; ++i) { 730 731 // Extract information about this button. 732 String title = defs[i][0]; 733 String methodStr = defs[i][1]; 734 String toolTip = defs[i][2]; 735 ImageIcon icon = null; 736 if (icons != null) 737 icon = icons[i]; 738 739 JToggleButton button = null; 740 if (title != null && !title.equals("")) { 741 if (icon != null) 742 button = new JToggleButton(title, icon); 743 else 744 button = new JToggleButton(title); 745 746 } else { 747 if (icon != null) { 748 button = new JToggleButton(icon); 749 Image image = icon.getImage(); 750 Dimension size = new Dimension(image.getWidth(parent), 751 image.getHeight(parent)); 752 button.setMaximumSize(size); 753 } 754 } 755 756 if (button != null) { 757 bGroup.add(button); 758 button.setAlignmentX(JToggleButton.CENTER_ALIGNMENT); 759 button.setAlignmentY(JToggleButton.CENTER_ALIGNMENT); 760 button.setMargin(new Insets(0, 0, 0, 0)); 761 if (toolTip != null) 762 button.setToolTipText(toolTip); 763 764 if (i == 0) 765 button.setSelected(true); 766 767 if (methodStr != null && !methodStr.equals("")) { 768 769 // Create an action listener by reflection that refers back to 770 // the specified method in the specified frame. 771 ActionListener listener = getActionListenerForMethod(parent, methodStr); 772 button.addActionListener(listener); 773 button.setActionCommand(title); 774 button.setEnabled(true); 775 776 } else { 777 // If no method, disable this menu item. 778 button.setEnabled(false); 779 } 780 781 } 782 } 783 784 return bGroup; 785 } 786 787 /** 788 * Build up a List of buttons from a description stored in a String array. The 789 * description contains, for each button, the text to appear in the button 790 * (<code>null</code> if no text), the path to an image file to display in the button 791 * (<code>null</code> for no image), the name of the method in the parent object that 792 * will be called when a button is clicked on (if <code>null</code> is passed, the 793 * button is disabled), and the tool tip text to display for the button 794 * (<code>null</code> for no tool tip). 795 * 796 * @param parent The parent object that will handle the user clicking on one of the 797 * buttons in this group. 798 * @param observer The object used to observe the reading in of the button icon 799 * images, if any (if no images being read in, this may be null). 800 * @param defs String array that describes each button. Item 0 is the text string 801 * that will be displayed in the button. If this is <code>null</code> 802 * no text is displayed. Item 1 is the file path to the image to 803 * display in this button. If <code>null</code> is passed, no image 804 * icon is displayed. Item 2 is the name of the method in "parent" 805 * that will handle the user clicking on this button item. If this is 806 * <code>null</code>, the menu button will be shown as disabled. Item 807 * 3 is the tool tip to be shown above this button item. If 808 * <code>null</code> is passed no tool tip is shown. 809 * @return A List containing all the buttons built up from the description supplied. 810 * @throws java.lang.NoSuchMethodException if a specified method in the parent object 811 * doesn't exist. 812 */ 813 public static List<JButton> buildButtonList(Object parent, ImageObserver observer, String[][] defs) throws NoSuchMethodException { 814 // Create a group for these buttons so that they toggle. 815 ArrayList<JButton> bList = new ArrayList(); 816 817 // Create buttons. 818 int numBtns = defs.length; 819 for (int i = 0; i < numBtns; ++i) { 820 821 // Extract information about this button. 822 String title = defs[i][0]; 823 String imagePath = defs[i][1]; 824 String methodStr = defs[i][2]; 825 String toolTip = defs[i][3]; 826 827 JButton button = null; 828 if (title != null && !title.equals("")) { 829 if (imagePath != null && !imagePath.equals("")) 830 button = new JButton(title, new ImageIcon(imagePath)); 831 else 832 button = new JButton(title); 833 834 } else { 835 if (imagePath != null && !imagePath.equals("")) { 836 ImageIcon icon = new ImageIcon(imagePath); 837 button = new JButton(icon); 838 Image image = icon.getImage(); 839 Dimension size = new Dimension(image.getWidth(observer), 840 image.getHeight(observer)); 841 button.setMaximumSize(size); 842 } 843 } 844 845 if (button != null) { 846 bList.add(button); 847 button.setAlignmentX(JButton.CENTER_ALIGNMENT); 848 button.setAlignmentY(JButton.CENTER_ALIGNMENT); 849 button.setMargin(new Insets(0, 0, 0, 0)); 850 if (toolTip != null && !toolTip.equals("")) 851 button.setToolTipText(toolTip); 852 853 if (methodStr != null && !methodStr.equals("")) { 854 855 // Create an action listener by reflection that refers back to 856 // the specified method in the specified frame. 857 ActionListener listener = getActionListenerForMethod(parent, methodStr); 858 button.addActionListener(listener); 859 button.setActionCommand(title); 860 button.setEnabled(true); 861 862 } else { 863 // If no method, disable this menu item. 864 button.setEnabled(false); 865 } 866 867 } 868 } 869 870 return bList; 871 } 872 873 /** 874 * Method that returns an action listener that simply calls the specified method in 875 * the specified class. This is little trick is done using the magic of reflection. 876 * 877 * @param target The object which contains the specified method. 878 * @param methodStr The name of the method to be called in the target object. This 879 * method must be contained in target, must be publicly accessible 880 * and must accept an ActionEvent object as the only parameter. 881 * @return An ActionListener that will call the specified method in the specified 882 * target object and pass to it an ActionEvent object. 883 * @throws NoSuchMethodException if the target object does not contain the specified 884 * method. 885 */ 886 public static ActionListener getActionListenerForMethod(Object target, String methodStr) 887 throws NoSuchMethodException { 888 Method m = target.getClass().getMethod(methodStr, new Class[]{ActionEvent.class}); 889 ActionListener listener = new GenericActionListener(target, m); 890 return listener; 891 } 892 893 /** 894 * Method that exits the program (with a warning message) if the current data is after 895 * the specified date. 896 * 897 * @param date The data and time when the program should expire. 898 * @param message The warning message to show if the program has expired. 899 */ 900 public static void checkDateAndDie(Calendar date, String message) { 901 if (Calendar.getInstance().after(date)) { 902 JOptionPane.showMessageDialog(null, message, "Expired", JOptionPane.ERROR_MESSAGE); 903 System.exit(0); 904 } 905 } 906 907 /** 908 * Method that returns the index to an option that a user has selected from an array 909 * of options using a standard input dialog. 910 * 911 * @param parent The parent Component for the dialog 912 * @param msg The message to display to the user. 913 * @param title The title of the dialog window. 914 * @param messageType One of the JOptionPane message type constants: 915 * ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, 916 * QUESTION_MESSAGE, or PLAIN_MESSAGE. 917 * @param selectionValues An array of Objects that gives the possible 918 * selections. 919 * @param initialSelectionValue The value used to initialize the input field. 920 * @return The index into the input array of the value the user has selected or -1 if 921 * the user canceled. 922 */ 923 public static int userSelectionFromArray(Component parent, Object msg, String title, int messageType, 924 Object[] selectionValues, Object initialSelectionValue) { 925 926 Object selection = JOptionPane.showInputDialog(parent, msg, title, messageType, null, 927 selectionValues, initialSelectionValue); 928 if (selection == null) 929 return -1; 930 931 int length = selectionValues.length; 932 int i = 0; 933 for (; i < length; ++i) 934 if (selection.equals(selectionValues[i])) 935 break; 936 937 return i; 938 } 939 940 /** 941 * Method that returns the bounded integer value that a user has entered in a standard 942 * input dialog. 943 * 944 * @param parent The parent Component for the dialog 945 * @param msg The message to display to the user. 946 * @param title The title of the dialog window. 947 * @param messageType One of the JOptionPane message type constants: ERROR_MESSAGE, 948 * INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, or 949 * PLAIN_MESSAGE. 950 * @param initialValue The initial value for the dialog's text field. 951 * @param lowerBound The lowest (most negative) value to allow the user to enter 952 * (use Integer.MIN_VALUE to allow any negative integer). 953 * @param upperBound The largest (most positive) value to allow the user to enter 954 * (use Integer.MAX_VALUE to allow any positive integer). 955 * @return The integer value the user has entered or <code>null</code> if the user has 956 * canceled. 957 */ 958 public static Integer userEnteredInteger(Component parent, String msg, String title, int messageType, 959 int initialValue, int lowerBound, int upperBound) { 960 Integer value = null; 961 String output; 962 do { 963 output = (String)JOptionPane.showInputDialog(parent, msg, title, messageType, null, 964 null, initialValue); 965 if (output != null) { 966 try { 967 value = Integer.valueOf(output); 968 if (value < lowerBound || value > upperBound) 969 throw new NumberFormatException(); 970 break; 971 } catch (NumberFormatException e) { 972 Toolkit.getDefaultToolkit().beep(); 973 } 974 } 975 } while (output != null); 976 977 return value; 978 } 979 980 /** 981 * Method that brings up a file chooser dialog and allows the user to select a file. 982 * 983 * @param parent The owner of the dialog (<code>null</code> is fine). 984 * @param mode Either FileDialog.LOAD or FileDialog.SAVE. 985 * @param message The message for the dialog. Something like "Choose a name for this 986 * file:". 987 * @param directory The directory to prompt the user with by default 988 * (<code>null</code> is fine). 989 * @param name The name of the file to prompt the user with by default 990 * (<code>null</code> is fine). 991 * @param filter The filename filter to use (<code>null</code> means no filter). 992 * @return The file selected by the user, or <code>null</code> if no file was 993 * selected. 994 */ 995 public static File selectFile(Component parent, int mode, String message, 996 String directory, String name, FilenameFilter filter) { 997 998 Frame frame; 999 if (parent == null) 1000 frame = new Frame(); 1001 else 1002 frame = getFrameForComponent(parent); 1003 1004 if (message == null) 1005 message = ""; 1006 1007 // Bring up a file chooser. 1008 FileDialog fd = new FileDialog(frame, message, mode); 1009 fd.addNotify(); 1010 1011 if (directory != null && (isWindows() || isMacOS()) && !directory.equals("")) { 1012 // Prompt the user with the existing directory path. 1013 File dirFile = new File(directory); 1014 if (!dirFile.isDirectory()) 1015 directory = dirFile.getParent(); 1016 if (new File(directory).exists()) 1017 fd.setDirectory(directory); 1018 } 1019 1020 if (name != null) 1021 // Prompt the user with the existing file name. 1022 fd.setFile(name); 1023 1024 if (filter != null) 1025 fd.setFilenameFilter(filter); 1026 1027 fd.setVisible(true); 1028 String fileName = fd.getFile(); 1029 1030 if (fileName != null) { 1031 1032 // Create the file reference to the chosen file. 1033 File chosenFile = new File(fd.getDirectory(), fileName); 1034 1035 return chosenFile; 1036 } 1037 1038 return null; 1039 } 1040 1041 /** 1042 * Method that brings up a file chooser dialog and allows the user to select a 1043 * directory. 1044 * 1045 * @param parent The owner of the dialog (<code>null</code> is fine). 1046 * @param mode Either FileDialog.LOAD or FileDialog.SAVE. 1047 * @param message The message for the dialog. Something like "Choose a name for this 1048 * directory:". 1049 * @param directory The directory to prompt the user with by default 1050 * (<code>null</code> is fine). 1051 * @param filter The filename filter to use (<code>null</code> means no filter). 1052 * @return The file selected by the user, or <code>null</code> if no file was 1053 * selected. 1054 */ 1055 public static File selectDirectory(Component parent, int mode, String message, 1056 String directory, FilenameFilter filter) { 1057 1058 Frame frame; 1059 if (parent == null) 1060 frame = new Frame(); 1061 else 1062 frame = getFrameForComponent(parent); 1063 1064 if (message == null) 1065 message = ""; 1066 1067 File theFile = null; 1068 if (isMacOS()) { 1069 // Use the native file chooser (the Java Swing one is inadequate for MacOS users). 1070 1071 // Bring up a directory chooser. 1072 System.setProperty("apple.awt.fileDialogForDirectories", "true"); 1073 FileDialog fd = new FileDialog(frame, message, mode); 1074 fd.addNotify(); 1075 1076 if (directory != null && !directory.equals("")) 1077 // Prompt the user with the existing directory path. 1078 fd.setDirectory(directory); 1079 1080 if (filter != null) 1081 fd.setFilenameFilter(filter); 1082 1083 fd.setVisible(true); 1084 String fileName = fd.getFile(); 1085 1086 if (fileName != null) 1087 // The user has chosen a directory. 1088 theFile = new File(fd.getDirectory(), fileName); 1089 1090 System.setProperty("apple.awt.fileDialogForDirectories", "false"); 1091 1092 } else { 1093 // Use the Java Swing file chooser. 1094 JFileChooser fc = new JFileChooser(directory); 1095 fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 1096 int returnVal; 1097 if (mode == FileDialog.LOAD) 1098 returnVal = fc.showOpenDialog(parent); 1099 else 1100 returnVal = fc.showSaveDialog(parent); 1101 1102 if (returnVal == JFileChooser.APPROVE_OPTION) 1103 // The user has chosen a directory. 1104 theFile = fc.getSelectedFile(); 1105 } 1106 1107 return theFile; 1108 } 1109 1110 /** 1111 * Method that displays a "Save As..." dialog asking the user to select or input a 1112 * file name to save a file to and returns a reference to the chosen file. This 1113 * version of selectFile automatically adds a user supplied extension to the file 1114 * returned if it doesn't already have it. N.B.: No test is made to see if the user 1115 * can actually write to that file! See: File.canWrite(). 1116 * 1117 * @param parent The owner of the dialog (<code>null</code> is fine). 1118 * @param message The message for the dialog. Something like "Choose a name 1119 * for this file:". 1120 * @param directory The directory to prompt the user with by default 1121 * (<code>null</code> is fine). 1122 * @param name The name of the file to prompt the user with by default 1123 * (<code>null</code> is fine). 1124 * @param filter The filename filter to use (<code>null</code> means no 1125 * filter). 1126 * @param extension The filename extension. This is appended to the filename 1127 * provided by the user if the filename input doesn't already 1128 * have it. Passing <code>null</code> means that no extension 1129 * will be forced onto the filename. 1130 * @param existsErrFmtMsg A MessageFormat compatible "file exists" error message that 1131 * will have the file name substituted into it. Pass something 1132 * like, "A file with the named \"{0}\" already exists. Do you 1133 * want to replace it?" 1134 * @param dlgTitle The title of the warning dialog that is shown if the 1135 * selected file already exists. 1136 * @return The file chosen by the user for saving out a file. Returns 1137 * <code>null</code> if the a valid file was not chosen. 1138 */ 1139 public static File selectFile4Save(Component parent, String message, String directory, String name, 1140 FilenameFilter filter, String extension, String existsErrFmtMsg, String dlgTitle) { 1141 1142 // Have the user choose a file. 1143 File chosenFile = selectFile(parent, FileDialog.SAVE, message, directory, name, filter); 1144 1145 chosenFile = addExtensionToFile(parent, chosenFile, extension, existsErrFmtMsg, dlgTitle); 1146 1147 return chosenFile; 1148 } 1149 1150 /** 1151 * Return a version of the provided file reference that has the specified extension on 1152 * it. This is intended to be used as part of processing a user input file name. 1153 * <ul> 1154 * <li>If the input file already has the extension, it is simply returned.</li> 1155 * <li>If the input file doesn't have the extension, it is added to the end.</li> 1156 * <li>If the modified file reference is an existing file, the user is asked if they 1157 * want to overwrite it.</li> 1158 * <li>If the user chooses to overwrite the existing file, then the file reference is 1159 * returned.</li> 1160 * <li>If the user chooses not to overwrite it, then <code>null</code> is 1161 * returned.</li> 1162 * </ul> 1163 * 1164 * @param parent The owner of the dialog (<code>null</code> is fine). 1165 * @param theFile The file to enforce an extension for. If <code>null</code>, 1166 * then this dialog does nothing. 1167 * @param extension The extension to ensure the file has. If <code>null</code>, 1168 * then this method does nothing. 1169 * @param existsErrFmtMsg A MessageFormat compatible "file exists" error message that 1170 * will have the file name substituted into it. Pass something 1171 * like, "A file with the named \"{0}\" already exists. Do you 1172 * want to replace it?" 1173 * @param dlgTitle The title of the warning dialog that is shown if the 1174 * selected file already exists. 1175 * @return a version of the provided file reference that has the specified extension 1176 * on it. 1177 */ 1178 public static File addExtensionToFile(Component parent, File theFile, String extension, 1179 String existsErrFmtMsg, String dlgTitle) { 1180 1181 if (theFile != null && extension != null) { 1182 if (!extension.startsWith(".")) 1183 extension = "." + extension; 1184 1185 String fileName = theFile.getName(); 1186 if (!fileName.toLowerCase().endsWith(extension)) { 1187 // Create a new file reference including the extension. 1188 theFile = new File(theFile.getParent(), fileName + extension); 1189 1190 // Since we have changed the name from what the user input, make sure the newly named file 1191 // doesn't already exist. 1192 if (theFile.exists()) { 1193 // Build up a message to show the user. 1194 String msg = MessageFormat.format(existsErrFmtMsg, fileName); 1195 1196 // Ask the user if they want to replace the existing file. 1197 int result = JOptionPane.showConfirmDialog(parent, msg, dlgTitle, 1198 JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); 1199 if (result != JOptionPane.YES_OPTION) 1200 // The user does not want to overwrite the existing file. 1201 theFile = null; 1202 } 1203 } 1204 } 1205 1206 return theFile; 1207 } 1208 1209 /** 1210 * Create an standard about handler for use in this application. The supplied 1211 * resource bundle must contain the following information for this to work: 1212 * aboutTextResName = The resource name of the HTML format file to display in the dialog. 1213 * Hyperlinks in this file will be active. 1214 * applicationIconResName = The resource name of the image to display as the application icon. 1215 * appName = The name of the application. 1216 * appAuthor = The name of the application author. 1217 * appAuthorEmail = The e-mail address of the application author. 1218 * appVersion = The application version. 1219 * appModDate = The date the application was last modified. 1220 * copyright = The copyright notice text. 1221 * 1222 * @param resBundle The resources for this program containing the required strings. 1223 * @param logger The logger to use to report errors or null for echoing to standard out. 1224 * @return the about menu for this application. 1225 */ 1226 public static AboutHandler standardAboutHandler(final ResourceBundle resBundle, final Logger logger) { 1227 1228 AboutHandler about = new AboutHandler() { 1229 @Override 1230 public void handleAbout​(AboutEvent e) { 1231 1232 // Load in the application's icon image. 1233 Icon appIcon = getResourceIcon(resBundle.getString("applicationIconResName"), logger); 1234 if (appIcon == null) 1235 // Use a generic icon since this app can't read it's custom icon. 1236 appIcon = UIManager.getIcon("OptionPane.informationIcon"); 1237 1238 // Read in the about box text information. 1239 String credits = readAboutText(resBundle, logger); 1240 1241 String aName = resBundle.getString("appName"); 1242 String aVersion = resBundle.getString("appVersion"); 1243 String aModDate = resBundle.getString("appModDate"); 1244 String copyright = resBundle.getString("copyright"); 1245 StandardMacAboutFrame f = new StandardMacAboutFrame(aName, aVersion + " - " + aModDate); 1246 f.setApplicationIcon(appIcon); 1247 f.setCopyright(copyright); 1248 f.setCredits(credits, "text/html"); 1249 f.setHyperlinkListener(new HyperlinkListener() { 1250 @Override 1251 public void hyperlinkUpdate(HyperlinkEvent e) { 1252 try { 1253 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) 1254 MDIApplication.browse(e.getURL().toURI()); 1255 } catch (Exception err) { 1256 err.printStackTrace(); 1257 } 1258 } 1259 }); 1260 f.setVisible(true); 1261 } 1262 }; 1263 1264 return about; 1265 } 1266 1267 /** 1268 * Method that handles reading in the contents of the text region of the about box 1269 * from a text file. 1270 */ 1271 private static String readAboutText(ResourceBundle resBundle, Logger logger) { 1272 String text; 1273 try { 1274 String urlStr = resBundle.getString("aboutTextResName"); 1275 URL url = ClassLoader.getSystemResource(urlStr); 1276 1277 InputStreamReader reader = new InputStreamReader(url.openStream(), "UTF-8"); 1278 StringWriter writer = new StringWriter(); 1279 int character = reader.read(); 1280 while (character != -1) { 1281 writer.write(character); 1282 character = reader.read(); 1283 } 1284 text = writer.toString(); 1285 1286 } catch (IOException ex) { 1287 if (logger != null) 1288 logger.log(Level.WARNING, null, ex); 1289 else 1290 ex.printStackTrace(); 1291 1292 // Create a backup set of credits if we didn't find the text file. 1293 text = "<html><body><b>Author:</b><br>" + resBundle.getString("appAuthor") + "<br>"; 1294 text += "<a href=\"mailto:" + resBundle.getString("appAuthorEmail") + "\">" + resBundle.getString("appAuthorEmail") + "</a><br>"; 1295 text += "<P ALIGN=CENTER><BR>" + resBundle.getString("appName") + " comes with ABSOLUTELY NO WARRANTY;"; 1296 text += "</body></html>"; 1297 } 1298 1299 return text; 1300 } 1301 1302 /** 1303 * Return the application icon for this program or null if one can not be found. 1304 * 1305 * @param name The name of the image file in the resource search path to return as 1306 * an icon. 1307 * @param logger The logger to use to report errors or null for echoing to standard 1308 * out. 1309 * @return The application icon for this program. 1310 */ 1311 public static ImageIcon getResourceIcon(String name, Logger logger) { 1312 1313 ImageIcon appIcon = null; 1314 try { 1315 // Load in the application's icon image. 1316 URL imgURL = ClassLoader.getSystemResource(name); 1317 appIcon = new ImageIcon(imgURL); 1318 } catch (Exception ex) { 1319 if (logger != null) 1320 logger.log(Level.WARNING, null, ex); 1321 else 1322 ex.printStackTrace(); 1323 } 1324 1325 return appIcon; 1326 } 1327 1328}