/* * org.openmicroscopy.shoola.env.ui.TopWindowManager * *------------------------------------------------------------------------------ * Copyright (C) 2006 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.env.ui; //Java imports import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import javax.swing.AbstractButton; import javax.swing.JFrame; //Third-party libraries //Application-internal dependencies /** * Controller and model of a top window. * <p>This component is normally used with a window that is linked to the * {@link TaskBar} by means of one or more display-trigger buttons. It keeps * track of the display state of its window and reacts to mouse clicks on the * display buttons accordingly.</p> * <p>The display state of the window is broken down as follows: * <ul> * <li><i>Unlinked</i>: The window is not connected to any native screen * resource.</li> * <li><i>Displayable</i>: The window is connected to a native peer, but * is not showing on screen.</li> * <li><i>On Screen</i>: The window has been painted on screen. This doesn't * necessarily imply a visible window. It may be hidden behind other * windows or may be iconified.</li> * </ul> * </p> * <p>When the window is off-screen (either in the <i>Unlinked</i> or * <i>Displayable</i> state), this controller reacts to a call to set the window * visible (<code>window.setVisible(true)</code>) or a click on any of the * display buttons by showing the window at the center of the screen, possibly * on top of all other windows and making it the active window — this * attempt might just partially succeed depending on the OS window manager. If * one of those events occur while the window is in the <i>On Screen</i> state, * a similar action is performed, but the window is restored to the previous * screen location, which is not going to be, generally speaking, the * center of the screen — for example, if the window is moved from the * center of the screen to another location and then iconified, it'll be * restored to that location.</p> * <p>If the window is in either the <i>On Screen</i> or <i>Displayable</i> * state and its <code>dispose</code> method is invoked, then this controller * will set the window's state to <i>Unlinked</i>. Notice that this could * happen implicitely, for example if the window's default close operation is * set to {@link JFrame#DISPOSE_ON_CLOSE}.</p> * <p>Finally, while in the <i>On Screen</i> state, any event that causes the * window to be hidden (a <code>window.setVisible(false)</code> call or a click * on the system-provided window-close button) results in a transition to the * <i>Displayble</i> state.</p> * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @version 2.2 * <small> * (<b>Internal version:</b> $Revision$ $Date$) * </small> * @since OME2.2 */ public class TopWindowManager { /** Identifies the <i>Unlinked</i> state. */ public static final int UNLINKED = 1; /** Identifies the <i>Displayable</i> state. */ public static final int DISPLAYABLE = 2; /** Identifies the <i>On Screen</i> state. */ public static final int ON_SCREEN = 3; /** The window this manager is for. */ private TopWindow window; /** * The set of buttons that have to trigger a Display event whenever clicked. * If there are no such buttons, then this array will be <code>0</code>- * length. */ private AbstractButton[] displayButtons; /** Holds the current state of the managed {@link #window}. */ private int state; /** * Finds out what is the state of the {@link #window} at the time * that was passed to this class' constructor and sets {@link #state} * accordingly. */ private void setInitialState() { if (window.isDisplayable()) state = (window.isShowing() ? ON_SCREEN : DISPLAYABLE); else state = UNLINKED; } /** * Registers event handlers to react to Display, Hide, and Dispose events * fired by the {@link #window}. */ private void attachWindowListeners() { WindowAdapter wa = new WindowAdapter() { public void windowClosed(WindowEvent we) { handleDispose(); } public void windowClosing(WindowEvent we) { handleHide(); } }; window.addWindowListener(wa); ComponentAdapter ca = new ComponentAdapter() { public void componentHidden(ComponentEvent ce) { handleHide(); } public void componentShown(ComponentEvent ce) { handleDisplay(); } }; window.addComponentListener(ca); } /** * Registers event handlers to react to Display events fired by the * {@link #displayButtons}. */ private void attachButtonsListeners() { ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent ae) { handleDisplay(); } }; for (int i = 0; i < displayButtons.length; ++i) displayButtons[i].addActionListener(al); } /** * Handles the <i>Display</i> event. * A <i>Display</i> event is caused by a call to set the {@link #window} * visible (<code>window.setVisible(true)</code>) or a click on any of the * {@link #displayButtons}. */ private void handleDisplay() { if (state == ON_SCREEN) toForeground(); else { window.setOnScreen(); toForeground(); state = ON_SCREEN; //NOTE: setOnScreen() shows the window on screen, which will //eventually cause another call to componentShown() and, in turn, //another call to this method. However, the state is now ON_SCREEN //so we just invoke toForeground(). This could be avoided if we //kept track of the fg/bg state of the window as well, but it would //make the implementation more complex. As an extra call to //toForeground() is harmless, we favour an easier implementation //over logic soundness. } } /** * Handle the <i>Hide</i> event. * A <i>Hide</i> event is caused by a <code>window.setVisible(false)</code> * call or a click on the system-provided window-close button. */ private void handleHide() { if (state == ON_SCREEN) state = DISPLAYABLE; //Do nothing if DISPLAYABLE or UNLINKED. //NOTE: This method will be invoked twice if the action associated to //the close button of the window is to hide the window -- this happens //b/c hanldeHide() is called in both windowClosing() and //componentHidden(). However, the same considerations made in the note //to handleDisplay() apply. } /** * Handle the <i>Dispose</i> event. * This is caused by a call to the window's <code>dispose</code> method. */ private void handleDispose() { state = UNLINKED; } /** * Attempts to bring the {@link #window} on top of all other windows and * make it the active window. * This attempt might just partially succeed depending on the OS window * manager. */ private void toForeground() { window.setExtendedState(JFrame.NORMAL); //Deiconify if iconified. window.toFront(); } /** * Creates a new instance to manage the specified window. * * @param window The window to manage. * @param displayButtons The set of buttons that have to trigger a * Display event whenever clicked. Pass * <code>null</code> if there are no such buttons. */ public TopWindowManager(TopWindow window, AbstractButton[] displayButtons) { if (window == null) throw new NullPointerException("No window."); this.window = window; if (displayButtons != null) { ArrayList<AbstractButton> buttons = new ArrayList<AbstractButton>(displayButtons.length); for (int i = 0; i < displayButtons.length; ++i) if (displayButtons[i] != null) buttons.add(displayButtons[i]); this.displayButtons = new AbstractButton[buttons.size()]; buttons.toArray(this.displayButtons); } else this.displayButtons = new AbstractButton[0]; setInitialState(); attachWindowListeners(); attachButtonsListeners(); } /** * Returns the current state of the managed window. * * @return One of the state identifiers defined by the static fields of * this class. */ public int getState() { return state; } }