/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander 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 3 of the License, or * (at your option) any later version. * * muCommander 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, see <http://www.gnu.org/licenses/>. */ package com.mucommander.ui.dialog; import java.awt.Component; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mucommander.commons.runtime.OsFamily; import com.mucommander.ui.helper.FocusRequester; /** * FocusDialog is a modal dialog which extends JDialog to provide the following additional functionalities : * <ul> * <li>focus can be requested on a specified JComponent once the dialog has been made visible</li> * <li>the screen location of the window can be set relatively to a Component specified in the constructor</li> * <li>a minimum and/or maximum size can be specified and will be used by {@link #pack()} to calculate the effective dialog size</li> * <li>by default, the 'Escape' key disposes the dialog, this can be disabled using {@link #setKeyboardDisposalEnabled(boolean)}</li> * </ul> * @author Maxence Bernard */ public class FocusDialog extends JDialog implements WindowListener { private static final Logger LOGGER = LoggerFactory.getLogger(FocusDialog.class); /** Minimum dimensions of this dialog, may be null */ private Dimension minimumDimension; /** Maximum dimensions of this dialog, may be null */ private Dimension maximumDimension; /** Has this window been activated yet ? */ private boolean firstTimeActivated; /** The component that will receive the focus when this window is activated for the first time, may be null */ private JComponent initialFocusComponent; private Component locationRelativeComp; private boolean keyboardDisposalEnabled = true; private final static String CUSTOM_DISPOSE_EVENT = "CUSTOM_DISPOSE_EVENT"; public FocusDialog(Frame owner, String title, Component locationRelativeComp) { super(owner, title, true); init(locationRelativeComp); } public FocusDialog(Dialog owner, String title, Component locationRelativeComp) { super(owner, title, true); init(locationRelativeComp); } private void init(Component locationRelativeComp) { this.locationRelativeComp = locationRelativeComp; setLocationRelativeTo(locationRelativeComp); JPanel contentPane = (JPanel)getContentPane(); contentPane.setBorder(new EmptyBorder(6, 8, 6, 8)); setResizable(true); // Important: dispose (release resources) window on close, default is HIDE_ON_CLOSE setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); // Catch escape key presses and have them close the dialog by mapping the escape keystroke to a custom dispose Action InputMap inputMap = contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap actionMap = contentPane.getActionMap(); AbstractAction disposeAction = new AbstractAction() { public void actionPerformed(ActionEvent e){ if(keyboardDisposalEnabled) cancel(); } }; // Maps the dispose action to the 'Escape' keystroke inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), CUSTOM_DISPOSE_EVENT); actionMap.put(CUSTOM_DISPOSE_EVENT, disposeAction); // Maps the dispose action to the 'Apple+W' keystroke under Mac OS X if(OsFamily.MAC_OS_X.isCurrent()) inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.META_MASK), CUSTOM_DISPOSE_EVENT); // Under Windows, Alt+F4 automagically disposes the dialog, nothing to do } /** * Method called when the user has canceled through the escape key. * <p> * This method is equivalent to a call to {@link #dispose()}. It's meant to be * overriden by those implementations of <code>FocusDialog</code> that need to run * code before canceling the dialog. * </p> */ public void cancel() { dispose(); } /** * Sets the component that will receive focus once this dialog has been made visible. * * @param initialFocusComponent the component that will receive focus once this dialog has been made visible, if * null, the first component in the dialog will receive focus. */ public void setInitialFocusComponent(JComponent initialFocusComponent) { this.initialFocusComponent = initialFocusComponent; if(initialFocusComponent==null) removeWindowListener(this); else addWindowListener(this); } /** * Sets a maximum width and height for this dialog. */ @Override public void setMaximumSize(Dimension dimension) { this.maximumDimension = dimension; } /** * Sets a minium width and height for this dialog. */ @Override public void setMinimumSize(Dimension dimension) { this.minimumDimension = dimension; } /** * Specifies whether this dialog can be automatically disposed using the 'Escape' key and 'Apple+W' under Mac OS X. * If enabled, {@link #dispose()} will be called when one of those keystrokes is pressed from any component * within this dialog. * * @param enabled true to enable automatic keyboard disposal, false to disable it */ public void setKeyboardDisposalEnabled(boolean enabled) { this.keyboardDisposalEnabled = enabled; } /** * Overrides Window.pack() to take into account minimum and maximum dialog size (if specified). */ @Override public void pack() { super.pack(); if(maximumDimension!=null) DialogToolkit.fitToMaxDimension(this, maximumDimension); else DialogToolkit.fitToScreen(this); if(minimumDimension!=null) DialogToolkit.fitToMinDimension(this, minimumDimension); } /** * Packs this dialog, makes it non-resizable and visible. */ public void showDialog() { pack(); if(locationRelativeComp==null) DialogToolkit.centerOnScreen(this); else setLocation(locationRelativeComp.getX()+(locationRelativeComp.getWidth()-getWidth())/2, locationRelativeComp.getY()+(locationRelativeComp.getHeight()-getHeight())/2); setVisible(true); } /** * Return <code>true</code> if the dialog has been activated (see WindowListener.windowActivated()). * * @return <code>true</code> if the dialog has been activated */ public boolean isActivated() { return firstTimeActivated; } //////////////////////////// // WindowListener methods // //////////////////////////// public void windowOpened(WindowEvent e) { } public void windowActivated(WindowEvent e) { // (this method is called each time the dialog is activated) if (!firstTimeActivated && initialFocusComponent!=null) { LOGGER.trace("requesting focus on initial focus component"); // First try using requestFocusInWindow() which is preferred over requestFocus(). If it fails // (returns false), call requestFocus: // "The focus behavior of this method can be implemented uniformly across platforms, and thus developers are // strongly encouraged to use this method over requestFocus when possible. Code which relies on requestFocus // may exhibit different focus behavior on different platforms." if(!initialFocusComponent.requestFocusInWindow()) { LOGGER.trace("requestFocusInWindow failed, calling requestFocus"); FocusRequester.requestFocus(initialFocusComponent); } firstTimeActivated = true; } } public void windowClosing(WindowEvent e) { } public void windowClosed(WindowEvent e) { } public void windowDeactivated(WindowEvent e) { } public void windowIconified(WindowEvent e) { } public void windowDeiconified(WindowEvent e) { } }