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