/*
* Copyright (c) 2008, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library 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.
*
* SQL Power Library 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 ca.sqlpower.swingui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.concurrent.Callable;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import ca.sqlpower.swingui.ChangeListeningDataEntryPanel.ErrorTextListener;
import ca.sqlpower.validation.Validated;
import ca.sqlpower.validation.swingui.ValidatableDataEntryPanel;
import ca.sqlpower.validation.swingui.ValidationHandler;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.factories.ButtonBarFactory;
public class DataEntryPanelBuilder {
static Logger logger = Logger.getLogger(DataEntryPanelBuilder.class);
public static final String OK_BUTTON_LABEL = Messages.getString("DataEntryPanelBuilder.okButton"); //$NON-NLS-1$
public static final String CANCEL_BUTTON_LABEL = Messages.getString("DataEntryPanelBuilder.cancelButton"); //$NON-NLS-1$
/**
* Works like
* {@link #createDataEntryPanelDialog(DataEntryPanel, Window, String, String, Callable, Callable, boolean)}
* with a <code>oneShot</code> argument of <code>false</code>.
*
* @param dataEntry
* The DataEntryPanel implementation
* @param dialogParent
* A Window object to be the dialog's parent
* @param dialogTitle
* The dialog title.
* @param actionButtonTitle
* The label text for the OK button
* @return The new JDialog, which has the panel in it along with OK and Cancel buttons
* @param okCall<Boolean> Call to be invoked when the OK/action button is
* pressed; does NOT need to dismiss the dialog we will do this if the call returns false
* @param cancelCall<Boolean> Call to be invoked when the cancel button is
* pressed; We will dismiss the dialog if the call returns true
* @return
*/
public static JDialog createDataEntryPanelDialog(
final DataEntryPanel dataEntry,
final Component dialogParent,
final String dialogTitle,
final String actionButtonTitle,
final Callable<Boolean> okCall,
final Callable<Boolean> cancelCall) {
return createDataEntryPanelDialog(
dataEntry, dialogParent, dialogTitle,
actionButtonTitle, okCall, cancelCall, false);
}
/**
* Builds a JDialog around an object that implements DataEntryPanel, to
* provide consistent behaviours such as Cancel button, <ESC> to close, and
* so on.
*
* @param dataEntry
* The DataEntryPanel implementation
* @param dialogParent
* A Window object to be the dialog's parent
* @param dialogTitle
* The dialog title.
* @param actionButtonTitle
* The label text for the OK button
* @param okCall
* Call to be invoked when the OK/action button is pressed; does
* NOT need to dismiss the dialog we will do this if the call
* returns false
* @param cancelCall
* Call to be invoked when the cancel button is pressed; We will
* dismiss the dialog if the call returns true. You can specify
* null for this parameter if no custom cancel behaviour is required.
* @param oneShot
* When true, the OK and Cancel buttons will be permanently
* deactivated once one of them has been pressed. If false, the
* buttons will reactivate once the <code>okCall</code> or
* <code>cancelCall</code> has returned.
* @return The new JDialog, which has the panel in it along with OK and
* Cancel buttons
*/
public static JDialog createDataEntryPanelDialog(
final DataEntryPanel dataEntry,
final Component dialogParent,
final String dialogTitle,
final String actionButtonTitle,
final Callable<Boolean> okCall,
final Callable<Boolean> cancelCall,
final boolean oneShot) {
final JDialog d = createDialog(dialogParent, dialogTitle);
JComponent panel = dataEntry.getPanel();
final Action okAction;
if (dataEntry instanceof MonitorableDataEntryPanel) {
MonitorableDataEntryPanel mdep = (MonitorableDataEntryPanel)dataEntry;
mdep.setDialog(d);
okAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
try {
this.setEnabled(false);
okCall.call();
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
if (!oneShot) {
this.setEnabled(true);
}
}
}
};
} else {
okAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
try {
this.setEnabled(false);
boolean close = okCall.call().booleanValue();
if (close) {
d.dispose();
}
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
if (!oneShot) {
this.setEnabled(true);
}
}
}
};
}
if (dataEntry instanceof ValidatableDataEntryPanel) {
ValidatableDataEntryPanel vdep = ((ValidatableDataEntryPanel) dataEntry);
ValidationHandler handler = vdep.getValidationHandler();
handler.setValidatedAction(okAction);
}
final JButton okButton = new JDefaultButton(okAction);
okButton.setText(actionButtonTitle);
Action closeAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
try {
boolean close = cancelCall.call().booleanValue();
if (close) {
d.dispose();
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
};
//attempts to call the cancel action when the user clicks
//the windows "x" button.
d.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
d.addWindowListener(new WindowListener() {
public void windowActivated(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowClosing(WindowEvent e) {
try {
boolean close = cancelCall.call().booleanValue();
if (close) {
d.dispose();
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public void windowDeactivated(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
});
//checks if it is a panel that needs to be validated before save.
if (dataEntry instanceof Validated) {
//links the saveAction to the handler
ValidationHandler handler = ((Validated)dataEntry).getHandler();
handler.setValidatedAction(okAction);
}
if (dataEntry instanceof Resizable) {
//resizes to the correct dimensions
ComponentListener cl = new ComponentListener(){
public void componentHidden(ComponentEvent e) {}
public void componentMoved(ComponentEvent e) {}
public void componentResized(ComponentEvent e) {
d.setSize(d.getPreferredSize());
}
public void componentShown(ComponentEvent e) {}
};
((Resizable)dataEntry).addResizeListener(cl);
}
SPSUtils.makeJDialogCancellable(d, closeAction);
JButton cancelButton = new JButton(closeAction);
cancelButton.setText(CANCEL_BUTTON_LABEL);
// Handle if the user presses Enter in the dialog - do OK action
d.getRootPane().setDefaultButton(okButton);
// Now build the GUI.
final JPanel cp = new JPanel(new BorderLayout());
final JLabel errorLabel = new JLabel();
errorLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
cp.add(errorLabel, BorderLayout.NORTH);
errorLabel.setVisible(false);
if (dataEntry instanceof ChangeListeningDataEntryPanel) {
((ChangeListeningDataEntryPanel) dataEntry).addErrorTextListener(new ErrorTextListener() {
public void textChanged(String s) {
errorLabel.setText(s);
boolean noError = s == null || s.trim().equals("");
errorLabel.setVisible(!noError);
okButton.setEnabled(noError);
d.pack();
}
});
}
cp.add(panel, BorderLayout.CENTER);
cp.add(ButtonBarFactory.buildOKCancelBar(okButton, cancelButton),
BorderLayout.SOUTH);
cp.setBorder(Borders.DIALOG_BORDER);
//d.add(cp);
d.setContentPane(cp);
d.pack();
d.setBounds((int) d.getBounds().getX(), (int) d.getBounds().getY(),
(int) Math.min(d.getBounds().getWidth(), d.getToolkit().getScreenSize().getWidth()),
(int) Math.min(d.getBounds().getHeight(), d.getToolkit().getScreenSize().getHeight()));
d.setLocationRelativeTo(dialogParent);
return d;
}
/**
* Returns a JDialog with the given Window as its parent, and with the given title.
* If the dialog parent is neither a Frame or Dialog, it will throw an IllegalArgumentException
*/
private static JDialog createDialog(final Component dialogParentComponent, final String dialogTitle) {
JDialog dialog;
Window dialogParent;
if (dialogParentComponent instanceof Window) {
dialogParent = (Window) dialogParentComponent;
} else {
dialogParent = SwingUtilities.getWindowAncestor(dialogParentComponent);
}
if ( dialogParent == null ) {
dialog = new JDialog();
if (logger.isDebugEnabled()) {
JOptionPane.showMessageDialog(null,
"This action called createDataEntryPanelDialog with DialogParent == null!"); //$NON-NLS-1$
}
} else if (dialogParent instanceof Frame) {
dialog = new JDialog((Frame) dialogParent, dialogTitle);
} else if (dialogParent instanceof Dialog) {
dialog = new JDialog((Dialog) dialogParent, dialogTitle);
} else {
throw new IllegalArgumentException(
"The dialogParent you gave me is not a " //$NON-NLS-1$
+ "Frame or Dialog (it is a " //$NON-NLS-1$
+ dialogParent.getClass().getName() + ")"); //$NON-NLS-1$
}
return dialog;
}
/**
* Build a JDialog around an object that implements DataEntryPanel, to
* provide consistent behaviours such as Cancel button, <ESC> to close, and
* so on.
*
* @param dataEntry
* The DataEntryPanel implementation
* @param dialogParent
* A Window class to be the parent, or null
* @param dialogTitle
* The display title.
* @param actionButtonTitle
* The title for the OK button
* @return The built JDialog
*/
public static JDialog createSingleButtonDataEntryPanelDialog(
final DataEntryPanel dataEntry,
final Component dialogParent,
final String dialogTitle,
final String actionButtonTitle) {
Action okAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
dataEntry.applyChanges();
}
};
return createSingleButtonDataEntryPanelDialog(dataEntry, dialogParent, dialogTitle,
actionButtonTitle, okAction );
}
/**
* Build a JDialog around an object that implements DataEntryPanel, to
* provide consistent behaviours such as Cancel button, <ESC> to close, and
* so on.
*
* @param dataEntry
* The DataEntryPanel implementation
* @param dialogParent
* A Window class to be the parent, or null
* @param dialogTitle
* The display title.
* @param actionButtonTitle
* The title for the OK button
* @return The built JDialog
*/
public static JDialog createDataEntryPanelDialog(
final DataEntryPanel dataEntry,
final Component dialogParent,
final String dialogTitle,
final String actionButtonTitle) {
Callable<Boolean> okCall = new Callable<Boolean>() {
public Boolean call() {
if (dataEntry.hasUnsavedChanges()) {
return new Boolean(dataEntry.applyChanges());
}
return new Boolean(true);
}
};
Callable<Boolean> cancelCall = new Callable<Boolean>() {
public Boolean call() {
if (dataEntry.hasUnsavedChanges()) {
//used to give a dialog asking whether you want to discard, but
//it was weird because you'd expect to discard changes when you
//pressed cancel on the current dialog to cause this to happen.
dataEntry.discardChanges();
}
//doesn't really make sense for cancel to fail
return new Boolean(true);
}
};
return createDataEntryPanelDialog(dataEntry, dialogParent, dialogTitle,
actionButtonTitle, okCall, cancelCall);
}
/**
* Build a JDialog around an object that implements DataEntryPanel, to
* provide consistent behaviours such as Cancel button, <ESC> to close, and
* so on.
* XXX Worry about modal vs non-modal
* XXX This is not called by anything
* @param dataEntry
* The DataEntryPanel implementation
* @param dialogParent
* A Window object to be the dialog's parent
* @param dialogTitle
* The dialog title.
* @param actionButtonTitle
* The label text for the OK button
* @param okAction Action to be invoked when the OK/action button is
* pressed; does NOT need to dismiss the dialog (we do that if applyChanges() returns true).
* @return The new JDialog, which has the panel in it along with OK and Cancel buttons
*/
public static JDialog createSingleButtonDataEntryPanelDialog(
final DataEntryPanel dataEntry,
final Component dialogParent,
final String dialogTitle,
final String actionButtonTitle,
final Action okAction ) {
final JDialog d = createDialog(dialogParent, dialogTitle);
JComponent panel = dataEntry.getPanel();
JButton okButton = new JDefaultButton(okAction);
okButton.setText(actionButtonTitle);
// In all cases we have to close the dialog.
Action closeAction = new CommonCloseAction(d);
okButton.addActionListener(closeAction);
SPSUtils.makeJDialogCancellable(d, closeAction);
okButton.addActionListener(new CommonCloseAction(d));
// Handle if the user presses Enter in the dialog - do OK action
d.getRootPane().setDefaultButton(okButton);
// Now build the GUI.
JPanel cp = new JPanel(new BorderLayout());
cp.setBorder(BorderFactory.createEmptyBorder(12,12,12,12));
cp.add(panel, BorderLayout.CENTER);
cp.add(ButtonBarFactory.buildCenteredBar(okButton),
BorderLayout.SOUTH);
cp.setBorder(Borders.DIALOG_BORDER);
//d.add(cp);
d.setContentPane(cp);
// XXX maybe pass yet another argument for this?
// d.setLocationRelativeTo(someFrame);
d.pack();
return d;
}
}