/**
* This file is part of JSurveyLib.
*
* JSurveyLib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JSurveyLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JSurveyLib. If not, see <http://www.gnu.org/licenses/>.
**/
package org.jsurveylib.gui.swing;
import org.jsurveylib.ClientSurvey;
import org.jsurveylib.Survey;
import org.jsurveylib.gui.swing.renderer.PageRenderer;
import org.jsurveylib.gui.swing.util.ChooserUtil;
import org.jsurveylib.model.Page;
import org.jsurveylib.model.SurveyElement;
import org.jsurveylib.model.listeners.AnswerListener;
import org.jsurveylib.model.listeners.PageListener;
import org.jsurveylib.model.listeners.SurveyResetListener;
import org.jsurveylib.model.question.InsertQuestionListener;
import org.jsurveylib.model.question.Question;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
/**
* This class displays the Survey object. It extends JPanel. Here is an example of how to add a SurveyPanel to your JFrame:
* <pre><code>
* try {
* ClientSurvey survey = new Survey("surveyGettingStarted.xml");
* SurveyPanel surveyPanel = new SurveyPanel(survey);
* survey.addSurveyListener(this);
* frame.setLayout(new BorderLayout());
* frame.add(surveyPanel, BorderLayout.CENTER);
* frame.setJMenuBar(new SurveyMenu(survey, frame));
* } catch (Exception e) {
* e.printStackTrace();
* }
* </code></pre>
* <p/>
* <b>The Survey, ClientSurvey, SurveyPanel and the SurveyAdapter are the ONLY objects that the client should interact with directly. Even then, there
* may be public methods in this class that are marked "for internal use only". Methods marked "for
* internal use only" may be renamed, removed or replaced in future versions. Because of this uncertain future,
* it is highly recommended you don't use these methods: Your application may not compile when you upgrade.
* Most other classes are marked "for internal use only" but even if they aren't, these are the only
* classes that you should be interacting with.</b>
* <p/>
* Copyright (c)2007, Daniel Kaplan
*
* @author Daniel Kaplan
* @since 7.10.4
*/
public class SurveyPanel extends JPanel implements AnswerListener, PageListener, InsertQuestionListener, SurveyResetListener {
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u>. These will likely disappear in the future.
*/
public static final Font FONT_SURVEY_TITLE = new Font("SansSerif", Font.BOLD, 16);
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u> These will likely disappear in the future.
*/
public static final Font FONT_PAGE_TITLE = new Font("SansSerif", Font.PLAIN, 14);
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u> These will likely disappear in the future.
*/
public static final Font FONT_LABEL = new Font("SansSerif", Font.PLAIN, 12);
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u> These will likely disappear in the future.
*/
public static final Font FONT_QUESTION_NUMBER = new Font("SansSerif", Font.BOLD, 12);
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u> These will likely disappear in the future.
*/
public static final Font FONT_QUESTION_TEXT = new Font("SansSerif", Font.PLAIN, 12);
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u> These will likely disappear in the future.
*/
public static final Font FONT_QUESTION_ANSWER = new Font("SansSerif", Font.PLAIN, 12);
// Page management
/**
* The panel containing the question objects
*/
private JPanel pageArea;
/**
* The card layout used to switch between pages
*/
private CardLayout pager;
/**
* The vector of pages
*/
private List<PageRenderer> pageComponents = new ArrayList<PageRenderer>();
/**
* The label indicating the current page
*/
private JLabel pageNumberLabel;
/**
* Navigation
*/
private JButton previousButton;
private JButton nextButton;
private JButton finishButton;
private Survey survey;
/**
* This will initialize the SurveyPanel object. The Panel queries the survey object for
* its content and renders it on the screen. The Survey will start on the first page.
*
* @param survey The survey object that is rendered by this SurveyPanel.
*/
public SurveyPanel(ClientSurvey survey) {
this.survey = (Survey) survey;
this.survey.addPageListener(this);
this.survey.addInsertQuestionListener(this);
this.survey.addSurveyResetListener(this);
reset(); //this will initialize the UI
}
private void reset() {
pageComponents.clear();
createUI();
switchPage(); //go to the first page
}
/**
* Create the User Interface of the survey
*/
private void createUI() {
removeAll();
//basic configuration
setLayout(new BorderLayout(12, 12));
setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
//the title of the survey
String title = survey.getTitle();
if (title != null && !title.equals("")) {
JLabel titleLabel = new JLabel(title);
titleLabel.setFont(SurveyPanel.FONT_SURVEY_TITLE);
add(titleLabel, BorderLayout.NORTH);
}
//the central area (with the questions) is warpped within a scrollpane
pageArea = new JPanel();
pager = new CardLayout();
pageArea.setLayout(pager);
//create the survey pages
createPages();
for (int i = 0; i < pageComponents.size(); i++) {
JScrollPane scroll = new JScrollPane(pageComponents.get(i),
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
pageArea.add(Integer.toString(i), scroll);
}
//scrollbar (for the case when we don't have enough space for the whole page)
add(pageArea, BorderLayout.CENTER);
//control buttons
JPanel panButtons = new JPanel(new GridLayout(1, 0, 6, 6));
previousButton = new JButton(survey.getStrings().getPreviousString());
previousButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
survey.goToPreviousPage();
}
});
panButtons.add(previousButton);
nextButton = new JButton(survey.getStrings().getNextString());
nextButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
survey.goToNextPage();
}
});
panButtons.add(nextButton);
finishButton = new JButton(survey.getStrings().getFinishString());
finishButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (survey.saveToFileOnFinish() && survey.isDirty()) {
try {
saveChanges();
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
survey.finish();
}
}
});
panButtons.add(finishButton);
JPanel statusArea = new JPanel(new BorderLayout());
statusArea.add(panButtons, BorderLayout.EAST);
pageNumberLabel = new JLabel();
pageNumberLabel.setFont(new Font("SansSerif", Font.PLAIN, 12));
statusArea.add(pageNumberLabel, BorderLayout.WEST);
add(statusArea, BorderLayout.SOUTH);
if (pageComponents.size() == 1) {
previousButton.setVisible(false);
nextButton.setVisible(false);
pageNumberLabel.setVisible(false);
}
this.invalidate();
this.validate();
}
private void saveChanges() throws IOException {
if (survey.getWorkingFilePath() != null) {
int result = JOptionPane.showConfirmDialog(null, survey.getStrings().getSaveToWorkingFileNotificationString() + survey.getWorkingFilePath(), survey.getStrings().getUnsavedChangesString(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
survey.saveXMLAnswers(survey.getWorkingFilePath());
survey.finish();
}
} else {
int result = JOptionPane.showConfirmDialog(null, survey.getStrings().getSaveToNewFileNotificationString(), survey.getStrings().getUnsavedChangesString(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
//this will fail if a JFrame is not used
ChooserUtil.saveSurveyChooser((JFrame) SwingUtilities.getRoot(this), survey);
if (!survey.isDirty()) {
//if the survey isn't dirty, that means the user saved with the save survey chooser
survey.finish();
}
}
}
}
/**
* Configure the buttons (set them enabled / disabled)
*/
private void configButtons() {
previousButton.setEnabled(survey.isPreviousPageAvailable());
nextButton.setEnabled(survey.isNextPageAvailable());
finishButton.setEnabled(survey.isLastPageAndComplete());
}
/**
* Break the question to the different pages
*/
private void createPages() {
for (Page page : survey.getPages()) {
String pageLabel = page.getLabel().trim();
PageRenderer pageRenderer = startNewPage(pageLabel);
//add the questions
for (SurveyElement element : page.getSurveyElements()) {
pageRenderer.addElement(element);
if (element instanceof Question) {
((Question) element).addAnswerListener(this);
}
}
pageRenderer.packElements();
}
}
/**
* Create a new page object and return its renderer
* @return The PageRenderer of this page
* @param pageLabel The label of this page
*/
private PageRenderer startNewPage(String pageLabel) {
PageRenderer cPage = new PageRenderer(survey.getStrings());
cPage.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), pageLabel));
pageComponents.add(cPage);
return cPage;
}
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u> This will be called when a question's answer has changed.
* The panel will react by enabling/disabling its buttons. This is for internal use because only JSurveyLib should be calling
* this method.
*
* @param question The question who's answer has changed
* @param evaluateScript This boolean is ignored
*/
public void answerChanged(Question question, boolean evaluateScript) {
configButtons();
}
/**
* <u><b><font color="red">FOR INTERNAL USE ONLY.</font></b></u> This method will be called to inform the
* panel that it should display a different page. This is internal because only the Survey object should be calling
* this method.
*/
public void currentPageChanged() {
switchPage();
}
private void switchPage() {
pager.show(pageArea, Integer.toString(survey.getCurrentPageNumber()));
pageNumberLabel.setText(survey.getStrings().getPageString() + " " + (survey.getCurrentPageNumberExcludingSkipped() + 1) + " " + survey.getStrings().getOfString() + " " + survey.getTotalPagesExcludingSkipped());
configButtons();
}
public void questionInserted(Question question, int page, int row) {
question.addAnswerListener(this);
pageComponents.get(page).insertQuestion(question, row);
}
public void surveyReset() {
reset();
}
}