1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.javatest.tool; 28 29 import java.awt.BorderLayout; 30 import java.awt.CardLayout; 31 import java.awt.Component; 32 import java.awt.Dimension; 33 import java.awt.GridBagConstraints; 34 import java.awt.GridBagLayout; 35 import java.awt.event.ActionEvent; 36 import java.awt.event.ActionListener; 37 import java.awt.event.WindowEvent; 38 import java.awt.event.WindowListener; 39 import java.util.Collection; 40 import java.util.Map; 41 import java.util.Properties; 42 import java.util.Set; 43 44 import javax.swing.BorderFactory; 45 import javax.swing.JButton; 46 import javax.swing.JComponent; 47 import javax.swing.JDialog; 48 import javax.swing.JFrame; 49 import javax.swing.JLabel; 50 import javax.swing.JPanel; 51 import javax.swing.JTree; 52 import javax.swing.event.TreeModelListener; 53 import javax.swing.event.TreeSelectionEvent; 54 import javax.swing.event.TreeSelectionListener; 55 import javax.swing.tree.DefaultTreeCellRenderer; 56 import javax.swing.tree.TreeModel; 57 import javax.swing.tree.TreeSelectionModel; 58 import javax.swing.tree.TreePath; 59 import com.sun.javatest.tool.jthelp.HelpBroker; 60 import com.sun.javatest.tool.jthelp.ContextHelpManager; 61 62 import java.awt.event.KeyEvent; 63 import javax.swing.AbstractAction; 64 import javax.swing.ActionMap; 65 import javax.swing.InputMap; 66 import javax.swing.KeyStroke; 67 68 /** 69 * An abstract class for a GUI panel that can be displayed to 70 * allow the user to edit some of the user preferences. 71 * A pane may have child panes, containing additional groups 72 * of preferences relevant to but not contained on this pane. 73 */ 74 public abstract class PreferencesPane extends JPanel { 75 /** 76 * Show a dialog to allow the user to edit the set of preferences. 77 * A collection of editing panes must be provided that each provide 78 * the GUI to edit a subset of the preferences. 79 * @param f the parent frame for the dialog 80 * @param panes the editing panes to be displayed in the dialog 81 * @param helpBroker a help broker to be used to provide context sensitive 82 * help for the dialog 83 */ 84 public static void showDialog(JFrame f, Preferences preferences, 85 PreferencesPane[] panes, HelpBroker helpBroker) { 86 //System.err.println("Preferences.showDialog"); 87 PrefsDialog d = new PrefsDialog(f, preferences, panes, helpBroker); 88 d.setVisible(true); 89 //System.err.println("Preferences.showDialog done"); 90 } 91 92 /** 93 * Set the help ID that gives the context sensitive help for 94 * this panel. The help will be displayed in the help broker 95 * specified when the dialog is displayed. 96 * @param helpID the ID for the context sensitive help for this panel 97 * @see PreferencesPane#showDialog 98 */ 99 protected void setHelp(String helpID) { 100 ContextHelpManager.setHelpIDString(this, helpID); 101 } 102 103 /** 104 * Get a text string which identifies the group of user preferences 105 * that can be edited on the panel. 106 * @return a text string to identify the preferences on this panel 107 */ 108 public abstract String getText(); 109 110 /** 111 * Load the values of the user preferences relevant to this panel 112 * from a given map object. 113 * @param m the map from which to load the user preferences into the 114 * GUI components 115 */ 116 public void load(Map<?, ?> m) { 117 PreferencesPane[] p = getChildPanes(); 118 if (p != null) { 119 for (int i = 0; i < p.length; i++) 120 p[i].load(m); 121 } 122 } 123 124 /** 125 * Save the values of the user preferences relevant to this panel 126 * into a given map object. 127 * @param m the map to which to save the user preferences from the 128 * GUI components 129 */ 130 public void save(Map<String, String> m) { 131 PreferencesPane[] p = getChildPanes(); 132 if (p != null) { 133 for (int i = 0; i < p.length; i++) 134 p[i].save(m); 135 } 136 } 137 138 /** 139 * Analyze the current values entered by the user and determine if 140 * they are valid. If they are, the return value should be null. 141 * By default the return value is null. 142 * @return null if all the values are valid. Otherwise, an 143 * <b>internationalized</b> string. 144 * @see com.sun.javatest.util.I18NResourceBundle 145 */ 146 public String validateValues() { 147 return null; 148 } 149 150 /** 151 * Get the set of child panes, if any, containing additional groups 152 * of preferences relevant to but not contained on this pane. 153 * @return an array of child panes, or null if none 154 */ 155 public PreferencesPane[] getChildPanes() { 156 return null; 157 } 158 159 private static class PrefsDialog 160 extends JDialog // can't be ToolDialog because might change desktop style 161 implements ActionListener, TreeModel, TreeSelectionListener, WindowListener 162 { 163 PrefsDialog(JFrame f, Preferences preferences, 164 PreferencesPane[] panes, HelpBroker helpBroker) { 165 // Don't use the argument frame 'f' as the parent of the dialog 166 // in case that frame is disposed while "apply"ing preferences. 167 // Instead, we use null (private hidden frame) as the parent, 168 // and merely center the dialog over the argument frame. 169 super((JFrame)null, /*modal:*/true); 170 owner = f; 171 this.preferences = preferences; 172 this.props = preferences.getProperties(); 173 this.panes = panes; 174 this.helpBroker = helpBroker; 175 176 uif = new UIFactory(getClass(), helpBroker); 177 initGUI(); 178 179 //System.err.println("Prefs.dialog addWindowListener " + owner.getName()); 180 owner.addWindowListener(this); 181 } 182 183 public void setVisible(boolean b) { 184 if (b) { 185 for (int i = 0; i < panes.length; i++) 186 panes[i].load(props); 187 } 188 super.setVisible(b); 189 190 if (!b) { 191 //System.err.println("Prefs.dialog removeWindowListener " + owner.getName()); 192 owner.removeWindowListener(this); 193 } 194 } 195 196 private void initGUI() { 197 //prefsTreeLeafIcon = uif.createIcon("prefs.leaf"); 198 setName("prefs"); 199 setTitle(uif.getI18NString("prefs.title")); 200 KeyStroke keystroke = KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0, false); 201 getRootPane().registerKeyboardAction(new ActionListener() { 202 @Override 203 public void actionPerformed(ActionEvent e) { 204 if (helpBroker != null) { 205 helpBroker.displayCurrentID(ContextHelpManager.getHelpIDString(getRootPane())); 206 } 207 } 208 }, keystroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 209 ContextHelpManager.setHelpIDString(getRootPane(), "ui.prefs.dialog.csh"); 210 Desktop.addHelpDebugListener(this); 211 uif.setAccessibleDescription(this, "prefs"); 212 213 main = uif.createPanel("prefs.main", new BorderLayout(), false); 214 215 initTree(); // add into main, WEST 216 initPanes(); // add into main, CENTER 217 initButtons(); // add into main, SOUTH 218 219 setContentPane(main); 220 Object[] path = new Object[2]; 221 path[0] = tree.getModel().getRoot(); 222 path[1] = getChildren(this)[0]; // assumes we always have at least one 223 tree.setSelectionPath(new TreePath(path)); 224 225 pack(); 226 setLocationRelativeTo(owner); 227 228 getRootPane().setDefaultButton(okBtn); 229 } 230 231 private void initTree() { 232 tree = new JTree(this); 233 tree.setName("prefs.tree"); 234 tree.addTreeSelectionListener(this); 235 tree.setEditable(false); 236 tree.setShowsRootHandles(true); 237 tree.setRootVisible(false); 238 uif.setAccessibleInfo(tree, "prefs.tree"); 239 240 int dpi = uif.getDotsPerInch(); 241 tree.setPreferredSize(new Dimension(2 * dpi, dpi)); 242 tree.setVisibleRowCount(10); 243 244 tree.setCellRenderer(new DefaultTreeCellRenderer() { 245 public Component getTreeCellRendererComponent(JTree tree, Object value, 246 boolean selected, 247 boolean expanded, 248 boolean leaf, int row, 249 boolean hasFocus) { 250 if (value instanceof PreferencesPane) { 251 return super.getTreeCellRendererComponent(tree, ((PreferencesPane)value).getText(), 252 selected, expanded, leaf, row, hasFocus); 253 } else { 254 return super.getTreeCellRendererComponent(tree, value, 255 selected, expanded, leaf, row, hasFocus); 256 } 257 } 258 /* 259 public Icon getLeafIcon() { 260 // consider use of blank icon? 261 return prefsTreeLeafIcon; 262 } 263 */ 264 }); 265 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 266 tree.expandRow(2); // expand two levels 267 268 // construct the tree side 269 JComponent treeStuff = uif.createScrollPane(tree); 270 treeStuff.setBorder(BorderFactory.createEtchedBorder()); 271 272 main.add(treeStuff, BorderLayout.WEST); 273 } 274 275 private void initButtons() { 276 okBtn = uif.createButton("prefs.ok", this); 277 cancelBtn = uif.createCancelButton("prefs.cancel", this); 278 helpBtn = uif.createHelpButton("prefs.help", "ui.prefs.dialog.csh"); 279 280 JButton[] btns = { okBtn, cancelBtn, helpBtn }; 281 282 // set all the buttons to the same preferred size, per JL&F 283 Dimension maxBtnDims = new Dimension(); 284 for (int i = 0; i < btns.length; i++) { 285 Dimension d = btns[i].getPreferredSize(); 286 maxBtnDims.width = Math.max(maxBtnDims.width, d.width); 287 maxBtnDims.height = Math.max(maxBtnDims.height, d.height); 288 } 289 290 for (int i = 0; i < btns.length; i++) 291 btns[i].setPreferredSize(maxBtnDims); 292 293 JPanel p = uif.createPanel("prefs.btns", false); 294 p.setLayout(new GridBagLayout()); 295 GridBagConstraints c = new GridBagConstraints(); 296 c.anchor = GridBagConstraints.EAST; 297 c.insets.top = 5; 298 c.insets.bottom = 11; // value from JL&F Guidelines 299 c.insets.right = 11; // value from JL&F Guidelines 300 c.weightx = 1; // first button absorbs space to the left 301 302 for (int i = 0; i < btns.length; i++) { 303 p.add(btns[i], c); 304 c.weightx = 0; 305 } 306 307 main.add(p, BorderLayout.SOUTH); 308 309 InputMap inputMap = p.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 310 ActionMap actionMap = p.getActionMap(); 311 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), UIFactory.CANCEL); 312 actionMap.put(UIFactory.CANCEL, new AbstractAction() { 313 public void actionPerformed(ActionEvent e) { 314 cancelBtn.doClick(250); 315 } 316 }); 317 } 318 319 private void initPanes() { 320 deck = uif.createPanel("prefs.deck", new CardLayout(), false); 321 addAllPanes(deck, panes); 322 323 int dpi = uif.getDotsPerInch(); 324 Dimension maxPrefSize = new Dimension(3 * dpi, 2 * dpi); 325 for (int i = 0; i < deck.getComponentCount(); i++) { 326 Dimension d = deck.getComponent(i).getPreferredSize(); 327 maxPrefSize.width = Math.max(maxPrefSize.width, d.width); 328 maxPrefSize.height = Math.max(maxPrefSize.height, d.height); 329 } 330 deck.setPreferredSize(maxPrefSize); 331 332 main.add(deck, BorderLayout.CENTER); 333 } 334 335 private void addAllPanes(JPanel deck, PreferencesPane[] panes) { 336 for (int i = 0; i < panes.length; i++) { 337 PreferencesPane pane = panes[i]; 338 339 JPanel p = uif.createPanel("prefs.card" + cardNum++, false); 340 p.setLayout(new BorderLayout()); 341 JLabel head = new JLabel(pane.getText()); 342 head.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 343 p.add(head, BorderLayout.NORTH); 344 345 pane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 346 p.add(uif.createScrollPane(pane), BorderLayout.CENTER); 347 deck.add(p, pane.getText()); 348 349 if (pane.getChildPanes() != null) 350 addAllPanes(deck, pane.getChildPanes()); 351 } 352 } 353 354 private int cardNum; 355 356 // ---------- ActionListener ------------------------------- 357 358 public void actionPerformed(ActionEvent e) { 359 Object src = e.getSource(); 360 if (src == okBtn) { 361 boolean b = okToSave(); 362 363 if (!b) 364 return; 365 366 setPreferences(panes); 367 preferences.save(); 368 setVisible(false); 369 } 370 else if (src == cancelBtn) { 371 for (int i = 0; i < panes.length; i++) 372 panes[i].load(props); 373 setVisible(false); 374 } 375 } 376 377 private boolean okToSave() { 378 String reason = null; 379 for (int i = 0; i < panes.length; i++) { 380 reason = panes[i].validateValues(); 381 if (reason != null) { 382 tree.setSelectionPath(new TreePath(new Object[] {this, panes[i]})); 383 break; 384 } 385 386 PreferencesPane[] p = panes[i].getChildPanes(); 387 if (p != null) { 388 for (int j = 0; j < p.length; j++) { 389 reason = p[j].validateValues(); 390 if (reason != null) { 391 tree.setSelectionPath(new TreePath( 392 new Object[] {this, panes[i], p[j]})); 393 break; 394 } 395 } // for j 396 397 if (reason != null) 398 break; 399 } 400 } // for i 401 402 if (reason != null) { 403 // show error dialog 404 uif.showLiteralError(uif.getI18NString("prefs.badPref.title"), 405 reason); 406 return false; 407 } else { 408 return true; 409 } 410 } 411 412 // ---------- TreeSelectionListener --------------------------------- 413 414 public void valueChanged(TreeSelectionEvent e) { 415 TreePath path = e.getNewLeadSelectionPath(); 416 if (path != null) { 417 PreferencesPane pane = (PreferencesPane) (path.getLastPathComponent()); 418 ((CardLayout)(deck.getLayout())).show(deck, pane.getText()); 419 } 420 } 421 422 // --------- TreeModel -------------------------------------- 423 424 public Object getChild(Object parent, int index) { 425 return getChildren(parent)[index]; 426 } 427 428 public int getChildCount(Object parent) { 429 return getChildren(parent).length; 430 } 431 432 public int getIndexOfChild(Object parent, Object child) { 433 PreferencesPane[] children = getChildren(parent); 434 for (int i = 0; i < children.length; i++) { 435 if (children[i] == child) 436 return i; 437 } 438 return -1; 439 } 440 441 public Object getRoot() { 442 return this; 443 } 444 445 public boolean isLeaf(Object node) { 446 PreferencesPane[] children = getChildren(node); 447 return (children == null || children.length == 0); 448 } 449 450 private void setPreferences(PreferencesPane[] panes) { 451 Map m = new Map() { 452 453 public void clear() { 454 throw new UnsupportedOperationException(); 455 } 456 457 public boolean containsKey(Object key) { 458 return props.containsKey(key); 459 } 460 461 public boolean containsValue(Object value) { 462 return props.containsValue(value); 463 } 464 465 public Set entrySet() { 466 return props.entrySet(); 467 } 468 469 public boolean equals(Object o) { 470 return props.equals(o); 471 } 472 473 public Object get(Object key) { 474 return props.get(key); 475 } 476 477 public int hashCode() { 478 return props.hashCode(); 479 } 480 481 public boolean isEmpty() { 482 return props.isEmpty(); 483 } 484 485 public Set keySet() { 486 return props.keySet(); 487 } 488 489 public Object put(Object key, Object value) { 490 Object oldValue = props.get(key); 491 if (oldValue == null || !oldValue.equals(value)) { 492 preferences.setPreference((String) key, (String) value); 493 } 494 return oldValue; 495 } 496 497 public void putAll(Map m) { 498 throw new UnsupportedOperationException(); 499 } 500 501 public Object remove(Object key) { 502 throw new UnsupportedOperationException(); 503 } 504 505 public int size() { 506 return props.size(); 507 } 508 509 public Collection values() { 510 return props.values(); 511 } 512 }; 513 514 for (int i = 0; i < panes.length; i++) { 515 panes[i].save(m); 516 } 517 } 518 519 // --------- TreeModelListener --------------------------------------- 520 521 public void addTreeModelListener(TreeModelListener l) { 522 } 523 524 public void removeTreeModelListener(TreeModelListener l) { 525 } 526 527 public void valueForPathChanged(TreePath path, Object newValue) { 528 } 529 530 // --------- WindowListener ----------------------------------------- 531 532 public void windowOpened(WindowEvent e) { 533 //System.err.println("Prefs.dialog " + e); 534 } 535 536 public void windowClosing(WindowEvent e) { 537 //System.err.println("Prefs.dialog " + e); 538 } 539 540 public void windowClosed(WindowEvent e) { 541 //System.err.println("Prefs.dialog " + e); 542 } 543 544 public void windowIconified(WindowEvent e) { 545 //System.err.println("Prefs.dialog " + e); 546 } 547 548 public void windowDeiconified(WindowEvent e) { 549 //System.err.println("Prefs.dialog " + e + " (" + e.getSource() + ")"); 550 if (e.getSource() == owner) 551 toFront(); 552 } 553 554 public void windowActivated(WindowEvent e) { 555 //System.err.println("Prefs.dialog " + e + " (" + e.getSource() + ")"); 556 if (e.getSource() == owner) 557 toFront(); 558 } 559 560 public void windowDeactivated(WindowEvent e) { 561 //System.err.println("Prefs.dialog " + e); 562 } 563 564 // ------------------------------------------------------------------ 565 566 private PreferencesPane[] getChildren(Object parent) { 567 return (parent == this ? panes : ((PreferencesPane)parent).getChildPanes()); 568 } 569 570 private JFrame owner; 571 private Preferences preferences; 572 private Properties props; 573 574 private HelpBroker helpBroker; 575 private PreferencesPane[] panes; 576 private UIFactory uif; 577 private JPanel main; 578 private JPanel deck; 579 private JButton okBtn; 580 private JButton cancelBtn; 581 private JButton helpBtn; 582 private JTree tree; 583 } 584 }