001 package hirondelle.movies.util.ui;
002
003 import hirondelle.movies.LaunchApplication;
004 import hirondelle.movies.util.Args;
005 import java.util.*;
006 import java.net.URL;
007 import javax.swing.*;
008 import javax.swing.border.Border;
009 import 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 */
018 public 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 }