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 }