View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.platform.swing.builder.window;
17  
18  import java.awt.Component;
19  import java.awt.Container;
20  import java.awt.Dimension;
21  import java.awt.Toolkit;
22  import java.awt.event.MouseListener;
23  import java.lang.reflect.InvocationTargetException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Iterator;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.concurrent.CopyOnWriteArrayList;
31  
32  import javax.swing.JDesktopPane;
33  import javax.swing.SwingUtilities;
34  
35  import net.sf.jguiraffe.gui.builder.event.FormMouseListener;
36  import net.sf.jguiraffe.gui.builder.window.InvariantWindowClosingStrategy;
37  import net.sf.jguiraffe.gui.builder.window.Window;
38  import net.sf.jguiraffe.gui.builder.window.WindowClosingStrategy;
39  import net.sf.jguiraffe.gui.builder.window.WindowData;
40  import net.sf.jguiraffe.gui.builder.window.WindowEvent;
41  import net.sf.jguiraffe.gui.builder.window.WindowListener;
42  import net.sf.jguiraffe.gui.platform.swing.builder.event.MouseEventAdapter;
43  
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  
47  /**
48   * <p>
49   * An internally used helper class for the Swing window package.
50   * </p>
51   * <p>
52   * This class implements some functionality common to all Swing window
53   * implementations. It further defines some helper methods. Because of Swing's
54   * inheritance hierarchy it is not possible to implement this functionality in
55   * common base classes, so the composition approach is used.
56   * </p>
57   *
58   * @author Oliver Heger
59   * @version $Id: WindowHelper.java 205 2012-01-29 18:29:57Z oheger $
60   */
61  class WindowHelper
62  {
63      /** The logger. */
64      private final Log log = LogFactory.getLog(getClass());
65  
66      /** Stores the associated Swing window. */
67      private final SwingWindow swingWindow;
68  
69      /** Stores the window's closing strategy. */
70      private WindowClosingStrategy windowClosingStrategy;
71  
72      /** Stores the window's parent. */
73      private Window parent;
74  
75      /** Stores the window's controller. */
76      private Object controller;
77  
78      /** Stores the registered window listeners. */
79      private final List<WindowListener> listeners;
80  
81      /** Stores the mouse listeners registered at this window. */
82      private final List<MouseEventAdapter> mouseListeners;
83  
84      /** A flag whether the window is to be centered when it is opened. */
85      private final boolean center;
86  
87      /**
88       * Creates a new instance of <code>WindowHelper</code> and initializes it
89       * with the associated window.
90       *
91       * @param window the window
92       * @param centerOnOpen a flag whether the window is to be centered
93       */
94      public WindowHelper(SwingWindow window, boolean centerOnOpen)
95      {
96          swingWindow = window;
97          listeners = new CopyOnWriteArrayList<WindowListener>();
98          mouseListeners = new LinkedList<MouseEventAdapter>();
99          center = centerOnOpen;
100     }
101 
102     /**
103      * Returns the associated swing window.
104      *
105      * @return the swing window
106      */
107     public SwingWindow getSwingWindow()
108     {
109         return swingWindow;
110     }
111 
112     /**
113      * Returns the window's closing strategy. This implementation will never
114      * return <b>null</b>. If no closing strategy has been set, a default
115      * instance will be returned.
116      *
117      * @return the window closing strategy
118      */
119     public WindowClosingStrategy getWindowClosingStrategy()
120     {
121         return (windowClosingStrategy != null) ? windowClosingStrategy
122                 : InvariantWindowClosingStrategy.DEFAULT_INSTANCE;
123     }
124 
125     /**
126      * Sets the window's closing strategy.
127      *
128      * @param windowClosingStrategy the new closing strategy
129      */
130     public void setWindowClosingStrategy(
131             WindowClosingStrategy windowClosingStrategy)
132     {
133         this.windowClosingStrategy = windowClosingStrategy;
134     }
135 
136     /**
137      * Returns the window's controller.
138      *
139      * @return the window's controller
140      */
141     public Object getWindowController()
142     {
143         return controller;
144     }
145 
146     /**
147      * Allows to set the window's controller.
148      *
149      * @param ctrl the new controller
150      */
151     public void setWindowController(Object ctrl)
152     {
153         controller = ctrl;
154     }
155 
156     /**
157      * Returns the window's parent.
158      *
159      * @return the parent window
160      */
161     public Window getParentWindow()
162     {
163         return parent;
164     }
165 
166     /**
167      * Sets the window's parent.
168      *
169      * @param parent the parent window
170      */
171     public void setParent(Window parent)
172     {
173         this.parent = parent;
174     }
175 
176     /**
177      * Registers the specified window listener at this window.
178      *
179      * @param l the listener to register (must not be <b>null</b>)
180      * @throws IllegalArgumentException if the listener is <b>null</b>
181      */
182     public void addWindowListener(WindowListener l)
183     {
184         if (l == null)
185         {
186             throw new IllegalArgumentException(
187                     "Window listener must not be null!");
188         }
189 
190         listeners.add(l);
191     }
192 
193     /**
194      * Removes the specified window listener from this window. If this listener
195      * is not registered at this window, this operation has no effect.
196      *
197      * @param l the listener to remove
198      */
199     public void removeWindowListener(WindowListener l)
200     {
201         listeners.remove(l);
202     }
203 
204     /**
205      * Returns a collection with all registered window listeners.
206      *
207      * @return a collection with the registered window listeners
208      */
209     public Collection<WindowListener> getWindowListeners()
210     {
211         return Collections.unmodifiableCollection(listeners);
212     }
213 
214     /**
215      * Dispatches a window activated event to all registered event listeners.
216      *
217      * @param src the source event
218      */
219     public void fireWindowActivated(Object src)
220     {
221         WindowEvent event = null;
222         for (WindowListener l : listeners)
223         {
224             if (event == null)
225             {
226                 event = createEvent(src, WindowEvent.Type.WINDOW_ACTIVATED);
227             }
228             l.windowActivated(event);
229         }
230     }
231 
232     /**
233      * Dispatches a window closed event to all registered event listeners.
234      *
235      * @param src the source event
236      */
237     public void fireWindowClosed(Object src)
238     {
239         WindowEvent event = null;
240         for (WindowListener l : listeners)
241         {
242             if (event == null)
243             {
244                 event = createEvent(src, WindowEvent.Type.WINDOW_CLOSED);
245             }
246             l.windowClosed(event);
247         }
248     }
249 
250     /**
251      * Dispatches a window closing event to all registered event listeners.
252      *
253      * @param src the source event
254      */
255     public void fireWindowClosing(Object src)
256     {
257         WindowEvent event = null;
258         for (WindowListener l : listeners)
259         {
260             if (event == null)
261             {
262                 event = createEvent(src, WindowEvent.Type.WINDOW_CLOSING);
263             }
264             l.windowClosing(event);
265         }
266     }
267 
268     /**
269      * Dispatches a window deactivated event to all registered event listeners.
270      *
271      * @param src the source event
272      */
273     public void fireWindowDeactivated(Object src)
274     {
275         WindowEvent event = null;
276         for (WindowListener l : listeners)
277         {
278             if (event == null)
279             {
280                 event = createEvent(src, WindowEvent.Type.WINDOW_DEACTIVATED);
281             }
282             l.windowDeactivated(event);
283         }
284     }
285 
286     /**
287      * Dispatches a window deiconified event to all registered event listeners.
288      *
289      * @param src the source event
290      */
291     public void fireWindowDeiconified(Object src)
292     {
293         WindowEvent event = null;
294         for (WindowListener l : listeners)
295         {
296             if (event == null)
297             {
298                 event = createEvent(src, WindowEvent.Type.WINDOW_DEICONIFIED);
299             }
300             l.windowDeiconified(event);
301         }
302     }
303 
304     /**
305      * Dispatches a window iconified event to all registered event listeners.
306      *
307      * @param src the source event
308      */
309     public void fireWindowIconified(Object src)
310     {
311         WindowEvent event = null;
312         for (WindowListener l : listeners)
313         {
314             if (event == null)
315             {
316                 event = createEvent(src, WindowEvent.Type.WINDOW_ICONIFIED);
317             }
318             l.windowIconified(event);
319         }
320     }
321 
322     /**
323      * Dispatches a window opened event to all registered event listeners.
324      *
325      * @param src the source event
326      */
327     public void fireWindowOpened(Object src)
328     {
329         WindowEvent event = null;
330         for (WindowListener l : listeners)
331         {
332             if (event == null)
333             {
334                 event = createEvent(src, WindowEvent.Type.WINDOW_OPENED);
335             }
336             l.windowOpened(event);
337         }
338     }
339 
340     /**
341      * Called when the window should be closed. Depending on the {@code force}
342      * parameter the closing strategy is triggered. If it allows closing the
343      * window or if the {@code force} parameter is <b>true</b>, the window is
344      * disposed.
345      *
346      * @param force the force flag
347      * @return a flag whether the window could be closed
348      */
349     public boolean closeWindow(boolean force)
350     {
351         if (force || getWindowClosingStrategy().canClose(getSwingWindow()))
352         {
353             getSwingWindow().dispose();
354             return true;
355         }
356 
357         return false;
358     }
359 
360     /**
361      * Adds a mouse listener to the associated window. This implementation
362      * creates an adapter that transforms Swing-specific mouse events to the
363      * standard mouse events supported by the <em>JGUIraffe</em> library. It is
364      * possible to add the same listener multiple times. If the listener is
365      * <b>null</b>, this method has no effect.
366      *
367      * @param l the mouse listener to be added
368      */
369     public void addMouseListener(FormMouseListener l)
370     {
371         if (l != null)
372         {
373             MouseEventAdapter adapter = new MouseEventAdapter(l, null, null);
374             getSwingWindow().getComponent().addMouseListener(adapter);
375 
376             synchronized (mouseListeners)
377             {
378                 mouseListeners.add(adapter);
379             }
380         }
381     }
382 
383     /**
384      * Removes the specified mouse listener from the associated window. If the
385      * listener is not registered at this window, this method has no effect.
386      * Only one listener registration is removed by a single method call. If the
387      * listener has been added multiple times, it is necessary to invoke this
388      * method the same number of times to fully remove the listener.
389      *
390      * @param l the mouse listener to be removed
391      */
392     public void removeMouseListener(FormMouseListener l)
393     {
394         MouseEventAdapter adapter = null;
395 
396         synchronized (mouseListeners)
397         {
398             for (Iterator<MouseEventAdapter> it = mouseListeners.iterator(); it
399                     .hasNext();)
400             {
401                 MouseEventAdapter a = it.next();
402                 if (a.getEventListener().equals(l))
403                 {
404                     it.remove();
405                     adapter = a;
406                     break;
407                 }
408             }
409         }
410 
411         if (adapter != null)
412         {
413             getSwingWindow().getComponent().removeMouseListener(adapter);
414         }
415     }
416 
417     /**
418      * Returns a collection with the mouse listeners that have been registered
419      * at this helper. This method is mainly used for testing purposes.
420      *
421      * @return a collection with the registered mouse listeners
422      */
423     public Collection<MouseListener> getMouseListeners()
424     {
425         return new ArrayList<MouseListener>(mouseListeners);
426     }
427 
428     /**
429      * Opens this window. This implementation ensures that this action is
430      * performed on the event dispatching thread, but synchronously.
431      */
432     public void openWindow()
433     {
434         log.debug("Opening window.");
435         if (SwingUtilities.isEventDispatchThread())
436         {
437             doOpenWindow();
438         }
439         else
440         {
441             try
442             {
443                 SwingUtilities.invokeAndWait(new Runnable()
444                 {
445                     public void run()
446                     {
447                         doOpenWindow();
448                     }
449                 });
450             }
451             catch (InterruptedException iex)
452             {
453                 // ignore
454                 log.info("Interrupted exception when opening window", iex);
455             }
456             catch (InvocationTargetException itex)
457             {
458                 log.error("Error when opening window", itex);
459                 throw new RuntimeException(itex.getMessage());
460             }
461         }
462     }
463 
464     /**
465      * Checks if in the given window data object the window's size is fully
466      * defined. If this is not the case, the window must be packed.
467      *
468      * @param data the window data object
469      * @return a flag if the window's size is defined
470      */
471     public static boolean sizeDefined(WindowData data)
472     {
473         return sizeDefined(data.getWidth(), data.getHeight());
474     }
475 
476     /**
477      * Checks if the given window size is fully defined. If this is not the
478      * case, the window must be packed.
479      * @param width the width of the window
480      * @param height the height of the window
481      * @return a flag whether the size is fully defined
482      */
483     public static boolean sizeDefined(int width, int height)
484     {
485         return width > 0 && height > 0;
486     }
487 
488     /**
489      * Tries to find a desktop pane in the given container or its children. This
490      * method is useful if an internal frame is to be added to a frame window's
491      * desktop. The container's children are recursively searched until a
492      * desktop pane component is found.
493      *
494      * @param container the container to search
495      * @return the desktop panel or <b>null</b> if none is found
496      */
497     public static JDesktopPane findDesktopPane(Container container)
498     {
499         if (container == null)
500         {
501             return null;
502         }
503 
504         if (container instanceof JDesktopPane)
505         {
506             return (JDesktopPane) container;
507         }
508 
509         Component[] components = container.getComponents();
510         for (int i = 0; i < components.length; i++)
511         {
512             if (components[i] instanceof Container)
513             {
514                 JDesktopPane result = findDesktopPane((Container) components[i]);
515                 if (result != null)
516                 {
517                     return result;
518                 }
519             }
520         }
521 
522         return null;
523     }
524 
525     /**
526      * Determines the bounds of the given component based on the passed in
527      * window data object. This method does the following: If the window's
528      * bounds are fully defined, they are simply set. If the location is
529      * missing, default values are set. The size is only set, if it is fully
530      * defined (otherwise the calling must ensure that the <code>pack()</code>
531      * method is invoked on the window).
532      *
533      * @param comp the component to initialize
534      * @param data the data object with the bounds
535      */
536     public static void initComponentBounds(Component comp, WindowData data)
537     {
538         comp.setLocation((data.getXPos() == WindowData.UNDEFINED) ? 0 : data
539                 .getXPos(), (data.getYPos() == WindowData.UNDEFINED) ? 0 : data
540                 .getYPos());
541         if (sizeDefined(data))
542         {
543             comp.setSize(data.getWidth(), data.getHeight());
544         }
545     }
546 
547     /**
548      * Opens the window directly. Called by <code>openWindow()</code>.
549      */
550     protected void doOpenWindow()
551     {
552         Component comp = getSwingWindow().getComponent();
553         if (!sizeDefined(comp.getWidth(), comp.getHeight()))
554         {
555             getSwingWindow().packWindow();
556         }
557         if (isCenter())
558         {
559             center(comp, getSwingWindow().getParentWindow());
560         }
561         comp.setVisible(true);
562     }
563 
564     /**
565      * Returns a flag whether the window is to be centered when it is opened.
566      *
567      * @return the center flag
568      */
569     protected boolean isCenter()
570     {
571         return center;
572     }
573 
574     /**
575      * Creates a window event to be passed to the registered listeners.
576      *
577      * @param source the source of the event
578      * @param type the type of the event
579      * @return the event
580      */
581     protected WindowEvent createEvent(Object source, WindowEvent.Type type)
582     {
583         return new WindowEvent(source, getSwingWindow(), type);
584     }
585 
586     /**
587      * Sets the location of the specified component so that it gets centered in
588      * the area of its parent window. The size of the component must have been
589      * determined before. If no parent window exists, the component is centered
590      * on the screen.
591      *
592      * @param c the component to center
593      * @param parent the parent window
594      */
595     void center(Component c, Window parent)
596     {
597         int parentX;
598         int parentY;
599         int parentW;
600         int parentH;
601 
602         if (parent == null)
603         {
604             Dimension scrSz = Toolkit.getDefaultToolkit().getScreenSize();
605             parentX = 0;
606             parentY = 0;
607             parentW = scrSz.width;
608             parentH = scrSz.height;
609         }
610         else
611         {
612             parentX = parent.getXPos();
613             parentY = parent.getYPos();
614             parentW = parent.getWidth();
615             parentH = parent.getHeight();
616         }
617 
618         c.setLocation((parentW - c.getWidth()) / 2 + parentX, (parentH - c
619                 .getHeight())
620                 / 2 + parentY);
621     }
622 }