components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/SettingsPanel.java
author Dan Labrecque <dan.labrecque@oracle.com>
Thu, 24 May 2012 04:16:47 -0400
changeset 827 0944d8c0158b
permissions -rw-r--r--
7169052 Integrate Visual Panels into Userland

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 */

package com.oracle.solaris.vp.util.swing;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import com.oracle.solaris.vp.util.misc.*;
import com.oracle.solaris.vp.util.swing.layout.*;

/**
 * The {@code SettingsPanel} class provides a skeleton for many UI panels.  Its
 * structure follows:
 * <p/>
 * <pre>
 * +-+-----------------------------------------+-+
 * | +-----------------------------------------+ |
 * | | {@link #getTitlePane Title pane}                              | |
 * | +-----------------------------------------+ |
 * | +-----------------------------------------+ |
 * | | {@link #getHelpPane Help pane}                               | |
 * | +-----------------------------------------+ |
 * | +-----------------------------------------+ |
 * | | {@link #getContentPane Content pane}                            | |
 * | +-----------------------------------------+ |
 * | +-----------------------------------------+ |
 * | | {@link #getButtonBar Button bar}                              | |
 * | +-----------------------------------------+ |
 * +-+-----------------------------------------+-+
 * </pre>
 * <p/>
 * Each component is added as part of a {@link ColumnLayout}, so
 * ColumnConstraint attributes for each of the components can be changed as
 * needed.  For example:
 * <pre>
 *     // Remove the spacing between the help and content panes
 *     ColumnLayoutConstraint constraint =
 *	   getLayout().getConstraint(getContentPane());
 *     constraint.setGap(0);
 * </pre>
 */
@SuppressWarnings({"serial"})
public class SettingsPanel extends JPanel {
    //
    // Inner classes
    //

    /**
     * AbstractAction that determines its enabledness based on whether its
     * button is non-null and it and its descendents are all visible.  This is
     * necessary so that the "submit" and "cancel" keybindings can be propagated
     * up the Component hierarchy if they don't apply.
     */
    protected abstract static class ButtonAction extends AbstractAction {
	//
	// ActionListener methods
	//

	@Override
	public void actionPerformed(ActionEvent e) {
	    AbstractButton button = getFirstVisibleButton();
	    if (button != null) {
		button.doClick(0);
	    }
	}

	//
	// Action methods
	//

	@Override
	public boolean isEnabled() {
	    return super.isEnabled() && getFirstVisibleButton() != null;
	}

	//
	// ButtonAction methods
	//

	public abstract AbstractButton[] getButtons();

	//
	// Private methods
	//

	private AbstractButton getFirstVisibleButton() {
	    BUTTONS: for (AbstractButton button : getButtons()) {
		if (button != null) {
		    for (Component c = button; c != null; c = c.getParent()) {
			if (!c.isVisible()) {
			    continue BUTTONS;
			}
		    }
		    return button;
		}
	    }
	    return null;
	}
    }

    //
    // Instance data
    //

    private ChangeableAggregator aggregator;
    private AutoHidePanel titlePane;
    private JLabel titleLabel;
    private AutoHidePanel helpPane;
    private JTextArea helpField;
    private AutoHidePanel contentPane;
    private SettingsButtonBar buttonBar;
    private AbstractButton[] cancelButtons;
    private AbstractButton[] submitButtons;

    //
    // Constructors
    //

    public SettingsPanel() {
	int gap = GUIUtil.getGap();

	helpField = new FlowTextArea();
	AutoHideTextArea.autoHide(helpField);

	helpPane = new AutoHidePanel();
	helpPane.setOpaque(false);
	helpPane.setLayout(new BorderLayout());
	helpPane.add(helpField, BorderLayout.CENTER);

	titleLabel = new AutoHideLabel();
	decorateTitle(titleLabel);
	titlePane = new AutoHidePanel();
	titlePane.setOpaque(false);
	titlePane.setLayout(new BorderLayout());
	titlePane.add(titleLabel, BorderLayout.CENTER);

	contentPane = new AutoHidePanel();
	contentPane.setLayout(new BorderLayout());
	contentPane.setOpaque(false);

	buttonBar = new SettingsButtonBar();

	ChangeListener listener =
	    new ChangeListener() {
		@Override
		public void stateChanged(ChangeEvent e) {
		    buttonBar.setChanged(aggregator.isChanged());
		}
	    };

	aggregator = new ChangeableAggregator(DebugUtil.toBaseName(this));
	aggregator.addChangeListener(listener);

	// Initialize
	listener.stateChanged(null);

	setOpaque(false);

	ColumnLayoutConstraint c = new ColumnLayoutConstraint(
	    HorizontalAnchor.FILL, gap);

	ColumnLayout layout = new ColumnLayout(VerticalAnchor.FILL);
	layout.setDefaultConstraint(c);
	setLayout(layout);

	add(titlePane, c);
	add(helpPane, c);
	add(contentPane, c.clone().setWeight(1));
	add(buttonBar, c);

	// Hitting enter anywhere in panel should submit it
	setSubmitButtons(buttonBar.getApplyButton(), buttonBar.getOkayButton(),
	    buttonBar.getForwardButton(), buttonBar.getCloseButton());
	KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
	String actName = "submit";
	getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
	    enter, actName);
	getActionMap().put(actName,
	    new ButtonAction() {
		@Override
		public AbstractButton[] getButtons() {
		    return getSubmitButtons();
		}
	    });

	// Hitting escape anywhere in panel should cancel it
	setCancelButtons(buttonBar.getCancelButton(),
	    buttonBar.getCloseButton());
	KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
	actName = "cancel";
	getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
	    escape, actName);
	getActionMap().put(actName,
	    new ButtonAction() {
		@Override
		public AbstractButton[] getButtons() {
		    return getCancelButtons();
		}
	    });
    }

    //
    // Container methods
    //

    @Override
    public ColumnLayout getLayout() {
	return (ColumnLayout)super.getLayout();
    }

    //
    // SettingsPanel methods
    //

    /**
     * Decorates the given label as a section title.
     * <p/>
     * This default implementation makes the font bold.
     */
    protected void decorateTitle(JLabel label) {
	label.setFont(label.getFont().deriveFont(Font.BOLD));
    }

    /**
     * Gets the button bar for this {@code SettingsPanel}.  The button bar is
     * initialized to be non-opaque.
     * <p/>
     * The button bar is invisible initially, but is made visible when an {@code
     * Action} is added to it.	See {@link SettingsButtonBar}.
     */
    public SettingsButtonBar getButtonBar() {
	return buttonBar;
    }

    /**
     * Gets the buttons to be examined whenever {@code Escape} is pressed
     * anywhere inside this {@code SettingsPanel}.  The first button that is
     * part of a fully-visible component hierarchy will be clicked.
     * <p/>
     * This value is initialized with the {@link #getButtonBar button bar}'s
     * cancel and close buttons.
     *
     * @return	    the buttons to be examined
     */
    public AbstractButton[] getCancelButtons() {
	return cancelButtons;
    }

    /**
     * Gets the content pane for this {@code SettingsPanel}.  The content pane
     * is initialized to be non-opaque with a {@code BorderLayout}.
     */
    public AutoHidePanel getContentPane() {
	return contentPane;
    }

    public ChangeableAggregator getChangeableAggregator() {
	return aggregator;
    }

    /**
     * Gets the {@code JTextField} initially contained in the {@link
     * #getHelpPane help pane}.  Setting the text of this {@code JTextField}
     * automatically sets its visibility (visible if non-{@code null}, invisible
     * otherwise).
     */
    public JTextArea getHelpField() {
	return helpField;
    }

    /**
     * Gets the help pane for this {@code SettingsPanel}.  The help pane is
     * initialized to be non-opaque with a {@code BorderLayout}, containing a
     * {@link #getHelpField JTextArea}.
     * <p/>
     * The help pane is invisible initially since it may not be needed.  Setting
     * the text in the {@link #getHelpField help field} will make it visible
     * automatically.
     */
    public AutoHidePanel getHelpPane() {
	return helpPane;
    }

    /**
     * Gets the buttons to be examined whenever {@code Escape} is pressed
     * anywhere inside this {@code SettingsPanel}.  The first button that is
     * part of a fully-visible component hierarchy will be clicked.
     * <p/>
     * This value is initialized with the {@link #getButtonBar button bar}'s
     * apply, okay, forward, and close buttons.
     *
     * @return	    the buttons to be examined
     */
    public AbstractButton[] getSubmitButtons() {
	return submitButtons;
    }

    /**
     * Gets the {@code JLabel} initially contained in the {@link #getTitlePane
     * title pane}.  Setting the icon or text of this {@code JLabel}
     * automatically sets its visibility (visible if either is non-{@code null},
     * invisible otherwise).
     */
    public JLabel getTitleLabel() {
	return titleLabel;
    }

    /**
     * Gets the title pane for this {@code SettingsPanel}.  The title pane is
     * initialized to be non-opaque with a {@code BorderLayout}, containing a
     * {@link #getTitleLabel JLabel}.
     * <p/>
     * The title pane is invisible initially since it may not be needed.
     * Setting the text/icon in the {@link #getTitleLabel title label} will make
     * it visible automatically.
     */
    public AutoHidePanel getTitlePane() {
	return titlePane;
    }

    /**
     * Sets the buttons to be examined whenever {@code Escape} is pressed
     * anywhere inside this {@code SettingsPanel}.  The first button that is
     * part of a fully-visible component hierarchy will be clicked.
     *
     * @param	    cancelButtons
     *		    the buttons to be examined
     */
    public void setCancelButtons(AbstractButton... cancelButtons) {
	this.cancelButtons = cancelButtons;
    }

    public void setContent(Component content) {
	setContent(content, true, false);
    }

    public void setContent(Component content, boolean center, boolean scroll) {
	contentPane.removeAll();

	if (content != null) {
	    if (center) {
		ColumnLayout layout = new ColumnLayout(VerticalAnchor.CENTER);
		JPanel centered = new JPanel(layout);
		centered.setOpaque(false);

		ColumnLayoutConstraint c =
		    new ColumnLayoutConstraint(HorizontalAnchor.CENTER);

		centered.add(content, c);
		content = centered;
	    }

	    if (scroll) {
		ExtScrollPane scrollPane = new ExtScrollPane(content);
		scrollPane.removeBorder();
		scrollPane.setOpaque(false);
		content = scrollPane;
	    }

	    contentPane.setLayout(new BorderLayout());
	    contentPane.add(content, BorderLayout.CENTER);
	}
    }

    /**
     * Sets the buttons to be examined whenever {@code Escape} is pressed
     * anywhere inside this {@code SettingsPanel}.  The first button that is
     * part of a fully-visible component hierarchy will be clicked.
     *
     * @param	    submitButtons
     *		    the buttons to be examined
     */
    public void setSubmitButtons(AbstractButton... submitButtons) {
	this.submitButtons = submitButtons;
    }
}