/* * Copyright (C) 2012 Brockmann Consult GmbH (info@brockmann-consult.de) * * 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 3 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, see http://www.gnu.org/licenses/ */ package org.esa.snap.ui; import org.esa.snap.core.util.SystemUtils; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import javax.swing.AbstractButton; import javax.swing.Box; import javax.swing.BoxLayout; 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 java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; import java.awt.Window; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The <code>AbstractDialog</code> is the base class for {@link ModalDialog} and {@link ModelessDialog}, * two helper classes used to quickly construct modal and modeless dialogs. The dialogs created with this * class have a unique border and font and a standard button row for the typical buttons like "OK", "Cancel" etc. * <p> * <p>Instances of a modal dialog are created with a parent component, a title, the actual dialog content component, and * a bit-combination of the standard buttons to be used. * <p> * <p>A limited way of input validation is provided by the <code>verifyUserInput</code> method which can be overridden * in order to return <code>false</code> if a user input is invalid. In this case the {@link #onOK()}, * {@link #onYes()} and {@link #onNo()} methods are NOT called. * * @author Norman Fomferra */ @NbBundle.Messages({ "CTL_AbstractDlg_NoHelpThemeAvailable=Sorry, no help theme available.", "CTL_AbstractDlg_NoHelpIDShowingStandard=Sorry, help for id '%s' not available.\nShowing standard help." }) public abstract class AbstractDialog { public static final int ID_OK = 0x0001; public static final int ID_YES = 0x0002; public static final int ID_NO = 0x0004; public static final int ID_APPLY = 0x0008; public static final int ID_CLOSE = 0x0010; public static final int ID_CANCEL = 0x0020; public static final int ID_RESET = 0x0040; public static final int ID_HELP = 0x0080; public static final int ID_OTHER = 0xAAAAAAAA; private final JDialog dialog; private final Window parent; private final int buttonMask; private int buttonId; private Component content; private boolean shown; private Map<Integer, AbstractButton> buttonMap; private JPanel buttonRow; // Java help support private String helpId; protected AbstractDialog(JDialog dialog, int buttonMask, Object[] otherButtons, String helpID) { this.parent = (Window) dialog.getParent(); this.dialog = dialog; this.buttonMask = buttonMask; this.buttonMap = new HashMap<>(5); setComponentName(dialog); setButtonID(0); initUI(otherButtons); setHelpID(helpID); } /** * Gets the underlying Swing dialog passed to the constructor. * * @return the underlying Swing dialog. */ public JDialog getJDialog() { return dialog; } /** * Gets the owner of the dialog. * * @return The owner of the dialog. */ public Window getParent() { return parent; } /** * @return The dialog's title. */ public String getTitle() { return dialog.getTitle(); } /** * @param title The dialog's title. */ public void setTitle(String title) { dialog.setTitle(title); } /** * Gets the button mask passed to the constructor. * * @return The button mask. */ public int getButtonMask() { return buttonMask; } /** * Gets the identifier for the most recently pressed button. * * @return The identifier for the most recently pressed button. */ public int getButtonID() { return buttonId; } /** * Sets the identifier for the most recently pressed button. * * @param buttonID The identifier for the most recently pressed button. */ protected void setButtonID(final int buttonID) { buttonId = buttonID; } /** * Gets the help identifier for the dialog. * * @return The help identifier. */ public String getHelpID() { return helpId; } /** * Sets the help identifier for the dialog. * * @param helpID The help identifier. */ public void setHelpID(String helpID) { helpId = helpID; updateHelpID(); } public JPanel getButtonPanel() { return buttonRow; } /** * Gets the dialog's content component. * * @return The dialog's content component. */ public Component getContent() { return content; } /** * Sets the dialog's content component. * * @param content The dialog's content component. */ public void setContent(Component content) { if (this.content != null) { dialog.getContentPane().remove(this.content); } this.content = content; dialog.getContentPane().add(this.content, BorderLayout.CENTER); dialog.validate(); updateHelpID(); } /** * Sets the dialog's content. * * @param content The dialog's content. */ public void setContent(Object content) { Component component; if (content instanceof Component) { component = (Component) content; } else { component = new JLabel(content.toString()); } setContent(component); } /** * Gets the button for the given identifier. * * @param buttonID The button identifier. * @return The button, or {@code null}. */ public AbstractButton getButton(int buttonID) { return buttonMap.get(buttonID); } /** * Shows the dialog. Overrides shall call {@code super.show()} at the end. * * @return the identifier of the last button pressed or zero if this is a modeless dialog. */ public int show() { setButtonID(0); if (!shown) { dialog.pack(); center(); } dialog.setVisible(true); shown = true; return getButtonID(); } /** * Hides the dialog. Overrides shall call {@code super.hide()} at the end. This method does nothing else than hiding the underlying Swing dialog. * * @see #getJDialog() */ public void hide() { dialog.setVisible(false); } /** * This method is called, when the user clicks the close button of the dialog's top window bar. * It can also be called directly. * Override to implement the dialog's default close behaviour. */ public abstract void close(); /** * Centers the dialog within its parent window. */ public void center() { UIUtils.centerComponent(dialog, parent); } /** * Shows an error dialog on top of this dialog. * * @param errorMessage The message. */ public void showErrorDialog(String errorMessage) { showErrorDialog(errorMessage, getJDialog().getTitle()); } public void showErrorDialog(String message, String title) { showMessageDialog(getJDialog(), message, title, JOptionPane.ERROR_MESSAGE); } public static void showErrorDialog(Component component, String message, String dialogTitle) { showMessageDialog(component, message, dialogTitle, JOptionPane.ERROR_MESSAGE); } /** * Shows an information dialog on top of this dialog. * * @param infoMessage The message. */ public void showInformationDialog(String infoMessage) { showInformationDialog(infoMessage, getJDialog().getTitle()); } public void showInformationDialog(String infoMessage, String title) { showMessageDialog(getJDialog(), infoMessage, title, JOptionPane.INFORMATION_MESSAGE); } public static void showInformationDialog(Component component, String message, String dialogTitle) { showMessageDialog(component, message, dialogTitle, JOptionPane.INFORMATION_MESSAGE); } /** * Shows a warning dialog on top of this dialog. * * @param warningMessage The message. */ public void showWarningDialog(String warningMessage) { showWarningDialog(warningMessage, getJDialog().getTitle()); } public void showWarningDialog(String warningMessage, String title) { showMessageDialog(getJDialog(), warningMessage, title, JOptionPane.WARNING_MESSAGE); } public static void showWarningDialog(Component component, String message, String dialogTitle) { showMessageDialog(component, message, dialogTitle, JOptionPane.WARNING_MESSAGE); } /** * Called if the "OK" button has been clicked. * The default implementation calls {@link #hide()}. * Clients should override this method to implement meaningful behaviour. */ protected void onOK() { hide(); } /** * Called if the "Yes" button has been clicked. * The default implementation calls {@link #hide()}. * Clients should override this method to implement meaningful behaviour. */ protected void onYes() { hide(); } /** * Called if the "No" button has been clicked. * The default implementation calls {@link #hide()}. * Clients should override this method to implement meaningful behaviour. */ protected void onNo() { hide(); } /** * Called if the "Cancel" button has been clicked. * The default implementation calls {@link #hide()}. * Clients should override this method to implement meaningful behaviour. */ protected void onCancel() { hide(); } /** * Called if the "Apply" button has been clicked. * The default implementation does nothing. * Clients should override this method to implement meaningful behaviour. */ protected void onApply() { } /** * Called if the "Close" button has been clicked. * The default implementation calls {@link #hide()}. * Clients should override this method to implement meaningful behaviour. */ protected void onClose() { hide(); } /** * Called if the reset button has been clicked. * The default implementation does nothing. * Clients should override this method to implement meaningful behaviour. */ protected void onReset() { } /** * Called if the help button has been clicked. * Clients should override this method to implement a different behaviour. */ protected void onHelp() { if (helpId == null || !new HelpCtx(helpId).display()) { showWarningDialog(String.format(Bundle.CTL_AbstractDlg_NoHelpIDShowingStandard(), helpId)); } } /** * Called if a non-standard button has been clicked. * The default implementation calls {@link #hide()}. * Clients should override this method to implement meaningful behaviour. */ protected void onOther() { hide(); } /** * Called in order to perform input validation. * * @return {@code true} if and only if the validation was successful. */ protected boolean verifyUserInput() { return true; } /** * Called by the constructor in order to initialise the user interface. * The default implementation does nothing. * * @param buttons The container into which new buttons shall be collected. */ protected void collectButtons(List<AbstractButton> buttons) { } private void initUI(Object[] otherItems) { buttonRow = new JPanel(); buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.X_AXIS)); int insetSize = UIDefaults.INSET_SIZE; JPanel contentPane = new JPanel(new BorderLayout(0, insetSize + insetSize / 2)); contentPane.setBorder(UIDefaults.DIALOG_BORDER); contentPane.add(buttonRow, BorderLayout.SOUTH); dialog.setResizable(true); dialog.setContentPane(contentPane); ArrayList<AbstractButton> buttons = new ArrayList<>(); collectButtons(buttons); if (otherItems != null) { for (Object otherItem : otherItems) { if (otherItem instanceof String) { String text = (String) otherItem; JButton otherButton = new JButton(text); otherButton.setName(getQualifiedPropertyName(text)); otherButton.addActionListener(e -> { setButtonID(ID_OTHER); if (verifyUserInput()) { onOther(); } }); buttons.add(otherButton); } else if (otherItem instanceof AbstractButton) { AbstractButton otherButton = (AbstractButton) otherItem; otherButton.addActionListener(e -> { setButtonID(ID_OTHER); if (verifyUserInput()) { onOther(); } }); buttons.add(otherButton); } } } if ((buttonMask & ID_OK) != 0) { JButton button = new JButton("OK"); button.setMnemonic('O'); button.setName(getQualifiedPropertyName("ok")); button.addActionListener(e -> { setButtonID(ID_OK); if (verifyUserInput()) { onOK(); } }); buttons.add(button); button.setDefaultCapable(true); getJDialog().getRootPane().setDefaultButton(button); registerButton(ID_OK, button); } if ((buttonMask & ID_YES) != 0) { JButton button = new JButton("Yes"); button.setMnemonic('Y'); button.setName(getQualifiedPropertyName("yes")); button.addActionListener(e -> { setButtonID(ID_YES); if (verifyUserInput()) { onYes(); } }); buttons.add(button); button.setDefaultCapable(true); getJDialog().getRootPane().setDefaultButton(button); registerButton(ID_YES, button); } if ((buttonMask & ID_NO) != 0) { JButton button = new JButton("No"); button.setMnemonic('N'); button.setName(getQualifiedPropertyName("no")); button.addActionListener(e -> { setButtonID(ID_NO); if (verifyUserInput()) { onNo(); } }); buttons.add(button); registerButton(ID_NO, button); } if ((buttonMask & ID_CANCEL) != 0) { JButton button = new JButton("Cancel"); button.setMnemonic('C'); button.setName(getQualifiedPropertyName("cancel")); button.addActionListener(e -> close()); buttons.add(button); button.setVerifyInputWhenFocusTarget(false); registerButton(ID_CANCEL, button); } if ((buttonMask & ID_APPLY) != 0) { JButton button = new JButton("Apply"); button.setMnemonic('A'); button.setName(getQualifiedPropertyName("apply")); button.addActionListener(e -> { setButtonID(ID_APPLY); if (verifyUserInput()) { onApply(); } }); buttons.add(button); button.setDefaultCapable(true); getJDialog().getRootPane().setDefaultButton(button); registerButton(ID_APPLY, button); } if ((buttonMask & ID_CLOSE) != 0) { JButton button = new JButton("Close"); button.setMnemonic('C'); button.setName(getQualifiedPropertyName("close")); button.addActionListener(e -> { setButtonID(ID_CLOSE); onClose(); }); button.setToolTipText("Close dialog window"); buttons.add(button); button.setVerifyInputWhenFocusTarget(false); registerButton(ID_CLOSE, button); } if ((buttonMask & ID_RESET) != 0) { JButton button = new JButton("Reset"); button.setName(getQualifiedPropertyName("reset")); button.setMnemonic('R'); button.addActionListener(e -> { final int buttonID = ID_RESET; setButtonID(buttonID); onReset(); }); buttons.add(button); registerButton(ID_RESET, button); } if ((buttonMask & ID_HELP) != 0) { JButton button = new JButton("Help"); button.setName(getQualifiedPropertyName("help")); button.setMnemonic('H'); button.addActionListener(e -> { setButtonID(ID_HELP); onHelp(); }); button.setToolTipText("Show help on this topic."); buttons.add(button); registerButton(ID_HELP, button); } buttonRow.add(Box.createHorizontalGlue()); for (int i = 0; i < buttons.size(); i++) { if (i != 0) { buttonRow.add(Box.createRigidArea(new Dimension(4, 0))); } buttonRow.add(buttons.get(i)); } dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { close(); } }); } protected String getQualifiedPropertyName(String name) { return getClass().getSimpleName() + "." + name; } protected void registerButton(int buttonID, AbstractButton button) { buttonMap.put(buttonID, button); } private void updateHelpID() { if (helpId == null) { return; } if (getJDialog().getContentPane() != null) { Container contentPane = getJDialog().getContentPane(); if (contentPane instanceof JComponent) { HelpCtx.setHelpIDString((JComponent) contentPane, helpId); } } } private static void showMessageDialog(Component dialog, String message, String dialogTitle, int messageType) { KeyEventDispatcher keyEventDispatcher = installKeyListener(message); JOptionPane.showMessageDialog(dialog, message, dialogTitle, messageType); uninstallKeyListener(keyEventDispatcher); } private static void uninstallKeyListener(KeyEventDispatcher keyEventDispatcher) { KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keyEventDispatcher); } private static KeyEventDispatcher installKeyListener(String message) { KeyEventDispatcher keyEventDispatcher = getKeyEventDispatcher(message); KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keyEventDispatcher); return keyEventDispatcher; } private static KeyEventDispatcher getKeyEventDispatcher(String message) { return e -> { if ((e.getKeyCode() == KeyEvent.VK_C) && e.isControlDown()) { SystemUtils.copyToClipboard(message); } e.consume(); return false; }; } private void setComponentName(JDialog dialog) { if (this.dialog.getName() == null && dialog.getTitle() != null) { dialog.setName(dialog.getTitle().toLowerCase().replaceAll(" ", "_")); } } }