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