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 }