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<String, String> 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<String, String> m = new Map<String, String>() {
 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<Map.Entry<String, String>> entrySet() {
 466                     return props.entrySet();
 467                 }
 468 
 469                 public boolean equals(Object o) {
 470                     return props.equals(o);
 471                 }
 472 
 473                 public String 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<String> keySet() {
 486                     return props.keySet();
 487                 }
 488 
 489                 public String put(String key, String value) {
 490                     String oldValue = props.get(key);
 491                     if (oldValue == null || !oldValue.equals(value)) {
 492                         preferences.setPreference(key, value);
 493                     }
 494                     return oldValue;
 495                 }
 496 
 497                 public void putAll(Map<? extends String, ? extends String> m) {
 498                     throw new UnsupportedOperationException();
 499                 }
 500 
 501                 public String remove(Object key) {
 502                     throw new UnsupportedOperationException();
 503                 }
 504 
 505                 public int size() {
 506                     return props.size();
 507                 }
 508 
 509                 public Collection<String> 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 Map<String, String> 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 }