/*
* Copyright 2008-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.griffon.runtime.swing;
import griffon.core.ApplicationEvent;
import griffon.core.GriffonApplication;
import griffon.core.env.ApplicationPhase;
import griffon.swing.SwingWindowDisplayHandler;
import griffon.swing.SwingWindowManager;
import griffon.util.GriffonNameUtils;
import org.codehaus.griffon.runtime.core.view.AbstractWindowManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.swing.JInternalFrame;
import javax.swing.WindowConstants;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import static griffon.util.GriffonNameUtils.requireNonBlank;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Objects.requireNonNull;
/**
* @author Andres Almiray
* @since 2.0.0
*/
public class DefaultSwingWindowManager extends AbstractWindowManager<Window> implements SwingWindowManager {
private static final Logger LOG = LoggerFactory.getLogger(DefaultSwingWindowManager.class);
private final WindowHelper windowHelper = new WindowHelper();
private final ComponentHelper componentHelper = new ComponentHelper();
private final InternalFrameHelper internalFrameHelper = new InternalFrameHelper();
private final Map<String, JInternalFrame> internalFrames = Collections.synchronizedMap(new LinkedHashMap<String, JInternalFrame>());
private boolean hideBeforeHandler = false;
@Inject
@Nonnull
public DefaultSwingWindowManager(@Nonnull GriffonApplication application, @Nonnull @Named("windowDisplayHandler") SwingWindowDisplayHandler windowDisplayHandler) {
super(application, windowDisplayHandler);
requireNonNull(application.getEventRouter(), "Argument 'application.eventRouter' must not be null");
}
/**
* Finds a JInternalFrame by name.
*
* @param name the value of the name: property
* @return a JInternalFrame if a match is found, null otherwise.
* @since 2.0.0
*/
public JInternalFrame findInternalFrame(String name) {
if (!GriffonNameUtils.isBlank(name)) {
for (JInternalFrame internalFrame : internalFrames.values()) {
if (name.equals(internalFrame.getName())) return internalFrame;
}
}
return null;
}
@Nonnull
@Override
public Set<String> getInternalWindowNames() {
return Collections.unmodifiableSet(internalFrames.keySet());
}
@Nullable
@Override
public String findInternalWindowName(@Nonnull JInternalFrame window) {
requireNonNull(window, ERROR_WINDOW_NULL);
synchronized (internalFrames) {
for (Map.Entry<String, JInternalFrame> e : internalFrames.entrySet()) {
if (e.getValue().equals(window)) {
return e.getKey();
}
}
}
return null;
}
@Override
public int indexOfInternal(@Nonnull JInternalFrame window) {
requireNonNull(window, ERROR_WINDOW_NULL);
synchronized (internalFrames) {
int index = 0;
for (JInternalFrame w : internalFrames.values()) {
if (window.equals(w)) {
return index;
}
index++;
}
}
return -1;
}
/**
* Returns the list of internal frames managed by this manager.
*
* @return a List of currently managed internal frames
* @since 2.0.0
*/
public Collection<JInternalFrame> getInternalFrames() {
return unmodifiableCollection(internalFrames.values());
}
/**
* Registers an internal frame on this manager if an only if the internal frame is not null
* and it's not registered already.
*
* @param name the value of the of the Window's name
* @param internalFrame the internal frame to be added to the list of managed internal frames
* @since 2.0.0
*/
public void attach(@Nonnull String name, @Nonnull JInternalFrame internalFrame) {
requireNonBlank(name, ERROR_NAME_BLANK);
requireNonNull(internalFrame, ERROR_WINDOW_NULL);
if (internalFrames.containsKey(name)) {
JInternalFrame window2 = internalFrames.get(name);
if (window2 != internalFrame) {
detach(name);
}
}
doAttach(internalFrame);
if (LOG.isDebugEnabled()) {
LOG.debug("Attaching internal frame with name: '" + name + "' at index " + internalFrames.size() + " " + internalFrame);
}
internalFrames.put(name, internalFrame);
event(ApplicationEvent.WINDOW_ATTACHED, asList(name, internalFrame));
}
protected void doAttach(@Nonnull JInternalFrame internalFrame) {
internalFrame.addInternalFrameListener(internalFrameHelper);
internalFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
}
/**
* Removes the internal frame from the list of manages internal frames if and only if it
* is registered with this manager.
*
* @param name the value of the of the Window's name
* @since 2.0.0
*/
public void detach(@Nonnull String name) {
requireNonBlank(name, ERROR_NAME_BLANK);
if (internalFrames.containsKey(name)) {
JInternalFrame window = internalFrames.get(name);
doDetach(window);
if (LOG.isDebugEnabled()) {
LOG.debug("Detaching internalFrame with name: '" + name + "' " + window);
}
internalFrames.remove(name);
event(ApplicationEvent.WINDOW_DETACHED, asList(name, window));
}
}
protected void doDetach(@Nonnull JInternalFrame internalFrame) {
internalFrame.removeInternalFrameListener(internalFrameHelper);
}
/**
* Shows the internal frame.<p>
* This method is executed <b>SYNCHRONOUSLY</b> in the UI thread.
*
* @param internalFrame the internal frame to show
* @since 2.0.0
*/
public void show(@Nonnull final JInternalFrame internalFrame) {
requireNonNull(internalFrame, ERROR_WINDOW_NULL);
if (!internalFrames.containsValue(internalFrame)) {
return;
}
String windowName = null;
int windowIndex = -1;
synchronized (internalFrames) {
int i = 0;
for (Map.Entry<String, JInternalFrame> entry : internalFrames.entrySet()) {
if (entry.getValue() == internalFrame) {
windowName = entry.getKey();
windowIndex = i;
break;
}
i++;
}
}
final String name = windowName;
final int index = windowIndex;
getApplication().getUIThreadManager().runInsideUIAsync(new Runnable() {
public void run() {
if (LOG.isDebugEnabled()) {
LOG.debug("Showing window with name: '" + name + "' at index " + index + " " + internalFrame);
}
//noinspection ConstantConditions
resolveSwingWindowDisplayHandler().show(name, internalFrame);
}
});
}
/**
* Hides the internal frame.<p>
* This method is executed <b>SYNCHRONOUSLY</b> in the UI thread.
*
* @param internalFrame the internal frame to hide
* @since 2.0.0
*/
public void hide(@Nonnull final JInternalFrame internalFrame) {
requireNonNull(internalFrame, ERROR_WINDOW_NULL);
if (!internalFrames.containsValue(internalFrame)) {
return;
}
String windowName = null;
int windowIndex = -1;
synchronized (internalFrames) {
int i = 0;
for (Map.Entry<String, JInternalFrame> entry : internalFrames.entrySet()) {
if (entry.getValue() == internalFrame) {
windowName = entry.getKey();
windowIndex = i;
break;
}
i++;
}
}
final String name = windowName;
final int index = windowIndex;
getApplication().getUIThreadManager().runInsideUIAsync(new Runnable() {
public void run() {
if (LOG.isDebugEnabled()) {
LOG.debug("Hiding window with name: '" + name + "' at index " + index + " " + internalFrame);
}
//noinspection ConstantConditions
resolveSwingWindowDisplayHandler().hide(name, internalFrame);
}
});
}
/**
* Should the window be hidden before all ShutdownHandlers be called ?
*
* @return current value
*/
public boolean isHideBeforeHandler() {
return hideBeforeHandler;
}
/**
* Set if the window should be hidden before all ShutdownHandler be called.
*
* @param hideBeforeHandler new value
*/
public void setHideBeforeHandler(boolean hideBeforeHandler) {
this.hideBeforeHandler = hideBeforeHandler;
}
@Nonnull
protected SwingWindowDisplayHandler resolveSwingWindowDisplayHandler() {
return (SwingWindowDisplayHandler) resolveWindowDisplayHandler();
}
@Override
protected void doAttach(@Nonnull Window window) {
requireNonNull(window, ERROR_WINDOW_NULL);
window.addWindowListener(windowHelper);
window.addComponentListener(componentHelper);
}
@Override
protected void doDetach(@Nonnull Window window) {
requireNonNull(window, ERROR_WINDOW_NULL);
window.removeWindowListener(windowHelper);
window.removeComponentListener(componentHelper);
}
@Override
protected boolean isWindowVisible(@Nonnull Window window) {
requireNonNull(window, ERROR_WINDOW_NULL);
return window.isVisible();
}
/**
* WindowAdapter that optionally invokes hide() when the window is about to be closed.
*
* @author Andres Almiray
*/
private class WindowHelper extends WindowAdapter {
public void windowClosing(WindowEvent event) {
if (getApplication().getPhase() == ApplicationPhase.SHUTDOWN) {
return;
}
int visibleWindows = countVisibleWindows();
if (isHideBeforeHandler() || visibleWindows > 0) {
hide(event.getWindow());
}
if (visibleWindows <= 1 && isAutoShutdown()) {
LOG.debug("Attempting to shutdown application");
if (!getApplication().shutdown()) show(event.getWindow());
}
}
}
/**
* ComponentAdapter that triggers application events when a window is shown/hidden.
*
* @author Andres Almiray
*/
private class ComponentHelper extends ComponentAdapter {
/**
* Triggers a <tt>WindowShown</tt> event with the window as sole argument
*/
public void componentShown(ComponentEvent event) {
Window window = (Window) event.getSource();
event(ApplicationEvent.WINDOW_SHOWN, asList(findWindowName(window), window));
}
/**
* Triggers a <tt>WindowHidden</tt> event with the window as sole argument
*/
public void componentHidden(ComponentEvent event) {
Window window = (Window) event.getSource();
event(ApplicationEvent.WINDOW_HIDDEN, asList(findWindowName(window), window));
}
}
/**
* InternalFrameAdapter that triggers application events when a window is shown/hidden,
* it also invokes hide() when the window is about to be closed.
*
* @author Andres Almiray
*/
private class InternalFrameHelper extends InternalFrameAdapter {
public void internalFrameClosing(InternalFrameEvent event) {
hide(event.getInternalFrame());
}
/**
* Triggers a <tt>WindowShown</tt> event with the internal frame as sole argument
*/
public void internalFrameOpened(InternalFrameEvent event) {
JInternalFrame window = (JInternalFrame) event.getSource();
event(ApplicationEvent.WINDOW_SHOWN, asList(findInternalWindowName(window), window));
}
/**
* Triggers a <tt>WindowHidden</tt> event with the internal frame as sole argument
*/
public void internalFrameClosed(InternalFrameEvent event) {
JInternalFrame window = (JInternalFrame) event.getSource();
event(ApplicationEvent.WINDOW_HIDDEN, asList(findInternalWindowName(window), window));
}
}
}