/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.ui.feedback;
import com.google.dart.tools.ui.DartToolsPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import java.lang.reflect.InvocationTargetException;
/**
* A dialog to collect user feedback.
*/
public class FeedbackDialog extends Dialog implements IRunnableContext, DisposeListener {
/**
* Key used to retrieve stored settings from the activator's cache.
*/
private static final String DIALOG_SETTING_CACHE_KEY = FeedbackDialog.class.getSimpleName();
/**
* Key for caching opt-in setting state.
*/
private static final String OPT_IN_SETTING_KEY = "opt.in.enabled"; //$NON-NLS-1$
private Button okButton;
private Text feedbackText;
private Button sendAdditionalDataButton;
private Link previewDataLink;
private Button sendScreenshotButton;
private Link previewScreenshotLink;
private ProgressMonitorPart progressMonitorPart;
/**
* FeedbackReport report for preview and submission.
*/
private final FeedbackReport feedbackReport;
/**
* Feedback image, cached for proper disposal.
*/
private Image feedbackImage;
/**
* The screenshot of the Editor/ Editor Plug-in just before the Send Feedback button was pressed.
*/
private Image screenshot;
/**
* Create the feedback dialog.
*
* @param parentShell the parent shell
* @param productName the name of the installed product (e.g., "Editor" vs. "Editor Plugin")
*/
public FeedbackDialog(Shell parentShell, String productName, Image screenshot) {
super(parentShell);
feedbackReport = new FeedbackReport(productName, screenshot);
this.screenshot = screenshot;
}
@Override
public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
throws InvocationTargetException, InterruptedException {
progressMonitorPart.getParent().setVisible(true);
ModalContext.run(runnable, fork, progressMonitorPart, getShell().getDisplay());
}
@Override
public void widgetDisposed(DisposeEvent e) {
if (feedbackImage != null) {
feedbackImage.dispose();
}
}
@Override
protected void buttonPressed(int buttonId) {
updateFeedbackTextForClick(buttonId);
super.buttonPressed(buttonId);
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText(FeedbackMessages.FeedbackDialog_Title);
newShell.addDisposeListener(this);
}
@Override
protected Control createButtonBar(Composite parent) {
//overriding to tweak layout
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 0; // this is incremented by createButton
layout.makeColumnsEqualWidth = true;
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.marginHeight = 0; //convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
layout.verticalSpacing = 0; //convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
//put some padding at the bottom
layout.marginBottom = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
composite.setLayout(layout);
GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.VERTICAL_ALIGN_CENTER);
composite.setLayoutData(data);
composite.setFont(parent.getFont());
// Add the buttons to the button bar.
createButtonsForButtonBar(composite);
return composite;
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
okButton = createButton(
parent,
IDialogConstants.OK_ID,
FeedbackMessages.FeedbackDialog_OK_Button_Text,
true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
updateEnablement();
}
@Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
Composite composite = new Composite(container, SWT.NONE);
GridLayout gl_composite = new GridLayout(2, false);
gl_composite.marginHeight = 0;
composite.setLayout(gl_composite);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
Label imageLabel = new Label(composite, SWT.NONE);
GridData data = new GridData(SWT.CENTER, SWT.TOP, false, false, 1, 1);
data.verticalSpan = 2;
imageLabel.setLayoutData(data);
ImageDescriptor imageDescriptor = DartToolsPlugin.getImageDescriptor("icons/insert_comment.png"); //$NON-NLS-1$
feedbackImage = imageDescriptor.createImage();
imageLabel.setImage(feedbackImage);
Label inviteText = new Label(composite, SWT.NONE);
inviteText.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 1, 1));
inviteText.setText(FeedbackMessages.FeedbackDialog_Description_Text);
feedbackText = new Text(composite, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI);
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
gd.minimumWidth = 420;
gd.minimumHeight = 160;
feedbackText.setLayoutData(gd);
//spacer
new Label(composite, SWT.NONE);
Composite logOptinComposite = new Composite(composite, SWT.NONE);
logOptinComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
GridLayout gl_logOptinComposite = new GridLayout(2, false);
gl_logOptinComposite.marginWidth = 0;
gl_logOptinComposite.horizontalSpacing = 0;
gl_logOptinComposite.verticalSpacing = 0;
gl_logOptinComposite.marginHeight = 0;
logOptinComposite.setLayout(gl_logOptinComposite);
sendAdditionalDataButton = new Button(logOptinComposite, SWT.CHECK);
sendAdditionalDataButton.setText(FeedbackMessages.FeedbackDialog_send_additional_data_optin_Text);
previewDataLink = new Link(logOptinComposite, SWT.NONE);
GridData gd_1 = new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1);
gd_1.verticalIndent = 2;
previewDataLink.setLayoutData(gd_1);
previewDataLink.setText(FeedbackMessages.FeedbackDialog_link_text);
sendScreenshotButton = new Button(logOptinComposite, SWT.CHECK);
sendScreenshotButton.setText(FeedbackMessages.FeedbackDialog_send_screenshot_optin_Text);
previewScreenshotLink = new Link(logOptinComposite, SWT.NONE);
GridData gd_2 = new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1);
gd_2.verticalIndent = 2;
previewScreenshotLink.setLayoutData(gd_2);
previewScreenshotLink.setText(FeedbackMessages.FeedbackDialog_link_screenshot_text);
//spacer
new Label(composite, SWT.NONE);
Composite monitorComposite = new Composite(composite, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.numColumns = 2;
monitorComposite.setLayout(layout);
monitorComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
GridLayout pmLayout = new GridLayout();
progressMonitorPart = new ProgressMonitorPart(monitorComposite, pmLayout, false);
gd = new GridData(GridData.FILL_HORIZONTAL);
progressMonitorPart.setLayoutData(gd);
monitorComposite.setVisible(false);
new Label(monitorComposite, SWT.NONE);
restoreSettings();
hookupListeners();
return container;
}
/**
* Return the dialog store to cache values into
*/
protected IDialogSettings getDialogSettings() {
IDialogSettings bundleSettings = DartToolsPlugin.getDefault().getDialogSettings();
IDialogSettings dialogSettings = bundleSettings.getSection(DIALOG_SETTING_CACHE_KEY);
if (dialogSettings == null) {
dialogSettings = bundleSettings.addNewSection(DIALOG_SETTING_CACHE_KEY);
}
return dialogSettings;
}
@Override
protected int getShellStyle() {
return SWT.MODELESS | SWT.CLOSE | SWT.MIN | SWT.MAX | SWT.RESIZE;
}
@Override
protected boolean isResizable() {
return true;
}
@Override
protected void okPressed() {
try {
if (submitFeedback().isOK()) {
saveSettings();
super.okPressed();
MessageDialog.openInformation(
getParentShell(),
FeedbackMessages.FeedbackDialog_feedback_sent_label,
FeedbackMessages.FeedbackDialog_feedback_sent_details);
return;
}
} catch (Throwable th) {
DartToolsPlugin.log(th);
}
MessageDialog.openError(
getParentShell(),
FeedbackMessages.FeedbackDialog_error_submitting_label,
FeedbackMessages.FeedbackDialog_error_submitting_detail);
setReturnCode(CANCEL);
}
/**
* Update button and link enablement.
*/
protected void updateEnablement() {
// Controls are null during dialog creation
if (okButton != null) {
boolean hasContent = !feedbackText.getText().isEmpty();
okButton.setEnabled(hasContent);
}
}
private void hookupListeners() {
feedbackText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
updateEnablement();
}
});
previewDataLink.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
try {
LogViewer logViewer = new LogViewer(
getShell(),
feedbackReport.getDetailString(sendLogData()));
logViewer.open();
} catch (Throwable th) {
MessageDialog.openError(
getParentShell(),
FeedbackMessages.FeedbackDialog_error_opening_log_label,
FeedbackMessages.FeedbackDialog_error_opening_log_detail);
}
}
});
sendAdditionalDataButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateEnablement();
}
});
previewScreenshotLink.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (screenshot == null) {
MessageDialog.openError(
getParentShell(),
FeedbackMessages.FeedbackDialog_error_opening_screenshot_label,
FeedbackMessages.FeedbackDialog_error_opening_screenshot_detail);
}
try {
ScreenshotViewer screenshotViewer = new ScreenshotViewer(getShell(), screenshot);
screenshotViewer.open();
} catch (Throwable t) {
MessageDialog.openError(
getParentShell(),
FeedbackMessages.FeedbackDialog_error_opening_screenshot_label,
FeedbackMessages.FeedbackDialog_error_opening_screenshot_detail);
}
}
});
sendScreenshotButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateEnablement();
}
});
}
private void restoreSettings() {
sendAdditionalDataButton.setSelection(getDialogSettings().getBoolean(OPT_IN_SETTING_KEY));
}
private void saveSettings() {
getDialogSettings().put(OPT_IN_SETTING_KEY, sendLogData());
}
private boolean sendLogData() {
return sendAdditionalDataButton.getSelection();
}
private boolean sendScreenshot() {
// TODO (jwren) remove this check. This is currently needed when
// OpenFeedbackDialogAction.SCREEN_CAPTURE_ENABLED is false.
if (sendScreenshotButton == null) {
return false;
} else {
return sendScreenshotButton.getSelection();
}
}
private IStatus submitFeedback() {
final IStatus[] status = new IStatus[1];
try {
run(false, false, new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException,
InterruptedException {
status[0] = new FeedbackSubmissionJob(new FeedbackWriter(
feedbackReport,
sendLogData(),
sendScreenshot())).run(monitor);
}
});
} catch (InvocationTargetException e) {
status[0] = DartToolsPlugin.createErrorStatus(e.getMessage());
} catch (InterruptedException e) {
status[0] = DartToolsPlugin.createErrorStatus(e.getMessage());
}
return status[0];
}
private void updateFeedbackTextForClick(int buttonId) {
feedbackReport.setFeedbackText(buttonId == IDialogConstants.OK_ID ? feedbackText.getText() : "");
}
}