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