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