/*
* MyDialog.java
*
* Copyright (C) 2010 Leo Osvald <leo.osvald@gmail.com>
*
* This file is part of SGLJ.
*
* SGLJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SGLJ 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sglj.swing.dialog;
import java.awt.Component;
import java.awt.Container;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ContainerAdapter;
import java.awt.event.ContainerEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
/**
* <p>Custom dialog; it provides control/validation, and access to
* buttons of the dialog.
* It also provides an ability to recursively add {@link KeyListener}
* listeners, so that each time a key is pressed, no matter which
* subcomponent has the focus, an event is generated.
*
* If an option is chosen or dialog is closed by clicking on "X",
* {@link #handleOption(Object)} method is called. All validation
* should be done in that method.
* Some of the options are supplied by setting option pane via
* constructor or {@link #setOptionPane(JOptionPane)} method.
* It is also possible to set a option pane of length 0, if there
* should be no options (other than clicking on the "X", for example).
* Buttons which corresponds to the options can be obtained via
* {@link #getOptionButton(int)} method.<br>
* To add {@link KeyListener} for the whole dialog (to all components,
* recursively), there exists the
* {@link #MyDialog(Frame, JOptionPane, KeyListener)} constructor.</p>
*
* <p>Dialog can be shown/hiddenDijalog by calling
* {@link #setVisible(boolean)} method.
* By default, the position of the dialog will be such that the center
* of the dialog is as close as possible to the center of the parent frame.
* </p>
* <p><b>Note:</b>Adding non-<code>null</code> {@link KeyListener}
* recursively may lead to "garbage" accumulation if new instances
* with the same {@link JOptionPane} are being creating.
*
* @see JDialog
* @see JOptionPane
* @see #handleOption(Object)
*
* @author Leo Osvald
* @version 0.63
*/
public abstract class MyDialog extends JDialog implements PropertyChangeListener {
private static final long serialVersionUID = 1L;
private final Frame invokerFrame;
private JOptionPane optionPane;
private final KeyListener dialogKeyListener;
private final RecursiveContainerListener recursiveContainerListener
= new RecursiveContainerListener();
private List<JButton> optionButtons;
public MyDialog(Frame frame, JOptionPane optionPane, KeyListener dialogKeyListener) {
super(frame);
this.invokerFrame = frame;
this.dialogKeyListener = dialogKeyListener;
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setOptionPane(optionPane);
}
public MyDialog(Frame frame, JOptionPane optionPane) {
this(frame, optionPane, null);
}
public MyDialog(Frame frame, KeyListener dialogKeyListener) {
this(frame, null, dialogKeyListener);
}
public MyDialog(Frame frame) {
this(frame, (KeyListener)null);
}
public void setOptionPane(JOptionPane optionPane) {
if(optionPane == null) return ; //if null, ignore
//remove all connected to the old pane
if(this.optionPane != null) {
getContentPane().remove(this.optionPane);
this.optionPane.removePropertyChangeListener(this);
}
//set the new one
this.optionPane = optionPane;
getContentPane().addContainerListener(recursiveContainerListener);
getContentPane().add(optionPane);
if(optionPane.getOptions() != null) {
optionButtons = new ArrayList<JButton>();
searchForOptionButtons(this);
}
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent e) {
MyDialog.this.optionPane.setValue(
Integer.valueOf(JOptionPane.CLOSED_OPTION));
};
});
optionPane.addPropertyChangeListener(this);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if(!isVisible()) return ;
if(evt.getSource() != optionPane) return ;
String property = evt.getPropertyName();
if(!JOptionPane.VALUE_PROPERTY.equals(property)
&& !JOptionPane.INPUT_VALUE_PROPERTY.equals(property))
return ;
Object value = optionPane.getValue();
if(value == JOptionPane.UNINITIALIZED_VALUE) return ;
//obavezno resetiraj
optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE);
handleOption(value);
}
private void recursivelyAddKeyListener(Component comp) {
System.out.println("Added component: " + comp);
comp.addKeyListener(dialogKeyListener);
if(comp instanceof Container) {
Container cont = (Container) comp;
addContainerListener(recursiveContainerListener);
Component[] children = cont.getComponents();
for(int i = 0; i < children.length; ++i)
recursivelyAddKeyListener(children[i]);
}
}
private void recursivelyRemoveKeyListener(Component comp) {
System.out.println("Removed component: " + comp);
comp.removeKeyListener(dialogKeyListener);
if(comp instanceof Container) {
Container cont = (Container) comp;
removeContainerListener(recursiveContainerListener);
Component[] children = cont.getComponents();
for(int i = 0; i < children.length; ++i)
recursivelyRemoveKeyListener(children[i]);
}
}
private class RecursiveContainerListener extends ContainerAdapter {
@Override
public void componentAdded(ContainerEvent e) {
if(dialogKeyListener != null)
recursivelyAddKeyListener(e.getChild());
}
@Override
public void componentRemoved(ContainerEvent e) {
if(dialogKeyListener != null)
recursivelyRemoveKeyListener(e.getChild());
}
}
@Override
public void dispose() {
if(dialogKeyListener != null)
recursivelyRemoveKeyListener(this);
super.dispose();
}
@Override
public void setVisible(boolean b) {
moveToCenter();
super.setVisible(b);
}
private void moveToCenter() {
final double eps = 1e-9;
double screenWidth = Toolkit.getDefaultToolkit().getScreenSize()
.getWidth();
double screenHeigth = Toolkit.getDefaultToolkit().getScreenSize()
.getHeight();
if(invokerFrame != null) { //if invoker frame actually exists
int x = (2*invokerFrame.getX() + invokerFrame.getWidth()
- getSize().width)/2;
int y = (2*invokerFrame.getY() + invokerFrame.getHeight()
- getSize().height)/2;
//if it would be completely out of the screen, move it to the edge
if(x < 0) x = 0;
else if(x >= screenWidth)
x = (int) (screenWidth - getWidth() + eps);
if(y < 0) y = 0;
else if(y >= screenHeigth)
y = (int) (screenHeigth - getHeight() + eps);
//move it to the center if possible (or at least to the edge)
setLocation(x, y);
} else { //otherwise, move it to center
int x = (int) (screenWidth - getWidth() + eps)/2;
int y = (int) (screenHeigth - getHeight()+eps)/2;
setLocation(x, y);
}
}
private void searchForOptionButtons(Component comp) {
if(comp instanceof JButton) {
JButton button = (JButton) comp;
if(button.getName() != null && button.getName().equals("OptionPane.button")) {
// System.out.println("Found button: (name = " + button.getName()
// + ", text = " + button.getText());
optionButtons.add(button);
}
}
if(comp instanceof Container) {
Container cont = (Container) comp;
Component[] children = cont.getComponents();
for(int i = 0; i < children.length; ++i)
searchForOptionButtons(children[i]);
}
}
/**
* This method must handle all possible options.<br>
* If a dialog is closed by clicking on "X", <code>option</code> will
* be equal to {@link JOptionPane#CLOSED_OPTION}.
* @param option option chosen by the user
* @see JOptionPane
*/
protected abstract void handleOption(Object option);
/**
* Returns the button which represents the <code>n</code>-th option.
* @param n 0-based index
* @return the corresponding button
*/
protected JButton getOptionButton(int n) {
return optionButtons.get(n);
}
/**
* Returns all buttons which represent an option.
* @return the list of option buttons
*/
protected List<JButton> getOptionButtons() {
return optionButtons;
}
/**
* This method is called when the user tries to close the dialog
* (or choose Cancel option if it exists).
* All validation and control should be done here, i.e.
* ask for confirmation, save state etc..
*/
protected abstract void onClose();
/**
* Default action for closing dialog.
* The {@link MyDialog#onClose()} is called when this action is
* performed.
*
* @author Leo Osvald
*
*/
protected class ActionClose extends AbstractAction {
private static final long serialVersionUID = -3704794791524647249L;
public ActionClose() {
super("closeDialog", null);
}
public ActionClose(String name, Icon icon) {
super(name, icon);
}
public ActionClose(String name, Icon icon,
String shortDescription, int mnemonicKey) {
super(name, icon);
putValue(SHORT_DESCRIPTION, shortDescription);
putValue(MNEMONIC_KEY, mnemonicKey);
}
@Override
public void actionPerformed(ActionEvent e) {
onClose();
}
}
}