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