/*
* org.openmicroscopy.shoola.util.ui.MessengerDialog
*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2015 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.util.ui;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;
import org.jdesktop.swingx.JXBusyLabel;
import info.clearthought.layout.TableLayout;
import org.apache.commons.collections.CollectionUtils;
import org.openmicroscopy.shoola.util.CommonsLangUtils;
import org.openmicroscopy.shoola.util.file.ImportErrorObject;
/**
* A dialog used to collect and send comments or error messages.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* after code from
* @author Brian Loranger
* <a href="mailto:brian.loranger@lifesci.dundee.ac.uk">
* brian.loranger@lifesci.dundee.ac.uk</a>
*
* @version 3.0
* <small>
* (<b>Internal version:</b> $Revision: $Date: $)
* </small>
* @since OME3.0
*/
public class MessengerDialog
extends JDialog
implements ActionListener, DocumentListener
{
/** Identifies the error dialog type. */
public static final int ERROR_TYPE = 0;
/** Identifies the error dialog type. */
public static final int COMMENT_TYPE = 1;
/** Identifies the error dialog type. */
public static final int SUBMIT_ERROR_TYPE = 2;
/** Bound property indicating to send the message. */
public static final String SEND_PROPERTY = "send";
/** Bound property indicating to close the window. */
public static final String CLOSE_MESSENGER_PROPERTY = "closeMessenger";
/** Action ID to close the dialog. */
private static final int CANCEL = 0;
/** Action ID to send comment and close the dialog. */
private static final int SEND = 1;
/** Action ID to copy on the clipboard. */
private static final int COPY = 3;
/** Action ID to indicate the consequence of not submitting files. */
private static final int SUBMIT = 4;
/** The default size of the window. */
private static final Dimension DEFAULT_SIZE = new Dimension(700, 400);
/** The tooltip of the {@link #cancelButton}. */
private static final String CANCEL_TOOLTIP = "Cancel your message";
/** The tooltip of the {@link #sendButton}. */
private static final String SEND_TOOLTIP = "Send the information to " +
"the development team";
/** The tooltip of the {@link #copyButton}. */
private static final String COPY_TOOLTIP = "Copy the Exception " +
"Message to the clipboard";
/** The default message displayed. */
private static final String MESSAGE = "Thank you for taking the time " +
"to send us your comments. \n\n" +
"Your feedback will be used to further the development of " +
"OMERO and improve our software. Any personal details you " +
"provide are purely optional, and will only be used for " +
"development purposes.\n";
/** The default message displayed. */
private static final String DEBUG_MESSAGE = "An error message has " +
"been generated by the application.\n\n" +
"To help us improve our software, please fill " +
"out the following form. Your personal details are purely " +
"optional, and will only be used for development purposes.\n\n" +
"Please note that your application may need to be restarted " +
"to work properly.\n";
/** The default message displayed. */
private static final String SUBMIT_MESSAGE = "Submit to the " +
"development team the files that failed to import.\n\n" +
"To help us improve our software, please fill " +
"out the following form. Your personal details are purely " +
"optional, and will only be used for development purposes.\n\n" +
"Please note that your application may need to be restarted " +
"to work properly.\n";
/** The default message displayed when a non valid e-mail is entered. */
private static final String EMAIL_MESSAGE = "The e-mail address " +
"entered \n does not seem to be valid. \n Please enter a new " +
"e-mail address.";
/**
* The default message displayed if user decides not to submit the files.
*/
private static final String SUBMIT_FILES_MESSAGE = "Choosing not " +
"to submit to the files will make it more difficult to " +
"fix the problem you are experimenting.\nAre you sure " +
"you do not want to submit the files?";
/** Value of the comment field. */
private static final String COMMENT_FIELD = "Comment: ";
/** Value of the comment field when an exception is specified. */
private static final String DEBUG_COMMENT_FIELD ="What you were doing" +
" when you crashed?";
/** Value of the field. */
private static final String EMAIL_FIELD = "Email: ";
/** The default tool-tip of the e-mail area. */
private static final String EMAIL_TOOLTIP = "Enter your email " +
"address here.";
/** The e-mail field's suffix. */
private static final String EMAIL_SUFFIX = " (Optional)";
/** Brief description of the error. */
private static final String ERROR_BRIEF = "Brief Description:";
/**
* One of the following constants: {@link #ERROR_TYPE} or
* {@link #COMMENT_TYPE}.
*/
private int dialogType;
/** Button to close and dispose of the window. */
private JButton cancelButton;
/** Button to post the message. */
private JButton sendButton;
/** The area displaying the <code>e-mail address</code>. */
private JTextField emailArea;
/** The comment Area. */
private MultilineLabel commentArea;
/** The e-mail address of the user submitting the message. */
private String emailAddress;
/** The exception to handle, <code>null</code> if no exception. */
private Exception exception;
/** The text pane displaying the error message. */
private JTextPane debugArea;
/** Button to copy the message on the clipBoard. */
private JButton copyButton;
/** The version of the server. */
private String serverVersion;
/** A brief description of the error. */
private String errorDescription;
/** The component displaying the files to send. */
private FileTable table;
/** Indicates the status of the files submission. */
private JXBusyLabel submitStatus;
/** Component indicating to submit the files or not. */
private JCheckBox submitFile;
/** Indicates the progress of the files submission.*/
private JXBusyLabel progress;
/** Indicates the progress of the files submission.*/
private JLabel progressLabel;
/**
* Displays the dialog indicating the consequence of not submitting
* the files.
*/
private void submitFilesControl()
{
if (!submitFile.isSelected()) {
MessageBox dialog = new MessageBox(this, "Submit Files",
SUBMIT_FILES_MESSAGE);
dialog.setResizable(false);
if (dialog.centerMsgBox() == MessageBox.NO_OPTION)
submitFile.setSelected(true);
}
}
/**
* Formats the specified button.
*
* @param b The button to format.
* @param mnemonic The key-code that indicates a mnemonic key.
* @param tooltip The button's tooltip.
* @param actionID The action id associated to the passed button.
*/
private void formatButton(JButton b, int mnemonic, String tooltip, int
actionID)
{
b.setMnemonic(mnemonic);
b.setOpaque(false);
b.setToolTipText(tooltip);
b.addActionListener(this);
b.setActionCommand(""+actionID);
}
/** Hides the window and disposes. */
private void close()
{
setVisible(false);
dispose();
firePropertyChange(CLOSE_MESSENGER_PROPERTY, Boolean.valueOf(false),
Boolean.valueOf(true));
}
/** Copies the error message on the clipboard. */
private void copy()
{
if (debugArea != null) {
debugArea.selectAll();
debugArea.copy();
}
}
/**
* Sends the error to the server.
*
* @param propertyName The name of the property.
*/
private void sendError(String propertyName)
{
String email = emailArea.getText().trim();
String comment = commentArea.getText().trim();
String error = null;
if (debugArea != null) error = debugArea.getText().trim();
MessengerDetails details = new MessengerDetails(email, comment);
details.setExtra(serverVersion);
details.setError(error);
firePropertyChange(propertyName, null, details);
close();
}
/**
* Sends the message.
*
* @param propertyName The name of the property to fire.
*/
private void send(String propertyName)
{
if (dialogType == SUBMIT_ERROR_TYPE) {
List<FileTableNode> files = null;
if (table != null) files = table.getSelectedFiles();
if (CollectionUtils.isEmpty(files)) {
sendError(propertyName);
} else {
String email = emailArea.getText().trim();
String comment = commentArea.getText().trim();
MessengerDetails details = new MessengerDetails(email, comment);
details.setExtra(serverVersion);
details.setObjectToSubmit(files);
submitStatus.setVisible(true);
submitStatus.setBusy(true);
details.setExceptionOnly(!submitFile.isSelected());
firePropertyChange(propertyName, null, details);
}
} else {
sendError(propertyName);
}
sendButton.setEnabled(false);
}
/** Initializes the various components. */
private void initComponents()
{
progress = new JXBusyLabel(new Dimension(16, 16));
progress.setVisible(false);
progressLabel = new JLabel();
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) { close(); }
});
cancelButton = new JButton("Cancel");
formatButton(cancelButton, 'C', CANCEL_TOOLTIP, CANCEL);
sendButton = new JButton("Send");
formatButton(sendButton, 'S', SEND_TOOLTIP, SEND);
emailArea = new JTextField(20);
emailArea.setToolTipText(EMAIL_TOOLTIP);
emailArea.setText(emailAddress);
commentArea = new MultilineLabel();
commentArea.setEditable(true);
commentArea.setBackground(UIUtilities.BACKGROUND_COLOR);
commentArea.setOpaque(true);
if (exception != null) {
debugArea = buildExceptionArea();
copyButton = new JButton("Copy to Clipboard");
formatButton(copyButton, 'C', COPY_TOOLTIP, COPY);
}
setAlwaysOnTop(true);
if (dialogType == COMMENT_TYPE) {
sendButton.setEnabled(false);
commentArea.getDocument().addDocumentListener(this);
}
submitStatus = new JXBusyLabel(new Dimension(16, 16));
submitStatus.setText("Uploading files");
submitStatus.setVisible(false);
submitFile = new JCheckBox("Files");
submitFile.setSelected(true);
submitFile.addActionListener(this);
submitFile.setActionCommand(""+SUBMIT);
}
/**
* Builds the UI component displaying the exception.
*
* @return See above.
*/
private JTextPane buildExceptionArea()
{
JTextPane pane = UIUtilities.buildExceptionArea();
StyledDocument document = pane.getStyledDocument();
Style style = pane.getLogicalStyle();
//Get the full debug text
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
exception.printStackTrace(pw);
try {
document.insertString(document.getLength(), sw.toString(), style);
} catch (BadLocationException e) {}
return pane;
}
/**
* Builds and lays out the panel hosting the <code>comment</code> details.
*
* @param comment The comment's text.
* @param mnemonic The key-code that indicates a mnemonic key.
* @return See above.
*/
private JPanel buildCommentAreaPanel(String comment, int mnemonic)
{
JPanel panel = new JPanel();
panel.setOpaque(false);
double size[][] = {{TableLayout.FILL}, {20, TableLayout.FILL}};
TableLayout layout = new TableLayout(size);
panel.setLayout(layout);
JScrollPane areaScrollPane = new JScrollPane(commentArea);
areaScrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
commentArea.setPreferredSize(new Dimension(300,100));
JLabel label = new JLabel(comment);
label.setOpaque(false);
label.setDisplayedMnemonic(mnemonic);
panel.add(label, "0, 0, LEFT, CENTER");
panel.add(areaScrollPane, "0, 1");
return panel;
}
/**
* Builds and lays out the panel hosting the <code>email</code> details.
*
* @param mnemonic The key-code that indicates a mnemonic key.
* @return See above.
*/
private JPanel buildEmailAreaPanel(int mnemonic)
{
double[][] size = null;
JPanel panel = new JPanel();
panel.setOpaque(false);
if (EMAIL_SUFFIX.length() == 0)
size = new double[][]{{TableLayout.PREFERRED, TableLayout.FILL},
{30}};
else
size = new double[][]
{{TableLayout.PREFERRED,TableLayout.FILL,
TableLayout.PREFERRED}, {30}};
TableLayout layout = new TableLayout(size);
panel.setLayout(layout);
JLabel label = new JLabel(EMAIL_FIELD);
label.setDisplayedMnemonic(mnemonic);
label.setLabelFor(emailArea);
label.setOpaque(false);
panel.add(label, "0, 0, RIGHT, CENTER");
panel.add(emailArea, "1, 0, FULL, CENTER");
if (EMAIL_SUFFIX.length() != 0)
panel.add(new JLabel(EMAIL_SUFFIX), "2, 0, LEFT, CENTER");
return panel;
}
/**
* Builds and lays out the panel hosting the debug information.
*
* @return See above.
*/
private JPanel buildDebugPane()
{
JPanel panel = new JPanel();
panel.setOpaque(false);
double tableSize[][] = {{TableLayout.FILL}, // columns
{TableLayout.FILL, 32}}; // rows
TableLayout layout = new TableLayout(tableSize);
panel.setLayout(layout);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JScrollPane pane = new JScrollPane(debugArea);
panel.add(pane, "0, 0");
panel.add(copyButton, "0, 1, CENTER, BOTTOM");
return panel;
}
/**
* Builds and lays out the panel hosting the collection of files to submit.
*
* @return See above.
*/
private JPanel buildFilesToSubmitPane(List<ImportErrorObject> toSubmit)
{
JPanel panel = new JPanel();
panel.setOpaque(false);
double tableSize[][] = {{TableLayout.FILL}, // columns
{TableLayout.FILL}}; // rows
TableLayout layout = new TableLayout(tableSize);
panel.setLayout(layout);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
table = new FileTable(toSubmit);
JScrollPane pane = new JScrollPane(table);
panel.add(pane, "0, 0");
return panel;
}
/**
* Builds and lays out the panel hosting the comments.
*
* @param comment The comment's text.
* @return See above.
*/
private JPanel buildCommentPane(String comment)
{
JPanel commentPanel = new JPanel();
int iconSpace = 0;
double tableSize[][] = {{iconSpace, (160 - iconSpace),
TableLayout.FILL}, // columns
{0, 0, 30, TableLayout.FILL}}; // rows
TableLayout layout = new TableLayout(tableSize);
commentPanel.setLayout(layout);
commentPanel.setBorder(
BorderFactory.createEmptyBorder(10, 10, 10, 10));
commentPanel.add(buildEmailAreaPanel('E'), "0, 2, 2, 2");
commentPanel.add(buildCommentAreaPanel(comment, 'W'), "0, 3, 2, 3");
if (CommonsLangUtils.isNotBlank(errorDescription)) {
layout.setRow(1, 30);
JPanel p = new JPanel();
p.add(UIUtilities.setTextFont(ERROR_BRIEF));
p.add(new JLabel(errorDescription));
commentPanel.add(UIUtilities.buildComponentPanel(p), "0, 1, 2, 1");
}
return commentPanel;
}
/**
* Builds the UI component hosting the debug information.
*
* @param toSubmit The collection of files to send.
* @return See above
*/
private JTabbedPane buildExceptionPane(List<ImportErrorObject> toSubmit)
{
JTabbedPane tPane = new JTabbedPane();
tPane.setOpaque(false);
if (dialogType == SUBMIT_ERROR_TYPE) {
tPane.addTab("Comments", null, buildCommentPane(COMMENT_FIELD),
"Your comments go here.");
tPane.addTab("Files to Send", null,
buildFilesToSubmitPane(toSubmit),
"The files to send to the development team.");
} else {
tPane.addTab("Comments", null,
buildCommentPane(DEBUG_COMMENT_FIELD),
"Your comments go here.");
tPane.addTab("Error Message", null, buildDebugPane(),
"The Exception Message.");
}
return tPane;
}
/**
* Builds and lays out the buttons.
*
* @param submit Collection of files to submit.
* @return See above.
*/
private JPanel buildToolBar(List<ImportErrorObject> toSubmit)
{
JPanel bars = new JPanel();
bars.setLayout(new BoxLayout(bars, BoxLayout.X_AXIS));
if (CollectionUtils.isNotEmpty(toSubmit)) {
boolean count = false;
Iterator<ImportErrorObject> j = toSubmit.iterator();
while (j.hasNext()) {
if (j.next().getFile() != null) {
count = true;
break;
}
}
JPanel row = new JPanel();
row.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
if (count) {
row.add(new JLabel("Submit Exceptions and: "));
row.add(UIUtilities.buildComponentPanel(submitFile));
}
JPanel p = new JPanel();
p.setBorder(null);
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
p.add(row);
JPanel progressPane = new JPanel();
progressPane.setLayout(new BoxLayout(progressPane,
BoxLayout.X_AXIS));
progressPane.add(progress);
progressPane.add(Box.createHorizontalStrut(5));
progressPane.add(progressLabel);
p.add(UIUtilities.buildComponentPanel(progressPane));
bars.add(UIUtilities.buildComponentPanel(p));
}
JPanel bar = new JPanel();
bar.setLayout(new BoxLayout(bar, BoxLayout.X_AXIS));
bar.add(cancelButton);
bar.add(Box.createHorizontalStrut(5));
bar.add(sendButton);
bar.add(Box.createHorizontalStrut(10));
bars.add(UIUtilities.buildComponentPanelRight(bar));
return bars;
}
/**
* Builds and lays out the GUI.
*
* @param toSubmit The collection of files to send.
*/
private void buildGUI(List<ImportErrorObject> toSubmit)
{
JComponent component;
Icon icon;
IconManager icons = IconManager.getInstance();
String message;
if (dialogType == SUBMIT_ERROR_TYPE) {
message = SUBMIT_MESSAGE;
component = buildExceptionPane(toSubmit);
icon = icons.getIcon(IconManager.SUBMIT_ICON_64);
if (icon == null) icon = UIManager.getIcon("OptionPane.errorIcon");
} else if (exception == null) {
message = MESSAGE;
icon = icons.getIcon(IconManager.COMMENT_ICON_64);
if (icon == null)
icon = UIManager.getIcon("OptionPane.questionIcon");
component = buildCommentPane(COMMENT_FIELD);
} else {
message = DEBUG_MESSAGE;
component = buildExceptionPane(null);
icon = icons.getIcon(IconManager.ERROR_ICON_64);
if (icon == null) icon = UIManager.getIcon("OptionPane.errorIcon");
}
Container c = getContentPane();
TitlePanel tp = new TitlePanel(getTitle(), message, icon);
c.setLayout(new BorderLayout(0, 0));
c.add(tp, BorderLayout.NORTH);
c.add(component, BorderLayout.CENTER);
c.add(buildToolBar(toSubmit),BorderLayout.SOUTH);
}
/**
* Initializes the dialog.
*
* @param title The title of the dialog.
* @param toSubmit The collection of files to send.
*/
private void initialize(String title, List<ImportErrorObject> toSubmit)
{
setTitle(title);
initComponents();
buildGUI(toSubmit);
setSize(DEFAULT_SIZE);
}
/**
* Creates a new instance.
*
* @param parent The parent of this dialog.
* @param title The dialog's title.
* @param emailAddress The e-mail address of the current user.
*/
public MessengerDialog(JFrame parent, String title, String emailAddress)
{
super(parent);
this.emailAddress = emailAddress;
dialogType = COMMENT_TYPE;
initialize(title, null);
}
/**
* Creates a new instance.
*
* @param parent The parent of this dialog.
* @param title The dialog's title.
* @param emailAddress The e-mail address of the current user.
* @param exception The exception to handle.
*/
public MessengerDialog(JFrame parent, String title, String emailAddress,
Exception exception)
{
super(parent);
dialogType = ERROR_TYPE;
this.emailAddress = emailAddress;
this.exception = exception;
initialize(title, null);
}
/**
* Creates a new instance.
*
* @param parent The parent of this dialog.
* @param title The dialog's title.
* @param emailAddress The e-mail address of the current user.
* @param toSubmit The object to submit.
*/
public MessengerDialog(JFrame parent, String title, String emailAddress,
List<ImportErrorObject> toSubmit)
{
super(parent);
this.dialogType = SUBMIT_ERROR_TYPE;
this.emailAddress = emailAddress;
initialize(title, toSubmit);
}
/**
* Sets a brief description of the error.
*
* @param description The value to set.
*/
public void setErrorDescription(String description)
{
errorDescription = description;
}
/**
* Sets the comment to send.
*
* @param comment The text to display.
*/
public void setComment(String comment)
{
if (comment != null && comment.trim().length() > 0)
commentArea.setText(comment);
}
/**
* Sets the version of the server.
*
* @param serverVersion The value to set.
*/
public void setServerVersion(String serverVersion)
{
this.serverVersion = serverVersion;
}
/**
* Returns the type associated to this widget.
*
* @return See above.
*/
public int getDialogType() { return dialogType; }
/**
* Sets the status of the file submission.
*
* @param text The text to display.
* @param hide Pass <code>true</code> to hide the progress,
* <code>false</code> otherwise.
*/
public void setSubmitStatus(String text, boolean hide)
{
progressLabel.setText(text);
progress.setVisible(!hide);
progress.setBusy(!hide);
}
/**
* Reacts to click on controls.
* @see ActionListener#actionPerformed(ActionEvent)
*/
public void actionPerformed(ActionEvent e)
{
int index = Integer.parseInt(e.getActionCommand());
switch (index) {
case CANCEL:
close();
break;
case SEND:
send(SEND_PROPERTY);
break;
case COPY:
copy();
break;
case SUBMIT:
submitFilesControl();
}
}
/**
* Sets the enabled flag of the {@link #sendButton} depending on the
* type of dialog we handle.
* @see DocumentListener#insertUpdate(DocumentEvent)
*/
public void insertUpdate(DocumentEvent e)
{
if (dialogType == COMMENT_TYPE) {
String text = commentArea.getText();
sendButton.setEnabled(text != null && text.trim().length() > 0);
}
}
/**
* Sets the enabled flag of the {@link #sendButton} depending on the
* type of dialog we handle.
* @see DocumentListener#removeUpdate(DocumentEvent)
*/
public void removeUpdate(DocumentEvent e)
{
if (dialogType == COMMENT_TYPE) {
String text = commentArea.getText();
sendButton.setEnabled(text != null && text.trim().length() > 0);
}
}
/**
* Required by the {@link DocumentListener} I/F but no-op implementation
* in our case.
* @see DocumentListener#changedUpdate(DocumentEvent)
*/
public void changedUpdate(DocumentEvent e) {}
@Override
public void setVisible(boolean b) {
super.setVisible(b);
pack();
}
}