/*
* $Id$
*
* Copyright (c) 2008-2009 by Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import net.miginfocom.swing.MigLayout;
import org.jdesktop.swingworker.SwingWorker;
import org.jdesktop.swingx.JXBusyLabel;
import org.jdesktop.swingx.JXHeader;
import VASSAL.Info;
import VASSAL.i18n.Resources;
import VASSAL.tools.swing.DetailsButton;
import VASSAL.tools.swing.FlowLabel;
import VASSAL.tools.version.VassalVersion;
import VASSAL.tools.version.VersionUtils;
/**
* @since 3.1.0
* @author Joel Uckelman
*/
public class BugDialog extends JDialog {
private static final long serialVersionUID = 1L;
private Throwable thrown;
private String errorLog;
private JPanel contents;
private CardLayout deck;
private JPanel buttons;
private CardLayout button_deck;
private JTextArea descriptionArea;
private JTextField emailField;
private JScrollPane descriptionScroll;
public BugDialog(Frame owner, Throwable thrown) {
super(owner, true);
this.thrown = thrown;
this.errorLog = BugUtils.getErrorLog();
//
// header
//
final JXHeader header = new JXHeader(
Resources.getString("BugDialog.heading"),
Resources.getString("BugDialog.message"),
new ImageIcon(BugDialog.class.getResource("/icons/48x48/bug.png"))
);
//
// dialog
//
setTitle(Resources.getString("BugDialog.title"));
setLocationRelativeTo(owner);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setResizable(true);
addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
if (checkRequest != null)
checkRequest.cancel(true);
if (sendRequest != null)
sendRequest.cancel(true);
}
});
add(header, BorderLayout.NORTH);
add(buildContentsPanel(), BorderLayout.CENTER);
add(buildButtonPanel(), BorderLayout.SOUTH);
showVersionCheckPanel();
pack();
}
private Component buildContentsPanel() {
deck = new CardLayout();
contents = new JPanel(deck);
contents.setBorder(BorderFactory.createEmptyBorder(12,12,0,12));
contents.add(buildVersionCheckPanel(), "versionCheckPanel");
contents.add(buildCurrentVersionPanel(), "currentVersionPanel");
contents.add(buildSendingBugReportPanel(), "sendingBugReportPanel");
contents.add(buildOldVersionPanel(), "oldVersionPanel");
contents.add(buildConnectionFailedPanel(), "connectionFailedPanel");
contents.add(buildEmergencySavePanel(), "emergencySavePanel");
return contents;
}
private Component buildButtonPanel() {
button_deck = new CardLayout();
buttons = new JPanel(button_deck);
buttons.add(buildVersionCheckButtons(), "versionCheckButtons");
buttons.add(buildCurrentVersionButtons(), "currentVersionButtons");
buttons.add(buildSendingBugReportButtons(), "sendingBugReportButtons");
buttons.add(buildOldVersionButtons(), "oldVersionButtons");
buttons.add(buildConnectionFailedButtons(), "connectionFailedButtons");
buttons.add(buildEmergencySaveButtons(), "emergencySaveButtons");
return buttons;
}
private Component buildVersionCheckPanel() {
final JXBusyLabel spinner = new JXBusyLabel(new Dimension(40,40));
spinner.setBusy(true);
final FlowLabel label =
new FlowLabel(Resources.getString("BugDialog.collecting_details"));
final JPanel panel = new JPanel(
new MigLayout("", "", "[]push[]push"));
panel.add(label, "cell 0 0, growx, pushx");
panel.add(spinner, "cell 0 1, align center");
return panel;
}
private Component buildVersionCheckButtons() {
final JButton cancelButton = new JButton(
new AbstractAction(Resources.getString(Resources.CANCEL)) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
dispose();
}
}
);
// FIXME: tags don't push buttons to ends?
final JPanel panel = new JPanel(new MigLayout("align right"));
panel.add(cancelButton, "tag cancel");
return panel;
}
private Component buildCurrentVersionPanel() {
final FlowLabel label = new FlowLabel(
Resources.getString("BugDialog.current_version_instructions"));
descriptionArea = new JTextArea(10, 1);
descriptionArea.setLineWrap(true);
descriptionArea.setWrapStyleWord(true);
descriptionScroll = new JScrollPane(descriptionArea);
final JLabel descriptionLabel =
new JLabel(Resources.getString("BugDialog.bug_description"));
descriptionLabel.setFont(
descriptionLabel.getFont().deriveFont(Font.BOLD));
descriptionLabel.setLabelFor(descriptionScroll);
emailField = new JTextField(26);
final JLabel emailLabel = new JLabel(
Resources.getString("BugDialog.user_email_address"));
emailLabel.setLabelFor(emailField);
final JScrollPane detailsScroll = buildDetailsScroll();
final DetailsButton detailsButton = new DetailsButton(
Resources.getString("Dialogs.show_details"),
Resources.getString("Dialogs.hide_details"),
detailsScroll
);
detailsButton.setBuddy(label);
final JPanel panel = new JPanel(new MigLayout(
"hidemode 3", "", "[]unrel[]rel[]unrel[]unrel[]rel[]"));
panel.add(label, "cell 0 0, growx, pushx");
panel.add(descriptionLabel, "cell 0 1");
panel.add(descriptionScroll, "cell 0 2, grow, push");
panel.add(emailLabel, "cell 0 3");
panel.add(emailField, "cell 0 3, growx, pushx");
panel.add(detailsButton, "cell 0 4");
panel.add(detailsScroll, "cell 0 5, grow, push");
return panel;
}
private Component buildCurrentVersionButtons() {
final JButton sendButton = new JButton(
new AbstractAction(Resources.getString("BugDialog.send_button")) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
showSendingBugReportPanel();
}
}
);
final JButton dontSendButton = new JButton(
new AbstractAction(Resources.getString("BugDialog.dont_send_button")) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
showEmergencySavePanel();
}
}
);
final JPanel panel = new JPanel(new MigLayout("align right"));
panel.add(sendButton, "tag ok");
panel.add(dontSendButton, "tag cancel");
return panel;
}
private JScrollPane buildDetailsScroll() {
final JTextArea detailsArea = new JTextArea(errorLog, 10, 20);
detailsArea.setEditable(false);
detailsArea.setTabSize(2);
return new JScrollPane(detailsArea);
}
private Component buildOldVersionPanel() {
final FlowLabel label = new FlowLabel(
Resources.getString("BugDialog.old_version_instructions"));
label.addHyperlinkListener(BrowserSupport.getListener());
final JScrollPane detailsScroll = buildDetailsScroll();
final DetailsButton detailsButton = new DetailsButton(
Resources.getString("Dialogs.show_details"),
Resources.getString("Dialogs.hide_details"),
detailsScroll
);
detailsButton.setBuddy(label);
final JPanel panel = new JPanel(new MigLayout(
"hidemode 3", "", "[]unrel[]rel[]"));
panel.add(label, "cell 0 0, growx, pushx");
panel.add(detailsButton, "cell 0 1");
panel.add(detailsScroll, "cell 0 2, grow, push");
return panel;
}
private Component buildOldVersionButtons() {
final JButton okButton = new JButton(
new AbstractAction(Resources.getString(Resources.OK)) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
showEmergencySavePanel();
}
}
);
final JPanel panel = new JPanel(new MigLayout("align right"));
panel.add(okButton, "tag ok");
return panel;
}
private Component buildConnectionFailedPanel() {
final String errorLogPath =
new File(Info.getConfDir(), "errorLog").getAbsolutePath();
final FlowLabel label = new FlowLabel(Resources.getString(
"BugDialog.connection_failed_instructions", errorLogPath));
label.addHyperlinkListener(BrowserSupport.getListener());
final JScrollPane detailsScroll = buildDetailsScroll();
final DetailsButton detailsButton = new DetailsButton(
Resources.getString("Dialogs.show_details"),
Resources.getString("Dialogs.hide_details"),
detailsScroll
);
detailsButton.setBuddy(label);
final JPanel panel = new JPanel(new MigLayout(
"hidemode 3", "", "[]unrel[]rel[]"));
panel.add(label, "cell 0 0, growx, pushx");
panel.add(detailsButton, "cell 0 1");
panel.add(detailsScroll, "cell 0 2, grow, push");
return panel;
}
private Component buildConnectionFailedButtons() {
final JButton okButton = new JButton(
new AbstractAction(Resources.getString(Resources.OK)) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
showEmergencySavePanel();
}
}
);
final JPanel panel = new JPanel(new MigLayout("align right"));
panel.add(okButton, "tag ok");
return panel;
}
private Component buildEmergencySavePanel() {
final FlowLabel label =
new FlowLabel(Resources.getString("BugDialog.how_to_proceed"));
final JPanel panel = new JPanel(new MigLayout("", "", "[]push"));
panel.add(label, "cell 0 0, growx, pushx");
return panel;
}
private Component buildEmergencySaveButtons() {
/*
final JButton saveButton = new JButton(
new AbstractAction("Save") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
emergencySave();
dispose();
}
}
);
final JButton dontSaveButton = new JButton(
new AbstractAction("Don't Save") {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
dispose();
}
}
);
*/
final JButton okButton = new JButton(
new AbstractAction(Resources.getString(Resources.OK)) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
dispose();
}
}
);
final JPanel panel = new JPanel(new MigLayout("align right"));
panel.add(okButton, "tag ok");
return panel;
}
private void showVersionCheckPanel() {
deck.show(contents, "versionCheckPanel");
button_deck.show(buttons, "versionCheckButtons");
}
private void showCurrentVersionPanel() {
deck.show(contents, "currentVersionPanel");
button_deck.show(buttons, "currentVersionButtons");
}
private void showSendingBugReportPanel() {
deck.show(contents, "sendingBugReportPanel");
button_deck.show(buttons, "sendingBugReportButtons");
sendRequest = new SendRequest();
sendRequest.execute();
}
private void showOldVersionPanel() {
deck.show(contents, "oldVersionPanel");
button_deck.show(buttons, "oldVersionButtons");
}
private void showConnectionFailedPanel() {
deck.show(contents, "connectionFailedPanel");
button_deck.show(buttons, "connectionFailedButtons");
}
private void showEmergencySavePanel() {
deck.show(contents, "emergencySavePanel");
button_deck.show(buttons, "emergencySaveButtons");
}
private CheckRequest checkRequest = null;
@Override
public void setVisible(boolean visible) {
if (visible && !isVisible()) {
checkRequest = new CheckRequest();
checkRequest.execute();
}
super.setVisible(visible);
}
private class CheckRequest extends SwingWorker<Boolean,Void> {
private Timer timer = null;
@Override
protected Boolean doInBackground() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
// Wait 3 seconds before counting down the latch to ensure
// that the user has sufficient time to read the message on
// the first pane.
timer = new Timer(2000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
latch.countDown();
}
});
timer.start();
// Make the request to the server and wait for the latch.
final VassalVersion running = new VassalVersion(Info.getVersion());
final Boolean cur = VersionUtils.isCurrent(running);
latch.await();
return cur;
}
@Override
protected void done() {
try {
if (get(10, TimeUnit.SECONDS)) showCurrentVersionPanel();
// else showCurrentVersionPanel();
else showOldVersionPanel();
// else showConnectionFailedPanel();
}
catch (CancellationException e) {
// cancelled by user, do nothing
timer.stop();
}
catch (InterruptedException e) {
timer.stop();
e.printStackTrace();
showConnectionFailedPanel();
}
catch (ExecutionException e) {
timer.stop();
e.printStackTrace();
showConnectionFailedPanel();
}
catch (TimeoutException e) {
timer.stop();
e.printStackTrace();
showConnectionFailedPanel();
}
}
}
private Component buildSendingBugReportPanel() {
final JXBusyLabel spinner = new JXBusyLabel(new Dimension(40,40));
spinner.setBusy(true);
final FlowLabel label =
new FlowLabel(Resources.getString("BugDialog.sending_bug_report"));
final JPanel panel = new JPanel(
new MigLayout("", "", "[]push[]push"));
panel.add(label, "cell 0 0, growx, pushx");
panel.add(spinner, "cell 0 1, align center");
return panel;
}
private Component buildSendingBugReportButtons() {
final JButton cancelButton = new JButton(
new AbstractAction(Resources.getString(Resources.CANCEL)) {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
dispose();
}
}
);
final JPanel panel = new JPanel(new MigLayout("align right"));
panel.add(cancelButton, "tag cancel");
return panel;
}
private SendRequest sendRequest = null;
private class SendRequest extends SwingWorker<Void,Void> {
private Timer timer = null;
@Override
protected Void doInBackground() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
// Wait 3 seconds before counting down the latch to ensure
// that the user has sufficient time to read the message on
// the first pane.
timer = new Timer(2000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
latch.countDown();
}
});
timer.start();
// Make the request to the server and wait for the latch.
BugUtils.sendBugReport(
emailField.getText(),
descriptionArea.getText(),
errorLog,
thrown
);
latch.await();
return null;
}
@Override
protected void done() {
try {
get(10, TimeUnit.SECONDS);
showEmergencySavePanel();
}
catch (CancellationException e) {
// cancelled by user, do nothing
timer.stop();
}
catch (InterruptedException e) {
timer.stop();
e.printStackTrace();
showConnectionFailedPanel();
}
catch (ExecutionException e) {
timer.stop();
e.printStackTrace();
showConnectionFailedPanel();
}
catch (TimeoutException e) {
timer.stop();
e.printStackTrace();
showConnectionFailedPanel();
}
}
}
// FIXME: add a page thanking the user for his bug report and providing
// a link to it at SF.
private void emergencySave() {
// FIXME: GameModule and GameState need save methods which take a filename
/*
final GameModule mod = GameModule.getGameModule();
if (mod != null) mod.save(false);
final GameState state = mod.getGameState();
if (state != null && state.isModified()) {
state.saveGame();
}
*/
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final BugDialog bd = new BugDialog(null, null);
bd.setVisible(true);
}
});
}
}