001package hirondelle.stocks.util.ui; 002 003import hirondelle.stocks.util.Args; 004import hirondelle.stocks.util.Consts; 005import hirondelle.stocks.util.Util; 006 007import java.util.*; 008import java.text.*; 009import java.net.URL; 010import javax.swing.*; 011import javax.swing.border.Border; 012import java.awt.*; 013import javax.swing.plaf.metal.MetalLookAndFeel; 014import hirondelle.stocks.preferences.GeneralLookPreferencesEditor; 015 016/** Static convenience methods for GUIs which eliminate code duplication.*/ 017public final class UiUtil { 018 019 /** 020 * <tt>pack</tt>, center, and <tt>show</tt> a window on the screen. 021 * 022 * <P>If the size of <tt>aWindow</tt> exceeds that of the screen, 023 * then the size of <tt>aWindow</tt> is reset to the size of the screen. 024 */ 025 public static void centerAndShow(Window aWindow){ 026 //note that the order here is important 027 028 aWindow.pack(); 029 /* 030 * If called from outside the event dispatch thread (as is 031 * the case upon startup, in the launch thread), then 032 * in principle this code is not thread-safe: once pack has 033 * been called, the component is realized, and (most) further 034 * work on the component should take place in the event-dispatch 035 * thread. 036 * 037 * In practice, it is exceedingly unlikely that this will lead 038 * to an error, since invisible components cannot receive events. 039 */ 040 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 041 Dimension window = aWindow.getSize(); 042 //ensure that no parts of aWindow will be off-screen 043 if (window.height > screen.height) { 044 window.height = screen.height; 045 } 046 if (window.width > screen.width) { 047 window.width = screen.width; 048 } 049 int xCoord = (screen.width/2 - window.width/2); 050 int yCoord = (screen.height/2 - window.height/2); 051 aWindow.setLocation( xCoord, yCoord ); 052 053 aWindow.setVisible(true); 054 } 055 056 /** 057 * A window is packed, centered with respect to a parent, and then shown. 058 * 059 * <P>This method is intended for dialogs only. 060 * 061 * <P>If centering with respect to a parent causes any part of the dialog 062 * to be off screen, then the centering is overidden, such that all of the 063 * dialog will always appear fully on screen, but it will still appear 064 * near the parent. 065 * 066 * @param aWindow must have non-null result for <tt>aWindow.getParent</tt>. 067 */ 068 public static void centerOnParentAndShow(Window aWindow){ 069 aWindow.pack(); 070 071 Dimension parent = aWindow.getParent().getSize(); 072 Dimension window = aWindow.getSize(); 073 int xCoord = 074 aWindow.getParent().getLocationOnScreen().x + 075 (parent.width/2 - window.width/2) 076 ; 077 int yCoord = 078 aWindow.getParent().getLocationOnScreen().y + 079 (parent.height/2 - window.height/2) 080 ; 081 082 //Ensure that no part of aWindow will be off-screen 083 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 084 int xOffScreenExcess = xCoord + window.width - screen.width; 085 if ( xOffScreenExcess > 0 ) { 086 xCoord = xCoord - xOffScreenExcess; 087 } 088 if (xCoord < 0 ) { 089 xCoord = 0; 090 } 091 int yOffScreenExcess = yCoord + window.height - screen.height; 092 if ( yOffScreenExcess > 0 ) { 093 yCoord = yCoord - yOffScreenExcess; 094 } 095 if (yCoord < 0) { 096 yCoord = 0; 097 } 098 099 aWindow.setLocation( xCoord, yCoord ); 100 aWindow.setVisible(true); 101 } 102 103 /** 104 * Return a border of dimensions recommended by the Java Look and Feel 105 * Design Guidelines, suitable for many common cases. 106 * 107 *<P>Each side of the border has size {@link UiConsts#STANDARD_BORDER}. 108 */ 109 public static Border getStandardBorder(){ 110 return BorderFactory.createEmptyBorder( 111 UiConsts.STANDARD_BORDER, 112 UiConsts.STANDARD_BORDER, 113 UiConsts.STANDARD_BORDER, 114 UiConsts.STANDARD_BORDER 115 ); 116 } 117 118 /** 119 * Return text which conforms to the Look and Feel Design Guidelines 120 * for the title of a dialog : the application name, a colon, then 121 * the name of the specific dialog. 122 * 123 *<P>Example return value: <tt>StocksMonitor: Preferences</tt> 124 * 125 * @param aSpecificDialogName must have visible content 126 */ 127 public static String getDialogTitle(String aSpecificDialogName){ 128 Args.checkForContent(aSpecificDialogName); 129 StringBuilder result = new StringBuilder(Consts.APP_NAME); 130 result.append(": "); 131 result.append(aSpecificDialogName); 132 return result.toString(); 133 } 134 135 /** 136 * Make a horizontal row of buttons of equal size, whch are equally spaced, 137 * and aligned on the right. 138 * 139 * <P>The returned component has border spacing only on the top (of the size 140 * recommended by the Look and Feel Design Guidelines). 141 * All other spacing must be applied elsewhere ; usually, this will only mean 142 * that the dialog's top-level panel should use {@link #getStandardBorder}. 143 * 144 * @param aButtons contains the buttons to be placed in a row. 145 */ 146 public static JComponent getCommandRow(java.util.List<JComponent> aButtons){ 147 equalizeSizes( aButtons ); 148 JPanel panel = new JPanel(); 149 LayoutManager layout = new BoxLayout(panel, BoxLayout.X_AXIS); 150 panel.setLayout(layout); 151 panel.setBorder(BorderFactory.createEmptyBorder(UiConsts.THREE_SPACES, 0, 0, 0)); 152 panel.add(Box.createHorizontalGlue()); 153 Iterator<JComponent> buttonsIter = aButtons.iterator(); 154 while (buttonsIter.hasNext()) { 155 panel.add(buttonsIter.next()); 156 if (buttonsIter.hasNext()) { 157 panel.add(Box.createHorizontalStrut(UiConsts.ONE_SPACE)); 158 } 159 } 160 return panel; 161 } 162 163 /** 164 * Make a vertical row of buttons of equal size, whch are equally spaced, 165 * and aligned on the right. 166 * 167 * <P>The returned component has border spacing only on the left (of the size 168 * recommended by the Look and Feel Design Guidelines). 169 * All other spacing must be applied elsewhere ; usually, this will only mean 170 * that the dialog's top-level panel should use {@link #getStandardBorder}. 171 * 172 * @param aButtons contains the buttons to be placed in a column 173 */ 174 public static JComponent getCommandColumn(java.util.List<JComponent> aButtons){ 175 equalizeSizes(aButtons); 176 JPanel panel = new JPanel(); 177 LayoutManager layout = new BoxLayout(panel, BoxLayout.Y_AXIS); 178 panel.setLayout(layout); 179 panel.setBorder( 180 BorderFactory.createEmptyBorder(0,UiConsts.THREE_SPACES, 0,0) 181 ); 182 //(no for-each is used here, because of the 'not-yet-last' check) 183 Iterator<JComponent> buttonsIter = aButtons.iterator(); 184 while (buttonsIter.hasNext()) { 185 panel.add(buttonsIter.next()); 186 if (buttonsIter.hasNext()) { 187 panel.add( Box.createVerticalStrut(UiConsts.ONE_SPACE) ); 188 } 189 } 190 panel.add(Box.createVerticalGlue()); 191 return panel; 192 } 193 194 /** 195 * Return an <tt>ImageIcon</tt> using its <tt>String</tt> identifier. 196 * 197 * @param aImageId starts with '/', and refers to an image resource 198 * which is accessible through {@link Class#getResource}. 199 */ 200 public static ImageIcon getImageIcon(String aImageId){ 201 if( ! aImageId.startsWith(BACK_SLASH) ){ 202 throw new IllegalArgumentException( 203 "Image identifier does not start with backslash: " + aImageId 204 ); 205 } 206 return fetchImageIcon(aImageId, UiUtil.class); 207 } 208 209 /** 210 * Return an <tt>ImageIcon</tt> using its <tt>String</tt> identifier, relative to 211 * a given class. 212 * 213 * @param aImageId does NOT start with '/', and must refer to an image resource which is 214 * accessible through {@link Class#getResource}. 215 * @param aClass the class relative to which the image is located. 216 */ 217 public static ImageIcon getImageIcon(String aImageId, Class<?> aClass){ 218 if( aImageId.startsWith(BACK_SLASH) ){ 219 throw new IllegalArgumentException( 220 "Image identifier starts with a backslash: " + aImageId 221 ); 222 } 223 return fetchImageIcon(aImageId, aClass); 224 } 225 226 /** 227 * Return a square icon which paints nothing, and whose dimensions correspond 228 * to the user preference for icon size. 229 * 230 * <P>A common problem occurs with text alignment in menus, where there is 231 * a mixture of menu items with and without an icon. Adding an empty icon 232 * to menu items which do not have one will adjust its alignment to match 233 * that of the others which do have an icon. 234 */ 235 public static Icon getEmptyIcon(){ 236 GeneralLookPreferencesEditor prefs = new GeneralLookPreferencesEditor(); 237 return prefs.hasLargeIcons() ? EmptyIcon.SIZE_24 : EmptyIcon.SIZE_16; 238 } 239 240 /** 241 * Return a <tt>Dimension</tt> whose size is defined not in terms of pixels, 242 * but in terms of a given percent of the screen's width and height. 243 * 244 *<P> Use to set the preferred size of a component to a certain 245 * percentage of the screen. 246 * 247 * @param aPercentWidth percentage width of the screen, in range <tt>1..100</tt>. 248 * @param aPercentHeight percentage height of the screen, in range <tt>1..100</tt>. 249 */ 250 public static final Dimension getDimensionFromPercent( 251 int aPercentWidth, int aPercentHeight 252 ){ 253 int low = 1; 254 int high = 100; 255 Args.checkForRange(aPercentWidth, low, high); 256 Args.checkForRange(aPercentHeight, low, high); 257 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 258 return calcDimensionFromPercent(screenSize, aPercentWidth, aPercentHeight); 259 } 260 261 /** 262 * Sets the items in <tt>aComponents</tt> to the same size. 263 * 264 * <P>Sets each component's preferred and maximum sizes. 265 * The actual size is determined by the layout manager, whcih adjusts 266 * for locale-specific strings and customized fonts. (See this 267 * <a href="http://java.sun.com/products/jlf/ed2/samcode/prefere.html">Sun doc</a> 268 * for more information.) 269 * 270 * @param aComponents items whose sizes are to be equalized 271 */ 272 public static void equalizeSizes(java.util.List<JComponent> aComponents) { 273 Dimension targetSize = new Dimension(0,0); 274 for(JComponent comp: aComponents ) { 275 Dimension compSize = comp.getPreferredSize(); 276 double width = Math.max(targetSize.getWidth(), compSize.getWidth()); 277 double height = Math.max(targetSize.getHeight(), compSize.getHeight()); 278 targetSize.setSize(width, height); 279 } 280 setSizes(aComponents, targetSize); 281 } 282 283 /** 284 * Create a pair of components, a <tt>JLabel</tt> and an associated 285 * <tt>JTextField</tt>, as is typically used for user input. 286 * 287 *<P>The <tt>JLabel</tt> appears on the left, and the <tt>JTextField</tt> 288 * appears on the same row, just to the right of the <tt>JLabel</tt>. 289 * The <tt>JLabel</tt> has a mnemonic which forwards focus to the 290 * <tt>JTextField</tt> when activated. 291 * 292 * @param aContainer holds the pair of components. 293 * @param aName text of the <tt>JLabel</tt> component. 294 * @param aInitialValue possibly-null initial value to appear 295 * in the <tt>JTextField</tt>; if <tt>null</tt>, then 296 * <tt>JTextField</tt> will be blank. 297 * @param aMnemonic <tt>KeyEvent</tt> field, used as the mnemonic for 298 * the <tt>JLabel</tt>. 299 * @param aConstraints applied to the <tt>JLabel</tt>; the corresponding 300 * constraints for the <tt>JTextField</tt> are the same as 301 * <tt>aConstraints</tt>, except for <tt>gridx</tt> being incremented by one; 302 * in addition, if <tt>aConstraints</tt> has <tt>weightx=0</tt> (the default), 303 * then the entry field will receive <tt>weightx=1.0</tt> (entry field gets more 304 * horizontal space upon resize). 305 * @param aTooltip possibly-null text displayed as tool tip for the 306 * <tt>JTextField</tt> ; if <tt>null</tt>, the tool tip is turned off. 307 * @return the user input <tt>JTextField</tt>. 308 */ 309 public static JTextField addSimpleEntryField( 310 Container aContainer, String aName, String aInitialValue, 311 int aMnemonic, GridBagConstraints aConstraints, String aTooltip 312 ){ 313 Args.checkForNull(aName); 314 315 JLabel label = new JLabel(aName); 316 label.setDisplayedMnemonic(aMnemonic); 317 aContainer.add( label, aConstraints ); 318 319 JTextField result = new JTextField(UiConsts.SIMPLE_FIELD_WIDTH); 320 label.setLabelFor(result); 321 result.setToolTipText(aTooltip); 322 if (aInitialValue != null) { 323 result.setText(aInitialValue); 324 } 325 aConstraints.gridx = ++aConstraints.gridx; 326 if (aConstraints.weightx == 0.0){ 327 aConstraints.weightx = 1.0; 328 } 329 aContainer.add(result, aConstraints); 330 return result; 331 } 332 333 /** 334 * Return a set of constraints with convenient default values. 335 * 336 *<P>Return constraints with these values : 337 *<ul> 338 * <li> <tt>gridx, gridy</tt> - set to <tt>aX, aY</tt> 339 * <li> <tt>anchor - GridBagConstraints.WEST</tt> 340 * <li> <tt>insets - Insets(0,0,0, UiConsts.ONE_SPACE)</tt> 341 *</ul> 342 * 343 *<P> All other items simply take their default values : 344 *<ul> 345 * <li> <tt>fill - GridBagConstraints.NONE</tt> 346 * <li> <tt>gridwidth, gridheight - 0, 0</tt> 347 * <li> <tt>weightx , weighty - 0, 0</tt> 348 * <li> <tt>ipadx, ipady - 0, 0</tt> 349 *</ul> 350 * 351 * <P>The caller is free to change the returned constraints, to customize for 352 * their particular needs. 353 * 354 * @param aY in range <tt>0..10</tt>. 355 * @param aX in range <tt>0..10</tt>. 356 */ 357 public static GridBagConstraints getConstraints(int aY, int aX){ 358 int low = 0; 359 int high = 10; 360 Args.checkForRange(aY, low, high); 361 Args.checkForRange(aX, low, high); 362 GridBagConstraints result = new GridBagConstraints(); 363 result.gridy = aY; 364 result.gridx = aX; 365 result.anchor = GridBagConstraints.WEST; 366 result.insets = new Insets(0,0,0,UiConsts.ONE_SPACE); 367 return result; 368 } 369 370 /** 371 * Return {@link #getConstraints(int, int)}, with the addition of setting 372 * <tt>gridwidth</tt> to <tt>aWidth</tt>, and setting 373 * <tt>gridheight</tt> to <tt>aHeight</tt>. 374 * 375 * <P>The caller is free to change the returned constraints, to customize for 376 * their particular needs. 377 * 378 * @param aY in range <tt>0..10</tt>. 379 * @param aX in range <tt>0..10</tt>. 380 * @param aWidth in range <tt>1..10</tt>. 381 * @param aHeight in range <tt>1..10</tt>. 382 */ 383 public static GridBagConstraints getConstraints(int aY, int aX, int aWidth, int aHeight){ 384 int low = 0; 385 int high = 10; 386 Args.checkForRange(aHeight, low, high); 387 Args.checkForRange(aWidth, low, high); 388 GridBagConstraints result = getConstraints(aY, aX); 389 result.gridheight = aHeight; 390 result.gridwidth = aWidth; 391 return result; 392 } 393 394 /** 395 * Create a pair of <tt>JLabel</tt> components, as is typically needed 396 * for display of a name-value pair. 397 * 398 * <P>The name appears on the left, and the value appears on the right, 399 * all on the same row. A colon and an empty space are appended to the name. 400 * 401 * <P> If the the length of "value" label is greater than 402 * {@link UiConsts#MAX_LABEL_LENGTH}, then the text is truncated, an ellipsis 403 * is placed at its end, and the full text is placed in a tooltip. 404 * 405 * @param aContainer holds the pair of components. 406 * @param aName text of the name <tt>JLabel</tt>. 407 * @param aValue possibly-null ; if null, then an empty <tt>String</tt> 408 * is used for the value; otherwise <tt>Object.toString</tt> is used. 409 * @param aConstraints for the name <tt>JLabel</tt>; the corresponding 410 * constraints for the value <tt>JLabel</tt> are mostly taken from 411 * <tt>aConstraints</tt>, except for <tt>gridx</tt> being incremented by one 412 * (<tt>weightx</tt> may differ as well - see <tt>aWeightOnDisplay</tt>.) 413 * @param aWeightOnDisplay if true, then set <tt>weightx</tt> for the value 414 * field to 1.0 (to give it more horizontal space upon resize). 415 * @return the <tt>JLabel</tt> for the value (which is usually variable). 416 */ 417 public static JLabel addSimpleDisplayField( 418 Container aContainer, String aName, Object aValue, 419 GridBagConstraints aConstraints, boolean aWeightOnDisplay 420 ){ 421 StringBuilder formattedName = new StringBuilder(aName); 422 formattedName.append(": "); 423 JLabel name = new JLabel( formattedName.toString() ); 424 aContainer.add( name, aConstraints ); 425 426 String valueText = (aValue != null? aValue.toString() : Consts.EMPTY_STRING); 427 JLabel value = new JLabel(valueText); 428 truncateLabelIfLong(value); 429 aConstraints.gridx = ++aConstraints.gridx; 430 if (aWeightOnDisplay){ 431 aConstraints.weightx = 1.0; 432 } 433 aContainer.add( value, aConstraints ); 434 435 return value; 436 } 437 438 /** 439 * Present a number of read-only items to the user as a vertical listing 440 * of <tt>JLabel</tt> name-value pairs. 441 * 442 * <P>Each pair is added in the style of 443 * {@link #addSimpleDisplayField} (its <tt>aConstraints</tt> param are those 444 * returned by {@link #getConstraints(int, int)}, and its <tt>aWeightOnDisplay</tt> 445 * param is set to <tt>true</tt>). 446 * 447 * <P>The order of presentation is determined by the iteration order of 448 * <tt>aNameValuePairs</tt>. 449 * 450 *<P>The number of items which should be presented using this method is limited, since 451 * no scrolling mechanism is given to the user. 452 * 453 * @param aContainer holds the display fields. 454 * @param aNameValuePairs has <tt>String</tt> keys for the names, 455 * and values are possibly null <tt>Object</tt>s; 456 * if null, then an empty <tt>String</tt> is displayed, otherwise 457 * <tt>Object.toString</tt> is called on the value and displayed. 458 */ 459 public static void addSimpleDisplayFields( 460 Container aContainer, Map<String, String> aNameValuePairs 461 ) { 462 Set<String> keys = aNameValuePairs.keySet(); 463 int rowIdx = 0; 464 for(String name: keys) { 465 String value = aNameValuePairs.get(name); 466 if(value == null){ 467 value = Consts.EMPTY_STRING; 468 } 469 UiUtil.addSimpleDisplayField( 470 aContainer, 471 name, 472 value, 473 UiUtil.getConstraints(rowIdx,0), 474 true 475 ); 476 ++rowIdx; 477 } 478 } 479 480 /** 481 * Adds "glue" (an empty component with desired resizing behavior) to the bottom 482 * row of a <tt>GridBagLayout</tt> of components. When resized, this glue will 483 * take up extra vertical space. 484 * 485 * <P>This method is especially useful for text data presented in a listing or 486 * tabular form. Such components naturally resize horizontally, while their vertical 487 * resizing should often be absent. If such a listing is resized vertically, then this 488 * glue can take up the remaining vertical space, keeping the text at the top. 489 * 490 * @param aPanel uses <tt>GridBagLayout</tt>, and contains components whose 491 * <tt>weighty</tt> values are all 0.0 (the default). 492 * @param aLastRowIdx index of the last row of components, in which the glue will be 493 * placed. 494 */ 495 public static void addVerticalGridGlue(JPanel aPanel, int aLastRowIdx) { 496 GridBagConstraints glueConstraints = UiUtil.getConstraints(aLastRowIdx,0); 497 glueConstraints.weighty = 1.0; 498 glueConstraints.fill = GridBagConstraints.VERTICAL; 499 aPanel.add(new JLabel(), glueConstraints); 500 } 501 502 /** 503 * Return a <tt>String</tt>, suitable for presentation to the end user, 504 * representing a percentage having two decimal places, using the default locale. 505 * 506 * <P>An example return value is "5.15%". The intent of this method is to 507 * provide a standard representation and number of decimals for the entire 508 * application. If a different number of decimal places is required, then 509 * the caller should use <tt>NumberFormat</tt> instead. 510 */ 511 public static String getLocalizedPercent( Number aNumber ){ 512 NumberFormat localFormatter = NumberFormat.getPercentInstance(); 513 localFormatter.setMinimumFractionDigits(2); 514 return localFormatter.format(aNumber.doubleValue()); 515 } 516 517 /** 518 * Return a <tt>String</tt>, suitable for presentation to the end user, 519 * representing an integral number with no decimal places, using the default 520 * locale. 521 * 522 * <P>An example return value is "8,000". The intent of this method is to 523 * provide a standard representation of integers for the entire 524 * application. 525 */ 526 public static String getLocalizedInteger( Number aNumber ) { 527 NumberFormat localFormatter = NumberFormat.getNumberInstance(); 528 return localFormatter.format(aNumber.intValue()); 529 } 530 531 /** 532 * Return a <tt>String</tt>, suitable for presentation to the end user, 533 * representing a date in <tt>DateFormat.SHORT</tt> and the default locale. 534 */ 535 public static String getLocalizedTime(Date aDate){ 536 DateFormat dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT); 537 return dateFormat.format(aDate); 538 } 539 540 /** 541 * Make the sytem emit a beep. 542 * 543 * <P>May not beep unless the speakers are turned on, so this cannot 544 * be guaranteed to work. 545 */ 546 public static void beep(){ 547 Toolkit.getDefaultToolkit().beep(); 548 } 549 550 /** 551 * An alternative to multi-line labels, for the presentation of 552 * several lines of text, and for which the line breaks are determined 553 * solely by the widget. 554 * 555 * @param aText must have visible content, doesn't contain newline characters or html. 556 * @return <tt>JTextArea</tt> which is not editable, has improved spacing over the 557 * supplied default (placing {@link UiConsts#ONE_SPACE} on the left and right), 558 * which wraps lines on word boundaries, and whose background color is the 559 * same as {@link javax.swing.plaf.metal.MetalLookAndFeel#getMenuBackground}. 560 */ 561 public static JTextArea getStandardTextArea(String aText){ 562 Args.checkForContent(aText); 563 if ( aText.indexOf(Consts.NEW_LINE) != -1 ){ 564 throw new IllegalArgumentException("Must not contain new line characters: " + aText); 565 } 566 JTextArea result = new JTextArea(aText); 567 result.setEditable(false); 568 result.setWrapStyleWord(true); 569 result.setLineWrap(true); 570 result.setMargin( new Insets(0,UiConsts.ONE_SPACE,0,UiConsts.ONE_SPACE) ); 571 //this is a bit hacky: the desired color is "secondary3", but cannot see how 572 //to reference it directly; hence, an element which uses secondary3 is used instead. 573 result.setBackground( MetalLookAndFeel.getMenuBackground() ); 574 575 return result; 576 } 577 578 /** 579 * An alternative to multi-line labels, for the presentation of 580 * several lines of text, and for which line breaks are determined 581 * solely by <tt>aText</tt>, and not by the widget. 582 * 583 * @param aText has visible content 584 * @return <tt>JTextArea</tt> which is not editable, has improved spacing over the 585 * supplied default (placing {@link UiConsts#ONE_SPACE} on the left and right), 586 * and whose background color is the same as 587 * {@link javax.swing.plaf.metal.MetalLookAndFeel#getMenuBackground}. 588 */ 589 public static JTextArea getStandardTextAreaHardNewLines(String aText){ 590 Args.checkForContent(aText); 591 JTextArea result = new JTextArea(aText); 592 result.setEditable(false); 593 result.setMargin(new Insets(0,UiConsts.ONE_SPACE,0,UiConsts.ONE_SPACE)); 594 result.setBackground( MetalLookAndFeel.getMenuBackground() ); 595 return result; 596 } 597 598 /** 599 * Imposes a uniform horizontal alignment on all items in a container. 600 * 601 *<P> Intended especially for <tt>BoxLayout</tt>, where all components need 602 * to share the same alignment in order for display to be reasonable. 603 * (Indeed, this method may only work for <tt>BoxLayout</tt>, since apparently 604 * it is the only layout to use <tt>setAlignmentX, setAlignmentY</tt>.) 605 * 606 * @param aContainer contains only <tt>JComponent</tt> objects. 607 */ 608 public static void alignAllX(Container aContainer, UiUtil.AlignX aAlignment){ 609 java.util.List<Component> components = Arrays.asList( aContainer.getComponents() ); 610 for(Component comp: components){ 611 JComponent jcomp = (JComponent)comp; 612 jcomp.setAlignmentX(aAlignment.getValue()); 613 } 614 } 615 616 /** Enumeration for horizontal alignment. */ 617 public enum AlignX { 618 LEFT(Component.LEFT_ALIGNMENT), 619 CENTER(Component.CENTER_ALIGNMENT), 620 RIGHT(Component.RIGHT_ALIGNMENT); 621 public float getValue(){ 622 return fValue; 623 } 624 private final float fValue; 625 private AlignX(float aValue){ 626 fValue = aValue; 627 } 628 } 629 630 /** 631 * Imposes a uniform vertical alignment on all items in a container. 632 * 633 *<P> Intended especially for <tt>BoxLayout</tt>, where all components need 634 * to share the same alignment in order for display to be reasonable. 635 * (Indeed, this method may only work for <tt>BoxLayout</tt>, since apparently 636 * it is the only layout to use <tt>setAlignmentX, setAlignmentY</tt>.) 637 * 638 * @param aContainer contains only <tt>JComponent</tt> objects. 639 */ 640 public static void alignAllY(Container aContainer, UiUtil.AlignY aAlignment){ 641 java.util.List components = Arrays.asList( aContainer.getComponents() ); 642 Iterator compsIter = components.iterator(); 643 while ( compsIter.hasNext() ) { 644 JComponent comp = (JComponent)compsIter.next(); 645 comp.setAlignmentY( aAlignment.getValue() ); 646 } 647 } 648 649 /** Type-safe enumeration vertical alignment. */ 650 public enum AlignY { 651 TOP(Component.TOP_ALIGNMENT), 652 CENTER(Component.CENTER_ALIGNMENT), 653 BOTTOM(Component.BOTTOM_ALIGNMENT); 654 float getValue(){ 655 return fValue; 656 } 657 private final float fValue; 658 private AlignY( float aValue){ 659 fValue = aValue; 660 } 661 } 662 663 /** 664 * Ensure that <tt>aRootPane</tt> has no default button associated with it. 665 * 666 * <P>Intended mainly for dialogs where the user is confirming a delete action. 667 * In this case, an explicit Yes or No is preferred, with no default action being 668 * taken when the user hits the Enter key. 669 */ 670 public static void noDefaultButton(JRootPane aRootPane){ 671 aRootPane.setDefaultButton(null); 672 } 673 674 // PRIVATE 675 676 private static final String BACK_SLASH = "/"; 677 678 /** 679 * If <tt>aIconName</tt> indicates that the icon is part of the standard graphic 680 * repository (by starting with "/toolbar"), then append either "16.gif" or 681 * "24.gif" to the name, according to the user's current preference for icon size. 682 */ 683 private static String addSizeToStandardIcon(String aIconName){ 684 assert( Util.textHasContent(aIconName) ); 685 StringBuilder result = new StringBuilder(aIconName); 686 if ( aIconName.startsWith("/toolbar") ) { 687 GeneralLookPreferencesEditor prefs = new GeneralLookPreferencesEditor(); 688 if ( prefs.hasLargeIcons() ) { 689 result.append("24.gif"); 690 } 691 else { 692 result.append("16.gif"); 693 } 694 } 695 return result.toString(); 696 } 697 698 private static void setSizes(java.util.List aComponents, Dimension aDimension){ 699 Iterator compsIter = aComponents.iterator(); 700 while ( compsIter.hasNext() ) { 701 JComponent comp = (JComponent) compsIter.next(); 702 comp.setPreferredSize((Dimension)aDimension.clone()); 703 comp.setMaximumSize((Dimension)aDimension.clone()); 704 } 705 } 706 707 private static Dimension calcDimensionFromPercent( 708 Dimension aSourceDimension, int aPercentWidth, int aPercentHeight 709 ){ 710 int width = aSourceDimension.width * aPercentWidth/100; 711 int height = aSourceDimension.height * aPercentHeight/100; 712 return new Dimension(width, height); 713 } 714 715 /** 716 * If aLabel has text which is longer than MAX_LABEL_LENGTH, then truncate 717 * the label text and place an ellipsis at the end; the original text is placed 718 * in a tooltip. 719 * 720 * This is particularly useful for displaying file names, whose length 721 * can vary widely between deployments. 722 */ 723 private static void truncateLabelIfLong(JLabel aLabel){ 724 String originalText = aLabel.getText(); 725 if (originalText.length() > UiConsts.MAX_LABEL_LENGTH){ 726 aLabel.setToolTipText( originalText ); 727 String truncatedText = 728 originalText.substring(0, UiConsts.MAX_LABEL_LENGTH) + Consts.ELLIPSIS 729 ; 730 aLabel.setText(truncatedText); 731 } 732 } 733 734 private static ImageIcon fetchImageIcon(String aImageId, Class<?> aClass){ 735 String imgLocation = addSizeToStandardIcon(aImageId); 736 URL imageURL = aClass.getResource(imgLocation); 737 if ( imageURL != null ) { 738 return new ImageIcon(imageURL); 739 } 740 else { 741 throw new IllegalArgumentException("Cannot retrieve image using id: " + aImageId); 742 } 743 } 744}