1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2011, 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.BorderLayout; 30 import java.awt.Color; 31 import java.awt.Component; 32 import java.awt.Dimension; 33 import java.awt.Font; 34 import java.awt.GridBagConstraints; 35 import java.awt.GridBagLayout; 36 import java.awt.HeadlessException; 37 import java.awt.KeyboardFocusManager; 38 import java.awt.Rectangle; 39 import java.awt.Toolkit; 40 import java.awt.event.ActionEvent; 41 import java.awt.event.KeyEvent; 42 import java.net.URL; 43 import java.util.HashMap; 44 import java.util.Map; 45 import javax.accessibility.AccessibleContext; 46 import javax.swing.AbstractAction; 47 import javax.swing.Action; 48 import javax.swing.ActionMap; 49 import javax.swing.BorderFactory; 50 import javax.swing.FocusManager; 51 import javax.swing.Icon; 52 import javax.swing.ImageIcon; 53 import javax.swing.InputMap; 54 import javax.swing.JComponent; 55 import javax.swing.JLabel; 56 import javax.swing.JOptionPane; 57 import javax.swing.JPanel; 58 import javax.swing.JTextArea; 59 import javax.swing.JTextField; 60 import javax.swing.JViewport; 61 import javax.swing.KeyStroke; 62 import javax.swing.Scrollable; 63 import javax.swing.SwingConstants; 64 import javax.swing.event.AncestorEvent; 65 import javax.swing.event.AncestorListener; 66 67 import com.sun.interview.ChoiceArrayQuestion; 68 import com.sun.interview.ChoiceQuestion; 69 import com.sun.interview.ErrorQuestion; 70 import com.sun.interview.FileListQuestion; 71 import com.sun.interview.FileQuestion; 72 import com.sun.interview.FloatQuestion; 73 import com.sun.interview.InetAddressQuestion; 74 import com.sun.interview.IntQuestion; 75 import com.sun.interview.Interview; 76 import com.sun.interview.ListQuestion; 77 import com.sun.interview.NullQuestion; 78 import com.sun.interview.PropertiesQuestion; 79 import com.sun.interview.Question; 80 import com.sun.interview.StringQuestion; 81 import com.sun.interview.StringListQuestion; 82 import com.sun.interview.TreeQuestion; 83 import com.sun.interview.YesNoQuestion; 84 import java.util.EventListener; 85 import javax.swing.JEditorPane; 86 import javax.swing.JScrollPane; 87 88 /** 89 * A panel which implements a {@link com.sun.interview.wizard.Wizard wizard} 90 * that asks a series of {@link com.sun.interview.Question questions} 91 * embodied in an {@link Interview interview}. 92 */ 93 class QuestionPanel extends JPanel 94 implements Scrollable 95 { 96 97 /** 98 * Create a panel in which to display the questions of the interview. 99 * The interview to be run. 100 */ 101 QuestionPanel(Interview i) { 102 interview = i; 103 104 initRenderers(); 105 initGUI(); 106 107 addAncestorListener(listener); 108 109 } 110 111 112 // ---------- Component stuff --------------------------------------- 113 114 public Dimension getPreferredSize() { 115 Dimension d = super.getPreferredSize(); 116 d.height = Math.max(d.height, PREFERRED_HEIGHT * DOTS_PER_INCH); 117 d.width = Math.max(d.width, PREFERRED_WIDTH * DOTS_PER_INCH); 118 return d; 119 } 120 121 // ---------- Scrollable stuff --------------------------------------- 122 123 public Dimension getPreferredScrollableViewportSize() { 124 Dimension maxD = new Dimension(PREFERRED_WIDTH * DOTS_PER_INCH, PREFERRED_HEIGHT * DOTS_PER_INCH); 125 Dimension ps = getPreferredSize(); 126 ps.width = Math.min(ps.width, maxD.width); 127 ps.height = Math.min(ps.height, maxD.height); 128 return ps; 129 } 130 131 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { 132 switch(orientation) { 133 case SwingConstants.VERTICAL: 134 return visibleRect.height / 10; 135 case SwingConstants.HORIZONTAL: 136 return visibleRect.width / 10; 137 default: 138 throw new IllegalArgumentException("Invalid orientation: " + orientation); 139 } 140 } 141 142 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { 143 switch(orientation) { 144 case SwingConstants.VERTICAL: 145 return visibleRect.height; 146 case SwingConstants.HORIZONTAL: 147 return visibleRect.width; 148 default: 149 throw new IllegalArgumentException("Invalid orientation: " + orientation); 150 } 151 } 152 153 public boolean getScrollableTracksViewportHeight() { 154 // We can't use getPreferred size here, because of situation, when 155 // getPreferredSize() gives default values. In this case, and if horizontal scroll exists, 156 // viewport height becomes less than getPreferredSize().height, but may be enough for components 157 // without any scrolling 158 if(getParent() instanceof JViewport) 159 return getParent().getHeight() > getPreferredSize().height; 160 else 161 return false; 162 } 163 164 public final boolean getScrollableTracksViewportWidth() { 165 166 // if (currentRenderer instanceof SizeSensitiveQuestionRenderer) { 167 // Dimension d; 168 // int visWidth = ((JViewport)getParent()).getExtentSize().width; 169 // int requiredWidth = valuePanel.getPreferredSize().width + TEXT_AREA_INSETS_LEFT_RIGHT*2; 170 // if(requiredWidth < visWidth) { 171 //// d = this.getPreferredSize(); 172 //// this.setPreferredSize(new Dimension(visWidth, 173 //// super.getPreferredSize().height)); 174 //// d = this.getPreferredSize(); 175 // setHorScrollStatus(false); 176 // return true; 177 // } 178 // else { 179 //// d = this.getPreferredSize(); 180 //// this.setPreferredSize(new Dimension(requiredWidth, 181 //// super.getPreferredSize().height)); 182 //// d = this.getPreferredSize(); 183 // setHorScrollStatus(true); 184 // return false; 185 // } 186 //// setHorScrollStatus(true); 187 //// return false; 188 // } 189 // else { 190 //// this.setPreferredSize(this.getSize()); 191 // setHorScrollStatus(false); 192 return true; 193 // } 194 } 195 196 // ---------- end of Scrollable stuff ----------------------------------- 197 198 void setNextAction(Action nextAction) { 199 this.nextAction = nextAction; 200 } 201 202 void saveCurrentResponse() /*throws Interview.Fault*/ { 203 if (valueSaver != null) 204 valueSaver.run(); 205 } 206 207 boolean isTagVisible() { 208 return propsPanel.isVisible(); 209 } 210 211 void setTagVisible(boolean v) { 212 propsPanel.setVisible(v); 213 } 214 215 private void initRenderers() { 216 renderers = new HashMap<>(); 217 renderers.put(ChoiceQuestion.class, new ChoiceQuestionRenderer()); 218 renderers.put(ChoiceArrayQuestion.class, new ChoiceArrayQuestionRenderer()); 219 renderers.put(FileQuestion.class, new FileQuestionRenderer()); 220 renderers.put(FileListQuestion.class, new FileListQuestionRenderer()); 221 renderers.put(FloatQuestion.class, new FloatQuestionRenderer()); 222 renderers.put(IntQuestion.class, new IntQuestionRenderer()); 223 renderers.put(InetAddressQuestion.class, new InetAddressQuestionRenderer()); 224 renderers.put(ListQuestion.class, new ListQuestionRenderer()); 225 renderers.put(NullQuestion.class, new NullQuestionRenderer()); 226 renderers.put(PropertiesQuestion.class, new PropertiesQuestionRenderer()); 227 renderers.put(StringQuestion.class, new StringQuestionRenderer()); 228 renderers.put(StringListQuestion.class, new StringListQuestionRenderer()); 229 renderers.put(TreeQuestion.class, new TreeQuestionRenderer()); 230 renderers.put(YesNoQuestion.class, new YesNoQuestionRenderer()); 231 setCustomRenderers(new HashMap<Class<? extends Question>, QuestionRenderer>()); 232 } 233 234 /** 235 * Create the basic GUI infrastructure. The content of the 236 * various areas are filled in from the questions that are 237 * asked. 238 */ 239 private void initGUI() { 240 /* 241 +---------------+-------------------------------+ 242 | | title | 243 | +-------------------------------+ 244 | | text area | 245 | graphic | | 246 | +-------------------------------+ 247 | | | 248 | | value | 249 | | | 250 | +-------------------------------+ 251 | | msg | 252 | +-------------------------------+ 253 | | tag info (optional) | 254 +---------------+-------------------------------+ 255 */ 256 setInfo(this, "qu", false); 257 setLayout(new GridBagLayout()); 258 GridBagConstraints c = new GridBagConstraints(); 259 260 graphicLabel = new JLabel(); 261 setInfo(graphicLabel, "qu.icon", false); 262 graphicLabel.setFocusable(false); 263 if (interview != null) { 264 URL u = interview.getDefaultImage(); 265 if (u != null) 266 graphicLabel.setIcon(new ImageIcon(u)); 267 } 268 c.anchor = GridBagConstraints.CENTER; 269 c.gridheight = GridBagConstraints.REMAINDER; 270 add(graphicLabel, c); 271 272 titleField = new JTextField(); 273 setInfo(titleField, "qu.title", true); 274 titleField.setEditable(false); 275 titleField.setBackground(new Color(102, 102, 153));//titleField.setBackground(MetalLookAndFeel.getPrimaryControlDarkShadow()); 276 titleField.setForeground(Color.WHITE);//titleField.setForeground(MetalLookAndFeel.getWindowBackground()); 277 Font f = titleField.getFont();//Font f = MetalLookAndFeel.getSystemTextFont(); 278 titleField.setFont(f.deriveFont(f.getSize() * 1.5f)); 279 c.fill = GridBagConstraints.HORIZONTAL; 280 c.gridwidth = GridBagConstraints.REMAINDER; 281 c.gridheight = 1; 282 c.weightx = 1; 283 add(titleField, c); 284 285 textArea = new JTextArea(3, 30); 286 setInfo(textArea, "qu.text", true); 287 textArea.setEditable(false); 288 textArea.setLineWrap(true); 289 textArea.setOpaque(false); 290 textArea.setBackground(new Color(255, 255, 255, 0)); 291 textArea.setBorder(null); 292 textArea.setWrapStyleWord(true); 293 // override JTextArea focus traversal keys, resetting them to 294 // the Component default (i.e. the same as for the parent.) 295 textArea.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null); 296 textArea.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null); 297 // set enter to be same as Next 298 { 299 InputMap im = textArea.getInputMap(); 300 im.put(enterKey, "next"); 301 ActionMap am = textArea.getActionMap(); 302 am.put("next", valueAction); 303 } 304 c.insets.top = TEXT_AREA_INSETS_TOP; 305 c.insets.left = c.insets.right = TEXT_AREA_INSETS_LEFT_RIGHT; 306 c.insets.bottom = TEXT_AREA_INSETS_BOTTOM; 307 c.fill = GridBagConstraints.BOTH; 308 add(textArea, c); 309 310 311 valuePanel = new JPanel(new BorderLayout()); 312 setInfo(valuePanel, "qu.vp", false); 313 valuePanel.setOpaque(true); 314 // set default action for enter to be same as Next 315 { 316 InputMap im = valuePanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 317 im.put(enterKey, "next"); 318 ActionMap am = valuePanel.getActionMap(); 319 am.put("next", valueAction); 320 } 321 322 c.insets.top = VALUE_PANEL_INSETS_TOP; 323 c.weighty = 1; 324 c.fill = GridBagConstraints.BOTH; 325 c.insets.bottom = VALUE_PANEL_INSETS_BOTTOM; 326 add(valuePanel, c); 327 328 c.fill = GridBagConstraints.BOTH; 329 valueMessageField = new JTextField(); 330 setInfo(valueMessageField, "qu.vmsg", false); 331 valueMessageField.setEditable(false); 332 valueMessageField.setOpaque(false); 333 valueMessageField.setFont(valueMessageField.getFont().deriveFont(Font.BOLD)); 334 valueMessageField.setBorder(null); 335 c.insets.top = VALUE_MESSAGE_FIELD_INSETS_TOP; 336 c.insets.bottom = VALUE_MESSAGE_FIELD_INSETS_BOTTOM; 337 c.weighty = 0; 338 add(valueMessageField, c); 339 340 propsPanel = new JPanel(new BorderLayout()); 341 propsPanel.setBorder(BorderFactory.createEtchedBorder()); 342 // replace by titled border "properties" if we get more than one... 343 propsPanel.setName("qu.props.pnl"); 344 propsPanel.setFocusable(false); 345 JLabel tagLabel = new JLabel(i18n.getString("qu.tag.lbl")); 346 setInfo(tagLabel, "qu.tag.lbl", true); 347 tagLabel.setDisplayedMnemonic(i18n.getString("qu.tag.mne").charAt(0)); 348 tagLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 7)); 349 propsPanel.add(tagLabel, BorderLayout.WEST); 350 tagField = new JTextField(); 351 tagField.setName("qu.tag.fld"); 352 tagField.setEditable(false); 353 tagField.setBorder(null); 354 tagLabel.setLabelFor(tagField); 355 propsPanel.add(tagField, BorderLayout.CENTER); 356 propsPanel.setVisible(false); 357 c.insets.top = PROPS_PANEL_INSETS_TOP; 358 c.insets.bottom = PROPS_PANEL_INSETS_BOTTOM; 359 add(propsPanel, c); 360 361 ActionMap actionMap = getActionMap(); 362 363 actionMap.put("hideProps", new AbstractAction() { 364 public void actionPerformed(ActionEvent e) { 365 propsPanel.setVisible(false); 366 } 367 }); 368 369 actionMap.put("showProps", new AbstractAction() { 370 public void actionPerformed(ActionEvent e) { 371 propsPanel.setVisible(true); 372 } 373 }); 374 375 actionMap.put("toggleProps", new AbstractAction() { 376 public void actionPerformed(ActionEvent e) { 377 propsPanel.setVisible(!propsPanel.isVisible()); 378 } 379 }); 380 381 InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 382 inputMap.put(KeyStroke.getKeyStroke("ctrl T"), "toggleProps"); 383 384 } 385 386 private void setInfo(JComponent jc, String uiKey, boolean addToolTip) { 387 jc.setName(uiKey); 388 AccessibleContext ac = jc.getAccessibleContext(); 389 ac.setAccessibleName(i18n.getString(uiKey + ".name")); 390 if (addToolTip) { 391 String tip = i18n.getString(uiKey + ".tip"); 392 jc.setToolTipText(tip); 393 ac.setAccessibleDescription(tip); 394 } 395 else 396 ac.setAccessibleDescription(i18n.getString(uiKey + ".desc")); 397 } 398 399 /** 400 * Show a question. The appropriate showXXX method 401 * is called, depending on the question, and then a response 402 * is awaited. 403 */ 404 public void showQuestion(Question q) { 405 //System.err.println("QP.showQuestion " + q.getTag() + " " + q); 406 if (q instanceof ErrorQuestion) { 407 showErrorQuestion((ErrorQuestion) q); 408 try { 409 // no current response to save :-) 410 interview.prev(); 411 } 412 catch (Interview.Fault ignore) { 413 } 414 return; 415 } 416 417 URL u = q.getImage(); 418 final Icon icon = (u == null ? null : new ImageIcon(u)); 419 420 if (icon != null) 421 graphicLabel.setIcon(icon); 422 423 titleField.setText(q.getSummary()); 424 textArea.setText(q.getText()); 425 tagField.setText(q.getTag()); 426 427 boolean focus = anyChildHasFocus(valuePanel); 428 valuePanel.removeAll(); 429 430 QuestionRenderer r = getRenderer(q); 431 432 //------------------------------------ 433 434 if (r == null) { 435 //System.err.println("no renderer for " + q.getTag() + " [" + q.getClass().getName() + "]"); 436 valueSaver = null; 437 } 438 else { 439 JComponent rc = r.getQuestionRendererComponent(q, valueAction); 440 if (rc == null) { 441 valueSaver = null; 442 if (focus) { 443 // no response area, so put focus back on question text 444 textArea.requestFocus(); 445 } 446 } 447 else { 448 if (rc.getName() == null) 449 rc.setName(r.getClass().getName()); 450 451 valueSaver = (Runnable) (rc.getClientProperty(QuestionRenderer.VALUE_SAVER)); 452 //System.err.println("QP.showQuestion valueSaver=" + valueSaver); 453 valuePanel.add(rc); 454 if (focus) { 455 //System.err.println("QP.showQuestion: setFocus"); 456 FocusManager fm = FocusManager.getCurrentManager(); 457 //fm.focusNextComponent(valuePanel); 458 fm.focusNextComponent(textArea); 459 } 460 } 461 } 462 463 if (q.isValueAlwaysValid()) 464 valueMessageField.setVisible(false); 465 else { 466 showValueMessage(null); 467 valueMessageField.setVisible(true); 468 } 469 470 // relayout the GUI 471 revalidate(); 472 repaint(); 473 474 currentRenderer = r; 475 currentQuestion = q; 476 } 477 478 private void showErrorQuestion(ErrorQuestion q) throws HeadlessException { 479 480 JEditorPane ePane = new JEditorPane(q.getTextMimeType(), q.getText()); 481 ePane.setEditable(false); 482 483 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 484 final Dimension minS = new Dimension(350, 100); 485 final Dimension maxS = new Dimension(Math.min(2*(screen.width/3), 800), Math.min(2*(screen.height/3), 600)); 486 Dimension p = ePane.getPreferredSize(); 487 488 p.setSize(Math.max(p.width, minS.width), Math.max(p.height, minS.height)); 489 p.setSize(Math.min(p.width, maxS.width), Math.min(p.height, maxS.height)); 490 ePane.setPreferredSize(p); 491 492 ePane.setCaretPosition(0); 493 JScrollPane sPane = new JScrollPane(ePane); 494 sPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 495 sPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 496 497 JOptionPane.showMessageDialog(this, sPane, q.getSummary(), JOptionPane.ERROR_MESSAGE); 498 499 } 500 501 /** 502 * This method invokes when config editor is going to be closed. 503 * This made to allow components handle feature closing 504 */ 505 public void prepareClosing() { 506 if(currentRenderer instanceof PropertiesQuestionRenderer) { 507 AncestorEvent e = new AncestorEvent(this, AncestorEvent.ANCESTOR_REMOVED, 508 valuePanel, valuePanel.getParent()); 509 Component[] childs = valuePanel.getComponents(); 510 for(int i = 0; i < childs.length; i++) { 511 EventListener[] l = childs[i].getListeners(AncestorListener.class); 512 for(int j = 0; j < l.length; j++) { 513 if(l[i] instanceof AncestorListener) { 514 ((AncestorListener)l[i]).ancestorRemoved(e); 515 } 516 } 517 } 518 } 519 } 520 521 public void showValueInvalidMessage() { 522 String msg = (currentRenderer == null 523 ? null 524 : currentRenderer.getInvalidValueMessage(currentQuestion)); 525 showValueMessage((msg == null ? INVALID_VALUE : msg), INVALID_VALUE_COLOR); 526 } 527 528 private void showValueMessage(String msg) { 529 showValueMessage(msg, Color.BLACK);//showValueMessage(msg, MetalLookAndFeel.getBlack()); 530 } 531 532 private void showValueMessage(String msg, Color c) { 533 if (msg == null || msg.length() == 0) { 534 valueMessageField.setText(""); 535 valueMessageField.setEnabled(false); 536 } 537 else { 538 valueMessageField.setForeground(c); 539 valueMessageField.setText(msg); 540 valueMessageField.setEnabled(true); 541 } 542 } 543 544 private QuestionRenderer getRenderer(Question q) { 545 QuestionRenderer result = null; 546 if (customRenderers != null) { 547 result = getRenderer(q, customRenderers); 548 } 549 if (result == null) { 550 result = getRenderer(q, renderers); 551 } 552 return result; 553 } 554 555 private QuestionRenderer getRenderer(Question q, Map rendMap) { 556 for (Class c = q.getClass(); c != null; c = c.getSuperclass()) { 557 QuestionRenderer r = (QuestionRenderer) (rendMap.get(c)); 558 if (r != null) 559 return r; 560 } 561 return null; 562 } 563 564 565 private boolean anyChildHasFocus(JPanel p) { 566 if (p.hasFocus()) 567 return true; 568 569 for (int i = 0; i < p.getComponentCount(); i++) { 570 Component c = (p.getComponent(i)); 571 if ((c instanceof JComponent && c.hasFocus()) 572 || (c instanceof JPanel && anyChildHasFocus((JPanel)c))) 573 return true; 574 } 575 return false; 576 } 577 578 private Interview interview; 579 private Question currentQuestion; 580 private QuestionRenderer currentRenderer; 581 private JLabel graphicLabel; 582 private JTextField titleField; 583 private JTextArea textArea; 584 private JPanel valuePanel; 585 private Runnable valueSaver; 586 private JTextField valueMessageField; 587 private JPanel propsPanel; 588 private JTextField tagField; 589 private Map<Class<? extends Question>, QuestionRenderer> renderers; 590 private Map<Class<? extends Question>, QuestionRenderer> customRenderers; 591 private Listener listener = new Listener(); 592 593 private static final I18NResourceBundle i18n = I18NResourceBundle.getDefaultBundle(); 594 private static String INVALID_VALUE = i18n.getString("qu.invalidValue.txt"); 595 private static Color INVALID_VALUE_COLOR = i18n.getErrorColor(); 596 597 private KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); 598 private Action valueAction = new AbstractAction() { 599 public void actionPerformed(ActionEvent e) { 600 String cmd = e.getActionCommand(); 601 if (cmd.equals(QuestionRenderer.EDITED)) 602 showValueMessage(null); 603 else { 604 if (nextAction != null) { 605 nextAction.actionPerformed(e); 606 } 607 else { 608 // default old behavior 609 try { 610 saveCurrentResponse(); 611 interview.next(); 612 } 613 catch (Interview.Fault ex) { 614 // exception normally means no more questions, 615 // which should only be because the value of the current 616 // question is invalid 617 // e.printStackTrace(); 618 // QuestionPanel.this.getToolkit().beep(); 619 showValueInvalidMessage(); 620 } 621 } 622 } 623 } 624 625 }; 626 627 private Action nextAction; // optionally settable 628 629 private static final int PREFERRED_HEIGHT = 3; // inches 630 private static final int PREFERRED_WIDTH = 4; // inches 631 private static final int DOTS_PER_INCH = Toolkit.getDefaultToolkit().getScreenResolution(); 632 private static final int TEXT_AREA_INSETS_TOP = 20; 633 634 private static final int TEXT_AREA_INSETS_LEFT_RIGHT = 10; 635 private static final int TEXT_AREA_INSETS_BOTTOM = 10; 636 637 private static final int VALUE_PANEL_INSETS_TOP = 0; 638 private static final int VALUE_PANEL_INSETS_BOTTOM = 10; 639 640 private static final int VALUE_MESSAGE_FIELD_INSETS_TOP = 0; 641 private static final int VALUE_MESSAGE_FIELD_INSETS_BOTTOM = 0; 642 643 private static final int PROPS_PANEL_INSETS_TOP = 0; 644 private static final int PROPS_PANEL_INSETS_BOTTOM = 10; 645 646 public void setCustomRenderers(Map<Class<? extends Question>, QuestionRenderer> customRenderers) { 647 this.customRenderers = customRenderers; 648 } 649 650 651 private class Listener 652 implements AncestorListener, Interview.Observer 653 { 654 655 // ---------- AncestorListener 656 657 public void ancestorAdded(AncestorEvent e) { 658 interview.addObserver(this); 659 showQuestion(interview.getCurrentQuestion()); 660 } 661 662 public void ancestorMoved(AncestorEvent e) { } 663 664 public void ancestorRemoved(AncestorEvent e) { 665 interview.removeObserver(this); 666 } 667 668 // ---------- Interview.Observer ---------- 669 670 public void pathUpdated() { 671 // if path is updated as a result of refresh, clear the error message 672 showValueMessage(null); 673 } 674 675 public void currentQuestionChanged(Question q) { 676 showQuestion(q); 677 } 678 679 public void finished() { } 680 681 } 682 683 }