Home | History | Annotate | Download | only in view
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 
     22 /*
     23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 package org.opensolaris.os.vp.panel.swing.view;
     28 
     29 import java.awt.*;
     30 import java.awt.event.*;
     31 import javax.swing.*;
     32 import javax.swing.event.*;
     33 import org.opensolaris.os.vp.util.misc.*;
     34 import org.opensolaris.os.vp.util.swing.*;
     35 import org.opensolaris.os.vp.util.swing.layout.*;
     36 
     37 /**
     38  * The {@code SettingsPanel} class provides a skeleton for many UI panels.  Its
     39  * structure follows:
     40  * <p/>
     41  * <pre>
     42  * +-+-----------------------------------------+-+
     43  * | +-----------------------------------------+ |
     44  * | | {@link #getTitlePane Title pane}				     | |
     45  * | +-----------------------------------------+ |
     46  * | +-----------------------------------------+ |
     47  * | | {@link #getHelpPane Help pane}				    | |
     48  * | +-----------------------------------------+ |
     49  * | +-----------------------------------------+ |
     50  * | | {@link #getContentPane Content pane}			       | |
     51  * | +-----------------------------------------+ |
     52  * | +-----------------------------------------+ |
     53  * | | {@link #getButtonBar Button bar}				     | |
     54  * | +-----------------------------------------+ |
     55  * +-+-----------------------------------------+-+
     56  * </pre>
     57  * <p/>
     58  * Each component is added as part of a {@link ColumnLayout}, so
     59  * ColumnConstraint attributes for each of the components can be changed as
     60  * needed.  For example:
     61  * <pre>
     62  *     // Remove the spacing between the help and content panes
     63  *     ColumnLayoutConstraint constraint =
     64  *	   getLayout().getConstraint(getContentPane());
     65  *     constraint.setGap(0);
     66  * </pre>
     67  */
     68 @SuppressWarnings({"serial"})
     69 public class SettingsPanel extends JPanel {
     70     //
     71     // Inner classes
     72     //
     73 
     74     /**
     75      * AbstractAction that determines its enabledness based on whether its
     76      * button is non-null and it and its descendents are all visible.  This is
     77      * necessary so that the "submit" and "cancel" keybindings can be propagated
     78      * up the Component hierarchy if they don't apply.
     79      */
     80     protected abstract static class ButtonAction extends AbstractAction {
     81 	//
     82 	// ActionListener methods
     83 	//
     84 
     85 	@Override
     86 	public void actionPerformed(ActionEvent e) {
     87 	    AbstractButton button = getFirstVisibleButton();
     88 	    if (button != null) {
     89 		button.doClick(0);
     90 	    }
     91 	}
     92 
     93 	//
     94 	// Action methods
     95 	//
     96 
     97 	@Override
     98 	public boolean isEnabled() {
     99 	    return super.isEnabled() && getFirstVisibleButton() != null;
    100 	}
    101 
    102 	//
    103 	// ButtonAction methods
    104 	//
    105 
    106 	public abstract AbstractButton[] getButtons();
    107 
    108 	//
    109 	// Private methods
    110 	//
    111 
    112 	private AbstractButton getFirstVisibleButton() {
    113 	    BUTTONS: for (AbstractButton button : getButtons()) {
    114 		if (button != null) {
    115 		    for (Component c = button; c != null; c = c.getParent()) {
    116 			if (!c.isVisible()) {
    117 			    continue BUTTONS;
    118 			}
    119 		    }
    120 		    return button;
    121 		}
    122 	    }
    123 	    return null;
    124 	}
    125     }
    126 
    127     private static class SettingsLabel extends JLabel {
    128 	//
    129 	// JLabel methods
    130 	//
    131 
    132 	@Override
    133 	public void setIcon(Icon icon) {
    134 	    super.setIcon(icon);
    135 	    setVisible();
    136 	}
    137 
    138 	@Override
    139 	public void setText(String text) {
    140 	    super.setText(text);
    141 	    setVisible();
    142 	}
    143 
    144 	//
    145 	// SettingsLabel methods
    146 	//
    147 
    148 	public void setVisible() {
    149 	    Container parent = getParent();
    150 	    if (parent != null) {
    151 		parent.setVisible(getText() != null || getIcon() != null);
    152 	    }
    153 	}
    154     }
    155 
    156     //
    157     // Instance data
    158     //
    159 
    160     private ChangeableAggregator aggregator;
    161     private JPanel titlePane;
    162     private JLabel titleLabel;
    163     private JPanel helpPane;
    164     private JLabel helpLabel;
    165     private JPanel contentPane;
    166     private SettingsButtonBar buttonBar;
    167     private AbstractButton[] cancelButtons;
    168     private AbstractButton[] submitButtons;
    169 
    170     //
    171     // Constructors
    172     //
    173 
    174     public SettingsPanel() {
    175 	int gap = GUIUtil.getGap();
    176 
    177 	helpLabel = new SettingsLabel();
    178 	helpPane = new JPanel();
    179 	helpPane.setOpaque(false);
    180 	helpPane.setLayout(new BorderLayout());
    181 	helpPane.setVisible(false);
    182 	helpPane.add(helpLabel, BorderLayout.CENTER);
    183 
    184 	titleLabel = new SettingsLabel();
    185 	decorateTitle(titleLabel);
    186 	titlePane = new JPanel();
    187 	titlePane.setOpaque(false);
    188 	titlePane.setLayout(new BorderLayout());
    189 	titlePane.setVisible(false);
    190 	titlePane.add(titleLabel, BorderLayout.CENTER);
    191 
    192 	contentPane = new JPanel();
    193 	contentPane.setOpaque(false);
    194 
    195 	buttonBar = new SettingsButtonBar();
    196 
    197 	ChangeListener listener =
    198 	    new ChangeListener() {
    199 		@Override
    200 		public void stateChanged(ChangeEvent e) {
    201 		    buttonBar.setChanged(aggregator.isChanged());
    202 		}
    203 	    };
    204 
    205 	aggregator = new ChangeableAggregator(DebugUtil.toBaseName(this));
    206 	aggregator.addChangeListener(listener);
    207 
    208 	// Initialize
    209 	listener.stateChanged(null);
    210 
    211 	setOpaque(true);
    212 	setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap));
    213 
    214 	ColumnLayoutConstraint c = new ColumnLayoutConstraint(
    215 	    HorizontalAnchor.FILL, gap);
    216 
    217 	ColumnLayout layout = new ColumnLayout(VerticalAnchor.FILL);
    218 	layout.setDefaultConstraint(c);
    219 	setLayout(layout);
    220 
    221 	add(titlePane, c);
    222 	add(helpPane, c);
    223 	add(contentPane, c.clone().setWeight(1));
    224 	add(buttonBar, c);
    225 
    226 	// Hitting enter anywhere in panel should submit it
    227 	setSubmitButtons(buttonBar.getApplyButton(), buttonBar.getOkayButton(),
    228 	    buttonBar.getForwardButton(), buttonBar.getCloseButton());
    229 	KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
    230 	String actName = "submit";
    231 	getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
    232 	    enter, actName);
    233 	getActionMap().put(actName,
    234 	    new ButtonAction() {
    235 		@Override
    236 		public AbstractButton[] getButtons() {
    237 		    return getSubmitButtons();
    238 		}
    239 	    });
    240 
    241 	// Hitting escape anywhere in panel should cancel it
    242 	setCancelButtons(buttonBar.getCancelButton(),
    243 	    buttonBar.getCloseButton());
    244 	KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
    245 	actName = "cancel";
    246 	getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
    247 	    escape, actName);
    248 	getActionMap().put(actName,
    249 	    new ButtonAction() {
    250 		@Override
    251 		public AbstractButton[] getButtons() {
    252 		    return getCancelButtons();
    253 		}
    254 	    });
    255     }
    256 
    257     //
    258     // Container methods
    259     //
    260 
    261     @Override
    262     public ColumnLayout getLayout() {
    263 	return (ColumnLayout)super.getLayout();
    264     }
    265 
    266     //
    267     // SettingsPanel methods
    268     //
    269 
    270     /**
    271      * Decorates the given label as a section title.
    272      * <p/>
    273      * This default implementation makes the font bold.
    274      */
    275     protected void decorateTitle(JLabel label) {
    276 	label.setFont(label.getFont().deriveFont(Font.BOLD));
    277     }
    278 
    279     /**
    280      * Gets the button bar for this {@code SettingsPanel}.  The button bar is
    281      * initialized to be non-opaque.
    282      * <p/>
    283      * The button bar is invisible initially, but is made visible when an {@code
    284      * Action} is added to it.	See {@link SettingsButtonBar}.
    285      */
    286     public SettingsButtonBar getButtonBar() {
    287 	return buttonBar;
    288     }
    289 
    290     /**
    291      * Gets the buttons to be examined whenever {@code Escape} is pressed
    292      * anywhere inside this {@code SettingsPanel}.  The first button that is
    293      * part of a fully-visible component hierarchy will be clicked.
    294      * <p/>
    295      * This value is initialized with the {@link #getButtonBar button bar}'s
    296      * cancel and close buttons.
    297      *
    298      * @return	    the buttons to be examined
    299      */
    300     public AbstractButton[] getCancelButtons() {
    301 	return cancelButtons;
    302     }
    303 
    304     /**
    305      * Gets the content pane for this {@code SettingsPanel}.  The content pane
    306      * is initialized to be non-opaque with a {@code BorderLayout}.
    307      */
    308     public JPanel getContentPane() {
    309 	return contentPane;
    310     }
    311 
    312     public ChangeableAggregator getChangeableAggregator() {
    313 	return aggregator;
    314     }
    315 
    316     /**
    317      * Gets the label contained in the {@link #getHelpPane help pane}. Setting
    318      * the icon or text of this label automatically sets the visibility of the
    319      * help pane (visible if either is non-{@code null}, invisible otherwise).
    320      */
    321     public JLabel getHelpLabel() {
    322 	return helpLabel;
    323     }
    324 
    325     /**
    326      * Gets the help pane for this {@code SettingsPanel}.  The help pane is
    327      * initialized to be non-opaque with a {@code BorderLayout}, containing a
    328      * {@link #getHelpLabel label}.
    329      * <p/>
    330      * The help pane is invisible initially since it may not be needed.  Setting
    331      * the text/icon in the {@link #getHelpLabel help label} will make it
    332      * visible automatically.
    333      */
    334     public JPanel getHelpPane() {
    335 	return helpPane;
    336     }
    337 
    338     /**
    339      * Gets the buttons to be examined whenever {@code Escape} is pressed
    340      * anywhere inside this {@code SettingsPanel}.  The first button that is
    341      * part of a fully-visible component hierarchy will be clicked.
    342      * <p/>
    343      * This value is initialized with the {@link #getButtonBar button bar}'s
    344      * apply, okay, forward, and close buttons.
    345      *
    346      * @return	    the buttons to be examined
    347      */
    348     public AbstractButton[] getSubmitButtons() {
    349 	return submitButtons;
    350     }
    351 
    352     /**
    353      * Gets the label contained in the {@link #getTitlePane title pane}. Setting
    354      * the icon or text of this label automatically sets the visibility of the
    355      * title pane (visible if either is non-{@code null}, invisible otherwise).
    356      */
    357     public JLabel getTitleLabel() {
    358 	return titleLabel;
    359     }
    360 
    361     /**
    362      * Gets the title pane for this {@code SettingsPanel}.  The title pane is
    363      * initialized to be non-opaque with a {@code BorderLayout}, containing a
    364      * {@link #getTitleLabel label}.
    365      * <p/>
    366      * The title pane is invisible initially since it may not be needed.
    367      * Setting the text/icon in the {@link #getTitleLabel title label} will make
    368      * it visible automatically.
    369      */
    370     public JPanel getTitlePane() {
    371 	return titlePane;
    372     }
    373 
    374     /**
    375      * Sets the buttons to be examined whenever {@code Escape} is pressed
    376      * anywhere inside this {@code SettingsPanel}.  The first button that is
    377      * part of a fully-visible component hierarchy will be clicked.
    378      *
    379      * @param	    cancelButtons
    380      *		    the buttons to be examined
    381      */
    382     public void setCancelButtons(AbstractButton... cancelButtons) {
    383 	this.cancelButtons = cancelButtons;
    384     }
    385 
    386     public void setContent(Component content) {
    387 	setContent(content, true, true);
    388     }
    389 
    390     public void setContent(Component content, boolean center, boolean scroll) {
    391 	contentPane.removeAll();
    392 
    393 	if (content != null) {
    394 	    if (center) {
    395 		ColumnLayout layout = new ColumnLayout(VerticalAnchor.CENTER);
    396 		JPanel centered = new JPanel(layout);
    397 		centered.setOpaque(false);
    398 
    399 		ColumnLayoutConstraint c =
    400 		    new ColumnLayoutConstraint(HorizontalAnchor.CENTER);
    401 
    402 		centered.add(content, c);
    403 		content = centered;
    404 	    }
    405 
    406 	    if (scroll) {
    407 		CommonScrollPane scrollPane = new CommonScrollPane(content);
    408 		scrollPane.removeBorder();
    409 		scrollPane.setOpaque(false);
    410 		content = scrollPane;
    411 	    }
    412 
    413 	    contentPane.setLayout(new BorderLayout());
    414 	    contentPane.add(content, BorderLayout.CENTER);
    415 	}
    416     }
    417 
    418     /**
    419      * Sets the buttons to be examined whenever {@code Escape} is pressed
    420      * anywhere inside this {@code SettingsPanel}.  The first button that is
    421      * part of a fully-visible component hierarchy will be clicked.
    422      *
    423      * @param	    submitButtons
    424      *		    the buttons to be examined
    425      */
    426     public void setSubmitButtons(AbstractButton... submitButtons) {
    427 	this.submitButtons = submitButtons;
    428     }
    429 }
    430