001package hirondelle.starfield.util; 002 003import java.util.*; 004import java.net.URL; 005import javax.swing.*; 006import javax.swing.border.Border; 007import java.awt.*; 008 009/** 010 Static convenience methods for GUIs which eliminate code duplication. 011 012 <P>Your application will likely need to add to such a class. For example, 013 using <tt>GrdiBagLayout</tt> usually benefits from utility methods to 014 reduce code repetition. 015*/ 016public final class UiUtil { 017 018 /** 019 <tt>pack</tt>, center, and <tt>show</tt> a window on the screen. 020 021 <P>If the size of <tt>aWindow</tt> exceeds that of the screen, 022 then the size of <tt>aWindow</tt> is reset to the size of the screen. 023 */ 024 public static void centerAndShow(Window aWindow){ 025 //note that the order here is important 026 027 aWindow.pack(); 028 /* 029 * If called from outside the event dispatch thread (as is 030 * the case upon startup, in the launch thread), then 031 * in principle this code is not thread-safe: once pack has 032 * been called, the component is realized, and (most) further 033 * work on the component should take place in the event-dispatch 034 * thread. 035 * 036 * In practice, it is exceedingly unlikely that this will lead 037 * to an error, since invisible components cannot receive events. 038 */ 039 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 040 Dimension window = aWindow.getSize(); 041 //ensure that no parts of aWindow will be off-screen 042 if (window.height > screen.height) { 043 window.height = screen.height; 044 } 045 if (window.width > screen.width) { 046 window.width = screen.width; 047 } 048 int xCoord = (screen.width/2 - window.width/2); 049 int yCoord = (screen.height/2 - window.height/2); 050 aWindow.setLocation( xCoord, yCoord ); 051 052 aWindow.setVisible(true); 053 } 054 055 /** 056 A window is packed, centered with respect to a parent, and then shown. 057 058 <P>This method is intended for dialogs only. 059 060 <P>If centering with respect to a parent causes any part of the dialog 061 to be off screen, then the centering is overidden, such that all of the 062 dialog will always appear fully on screen, but it will still appear 063 near the parent. 064 065 @param aWindow must have non-null result for <tt>aWindow.getParent</tt>. 066 */ 067 public static void centerOnParentAndShow(Window aWindow){ 068 aWindow.pack(); 069 070 Dimension parent = aWindow.getParent().getSize(); 071 Dimension window = aWindow.getSize(); 072 int xCoord = 073 aWindow.getParent().getLocationOnScreen().x + 074 (parent.width/2 - window.width/2) 075 ; 076 int yCoord = 077 aWindow.getParent().getLocationOnScreen().y + 078 (parent.height/2 - window.height/2) 079 ; 080 081 //Ensure that no part of aWindow will be off-screen 082 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 083 int xOffScreenExcess = xCoord + window.width - screen.width; 084 if ( xOffScreenExcess > 0 ) { 085 xCoord = xCoord - xOffScreenExcess; 086 } 087 if (xCoord < 0 ) { 088 xCoord = 0; 089 } 090 int yOffScreenExcess = yCoord + window.height - screen.height; 091 if ( yOffScreenExcess > 0 ) { 092 yCoord = yCoord - yOffScreenExcess; 093 } 094 if (yCoord < 0) { 095 yCoord = 0; 096 } 097 098 aWindow.setLocation( xCoord, yCoord ); 099 aWindow.setVisible(true); 100 } 101 102 /** 103 Return a border of dimensions recommended by the Java Look and Feel 104 Design Guidelines, suitable for many common cases. 105 106 <P>Each side of the border has size {@link UiConsts#STANDARD_BORDER}. 107 */ 108 public static Border getStandardBorder(){ 109 return BorderFactory.createEmptyBorder( 110 UiConsts.STANDARD_BORDER, 111 UiConsts.STANDARD_BORDER, 112 UiConsts.STANDARD_BORDER, 113 UiConsts.STANDARD_BORDER 114 ); 115 } 116 117 /** 118 Return text which conforms to the Look and Feel Design Guidelines 119 for the title of a dialog : the application name, a colon, then 120 the name of the specific dialog. 121 122 <P>Example return value: <tt>My Movies: Login</tt> 123 124 @param aSpecificDialogName must have visible content 125 */ 126 public static String getDialogTitle(String aSpecificDialogName){ 127 Args.checkForContent(aSpecificDialogName); 128 StringBuilder result = new StringBuilder(Consts.APP_NAME); 129 result.append(": "); 130 result.append(aSpecificDialogName); 131 return result.toString(); 132 } 133 134 /** 135 Make a horizontal row of buttons of equal size, whch are equally spaced, 136 and aligned on the right. 137 138 <P>The returned component has border spacing only on the top (of the size 139 recommended by the Look and Feel Design Guidelines). 140 All other spacing must be applied elsewhere ; usually, this will only mean 141 that the dialog's top-level panel should use {@link #getStandardBorder}. 142 143 @param aButtons contains the buttons to be placed in a row. 144 */ 145 public static JComponent getCommandRow(java.util.List<JComponent> aButtons){ 146 equalizeSizes( aButtons ); 147 JPanel panel = new JPanel(); 148 LayoutManager layout = new BoxLayout(panel, BoxLayout.X_AXIS); 149 panel.setLayout(layout); 150 panel.setBorder(BorderFactory.createEmptyBorder(UiConsts.THREE_SPACES, 0, 0, 0)); 151 panel.add(Box.createHorizontalGlue()); 152 Iterator<JComponent> buttonsIter = aButtons.iterator(); 153 while (buttonsIter.hasNext()) { 154 panel.add( buttonsIter.next() ); 155 if (buttonsIter.hasNext()) { 156 panel.add(Box.createHorizontalStrut(UiConsts.ONE_SPACE)); 157 } 158 } 159 return panel; 160 } 161 162 /** 163 Make a vertical row of buttons of equal size, whch are equally spaced, 164 and aligned on the right. 165 166 <P>The returned component has border spacing only on the left (of the size 167 recommended by the Look and Feel Design Guidelines). 168 All other spacing must be applied elsewhere ; usually, this will only mean 169 that the dialog's top-level panel should use {@link #getStandardBorder}. 170 171 @param aButtons contains the buttons to be placed in a column 172 */ 173 public static JComponent getCommandColumn( java.util.List<JComponent> aButtons ){ 174 equalizeSizes( aButtons ); 175 JPanel panel = new JPanel(); 176 LayoutManager layout = new BoxLayout(panel, BoxLayout.Y_AXIS); 177 panel.setLayout( layout ); 178 panel.setBorder( 179 BorderFactory.createEmptyBorder(0,UiConsts.THREE_SPACES, 0,0) 180 ); 181 //(no for-each is used here, because of the 'not-yet-last' check) 182 Iterator<JComponent> buttonsIter = aButtons.iterator(); 183 while ( buttonsIter.hasNext() ) { 184 panel.add(buttonsIter.next()); 185 if ( buttonsIter.hasNext() ) { 186 panel.add( Box.createVerticalStrut(UiConsts.ONE_SPACE) ); 187 } 188 } 189 panel.add( Box.createVerticalGlue() ); 190 return panel; 191 } 192 193 /** Return the currently active frame. */ 194 public static Frame getActiveFrame() { 195 Frame result = null; 196 Frame[] frames = Frame.getFrames(); 197 for (int i = 0; i < frames.length; i++) { 198 Frame frame = frames[i]; 199 if (frame.isVisible()) { //Component method 200 result = frame; 201 break; 202 } 203 } 204 return result; 205 } 206 207 /** 208 Return a <tt>Dimension</tt> whose size is defined not in terms of pixels, 209 but in terms of a given percent of the screen's width and height. 210 211 <P> Use to set the preferred size of a component to a certain 212 percentage of the screen. 213 214 @param aPercentWidth percentage width of the screen, in range <tt>1..100</tt>. 215 @param aPercentHeight percentage height of the screen, in range <tt>1..100</tt>. 216 */ 217 public static final Dimension getDimensionFromPercent(int aPercentWidth, int aPercentHeight){ 218 int low = 1; 219 int high = 100; 220 Args.checkForRange(aPercentWidth, low, high); 221 Args.checkForRange(aPercentHeight, low, high); 222 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 223 return calcDimensionFromPercent(screenSize, aPercentWidth, aPercentHeight); 224 } 225 226 /** 227 Sets the items in <tt>aComponents</tt> to the same size. 228 229 <P>Sets each component's preferred and maximum sizes. 230 The actual size is determined by the layout manager, whcih adjusts 231 for locale-specific strings and customized fonts. (See this 232 <a href="http://java.sun.com/products/jlf/ed2/samcode/prefere.html">Sun doc</a> 233 for more information.) 234 235 @param aComponents items whose sizes are to be equalized 236 */ 237 public static void equalizeSizes(java.util.List<JComponent> aComponents) { 238 Dimension targetSize = new Dimension(0,0); 239 for(JComponent comp: aComponents ) { 240 Dimension compSize = comp.getPreferredSize(); 241 double width = Math.max(targetSize.getWidth(), compSize.getWidth()); 242 double height = Math.max(targetSize.getHeight(), compSize.getHeight()); 243 targetSize.setSize(width, height); 244 } 245 setSizes(aComponents, targetSize); 246 } 247 248 /** 249 Make the system emit a beep. 250 251 <P>May not beep unless the speakers are turned on, so this cannot 252 be guaranteed to work. 253 */ 254 public static void beep(){ 255 Toolkit.getDefaultToolkit().beep(); 256 } 257 258 /** 259 Imposes a uniform horizontal alignment on all items in a container. 260 261 <P> Intended especially for <tt>BoxLayout</tt>, where all components need 262 to share the same alignment in order for display to be reasonable. 263 (Indeed, this method may only work for <tt>BoxLayout</tt>, since apparently 264 it is the only layout to use <tt>setAlignmentX, setAlignmentY</tt>.) 265 266 @param aContainer contains only <tt>JComponent</tt> objects. 267 */ 268 public static void alignAllX(Container aContainer, UiUtil.AlignX aAlignment){ 269 java.util.List<Component> components = Arrays.asList( aContainer.getComponents() ); 270 for(Component comp: components){ 271 JComponent jcomp = (JComponent)comp; 272 jcomp.setAlignmentX( aAlignment.getValue() ); 273 } 274 } 275 276 /** Enumeration for horizontal alignment. */ 277 public enum AlignX { 278 LEFT(Component.LEFT_ALIGNMENT), 279 CENTER(Component.CENTER_ALIGNMENT), 280 RIGHT(Component.RIGHT_ALIGNMENT); 281 public float getValue(){ 282 return fValue; 283 } 284 private final float fValue; 285 private AlignX(float aValue){ 286 fValue = aValue; 287 } 288 } 289 290 /** 291 Imposes a uniform vertical alignment on all items in a container. 292 293 <P> Intended especially for <tt>BoxLayout</tt>, where all components need 294 to share the same alignment in order for display to be reasonable. 295 (Indeed, this method may only work for <tt>BoxLayout</tt>, since apparently 296 it is the only layout to use <tt>setAlignmentX, setAlignmentY</tt>.) 297 298 @param aContainer contains only <tt>JComponent</tt> objects. 299 */ 300 public static void alignAllY(Container aContainer, UiUtil.AlignY aAlignment){ 301 java.util.List components = Arrays.asList( aContainer.getComponents() ); 302 Iterator compsIter = components.iterator(); 303 while ( compsIter.hasNext() ) { 304 JComponent comp = (JComponent)compsIter.next(); 305 comp.setAlignmentY( aAlignment.getValue() ); 306 } 307 } 308 309 /** Type-safe enumeration vertical alignment. */ 310 public enum AlignY { 311 TOP(Component.TOP_ALIGNMENT), 312 CENTER(Component.CENTER_ALIGNMENT), 313 BOTTOM(Component.BOTTOM_ALIGNMENT); 314 float getValue(){ 315 return fValue; 316 } 317 private final float fValue; 318 private AlignY( float aValue){ 319 fValue = aValue; 320 } 321 } 322 323 /** 324 Ensure that <tt>aRootPane</tt> has no default button associated with it. 325 326 <P>Intended mainly for dialogs where the user is confirming a delete action. 327 In this case, an explicit Yes or No is preferred, with no default action being 328 taken when the user hits the Enter key. 329 */ 330 public static void noDefaultButton(JRootPane aRootPane){ 331 aRootPane.setDefaultButton(null); 332 } 333 334 /** 335 Create an icon for use by a given class. 336 337 Returns <tt>null</tt> if the icon cannot be found. 338 339 @param aPath path to the file, relative to the calling class, as in '../images/blah.png' 340 @param aDescription description of the image 341 @param aClass class that needs to use the image 342 */ 343 public static ImageIcon createImageIcon(String aPath, String aDescription, Class aClass) { 344 ImageIcon result = null; 345 URL imageURL = aClass.getResource(aPath); //resolves to an absolute path 346 if (imageURL != null) { 347 result = new ImageIcon(imageURL, aDescription); 348 } 349 return result; 350 } 351 352 // PRIVATE 353 354 private static void setSizes(java.util.List aComponents, Dimension aDimension){ 355 Iterator compsIter = aComponents.iterator(); 356 while ( compsIter.hasNext() ) { 357 JComponent comp = (JComponent) compsIter.next(); 358 comp.setPreferredSize( (Dimension)aDimension.clone() ); 359 comp.setMaximumSize( (Dimension)aDimension.clone() ); 360 } 361 } 362 363 private static Dimension calcDimensionFromPercent(Dimension aSourceDimension, int aPercentWidth, int aPercentHeight){ 364 int width = aSourceDimension.width * aPercentWidth/100; 365 int height = aSourceDimension.height * aPercentHeight/100; 366 return new Dimension(width, height); 367 } 368}