/* * MultipleChoicePanel.java * * Created on 31 August 2006, 20:48 */ package uk.co.bytemark.vm.enigma.inquisition.gui.quiz; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextPane; import javax.swing.JToggleButton; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import uk.co.bytemark.vm.enigma.inquisition.questions.Answer; import uk.co.bytemark.vm.enigma.inquisition.questions.MultipleChoiceAnswer; import uk.co.bytemark.vm.enigma.inquisition.questions.MultipleChoiceQuestion; import uk.co.bytemark.vm.enigma.inquisition.questions.MultipleChoiceQuestionInstance; import uk.co.bytemark.vm.enigma.inquisition.questions.MultipleChoiceRenderingHelper; import uk.co.bytemark.vm.enigma.inquisition.questions.Option; import uk.co.bytemark.vm.enigma.inquisition.questions.Question; import uk.co.bytemark.vm.enigma.inquisition.questions.QuestionRenderingHelper; import uk.co.bytemark.vm.enigma.inquisition.questions.QuestionSetManager; public class MultipleChoicePanel extends QuestionPanel { private static final Color CORRECT_COLOUR = new Color(0.6f, 0.85f, 0.5f); private final MultipleChoiceQuestion question; private final boolean stateNumberOfOptionsNeeded; private JToggleButton[] toggleButtons; private JLabel[] answerLabels; private final boolean[] oldCheckBoxEnabledStates; private boolean inQuestionMode = true; private final MultipleChoiceQuestionInstance questionInstance; public MultipleChoicePanel(MultipleChoiceQuestion question, AnswerChangedObserver answerChangedObserver, boolean stateNumberOfOptionsNeeded) { this.question = question; this.stateNumberOfOptionsNeeded = stateNumberOfOptionsNeeded; questionInstance = new MultipleChoiceQuestionInstance(question); List<Option> options = questionInstance.getOrderedOptions(); if (options.size() > MultipleChoiceRenderingHelper.maximumNumberOfOptions()) throw new IllegalArgumentException("Too many options in question: " + question.getOptions().size() + ", max: " + MultipleChoiceRenderingHelper.maximumNumberOfOptions()); setLayout(new BorderLayout()); splitPane = makeSplitPane(answerChangedObserver); add(splitPane, BorderLayout.CENTER); questionTextPane.setCaretPosition(0); oldCheckBoxEnabledStates = new boolean[options.size()]; } // Layout hack to attempt to get the options panel to layout correctly public void fixDividerLocation() { splitPane.resetToPreferredSizes(); } // // // Count the height of the labels // int total = 100; // Some leeway // for (JLabel label : answerLabels) { // total += label.getHeight(); // // optionsPanel.setPreferredSize(new Dimension((int) optionsPanel.getPreferredSize().getWidth(), total)); // // jSplitPane1.setDividerLocation((int) (this.getHeight() - optionsPanel.getPreferredSize().getHeight()) - // // 20); // } // int location = (this.getHeight() - total); // location = Math.max(50, location); // Always allow some of the question text to be visible // splitPane.setDividerLocation(location); // // } @Override public MultipleChoiceAnswer getAnswer() { List<Option> options = questionInstance.getOrderedOptions(); Set<Option> optionsSelected = new HashSet<Option>(); for (int i = 0; i < toggleButtons.length; i++) if (toggleButtons[i].isSelected()) optionsSelected.add(options.get(i)); return new MultipleChoiceAnswer(optionsSelected); } @Override public void setAnswer(Answer answer) { if (!(answer instanceof MultipleChoiceAnswer)) throw new IllegalArgumentException("Should be of type MultipleChoiceAnswer"); MultipleChoiceAnswer multipleChoiceAnswer = (MultipleChoiceAnswer) answer; if (multipleChoiceAnswer.equals(getAnswer())) return; Set<Option> optionsSelected = multipleChoiceAnswer.getOptionsSelected(); List<Option> orderedOptions = questionInstance.getOrderedOptions(); for (int i = 0; i < toggleButtons.length; i++) { toggleButtons[i].setSelected(optionsSelected.contains(orderedOptions.get(i))); } } private JSplitPane makeSplitPane(AnswerChangedObserver answerChangedObserver) { JSplitPane splitPane_ = new JSplitPane(); splitPane_.setOrientation(JSplitPane.VERTICAL_SPLIT); splitPane_.setResizeWeight(1.0); questionTextPane = new JTextPane(); questionTextPane.setContentType("text/html"); questionTextPane.setEditable(false); questionTextPane.setText(getQuestionText()); splitPane_.setTopComponent(new JScrollPane(questionTextPane)); optionsPanel = createOptionsPanel(answerChangedObserver); splitPane_.setBottomComponent(optionsPanel); return splitPane_; } @Override public void enterReviewMode() { if (!inQuestionMode) return; inQuestionMode = false; List<Option> orderedOptions = questionInstance.getOrderedOptions(); for (int i = 0; i < toggleButtons.length; i++) { oldCheckBoxEnabledStates[i] = toggleButtons[i].isEnabled(); toggleButtons[i].setEnabled(false); if (orderedOptions.get(i).isCorrect()) { answerLabels[i].setOpaque(true); answerLabels[i].setBackground(CORRECT_COLOUR); } } } @Override public void enterQuestionMode() { if (inQuestionMode) return; inQuestionMode = true; // Add old states if there's been a previous for (int i = 0; i < toggleButtons.length; i++) { toggleButtons[i].setEnabled(oldCheckBoxEnabledStates[i]); answerLabels[i].setOpaque(false); } } @Override public String getExplanationText() { return MultipleChoiceRenderingHelper.getExplanationText(questionInstance); } String getQuestionText() { return QuestionRenderingHelper.syntaxHighlightForJava(question.getSubstitutedQuestionText()); } @Override public Question getQuestion() { return question; } private JSplitPane splitPane; private JPanel optionsPanel; private JTextPane questionTextPane; private JPanel createOptionsPanel(final AnswerChangedObserver answerChangedObserver) { List<Option> orderedOptions = questionInstance.getOrderedOptions(); JPanel panel = new JPanel(); panel.setLayout(new GridBagLayout()); int numberOfOptions = orderedOptions.size(); toggleButtons = new JToggleButton[numberOfOptions]; answerLabels = new JLabel[numberOfOptions]; for (int i = 0; i < numberOfOptions; i++) { String optionLabelText = MultipleChoiceRenderingHelper.getOptionLabel(i) + ")"; JToggleButton toggleButton = useSingleMode() ? new JRadioButton(optionLabelText) : new JCheckBox( optionLabelText); toggleButton.setHorizontalAlignment(SwingConstants.RIGHT); toggleButton.setHorizontalTextPosition(SwingConstants.LEFT); toggleButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { answerChangedObserver.answerChanged(getAnswer()); if (stateNumberOfOptionsNeeded && !useSingleMode()) { int selectedCount = 0; for (JToggleButton button : toggleButtons) { if (button.isSelected()) selectedCount++; } if (selectedCount == question.numberOfCorrectOptions()) { for (JToggleButton button : toggleButtons) { if (!button.isSelected()) { button.setEnabled(false); } else { button.setEnabled(true); } } } else { for (JToggleButton button : toggleButtons) { button.setEnabled(true); } } } } }); toggleButtons[i] = toggleButton; GridBagConstraints toggleButtonConstraints = new GridBagConstraints(); toggleButtonConstraints.gridx = 0; toggleButtonConstraints.gridy = i; toggleButtonConstraints.insets = new Insets(0, 0, 0, 10); toggleButtonConstraints.anchor = GridBagConstraints.LINE_END; panel.add(toggleButton, toggleButtonConstraints); JLabel answerLabel = new JLabel(); answerLabel.setText("<html><body>" + orderedOptions.get(i).getOptionText() + "</body></html>"); answerLabels[i] = answerLabel; GridBagConstraints answerLabelConstraints = new GridBagConstraints(); answerLabelConstraints.gridx = 1; answerLabelConstraints.gridy = i; answerLabelConstraints.anchor = GridBagConstraints.LINE_START; answerLabelConstraints.fill = GridBagConstraints.HORIZONTAL; answerLabelConstraints.weightx = 1.0; panel.add(answerLabel, answerLabelConstraints); } // If these are going to be radio buttons, add them to a button group if (useSingleMode()) { ButtonGroup group = new ButtonGroup(); for (JToggleButton toggleButton : toggleButtons) group.add(toggleButton); } panel.setBorder(BorderFactory.createTitledBorder(MultipleChoiceRenderingHelper.optionsTitle(question, stateNumberOfOptionsNeeded))); return panel; } private boolean useSingleMode() { if (question.isSingleOptionMode()) return true; else if (stateNumberOfOptionsNeeded && question.numberOfCorrectOptions() == 1) return true; else return false; } public static void main(String[] args) { Question question = QuestionSetManager.loadBundledQuestionSets().iterator().next().iterator().next(); final MultipleChoicePanel multipleChoicePanel = new MultipleChoicePanel((MultipleChoiceQuestion) question, new AnswerChangedObserver() { public void answerChanged(Answer answer) { // Do nothing } }, false); SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame(); frame.add(multipleChoicePanel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } }); } }