DefaultSwingWindowManager.java
001 /*
002  * Copyright 2008-2014 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package org.codehaus.griffon.runtime.swing;
017 
018 import griffon.core.ApplicationEvent;
019 import griffon.core.GriffonApplication;
020 import griffon.core.env.ApplicationPhase;
021 import griffon.core.event.EventRouter;
022 import griffon.exceptions.InstanceNotFoundException;
023 import griffon.swing.SwingWindowDisplayHandler;
024 import griffon.swing.SwingWindowManager;
025 import griffon.util.GriffonNameUtils;
026 import org.codehaus.griffon.runtime.core.view.AbstractWindowManager;
027 import org.slf4j.Logger;
028 import org.slf4j.LoggerFactory;
029 
030 import javax.annotation.Nonnull;
031 import javax.inject.Inject;
032 import javax.inject.Named;
033 import javax.swing.JInternalFrame;
034 import javax.swing.WindowConstants;
035 import javax.swing.event.InternalFrameAdapter;
036 import javax.swing.event.InternalFrameEvent;
037 import java.awt.Window;
038 import java.awt.event.ComponentAdapter;
039 import java.awt.event.ComponentEvent;
040 import java.awt.event.WindowAdapter;
041 import java.awt.event.WindowEvent;
042 import java.util.*;
043 
044 import static griffon.util.GriffonNameUtils.requireNonBlank;
045 import static java.util.Arrays.asList;
046 import static java.util.Collections.unmodifiableCollection;
047 import static java.util.Objects.requireNonNull;
048 
049 /**
050  @author Andres Almiray
051  @since 2.0.0
052  */
053 public class DefaultSwingWindowManager extends AbstractWindowManager<Window> implements SwingWindowManager {
054     private static final Logger LOG = LoggerFactory.getLogger(DefaultSwingWindowManager.class);
055     private final WindowHelper windowHelper = new WindowHelper();
056     private final ComponentHelper componentHelper = new ComponentHelper();
057     private final InternalFrameHelper internalFrameHelper = new InternalFrameHelper();
058     private final Map<String, JInternalFrame> internalFrames = Collections.synchronizedMap(new LinkedHashMap<String, JInternalFrame>());
059     private boolean hideBeforeHandler = false;
060 
061     @Inject
062     @Nonnull
063     public DefaultSwingWindowManager(@Nonnull GriffonApplication application, @Nonnull @Named("windowDisplayHandler"SwingWindowDisplayHandler windowDisplayHandler) {
064         super(application, windowDisplayHandler);
065         requireNonNull(application.getEventRouter()"Argument 'application.eventRouter' must not be null");
066     }
067 
068     /**
069      * Finds a JInternalFrame by name.
070      *
071      @param name the value of the name: property
072      @return a JInternalFrame if a match is found, null otherwise.
073      @since 2.0.0
074      */
075     public JInternalFrame findInternalFrame(String name) {
076         if (!GriffonNameUtils.isBlank(name)) {
077             for (JInternalFrame internalFrame : internalFrames.values()) {
078                 if (name.equals(internalFrame.getName())) return internalFrame;
079             }
080         }
081         return null;
082     }
083 
084     /**
085      * Returns the list of internal frames managed by this manager.
086      *
087      @return a List of currently managed internal frames
088      @since 2.0.0
089      */
090     public Collection<JInternalFrame> getInternalFrames() {
091         return unmodifiableCollection(internalFrames.values());
092     }
093 
094     /**
095      * Registers an internal frame on this manager if an only if the internal frame is not null
096      * and it's not registered already.
097      *
098      @param name          the value of the of the Window's name
099      @param internalFrame the internal frame to be added to the list of managed internal frames
100      @since 2.0.0
101      */
102     public void attach(@Nonnull String name, @Nonnull JInternalFrame internalFrame) {
103         requireNonBlank(name, ERROR_NAME_BLANK);
104         requireNonNull(internalFrame, ERROR_WINDOW_NULL);
105         if (internalFrames.containsKey(name)) {
106             JInternalFrame window2 = internalFrames.get(name);
107             if (window2 != internalFrame) {
108                 detach(name);
109             }
110         }
111 
112         doAttach(internalFrame);
113 
114         if (LOG.isDebugEnabled()) {
115             LOG.debug("Attaching internal frame with name: '" + name + "' at index " + internalFrames.size() " " + internalFrame);
116         }
117         internalFrames.put(name, internalFrame);
118     }
119 
120     protected void doAttach(@Nonnull JInternalFrame internalFrame) {
121         internalFrame.addInternalFrameListener(internalFrameHelper);
122         internalFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
123     }
124 
125     /**
126      * Removes the internal frame from the list of manages internal frames if and only if it
127      * is registered with this manager.
128      *
129      @param name the value of the of the Window's name
130      @since 2.0.0
131      */
132     public void detach(@Nonnull String name) {
133         requireNonBlank(name, ERROR_NAME_BLANK);
134         if (internalFrames.containsKey(name)) {
135             JInternalFrame window = internalFrames.get(name);
136 
137             doDetach(window);
138 
139             if (LOG.isDebugEnabled()) {
140                 LOG.debug("Detaching internalFrame with name: '" + name + "' " + window);
141             }
142             internalFrames.remove(name);
143         }
144     }
145 
146     protected void doDetach(@Nonnull JInternalFrame internalFrame) {
147         internalFrame.removeInternalFrameListener(internalFrameHelper);
148     }
149 
150     /**
151      * Shows the internal frame.<p>
152      * This method is executed <b>SYNCHRONOUSLY</b> in the UI thread.
153      *
154      @param internalFrame the internal frame to show
155      @since 2.0.0
156      */
157     public void show(@Nonnull final JInternalFrame internalFrame) {
158         requireNonNull(internalFrame, ERROR_WINDOW_NULL);
159         if (!internalFrames.containsValue(internalFrame)) {
160             return;
161         }
162 
163         String windowName = null;
164         int windowIndex = -1;
165         synchronized (internalFrames) {
166             int i = 0;
167             for (Map.Entry<String, JInternalFrame> entry : internalFrames.entrySet()) {
168                 if (entry.getValue() == internalFrame) {
169                     windowName = entry.getKey();
170                     windowIndex = i;
171                     break;
172                 }
173                 i++;
174             }
175         }
176 
177         final String name = windowName;
178         final int index = windowIndex;
179 
180         getApplication().getUIThreadManager().runInsideUIAsync(new Runnable() {
181             public void run() {
182                 if (LOG.isDebugEnabled()) {
183                     LOG.debug("Showing window with name: '" + name + "' at index " + index + " " + internalFrame);
184                 }
185                 //noinspection ConstantConditions
186                 resolveSwingWindowDisplayHandler().show(name, internalFrame);
187             }
188         });
189     }
190 
191     /**
192      * Hides the internal frame.<p>
193      * This method is executed <b>SYNCHRONOUSLY</b> in the UI thread.
194      *
195      @param internalFrame the internal frame to hide
196      @since 2.0.0
197      */
198     public void hide(@Nonnull final JInternalFrame internalFrame) {
199         requireNonNull(internalFrame, ERROR_WINDOW_NULL);
200         if (!internalFrames.containsValue(internalFrame)) {
201             return;
202         }
203 
204         String windowName = null;
205         int windowIndex = -1;
206         synchronized (internalFrames) {
207             int i = 0;
208             for (Map.Entry<String, JInternalFrame> entry : internalFrames.entrySet()) {
209                 if (entry.getValue() == internalFrame) {
210                     windowName = entry.getKey();
211                     windowIndex = i;
212                     break;
213                 }
214                 i++;
215             }
216         }
217 
218         final String name = windowName;
219         final int index = windowIndex;
220 
221         getApplication().getUIThreadManager().runInsideUIAsync(new Runnable() {
222             public void run() {
223                 if (LOG.isDebugEnabled()) {
224                     LOG.debug("Hiding window with name: '" + name + "' at index " + index + " " + internalFrame);
225                 }
226                 //noinspection ConstantConditions
227                 resolveSwingWindowDisplayHandler().hide(name, internalFrame);
228             }
229         });
230     }
231 
232     /**
233      * Should the window be hidden before all ShutdownHandlers be called ?
234      *
235      @return current value
236      */
237     public boolean isHideBeforeHandler() {
238         return hideBeforeHandler;
239     }
240 
241     /**
242      * Set if the window should be hidden before all ShutdownHandler be called.
243      *
244      @param hideBeforeHandler new value
245      */
246     public void setHideBeforeHandler(boolean hideBeforeHandler) {
247         this.hideBeforeHandler = hideBeforeHandler;
248     }
249 
250     @Nonnull
251     protected SwingWindowDisplayHandler resolveSwingWindowDisplayHandler() {
252         return (SwingWindowDisplayHandlerresolveWindowDisplayHandler();
253     }
254 
255     @Override
256     protected void doAttach(@Nonnull Window window) {
257         requireNonNull(window, ERROR_WINDOW_NULL);
258         window.addWindowListener(windowHelper);
259         window.addComponentListener(componentHelper);
260     }
261 
262     @Override
263     protected void doDetach(@Nonnull Window window) {
264         requireNonNull(window, ERROR_WINDOW_NULL);
265         window.removeWindowListener(windowHelper);
266         window.removeComponentListener(componentHelper);
267     }
268 
269     @Override
270     protected boolean isWindowVisible(@Nonnull Window window) {
271         requireNonNull(window, ERROR_WINDOW_NULL);
272         return window.isVisible();
273     }
274 
275     /**
276      * WindowAdapter that optionally invokes hide() when the window is about to be closed.
277      *
278      @author Andres Almiray
279      */
280     private class WindowHelper extends WindowAdapter {
281         public void windowClosing(WindowEvent event) {
282             if (getApplication().getPhase() == ApplicationPhase.SHUTDOWN) {
283                 return;
284             }
285             int visibleWindows = countVisibleWindows();
286 
287             if (isHideBeforeHandler() || visibleWindows > 0) {
288                 hide(event.getWindow());
289             }
290 
291             if (visibleWindows <= && isAutoShutdown()) {
292                 LOG.debug("Attempting to shutdown application");
293                 if (!getApplication().shutdown()) show(event.getWindow());
294             }
295         }
296     }
297 
298     /**
299      * ComponentAdapter that triggers application events when a window is shown/hidden.
300      *
301      @author Andres Almiray
302      */
303     private class ComponentHelper extends ComponentAdapter {
304         /**
305          * Triggers a <tt>WindowShown</tt> event with the window as sole argument
306          */
307         public void componentShown(ComponentEvent event) {
308             event(ApplicationEvent.WINDOW_SHOWN, asList(event.getSource()));
309         }
310 
311         /**
312          * Triggers a <tt>WindowHidden</tt> event with the window as sole argument
313          */
314         public void componentHidden(ComponentEvent event) {
315             event(ApplicationEvent.WINDOW_HIDDEN, asList(event.getSource()));
316         }
317     }
318 
319     /**
320      * InternalFrameAdapter that triggers application events when a window is shown/hidden,
321      * it also invokes hide() when the window is about to be closed.
322      *
323      @author Andres Almiray
324      */
325     private class InternalFrameHelper extends InternalFrameAdapter {
326         public void internalFrameClosing(InternalFrameEvent event) {
327             hide(event.getInternalFrame());
328         }
329 
330         /**
331          * Triggers a <tt>WindowShown</tt> event with the internal frame as sole argument
332          */
333         public void internalFrameOpened(InternalFrameEvent event) {
334             event(ApplicationEvent.WINDOW_SHOWN, asList(event.getSource()));
335 
336         }
337 
338         /**
339          * Triggers a <tt>WindowHidden</tt> event with the internal frame as sole argument
340          */
341         public void internalFrameClosed(InternalFrameEvent event) {
342             event(ApplicationEvent.WINDOW_HIDDEN, asList(event.getSource()));
343         }
344     }
345 
346     private void event(@Nonnull ApplicationEvent evt, List<Object> args) {
347         try {
348             EventRouter eventRouter = getApplication().getEventRouter();
349             eventRouter.publishEvent(evt.getName(), args);
350         catch (InstanceNotFoundException infe) {
351             if (getApplication().getPhase() != ApplicationPhase.SHUTDOWN) {
352                 throw infe;
353             }
354         }
355     }
356 }