| GuiUtil.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 as published by
5 * the Free Software Foundation. This program is distributed in the hope
6 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * Copyright: 2005
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: GuiUtil.java 2091 2011-03-07 04:15:31Z dmsmith $
21 */
22 package org.crosswire.common.swing;
23
24 import java.awt.Button;
25 import java.awt.Component;
26 import java.awt.ComponentOrientation;
27 import java.awt.Container;
28 import java.awt.Dialog;
29 import java.awt.Dimension;
30 import java.awt.Frame;
31 import java.awt.Insets;
32 import java.awt.Label;
33 import java.awt.TextComponent;
34 import java.awt.Toolkit;
35 import java.awt.Window;
36 import java.net.URL;
37 import java.util.Locale;
38 import java.util.MissingResourceException;
39
40 import javax.swing.AbstractButton;
41 import javax.swing.ImageIcon;
42 import javax.swing.JButton;
43 import javax.swing.JComboBox;
44 import javax.swing.JComponent;
45 import javax.swing.JLabel;
46 import javax.swing.JMenu;
47 import javax.swing.JOptionPane;
48 import javax.swing.JScrollPane;
49 import javax.swing.JToolTip;
50 import javax.swing.text.JTextComponent;
51
52 import org.crosswire.common.util.Language;
53 import org.crosswire.common.util.Logger;
54 import org.crosswire.common.util.ResourceUtil;
55
56 /**
57 * Various GUI Utilities.
58 *
59 * @see gnu.lgpl.License for license details.<br>
60 * The copyright to this program is held by it's authors.
61 * @author Joe Walker [joe at eireneh dot com]
62 */
63 public final class GuiUtil {
64 /**
65 * Prevent instantiation
66 */
67 private GuiUtil() {
68 }
69
70 /**
71 * Returns the Icon associated with the name from the resources. The
72 * resource should be in the path.
73 *
74 * @param name
75 * Name of the icon file i.e., help16.gif
76 * @return the image or null if the icon is not found.
77 */
78 public static ImageIcon getIcon(String name) {
79 // It is expected that there is a 3 char extension
80 // So the minimum file name length is 5
81 if (name == null || name.length() < 5) {
82 return null;
83 }
84
85 try {
86 URL url = ResourceUtil.getResource(name);
87 return new ImageIcon(url);
88 } catch (MissingResourceException ex) {
89 log.error("Failed to find icon name='" + name + "'", ex);
90 return null;
91 }
92 }
93
94 /**
95 * Find the parent window.
96 *
97 * @param com
98 * a component to find the frame of.
99 * @return The parent Window
100 */
101 public static Window getWindow(Component com) {
102 Component temp = com;
103
104 if (temp == null) {
105 return getRootFrame();
106 }
107
108 while (!(temp instanceof Frame || temp instanceof Dialog)) {
109 temp = temp.getParent();
110 if (temp == null) {
111 return getRootFrame();
112 }
113 }
114
115 return (Window) temp;
116 }
117
118 /**
119 * Find the best frame to which to root a dialog, generally the largest
120 * visible frame of the application.
121 *
122 * @return the best frame.
123 */
124 public static Frame getRootFrame() {
125 return getFrame(null);
126 }
127
128 /**
129 * Find the parent Frame. This method can do more than simply walking up the
130 * tree to find a parent frame by looking for default frames from
131 * JOptionPane and by looking for all visible Frames. We can be sure to
132 * return a Frame from this method even if null is passed in.
133 *
134 * @param parent
135 * a component to find the frame of.
136 * @return The parent Frame
137 */
138 public static Frame getFrame(Component parent) {
139 if (parent == null) {
140 // So we can't get one by walking up the tree so we will have to
141 // get one from somewhere else.
142 // Firstly someone might have called JOptionPane.setRootFrame()
143 // to give us a reasonable default so try there
144 Frame option = JOptionPane.getRootFrame();
145
146 // If a default has not been set, JOptionPane.getRootFrame() calls
147 // SwingUtilities.getSharedOwnerFrame() to get a new invisible frame
148 // and we may be able to do better than that. Unfortunately the
149 // getSharedOwnerFrame() method is not public so we have to trick
150 // our way to finding if we got a duff default
151 if (!option.getClass().getName().startsWith("javax.swing.SwingUtilities$"))
152 {
153 // So we think the JOptionPane root frame is our creation
154 return option;
155 }
156
157 // We might be able to get a better default by looking through all
158 // the frames and picking the biggest visible one.
159 Frame best = null;
160 int bestSize = 0;
161
162 Frame[] frames = Frame.getFrames();
163 for (int i = 0; i < frames.length; i++) {
164 Frame frame = frames[i];
165 if (frame.isVisible()) {
166 // So this frame is a candidate
167 int thisSize = frame.getWidth() * frame.getHeight();
168 if (best == null || thisSize > bestSize) {
169 best = frame;
170 bestSize = thisSize;
171 }
172 }
173 }
174
175 // So if we found a frame from searching then use that
176 if (best != null) {
177 return best;
178 }
179
180 // if all else fails we will have to use the invisible frame
181 // provided by JOptionPane
182 return option;
183 }
184
185 if (parent instanceof Frame) {
186 return (Frame) parent;
187 }
188
189 // So we walk up the tree looking for a frame
190 return getFrame(parent.getParent());
191 }
192
193 /**
194 * Find the parent Frame
195 *
196 * @param com
197 * a component to find the frame of.
198 * @return The parent Frame
199 */
200 public static Dialog getDialog(Component com) {
201 Component temp = com;
202
203 while (!(temp instanceof Dialog)) {
204 temp = temp.getParent();
205 if (temp == null) {
206 return null;
207 }
208 }
209
210 return (Dialog) temp;
211 }
212
213 /**
214 * Move the specified window to the center of the screen. It is the
215 * programmer's responsibility to ensure that the window fits on the screen.
216 *
217 * @param win
218 * The window to be moved
219 */
220 public static void centerOnScreen(Window win) {
221 win.setLocationRelativeTo(null);
222 }
223
224 /**
225 * Set the size of the window, but no bigger than the screen. If the
226 * platform supports it, this may maximize the window.
227 * <p>
228 * On platforms that allow docking of other windows, this routine does not
229 * take that into account for sizes that are near that of the screen. For
230 * example, on Windows XP, the user may have a task bar showing permanently
231 * on one side of the screen and another application's toolbar on another
232 * side of the screen. If the requested size of the window is less than the
233 * screen size in a particular dimension, it will not be able to adjust for
234 * it.
235 * <p>
236 * For that reason, either have the application significantly smaller than
237 * the screen size or maximize the window.
238 *
239 * @param win
240 * the window to resize
241 * @param requestedDim
242 * how wide and tall to make the window, if possible
243 * @return whether the window has been maximized horizontally
244 * (Frame.MAXIMIZED_HORIZ), vertically (Frame.MAXIMIZED_VERT
245 */
246 public static int setSize(Window win, Dimension requestedDim) {
247 // If possible we try to honor the request
248 Dimension honoredDim = (Dimension) requestedDim.clone();
249 Toolkit tk = win.getToolkit();
250 Dimension originalDim = win.getSize();
251 Dimension screenDim = tk.getScreenSize();
252 boolean isFrame = win instanceof Frame;
253
254 // Frames may allow for maximizing in one direction the other or both
255 // store any state changes in requestedState
256 int honoredState = 0;
257
258 // If the window is wider than the screen, limit it to the width of the
259 // screen
260 if (honoredDim.width >= screenDim.width) {
261 honoredDim.width = screenDim.width;
262 }
263
264 if (honoredDim.height >= screenDim.height) {
265 honoredDim.height = screenDim.height;
266 }
267
268 if (isFrame && honoredDim.equals(screenDim) && tk.isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
269 honoredState |= Frame.MAXIMIZED_BOTH;
270 }
271
272 // If either the height or the width changed then use it.
273 if (!honoredDim.equals(originalDim)) {
274 win.setSize(honoredDim);
275 }
276
277 // One of the dimensions may have changed via setSize,
278 // and the other may be waiting to change here
279 if (honoredState != 0 && win instanceof Frame) {
280 Frame frame = (Frame) win;
281 // Make sure to preserve existing states
282 frame.setExtendedState(honoredState | frame.getExtendedState());
283 }
284
285 // setExtendedState can change it.
286 Dimension finalDim = win.getSize();
287 honoredDim.width = finalDim.width;
288 honoredDim.height = finalDim.height;
289
290 log.warn("Window size was: " + requestedDim + " is: " + honoredDim);
291
292 return honoredState;
293 }
294
295 /**
296 * Set the window size relative to the current screen size.
297 *
298 * @param win
299 * The window to resize
300 * @param percentOfScreen
301 * The amount of space that the window should take up
302 */
303 public static void setSize(Window win, float percentOfScreen) {
304 Toolkit tk = win.getToolkit();
305 Dimension screenSize = tk.getScreenSize();
306
307 int width = Float.valueOf(screenSize.width * percentOfScreen).intValue();
308 int height = Float.valueOf(screenSize.height * percentOfScreen).intValue();
309 Dimension winSize = new Dimension(width, height);
310
311 win.setSize(winSize);
312 }
313
314 /**
315 * Set the size of the main window to a default size.
316 *
317 * @param win
318 * The window to resize
319 */
320 public static void defaultDesktopSize(Window win) {
321 float defaultPercent = 0.75F;
322 setSize(win, defaultPercent);
323 }
324
325 /**
326 * Maximize the specified window. It would be good if we could detect where
327 * the taskbar was and not obscure it ...
328 *
329 * @param win
330 * The window to be moved
331 */
332 public static void maximizeWindow(Window win) {
333 Toolkit tk = Toolkit.getDefaultToolkit();
334 // Check to see if the window supports maximizing
335 if (win instanceof Frame && tk.isFrameStateSupported(Frame.MAXIMIZED_BOTH)) {
336 Frame frame = (Frame) win;
337 // Make sure to preserve existing states
338 frame.setExtendedState(Frame.MAXIMIZED_BOTH | frame.getExtendedState());
339 } else {
340 // No, then just simulate it.
341 Dimension screenDim = tk.getScreenSize();
342 win.setLocation(0, 0);
343 win.setSize(screenDim);
344 }
345 }
346
347 /**
348 * Cause a component to refresh its contents when it is changed by the
349 * program and needs to be redrawn.
350 *
351 * @param c
352 * the component to refresh
353 */
354 public static void refresh(Component c) {
355 c.invalidate();
356 c.validate();
357 c.repaint();
358 if (c instanceof JComponent) {
359 ((JComponent) c).revalidate();
360 }
361 }
362
363 /**
364 * A more restricted version of pack() for component responding to live
365 * component tweaks. Assuming that the window already has a sensible on
366 * screen size, do a pack, but don't let the window grow or shrink by more
367 * than 10%.
368 *
369 * @param win
370 * The window to be packed
371 */
372 public static void restrainedRePack(Window win) {
373 Dimension orig = win.getSize();
374 Dimension max = new Dimension((int) (orig.width * 1.1), (int) (orig.height * 1.1));
375 Dimension min = new Dimension((int) (orig.width / 1.1), (int) (orig.height / 1.1));
376
377 win.pack();
378
379 // If the window is wider than 110% of its original size, clip it
380 if (win.getSize().width > max.width) {
381 win.setSize(max.width, win.getSize().height);
382 }
383
384 // If the window is taller than 110% of its original size, clip it
385 if (win.getSize().height > max.height) {
386 win.setSize(win.getSize().width, max.height);
387 }
388
389 // If the window is narrower than 90% of its original size, grow it
390 if (win.getSize().width < min.width) {
391 win.setSize(min.width, win.getSize().height);
392 }
393
394 // If the window is shorter than 90% of its original size, grow it
395 if (win.getSize().height < min.height) {
396 win.setSize(win.getSize().width, min.height);
397 }
398
399 Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
400
401 // If the window is wider than the screen, clip it
402 if (screenDim.width < win.getSize().width) {
403 win.setSize(screenDim.width, win.getSize().height);
404 }
405
406 // If the window is taller than the screen, clip it
407 if (screenDim.height < win.getSize().height) {
408 win.setSize(win.getSize().width, screenDim.height);
409 }
410
411 refresh(win);
412
413 // log.log(Level.INFO, "Failure", ex);
414 // log.fine("Size was "+orig);
415 // log.fine("Size is "+win.getSize());
416 }
417
418 /**
419 * A more restricted version of pack() when the component is being pack()ed
420 * for the first time. Since this is a 'first time only' pack we are only
421 * concerned with screen size, and not any growths/shrinkages like
422 * restrainedRePack(Window).
423 *
424 * @param win
425 * The window to be packed
426 * @param maxx
427 * The maximum fraction (0.0 to 1.0) of the screen to be taken up
428 * horizontally
429 * @param maxy
430 * The maximum fraction (0.0 to 1.0) of the screen to be taken up
431 * vertically
432 * @see GuiUtil#restrainedRePack(Window)
433 */
434 public static void restrainedPack(Window win, float maxx, float maxy) {
435 win.pack();
436
437 Dimension screen_dim = Toolkit.getDefaultToolkit().getScreenSize();
438
439 // If the window is wider than the screen, clip it
440 if (win.getSize().width > (screen_dim.width * maxx)) {
441 win.setSize((int) (screen_dim.width * maxx), win.getSize().height);
442 }
443
444 // If the window is taller than the screen, clip it
445 if (win.getSize().height > (screen_dim.height * maxy)) {
446 win.setSize(win.getSize().width, (int) (screen_dim.height * maxy));
447 }
448
449 refresh(win);
450
451 // log.log(Level.INFO, "Failure", ex);
452 // log.fine("Size was "+orig);
453 // log.fine("Size is "+win.getSize());
454 }
455
456 /**
457 * Set the size of a component
458 */
459 public static void enforceMinimumSize(Component comp, int min_width, int min_height) {
460 if (comp.getSize().width < min_width) {
461 comp.setSize(min_width, comp.getSize().height);
462 }
463
464 if (comp.getSize().height < min_height) {
465 comp.setSize(comp.getSize().width, min_height);
466 }
467 }
468
469 /**
470 * Attempts to get the text from a generic Component. The Components that we
471 * can get some text from include: <li>
472 * JTextComponent <li>JLabel <li>AbstractButton <li>JComboBox <li>JToolTip
473 * <li>TextComponent <li>Button <li>Label <li>JScrollPane (recurse using the
474 * View) The others are done using toString()
475 *
476 * @param comp
477 * The object containing the needed text.
478 */
479 public static String getText(Component comp) {
480 if (comp instanceof JTextComponent) {
481 return ((JTextComponent) comp).getText();
482 }
483
484 if (comp instanceof JLabel) {
485 return ((JLabel) comp).getText();
486 }
487
488 if (comp instanceof AbstractButton) {
489 return ((AbstractButton) comp).getText();
490 }
491
492 if (comp instanceof JComboBox) {
493 return ((JComboBox) comp).getSelectedItem().toString();
494 }
495
496 if (comp instanceof JToolTip) {
497 return ((JToolTip) comp).getTipText();
498 }
499
500 if (comp instanceof TextComponent) {
501 return ((TextComponent) comp).getText();
502 }
503
504 if (comp instanceof Button) {
505 return ((Button) comp).getLabel();
506 }
507
508 if (comp instanceof Label) {
509 return ((Label) comp).getText();
510 }
511
512 if (comp instanceof JScrollPane) {
513 JScrollPane scr = (JScrollPane) comp;
514 Component sub = scr.getViewport().getView();
515 if (sub != null) {
516 return getText(sub);
517 }
518 }
519
520 return comp.toString();
521 }
522
523 /**
524 * Recursively apply default component orientation to the component and all
525 * it contains.
526 *
527 * @param comp
528 * the root of the tree to which orientation needs to be applied
529 */
530 public static void applyDefaultOrientation(Component comp) {
531 applyOrientation(comp, new Language(Locale.getDefault().getLanguage()).isLeftToRight());
532 }
533
534 /**
535 * Recursively apply component orientation to the component and all it
536 * contains.
537 *
538 * @param comp
539 * the root of the tree to which orientation needs to be applied
540 * @param ltr
541 * whether the orientation is left to right or not.
542 */
543 public static void applyOrientation(Component comp, boolean ltr) {
544 applyOrientation(comp, ltr ? ComponentOrientation.LEFT_TO_RIGHT : ComponentOrientation.RIGHT_TO_LEFT);
545 }
546
547 /**
548 * Recursively apply component orientation to the component and all it
549 * contains.
550 *
551 * @param comp
552 * the root of the tree to which orientation needs to be applied
553 * @param orientation
554 * the orientation to be applied
555 */
556 public static void applyOrientation(Component comp, ComponentOrientation orientation) {
557 comp.setComponentOrientation(orientation);
558
559 if (comp instanceof JMenu) {
560 JMenu menu = (JMenu) comp;
561 int ncomponents = menu.getMenuComponentCount();
562 for (int i = 0; i < ncomponents; ++i) {
563 applyOrientation(menu.getMenuComponent(i), orientation);
564 }
565 } else if (comp instanceof Container) {
566 Container container = (Container) comp;
567 int ncomponents = container.getComponentCount();
568 for (int i = 0; i < ncomponents; ++i) {
569 applyOrientation(container.getComponent(i), orientation);
570 }
571 }
572 }
573
574 public static JButton flatten(JButton button) {
575 button.setBorderPainted(false);
576 button.setContentAreaFilled(false);
577 button.setText(null);
578 button.setMargin(new Insets(0, 0, 0, 0));
579 return button;
580 }
581
582 /**
583 * The log stream
584 */
585 private static final Logger log = Logger.getLogger(GuiUtil.class);
586
587 }
588