1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2001, 2016, 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.interview.wizard;
  28 
  29 import java.awt.Color;
  30 import java.awt.Component;
  31 import java.awt.GridBagConstraints;
  32 import java.awt.GridBagLayout;
  33 import java.awt.Dimension;
  34 import java.awt.event.ActionEvent;
  35 import java.awt.event.ActionListener;
  36 import java.io.File;
  37 import java.util.EventObject;
  38 
  39 import javax.swing.DefaultCellEditor;
  40 import javax.swing.JButton;
  41 import javax.swing.JComboBox;
  42 import javax.swing.JFileChooser;
  43 import javax.swing.JPanel;
  44 import javax.swing.JTable;
  45 import javax.swing.JTextField;
  46 import javax.swing.JCheckBox;
  47 import javax.swing.JRadioButton;
  48 import javax.swing.ButtonGroup;
  49 import javax.swing.Box;
  50 import javax.swing.BoxLayout;
  51 import javax.swing.event.CellEditorListener;
  52 import javax.swing.table.DefaultTableCellRenderer;
  53 import javax.swing.table.TableCellEditor;
  54 import javax.swing.border.LineBorder;
  55 
  56 import com.sun.interview.ExtensionFileFilter;
  57 import com.sun.interview.FileFilter;
  58 import com.sun.interview.PropertiesQuestion;
  59 import com.sun.interview.PropertiesQuestion.BooleanConstraints;
  60 import com.sun.interview.PropertiesQuestion.FilenameConstraints;
  61 import com.sun.interview.PropertiesQuestion.FloatConstraints;
  62 import com.sun.interview.PropertiesQuestion.IntConstraints;
  63 import com.sun.interview.PropertiesQuestion.StringConstraints;
  64 import com.sun.interview.PropertiesQuestion.ValueConstraints;
  65 import com.sun.javatest.tool.UIFactory;
  66 
  67 /**
  68  * Utilities for rendering questions.
  69  */
  70 public class RenderingUtilities {
  71     public static class PCE implements TableCellEditor {
  72         private DefaultCellEditor cbCE;
  73         private DefaultCellEditor tfCE;
  74         private DefaultCellEditor delegate;
  75         private PropertiesQuestion q;
  76 
  77         public PCE(PropertiesQuestion q) {
  78             cbCE =  new PropCellEditor(new JComboBox<Object>(), q);
  79             tfCE =  new RestrainedCellEditor(new JTextField(), q);
  80             this.q = q;
  81         }
  82 
  83         public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
  84             assignDelegate(table, row, column);
  85             return delegate.getTableCellEditorComponent(table, value, isSelected, row, column);
  86         }
  87 
  88         public static boolean gotChoice(ValueConstraints vc) {
  89             if (vc == null) {
  90                 return false;
  91             }
  92 
  93             if (vc.isUnsetAllowed()) {
  94                return true;
  95             }
  96 
  97             if (vc instanceof IntConstraints) {
  98                 IntConstraints ic = (IntConstraints) vc;
  99                 if (ic.getSuggestions() == null ||
 100                     ic.getSuggestions().length == 0 ||
 101                     ic.getUpperBound() == ic.getLowerBound() ||
 102                     ic.getSuggestions().length == 1 && ! ic.isCustomValuesAllowed()) {
 103                     return false;
 104                 }
 105             }
 106 
 107             if (vc instanceof FloatConstraints) {
 108                 FloatConstraints fc = (FloatConstraints) vc;
 109                 if (fc.getSuggestions() == null ||
 110                     fc.getSuggestions().length == 0 ||
 111                     fc.getUpperBound() == fc.getLowerBound() ||
 112                     fc.getSuggestions().length == 1 && ! fc.isCustomValuesAllowed()) {
 113                     return false;
 114                 }
 115             }
 116 
 117             if (vc instanceof StringConstraints) {
 118                 StringConstraints sc = (StringConstraints) vc;
 119                 if (sc.getSuggestions() == null ||
 120                     sc.getSuggestions().length == 0 ||
 121                     sc.getSuggestions().length == 1 && ! sc.isCustomValuesAllowed()) {
 122                     return false;
 123                 }
 124             }
 125 
 126             return true;
 127         }
 128 
 129         private void assignDelegate(JTable table, int row, int column) {
 130             int columns = table.getColumnCount();
 131             Object [] values = new Object[columns];
 132             for (int i = 0; i < columns; i++) {
 133                 values[i] = table.getValueAt(row, i);
 134             }
 135             String key = q.getConstraintKeyFromRow(values);
 136             delegate = gotChoice(q.getConstraints(key))?
 137                     cbCE :
 138                     tfCE;
 139         }
 140 
 141         public Object getCellEditorValue() {
 142             return delegate.getCellEditorValue();
 143         }
 144 
 145         public boolean isCellEditable(EventObject anEvent) {
 146             return delegate == null || delegate.isCellEditable(anEvent);
 147         }
 148 
 149         public boolean shouldSelectCell(EventObject anEvent) {
 150             return delegate.shouldSelectCell(anEvent);
 151         }
 152 
 153         public boolean stopCellEditing() {
 154             return delegate.stopCellEditing();
 155         }
 156 
 157         public void cancelCellEditing() {
 158             delegate.cancelCellEditing();
 159         }
 160 
 161         public void addCellEditorListener(CellEditorListener l) {
 162             delegate.addCellEditorListener(l);
 163         }
 164 
 165         public void removeCellEditorListener(CellEditorListener l) {
 166             delegate.removeCellEditorListener(l);
 167         }
 168     }
 169 
 170     static class RestrainedCellEditor extends DefaultCellEditor {
 171         protected RestrainedCellEditor(JTextField tf, PropertiesQuestion q) {
 172             super(tf);
 173             this.q = q;
 174         }
 175 
 176         @Override
 177         public Component getTableCellEditorComponent(JTable table, Object value,
 178                 boolean isSelected, int row, int column) {
 179 
 180             int columns = table.getColumnCount();
 181             Object [] values = new Object[columns];
 182             for (int i = 0; i < columns; i++) {
 183                 values[i] = table.getValueAt(row, i);
 184             }
 185             String key = q.getConstraintKeyFromRow(values);
 186 
 187             ValueConstraints vc = q.getConstraints(key);
 188 
 189             JTextField tf = (JTextField)getComponent();
 190             tf.setText(value.toString());
 191             tf.setFocusable(false);
 192 
 193             if (vc instanceof IntConstraints) {
 194                 IntConstraints ic = (IntConstraints)vc;
 195                 tf.setEditable(ic.isCustomValuesAllowed());
 196             }
 197 
 198             if (vc instanceof StringConstraints){
 199                 StringConstraints sc = (StringConstraints)vc;
 200                 tf.setEditable(sc.isCustomValuesAllowed());
 201             }
 202 
 203             if (vc instanceof FloatConstraints) {
 204                 FloatConstraints fc = (FloatConstraints)vc;
 205                 tf.setEditable(fc.isCustomValuesAllowed());
 206             }
 207 
 208             return tf;
 209         }
 210 
 211         private PropertiesQuestion q;
 212     }
 213 
 214 
 215 
 216     /**
 217      * Table cell renderer for enforcing constraints on the combo box used
 218      * for editing.
 219      */
 220     public static class PropCellEditor extends DefaultCellEditor {
 221         protected PropCellEditor(JComboBox<Object> box) {
 222             super(box);
 223         }
 224 
 225         PropCellEditor(JComboBox<Object> box, PropertiesQuestion q) {
 226             this(box);
 227             question = q;
 228         }
 229 
 230         /**
 231          * For use when this renderer is being used outside the context of
 232          * an interview and question.
 233          */
 234         PropCellEditor(JComboBox<Object> box, ValueConstraints rules) {
 235             this(box);
 236             this.rules = rules;
 237         }
 238 
 239         @Override
 240         public Object getCellEditorValue() {
 241             if (rules != null){
 242                 if (rules instanceof BooleanConstraints){
 243                     if (((BooleanConstraints)rules).isYesNo()) {
 244                         return yesNoBox.getValue();
 245                     }
 246                     else {
 247                         return jCheckBox.isSelected() ? BooleanConstraints.TRUE : BooleanConstraints.FALSE;
 248                     }
 249                 }
 250             }
 251             return super.getCellEditorValue();
 252         }
 253 
 254     @Override
 255         public Component getTableCellEditorComponent(final JTable table, Object value,
 256                  boolean isSelected, final int row, final int column) {
 257 
 258             int columns = table.getColumnCount();
 259             Object [] values = new Object[columns];
 260             for (int i = 0; i < columns; i++) {
 261                 values[i] = table.getValueAt(row, i);
 262             }
 263             String key = question.getConstraintKeyFromRow(values);
 264 
 265             rules = question.getConstraints(key);
 266 
 267             if (rules instanceof BooleanConstraints){
 268 
 269                 if (((BooleanConstraints) rules).isYesNo()) {
 270                     if (yesNoBox == null){
 271                         yesNoBox = new YesNoBox();
 272                     }
 273                     yesNoBox.selectYes(BooleanConstraints.YES.equals(value));
 274                     return yesNoBox;
 275                 } else {
 276                     if (jCheckBox == null) {
 277                         jCheckBox = new JCheckBox();
 278                     }
 279                     jCheckBox.setSelected(BooleanConstraints.TRUE.equals(value));
 280                     return jCheckBox;
 281                 }
 282 
 283             }
 284 
 285             final JComboBox<Object> cb = (JComboBox<Object>)getComponent();
 286             cb.setEditable(true);
 287             cb.removeAllItems();
 288             cb.addItem(value);
 289             cb.setSelectedIndex(0);
 290             if (rules != null)
 291                 setConstraints(cb, rules);
 292 
 293             String valid = question.isValueValid(key);
 294             if (valid != null) {
 295                 cb.setToolTipText(valid);
 296             }
 297             else
 298                 cb.setBackground(Color.WHITE);
 299 
 300             if (!(rules instanceof FilenameConstraints)) {
 301                 return cb;
 302             }
 303             else {      // file chooser
 304                 final FilenameConstraints fc = (FilenameConstraints)rules;
 305                 JPanel p = new JPanel();
 306                 p.setName("filename field");
 307                 p.setFocusable(false);
 308                 p.setLayout(new GridBagLayout());
 309                 GridBagConstraints gbc = new GridBagConstraints();
 310                 gbc.anchor = GridBagConstraints.LINE_START;
 311                 gbc.fill = GridBagConstraints.HORIZONTAL;
 312                 gbc.weightx = 1.0;
 313                 gbc.gridy = 0;
 314                 p.add(cb, gbc);
 315 
 316                 gbc.fill = GridBagConstraints.NONE;
 317 
 318                 // configure the button
 319                 final JButton browseBtn = new JButton("...");
 320                 final JFileChooser chooser = FileQuestionRenderer.createChooser(
 321                                                 key, fc.getFilters());
 322                 // setup chooser
 323                 File f = new File((String)(cb.getSelectedItem()));
 324                 if (!f.exists()) {
 325                     File dir = fc.getBaseDirectory();
 326                     if (dir == null)
 327                         dir = new File(System.getProperty("user.dir"));
 328                     chooser.setCurrentDirectory(dir);
 329                 }
 330                 else {
 331                     chooser.setSelectedFile(f);
 332                 }
 333 
 334                 browseBtn.setName("file.browse.btn");
 335                 browseBtn.setMnemonic(i18n.getString("file.browse.mne").charAt(0));
 336                 browseBtn.setToolTipText(i18n.getString("file.browse.tip"));
 337                 browseBtn.addActionListener(new ActionListener() {
 338                     public void actionPerformed(ActionEvent e) {
 339                         // default chooser to point at specified entry
 340                         String s = (String)(cb.getSelectedItem());
 341                         if (s != null && s.length() > 0) {
 342                             File f = new File(s);
 343                             File baseDir = fc.getBaseDirectory();
 344                             if (!f.isAbsolute() && baseDir != null)
 345                                 f = new File(baseDir, s);
 346                             chooser.setSelectedFile(f);
 347                         }
 348 
 349                         int opt = chooser.showDialog(browseBtn, "Select");
 350                         if (opt == JFileChooser.APPROVE_OPTION) {
 351 
 352                             String path = chooser.getSelectedFile().getPath();
 353                             FileFilter ff = SwingFileFilter.unwrap(chooser.getFileFilter());
 354 
 355                             if (ff != null && ff instanceof ExtensionFileFilter) {
 356                                 ExtensionFileFilter eff = (ExtensionFileFilter) ff;
 357                                 path = eff.ensureExtension(path);
 358                             }
 359                             File baseDir = fc.getBaseDirectory();
 360                             if (baseDir != null) {
 361                                 String bp = baseDir.getPath();
 362                                 if (path.startsWith(bp + File.separatorChar))
 363                                     path = path.substring(bp.length() + 1);
 364                             }
 365                             if (cb.getSelectedIndex() != -1) {
 366                                 cb.removeItemAt(cb.getSelectedIndex());
 367                             }
 368                             cb.addItem(path);
 369                             cb.setSelectedItem(path);
 370                             table.getModel().setValueAt(path, row, column);
 371                         }
 372                     }
 373                 });
 374                 p.add(browseBtn, gbc);
 375                 return p;
 376             }
 377         }
 378 
 379         private void setConstraints(JComboBox<Object> cb, ValueConstraints rules) {
 380             if (rules instanceof IntConstraints) {
 381                 // attach input filter
 382                 // add suggestions
 383                 IntConstraints intRules = (IntConstraints)rules;
 384                 cb.setEditable(intRules.isCustomValuesAllowed());
 385 
 386                 int[] sugs = intRules.getSuggestions();
 387                 if (sugs != null)
 388                     // add all suggestions
 389                     for (int i = 0; i < sugs.length; i++) {
 390                         if (!Integer.toString(sugs[i]).equals(cb.getItemAt(0)))
 391                             cb.addItem(Integer.toString(sugs[i]));
 392                     }
 393             }
 394             else if (rules instanceof FloatConstraints) {
 395                 // attach input filter
 396                 // add suggestions
 397                 FloatConstraints fltRules = (FloatConstraints)rules;
 398                 float[] sugs = fltRules.getSuggestions();
 399                 cb.setEditable(fltRules.isCustomValuesAllowed());
 400 
 401                 if (sugs != null)
 402                     // add all suggestions
 403                     for (int i = 0; i < sugs.length; i++) {
 404                         if (!Float.toString(sugs[i]).equals(cb.getItemAt(0)))
 405                             cb.addItem(Float.toString(sugs[i]));
 406                     }
 407             }
 408             else if (rules instanceof StringConstraints) {
 409                 StringConstraints strRules = (StringConstraints)rules;
 410                 cb.setEditable(strRules.isCustomValuesAllowed());
 411 
 412                 String[] sugs = strRules.getSuggestions();
 413                 if (sugs != null) {
 414                     if (strRules.isCustomValuesAllowed()) {
 415                         // add all suggestions
 416                         for (int i = 0; i < sugs.length; i++) {
 417                             if (!sugs[i].equals(cb.getItemAt(0)))
 418                                 cb.addItem(sugs[i]);
 419                         }   // for
 420                     }
 421                     else
 422                         configureSet(cb, sugs, true, strRules.isUnsetAllowed());
 423                 }
 424                 else {}
 425             }
 426             else if (rules instanceof FilenameConstraints) {
 427                 FilenameConstraints strRules = (FilenameConstraints)rules;
 428                 cb.setEditable(true);
 429 
 430                 File[] sugs = strRules.getSuggestions();
 431                 if (sugs != null) {
 432                     // add all suggestions
 433                     for (int i = 0; i < sugs.length; i++) {
 434                         if (!sugs[i].getPath().equalsIgnoreCase((String)(cb.getItemAt(0))))
 435                             cb.addItem(sugs[i].getPath());
 436                     }   // for
 437                 }
 438                 else {}
 439             }
 440             else {      // generic constraints
 441                 ValueConstraints vRules = rules;
 442             }
 443         }
 444 
 445         /**
 446          * Add set of choices to combo box.  Handles special cases, such as
 447          * when the current value matches one of the possible choices.  Also
 448          * adds a blank choice (unset).  Assume the current value in the
 449          * combo box is the one at index zero.
 450          */
 451         private void configureSet(JComboBox<Object> cb, String[] possible,
 452                                 boolean ignoreCase, boolean isUnsetAllowed) {
 453             // wishlist: i18n
 454             //           values which are independent of locale
 455             String curr = (String)(cb.getItemAt(0));
 456 
 457             // add unset choice if allowed and needed
 458             if (isUnsetAllowed)
 459                 cb.addItem("");
 460 
 461             for (int i = 0; i < possible.length; i++)
 462                 cb.addItem(possible[i]);
 463 
 464             for (int i = 0; i < possible.length; i++) {
 465                 if (compareStr(curr, possible[i], ignoreCase)) {
 466                     cb.removeItemAt(0);
 467                     cb.setSelectedIndex(i + (isUnsetAllowed ? 1 : 0));
 468                     return;
 469                 }
 470             }   // for
 471 
 472             // no matches, delete current value, set empty
 473             // should select a better default?  try from defaultValue?
 474             cb.removeItemAt(0);
 475             cb.setSelectedIndex(0);
 476         }
 477 
 478         private boolean compareStr(String s1, String s2, boolean ignoreCase) {
 479             if (ignoreCase)
 480                 return s1.equalsIgnoreCase(s2);
 481             else
 482                 return s1.equals(s2);
 483         }
 484 
 485         private PropertiesQuestion question;
 486         private ValueConstraints rules;
 487         private JCheckBox jCheckBox;
 488         private YesNoBox yesNoBox;
 489     }   // editor cell
 490 
 491 
 492     /**
 493      * Table cell renderer for use when a cell is not being edited.
 494      */
 495     public static class PropCellRenderer extends DefaultTableCellRenderer {
 496         PropCellRenderer(PropertiesQuestion q) {
 497             this.q = q;
 498         }
 499 
 500         public Component getTableCellRendererComponent(JTable table, Object value,
 501                   boolean isSelected, boolean hasFocus, int row, int column) {
 502 
 503             if (column == 1){
 504                 String keyName = q.getKeyPropertyName((String)table.getValueAt(row, 0));
 505                 PropertiesQuestion.ValueConstraints constraints = q.getConstraints(keyName);
 506                 if (constraints instanceof BooleanConstraints){
 507                     if (((BooleanConstraints) constraints).isYesNo()) {
 508                         yesNoBox = new YesNoBox();
 509                         yesNoBox.selectYes(BooleanConstraints.YES.equals(value));
 510                         return yesNoBox;
 511 
 512                     }
 513                     else{
 514                         jCheckBox = new JCheckBox();
 515                         jCheckBox.setSelected(BooleanConstraints.TRUE.equals(value));
 516                         return jCheckBox;
 517                     }
 518                 }
 519                 else{
 520                     jComboBox = new JComboBox<>();
 521                     jComboBox.addItem(value);
 522                     jComboBox.setEditable(true);
 523                     if ( q.isValueValid(q.getKeyPropertyName((String)table.getValueAt(row, 0))) != null ) {
 524                         jComboBox.setBorder(new LineBorder(Color.RED, 2));
 525                     }
 526                     return jComboBox;
 527                 }
 528             }
 529             else {
 530                 Component c = super.getTableCellRendererComponent(table, value, isSelected,
 531                         hasFocus, row, column);
 532                 c.setBackground(UIFactory.Colors.WINDOW_BACKGROUND.getValue());
 533                 ((DefaultTableCellRenderer)c).setHorizontalAlignment(DefaultTableCellRenderer.RIGHT);
 534                 return c;
 535             }
 536             // XXX needs i18n and 508
 537         }
 538 
 539         public PropertiesQuestion getQuestion() {
 540             return q;
 541         }
 542 
 543         PropertiesQuestion q;
 544         JCheckBox jCheckBox;
 545         YesNoBox yesNoBox;
 546         JComboBox<Object> jComboBox;
 547     }   // non-editing cell
 548 
 549     static class YesNoBox extends JPanel{
 550 
 551         private JRadioButton yesButton;
 552         private JRadioButton noButton;
 553         private ButtonGroup bgroup;
 554 
 555         public YesNoBox(){
 556             super();
 557             setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
 558 
 559             bgroup = new ButtonGroup();
 560             yesButton = new JRadioButton(BooleanConstraints.YES);
 561             noButton = new JRadioButton(BooleanConstraints.NO);
 562             bgroup.add(yesButton);
 563             bgroup.add(noButton);
 564 
 565             add(yesButton);
 566             add(Box.createRigidArea(new Dimension(5,0)));
 567             add(noButton);
 568         }
 569 
 570         public String getValue(){
 571             if (yesButton.isSelected()){
 572                 return BooleanConstraints.YES;
 573             }
 574             return BooleanConstraints.NO;
 575         }
 576 
 577         public void selectYes(boolean value){
 578             yesButton.setSelected(value);
 579             noButton.setSelected(!value);
 580         }
 581 
 582     }
 583 
 584     private static final I18NResourceBundle i18n = I18NResourceBundle.getDefaultBundle();
 585 }