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