1 /***
2 *
3 */
4 package org.telscenter.pas.steps;
5
6 import info.clearthought.layout.TableLayout;
7
8 import java.awt.BorderLayout;
9 import java.awt.Component;
10 import java.awt.Dimension;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.awt.event.ComponentEvent;
14 import java.awt.event.ComponentListener;
15 import java.io.InputStream;
16 import java.io.Serializable;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.logging.Logger;
24
25 import javax.swing.AbstractAction;
26 import javax.swing.BorderFactory;
27 import javax.swing.Box;
28 import javax.swing.BoxLayout;
29 import javax.swing.ButtonGroup;
30 import javax.swing.ImageIcon;
31 import javax.swing.JButton;
32 import javax.swing.JComponent;
33 import javax.swing.JLabel;
34 import javax.swing.JPanel;
35 import javax.swing.JRadioButton;
36 import javax.swing.JScrollPane;
37 import javax.swing.JTabbedPane;
38 import javax.swing.JTextArea;
39 import javax.swing.ScrollPaneConstants;
40 import javax.swing.event.DocumentEvent;
41 import javax.swing.event.DocumentListener;
42 import javax.swing.text.BadLocationException;
43 import javax.swing.text.Document;
44
45 import net.sf.sail.jaxb.extension.BlockInteractionType;
46
47 import org.apache.commons.io.IOUtils;
48 import org.apache.commons.lang.StringUtils;
49 import org.imsglobal.xsd.imsqti_v2p0.ChoiceInteractionType;
50 import org.imsglobal.xsd.imsqti_v2p0.ExtendedTextInteractionType;
51 import org.imsglobal.xsd.imsqti_v2p0.SimpleChoiceType;
52 import org.telscenter.pas.common.ui.CommonUI;
53 import org.telscenter.pas.common.ui.panel.CardPanel;
54 import org.telscenter.pas.ui.icons.PasCommonIconProvider;
55 import org.telscenter.pas.util.QTIUtils;
56 import org.w3c.tidy.ant.JTidyTask;
57 import org.xhtmlrenderer.simple.XHTMLPanel;
58
59 /***
60 * @author aperritano
61 *
62 */
63 public abstract class JaxbQtiStepUI extends JPanel implements IQtiStepUI {
64
65 protected static final Logger logger = Logger.getLogger(JaxbQtiStepUI.class
66 .getName());
67
68 private static final long serialVersionUID = 1L;
69
70 public static final ImageIcon greenCheck = new ImageIcon(
71 PasCommonIconProvider.getImage("guessCorrect.gif"));
72
73 public static final ImageIcon redX = new ImageIcon(PasCommonIconProvider.getImage("guessWrong.gif"));
74
75 protected String RESPONSE_ID = "RESPONSE_ID";
76
77 protected transient Map<ChoiceInteractionType, List<JRadioButton>> choiceInteractionToChoiceButtonLists = new HashMap<ChoiceInteractionType, List<JRadioButton>>();
78
79 protected CardPanel cardPanel = new CardPanel();
80
81 protected JPanel mainPanel;
82
83 protected JButton previousTabButton;
84
85 protected JButton nextTabButton;
86
87 protected JTabbedPane tabbedPane;
88
89 protected Map<JButton, ExtendedTextInteractionType> placeholderButtonsToTextInteractions = new HashMap<JButton, ExtendedTextInteractionType>();
90
91 protected Map<ExtendedTextInteractionType, JTextArea> textInteractionsToTextareas = new HashMap<ExtendedTextInteractionType, JTextArea>();
92
93 protected ImageIcon checkIcon = new ImageIcon(PasCommonIconProvider
94 .getImage("accept.png"));
95
96 protected boolean showTabButtons;
97
98 protected boolean showGetStartedPanel;
99
100 protected boolean showPlaceHolderPanel = true;
101
102 protected Map<BlockInteractionType, Integer> interactionsToTabindex = new HashMap<BlockInteractionType, Integer>();
103
104 protected JaxbQtiStep assessment;
105
106 protected static String ITEM_COMPLETED = "ITEM_COMPLETED";
107
108 private static int PROMPT_PANEL_WIDTH = 300;
109
110 private static int PROMPT_PANEL_HEIGHT = 300;
111
112 /***
113 *
114 */
115
116 public JaxbQtiStepUI(){
117
118
119 }
120
121 public JaxbQtiStepUI(JaxbQtiStep assessment) {
122 this.assessment = assessment;
123 initUI();
124 }
125
126 protected abstract void initUI();
127
128 class NextTabAction extends AbstractAction {
129
130 public NextTabAction(String title, ImageIcon icon) {
131 super(title, icon);
132 }
133
134 public void actionPerformed(ActionEvent e) {
135 tabbedPane.setSelectedIndex(tabbedPane.getSelectedIndex() + 1);
136 checkTab();
137 }
138 }
139
140 class PreviousTabAction extends AbstractAction {
141
142 public PreviousTabAction(String title, ImageIcon icon) {
143 super(title, icon);
144 }
145
146 public void actionPerformed(ActionEvent e) {
147 tabbedPane.setSelectedIndex(tabbedPane.getSelectedIndex() - 1);
148 checkTab();
149 }
150 }
151
152 class GetStartedAction extends AbstractAction {
153
154 /***
155 *
156 */
157 public GetStartedAction(String title) {
158 super(title);
159
160 }
161
162 public void actionPerformed(ActionEvent e) {
163 JButton button = (JButton) e.getSource();
164
165 ExtendedTextInteractionType et = placeholderButtonsToTextInteractions
166 .get(button);
167
168 JTextArea area = textInteractionsToTextareas.get(et);
169
170 String textInArea = area.getText();
171
172 String placeholderText = et.getPlaceholderText();
173 StringBuffer buff;
174 if (placeholderText != null)
175 buff = new StringBuffer(placeholderText);
176 else
177 buff = new StringBuffer();
178
179 buff.append(" ").append(textInArea);
180
181 area.setText("");
182 area.setText(buff.toString());
183 button.setEnabled(false);
184
185 }
186
187 }
188
189 protected void showTabChecked(int selectedTab) {
190 tabbedPane.setIconAt(selectedTab, checkIcon);
191 ((JComponent) tabbedPane.getComponentAt(selectedTab))
192 .putClientProperty(ITEM_COMPLETED, new Boolean(true));
193 }
194
195 protected void showTabUnChecked(int selectedTab) {
196 tabbedPane.setIconAt(selectedTab, null);
197 ((JComponent) tabbedPane.getComponentAt(selectedTab))
198 .putClientProperty(ITEM_COMPLETED, new Boolean(false));
199 }
200
201 class AssDocumentAdapter implements DocumentListener {
202
203 protected ExtendedTextInteractionType eti;
204
205 public AssDocumentAdapter(ExtendedTextInteractionType eti) {
206 this.eti = eti;
207 }
208
209 public void insertUpdate(DocumentEvent e) {
210
211 if(assessment.getTabbed() ) {
212 showTabChecked(tabbedPane.getSelectedIndex());
213 }
214 insertReponses(e);
215
216 }
217
218 public void removeUpdate(DocumentEvent e) {
219
220 Document document = e.getDocument();
221 if (document.getLength() == 0) {
222
223
224 Set<JButton> keySet = placeholderButtonsToTextInteractions.keySet();
225
226 for (Iterator<JButton> iter = keySet.iterator(); iter.hasNext();) {
227 JButton button = iter.next();
228 ExtendedTextInteractionType newEti = placeholderButtonsToTextInteractions
229 .get(button);
230
231 if (this.eti.equals(newEti)) {
232
233 if (eti.getPlaceholderText() != null)
234 button.setEnabled(true);
235
236 }
237
238 }
239
240
241 if( assessment.getTabbed() ) {
242 showTabUnChecked(tabbedPane.getSelectedIndex());
243 }
244
245 }
246 insertReponses(e);
247 }
248
249 public void changedUpdate(DocumentEvent e) {
250
251
252 }
253
254 /***
255 * @param e
256 */
257 private void insertReponses(DocumentEvent e) {
258 Document document = e.getDocument();
259 String responseID = (String) document.getProperty(RESPONSE_ID);
260 if (responseID == null)
261 throw new NullPointerException();
262
263 String responseText = null;
264 try {
265 responseText = document.getText(0, document.getLength());
266 } catch (BadLocationException e1) {
267 e1.printStackTrace();
268 }
269 assessment.getResponses().put(responseID, responseText);
270
271
272 }
273
274 }
275
276 public boolean isCompleted() {
277
278 if( tabbedPane == null )
279 return true;
280
281 if( tabbedPane.getComponents() == null)
282 return true;
283
284 Component[] components = tabbedPane.getComponents();
285
286
287
288 for (int i = 0; i < components.length; i++) {
289 JComponent component = (JComponent) components[i];
290 Boolean clientProperty = (Boolean) component
291 .getClientProperty(ITEM_COMPLETED);
292 if (clientProperty != null) {
293 if (clientProperty.booleanValue() == false)
294 return false;
295 }
296 }
297
298 return true;
299
300 }
301
302 protected void checkTab() {
303 if(tabbedPane != null){
304 int numOfTabs = tabbedPane.getTabCount();
305 int selectedTabIndex = tabbedPane.getSelectedIndex();
306
307 nextTabButton.setToolTipText("Flip to the next tab");
308 nextTabButton.setEnabled(false);
309 previousTabButton.setEnabled(false);
310 previousTabButton.setToolTipText("Flip to the previous tab");
311
312
313 if ((numOfTabs > 1) && (selectedTabIndex != (numOfTabs - 1))) {
314 nextTabButton.setEnabled(true);
315 } else {
316 nextTabButton.setEnabled(false);
317 }
318
319
320 if ((numOfTabs > 1) && (selectedTabIndex != 0)) {
321 previousTabButton.setEnabled(true);
322 } else {
323 previousTabButton.setEnabled(false);
324 }
325 }
326 }
327
328 /***
329 * @param interaction
330 * @param itemIndex
331 * @return
332 */
333 protected JPanel createAssessmentItemPanel(
334 final BlockInteractionType interaction) {
335 if (interaction instanceof ExtendedTextInteractionType) {
336 final ExtendedTextInteractionType textInteraction = (ExtendedTextInteractionType) interaction;
337 return createTextInteractionPanel(textInteraction);
338 } else if (interaction instanceof ChoiceInteractionType) {
339 final ChoiceInteractionType choiceInteraction = (ChoiceInteractionType) interaction;
340 return createChoiceInteractionPanel(choiceInteraction);
341 } else {
342 throw new IllegalArgumentException("normal BlockeInteraction not supported yet?");
343 }
344 }
345
346 protected JPanel createChoiceInteractionPanel(
347 final ChoiceInteractionType choiceInteraction) {
348 JPanel formPanel = new JPanel();
349
350 double pref = TableLayout.PREFERRED;
351 double fill = TableLayout.FILL;
352 double vSpace = 5;
353 double hSpace = 6;
354 double size[][] = { { fill
355 },
356
357 { pref, vSpace, pref, vSpace, pref }
358
359 };
360 formPanel.setLayout(new TableLayout(size));
361 formPanel.setOpaque(false);
362
363 JComponent promptPanel = createPromptPanel((BlockInteractionType) choiceInteraction);
364
365 promptPanel.setBorder(BorderFactory.createEmptyBorder());
366
367 formPanel.add(promptPanel, "0,0");
368
369 JPanel choiceAreaPanel = createChoiceAreaPanel(choiceInteraction);
370 choiceAreaPanel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
371 formPanel.add(choiceAreaPanel, "0,2, l, t");
372
373 return formPanel;
374 }
375
376 /***
377 * @param choiceInteraction
378 * @return
379 */
380 protected JPanel createChoiceAreaPanel(
381 final ChoiceInteractionType choiceInteraction) {
382
383 List<JRadioButton> choiceButtonList = new ArrayList<JRadioButton>();
384 List<SimpleChoiceType> simpleChoices = choiceInteraction.getSimpleChoice();
385 ButtonGroup group = new ButtonGroup();
386 JPanel buttonPanel = new JPanel();
387
388
389 buttonPanel.setOpaque(false);
390 buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.PAGE_AXIS));
391 String studentAnswer = JaxbQtiStep.getStudentAnswer(assessment,
392 (BlockInteractionType) choiceInteraction);
393 for (SimpleChoiceType sc : simpleChoices) {
394 JRadioButton scButton = new JRadioButton();
395
396
397 if (studentAnswer != null
398 && studentAnswer.equals(sc.getIdentifier())) {
399 scButton.setSelected(true);
400 } else {
401 scButton.setSelected(false);
402 }
403 scButton.setOpaque(false);
404 scButton.putClientProperty(QTIUtils.SIMPLE_CHOICE, sc);
405 scButton.putClientProperty(RESPONSE_ID, choiceInteraction
406 .getResponseIdentifier());
407 scButton.addActionListener(new ActionListener() {
408
409 public void actionPerformed(ActionEvent e) {
410 JRadioButton radioButton = (JRadioButton) e.getSource();
411
412 if (radioButton.isSelected()) {
413 SimpleChoiceType sc = ((SimpleChoiceType) radioButton
414 .getClientProperty(QTIUtils.SIMPLE_CHOICE));
415
416 assessment.getResponses().put(
417 (String) radioButton
418 .getClientProperty(RESPONSE_ID),
419 sc.getIdentifier());
420
421 System.out.println("responses: "
422 + assessment.getResponses().toString());
423 if(tabbedPane != null)
424 showTabChecked(tabbedPane.getSelectedIndex());
425 }
426 }
427 });
428
429 List<Serializable> choiceContent = sc.getContent();
430 for (Serializable serializable : choiceContent) {
431 if (serializable instanceof String) {
432
433 String choiceString = StringUtils.trimToNull((String) serializable);
434
435 if( choiceString != null)
436 scButton.setText(choiceString);
437 }
438 }
439
440
441
442
443 choiceButtonList.add(scButton);
444 buttonPanel.add(scButton);
445
446 group.add(scButton);
447 }
448 buttonPanel.add(Box.createVerticalStrut(10));
449 choiceInteractionToChoiceButtonLists.put(choiceInteraction,
450 choiceButtonList);
451
452
453
454
455 return buttonPanel;
456
457 }
458
459 /***
460 * @param textInteraction
461 * @return
462 */
463 private JPanel createTextInteractionPanel(
464 final ExtendedTextInteractionType textInteraction) {
465
466 JPanel formPanel = new JPanel();
467 formPanel.setBorder(BorderFactory.createEmptyBorder());
468 double pref = TableLayout.PREFERRED;
469 double fill = TableLayout.FILL;
470 double vSpace = 5;
471 double hSpace = 6;
472 double size[][] = { { fill
473 },
474
475 { pref, vSpace, pref, vSpace, fill }
476
477 };
478
479 JComponent promptPanel = createPromptPanel((BlockInteractionType) textInteraction);
480
481 promptPanel.setBorder(BorderFactory.createEmptyBorder());
482
483
484
485
486
487
488 String placeholderText = textInteraction.getPlaceholderText();
489
490 placeholderText = StringUtils.trimToNull(placeholderText);
491 JComponent textAreaPanel = createTextAreaPanel(textInteraction);
492 textAreaPanel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
493
494 formPanel.setLayout(new TableLayout(size));
495 formPanel.setOpaque(false);
496
497 formPanel.add(promptPanel, "0,0");
498
499
500 if( showPlaceHolderPanel ) {
501
502 if (placeholderText != null) {
503 if( assessment.isInjectPrompt() ) {
504 formPanel.add(textAreaPanel, "0,2");
505 } else {
506 JPanel getStartedButtonPanel = createGetStartedButtonPanel(textInteraction);
507 getStartedButtonPanel.setBorder(BorderFactory.createEmptyBorder(0,
508 4, 0, 0));
509 formPanel.add(getStartedButtonPanel, "0,2");
510 formPanel.add(textAreaPanel, "0,4");
511 }
512
513
514
515 } else {
516 formPanel.add(textAreaPanel, "0,2");
517 }
518 } else {
519
520 formPanel.add(textAreaPanel, "0,2");
521 }
522 JPanel t = new JPanel(new BorderLayout(0, 0));
523 t.add(formPanel, BorderLayout.NORTH);
524
525 return t;
526
527 }
528
529 /***
530 * @param textInteraction
531 * @param itemIndex
532 * @return
533 */
534 private JComponent createTextAreaPanel(
535 ExtendedTextInteractionType textInteraction) {
536
537 JTextArea answerArea = getAnswerArea(textInteraction);
538
539 String studentAnswer = JaxbQtiStep.getStudentAnswer(assessment,
540 (BlockInteractionType) textInteraction);
541
542 String placeholderText = textInteraction.getPlaceholderText();
543
544 if (studentAnswer != null) {
545 answerArea.setText(studentAnswer);
546 } else {
547 if (placeholderText != null || StringUtils.trimToNull(placeholderText) != null) {
548 if( assessment.isInjectPrompt() ) {
549 answerArea.setText(placeholderText);
550 }
551 }
552 }
553
554 textInteractionsToTextareas.put(textInteraction, answerArea);
555
556 int expectedLines = textInteraction.getExpectedLines().intValue();
557
558 if (expectedLines == 0) {
559 answerArea.setRows(10);
560 } else {
561 answerArea.setRows(expectedLines);
562 }
563
564 answerArea.setColumns(35);
565
566 answerArea.getDocument().putProperty(RESPONSE_ID,
567 answerArea.getClientProperty(RESPONSE_ID));
568
569 answerArea.getDocument().addDocumentListener(
570 new AssDocumentAdapter(textInteraction));
571
572 JScrollPane spane = getScrollPane(answerArea);
573
574 return spane;
575 }
576
577 /***
578 * @param answerArea
579 * @return
580 */
581 private JScrollPane getScrollPane(JTextArea answerArea) {
582 JScrollPane spane = new JScrollPane(answerArea);
583 spane.setOpaque(false);
584 spane
585 .setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
586 spane
587 .setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
588 spane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
589 spane.getVerticalScrollBar().getModel().setValue(
590 spane.getVerticalScrollBar().getModel().getMinimum());
591 spane.setPreferredSize(new Dimension(answerArea
592 .getPreferredScrollableViewportSize().width, answerArea
593 .getPreferredScrollableViewportSize().height + 5));
594
595
596 spane.requestFocusInWindow();
597
598 return spane;
599 }
600
601 /***
602 * @param textInteraction
603 * @return
604 */
605 private JTextArea getAnswerArea(ExtendedTextInteractionType textInteraction) {
606 JTextArea answerArea = new JTextArea();
607 answerArea.setName(textInteraction.getResponseIdentifier());
608
609 answerArea.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
610
611
612
613 answerArea.putClientProperty(RESPONSE_ID, textInteraction
614 .getResponseIdentifier());
615 answerArea.setFocusable(true);
616 answerArea.requestFocusInWindow();
617 answerArea.setEditable(true);
618 answerArea.setBorder(CommonUI.createEtchedBorder());
619 answerArea.setOpaque(true);
620 answerArea.setWrapStyleWord(true);
621 answerArea.setLineWrap(true);
622 answerArea.setFocusCycleRoot(true);
623 return answerArea;
624 }
625
626 /***
627 * @param textInteraction
628 * @return
629 */
630 private JPanel createGetStartedButtonPanel(
631 ExtendedTextInteractionType textInteraction) {
632 JPanel startedPanel = new JPanel(new BorderLayout(0, 0));
633 startedPanel.setOpaque(false);
634
635 JButton showPlaceholderButton = new JButton(new GetStartedAction(Messages.getString("Note.GET_STARTED")));
636
637 CommonUI.makeButtonTransparent(showPlaceholderButton);
638 CommonUI.makeLink(showPlaceholderButton);
639
640
641 startedPanel.add(showPlaceholderButton, BorderLayout.WEST);
642 placeholderButtonsToTextInteractions.put(showPlaceholderButton,
643 textInteraction);
644
645 return startedPanel;
646 }
647
648 /***
649 * @param formPanel
650 * @param textI
651 * @return
652 * @return
653 */
654 public static JComponent createPromptPanel(BlockInteractionType blockInteraction) {
655
656 if (blockInteraction == null)
657 return new JLabel("There was an error creating this prompt.");
658
659 final XHTMLPanel panel = new XHTMLPanel();
660 panel.setName("promptPanel");
661
662
663 panel.setOpaque(false);
664 panel.addComponentListener(new ComponentListener() {
665
666 public void componentHidden(ComponentEvent e) {
667
668
669 }
670
671 public void componentMoved(ComponentEvent e) {
672
673
674 }
675
676 public void componentResized(ComponentEvent e) {
677
678
679 }
680
681 public void componentShown(ComponentEvent e) {
682
683
684 }
685 });
686
687
688
689 InputStream stream = null;
690 String html = null;
691
692 List<Serializable> blockContent = blockInteraction.getPrompt().getContent();
693 for (Serializable serializable : blockContent) {
694 if (serializable instanceof String) {
695 html = (String) serializable;
696 }
697 }
698
699
700 String tidyHtml = CommonUI.tidyHtml(html);
701 System.out.println("New String + " + tidyHtml);
702 try {
703 stream = IOUtils.toInputStream(tidyHtml);
704 panel.setDocument(stream, null);
705 } catch (NullPointerException e) {
706 e.printStackTrace();
707 } catch (Exception e) {
708 e.printStackTrace();
709 } finally {
710 IOUtils.closeQuietly(stream);
711 }
712
713
714
715 return panel;
716
717 }
718
719 public static JComponent createIntroTextPanel(String introText) {
720
721 if (introText == null)
722 return new JLabel("There is no introductory text.");
723
724 final XHTMLPanel panel = new XHTMLPanel();
725 panel.setName("introPanel");
726
727
728 panel.setOpaque(false);
729 panel.addComponentListener(new ComponentListener() {
730
731 public void componentHidden(ComponentEvent e) {
732
733
734 }
735
736 public void componentMoved(ComponentEvent e) {
737
738
739 }
740
741 public void componentResized(ComponentEvent e) {
742
743
744 }
745
746 public void componentShown(ComponentEvent e) {
747
748
749 }
750 });
751
752
753
754 InputStream stream = null;
755 try {
756 stream = IOUtils.toInputStream(introText);
757 panel.setDocument(stream, null);
758 } catch (NullPointerException e) {
759 e.printStackTrace();
760 } catch (Exception e) {
761 e.printStackTrace();
762 } finally {
763 IOUtils.closeQuietly(stream);
764 }
765
766
767
768 return panel;
769
770 }
771
772
773 /***
774 * @return the tabbedPane
775 */
776 public JTabbedPane getTabbedPane() {
777 return tabbedPane;
778 }
779
780 /***
781 * @param tabbedPane
782 * the tabbedPane to set
783 */
784 public void setTabbedPane(JTabbedPane tabbedPane) {
785 this.tabbedPane = tabbedPane;
786 }
787
788 }