/*
* Copyright 2013 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.debug.ui.internal.dialogs;
import com.google.dart.tools.debug.core.configs.DartServerLaunchConfigurationDelegate;
import com.google.dart.tools.debug.core.configs.DartiumLaunchConfigurationDelegate;
import com.google.dart.tools.debug.core.util.IRemoteConnectionDelegate;
import com.google.dart.tools.debug.core.webkit.ChromiumTabInfo;
import com.google.dart.tools.debug.core.webkit.DefaultChromiumTabChooser;
import com.google.dart.tools.debug.core.webkit.IChromiumTabChooser;
import com.google.dart.tools.debug.ui.internal.DartDebugUIPlugin;
import com.google.dart.tools.debug.ui.internal.view.DebuggerViewManager;
import com.google.dart.tools.ui.themes.Fonts;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;
import org.eclipse.ui.dialogs.ListDialog;
import java.util.List;
/**
* A dialog to connect to remote debug instances.
*/
public class RemoteConnectionDialog extends TitleAreaDialog {
/**
* A class to choose a tab from the given list of tabs.
*/
public static class ChromeTabChooser implements IChromiumTabChooser {
public ChromeTabChooser() {
}
@Override
public ChromiumTabInfo chooseTab(final List<ChromiumTabInfo> tabs) {
if (tabs.size() == 0) {
return null;
}
int tabCount = 0;
for (ChromiumTabInfo tab : tabs) {
if (!tab.isChromeExtension()) {
tabCount++;
}
}
// If there is exactly one tab that is not a Chrome extension.
if (tabCount == 1) {
for (ChromiumTabInfo tab : tabs) {
if (!tab.isChromeExtension()) {
return tab;
}
}
}
final ChromiumTabInfo[] result = new ChromiumTabInfo[1];
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
ListDialog dlg = new ListDialog(
PlatformUI.getWorkbench().getWorkbenchWindows()[0].getShell());
dlg.setInput(tabs);
dlg.setTitle("Select tab for remote connection");
dlg.setContentProvider(new ArrayContentProvider());
dlg.setLabelProvider(new TabLabelProvider());
if (dlg.open() == Window.OK) {
result[0] = (ChromiumTabInfo) dlg.getResult()[0];
}
}
});
if (result[0] != null) {
return result[0];
}
return new DefaultChromiumTabChooser().chooseTab(tabs);
}
}
public static class ConnectionJob extends Job {
private IRemoteConnectionDelegate connectionDelegate;
private String host;
private int port;
private IContainer container;
private boolean usePubServe;
public ConnectionJob(IRemoteConnectionDelegate connectionDelegate, String host, int port,
IContainer container, boolean usePubServe) {
super("Connecting...");
this.connectionDelegate = connectionDelegate;
this.host = host;
this.port = port;
this.container = container;
this.usePubServe = usePubServe;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
connectionDelegate.performRemoteConnection(host, port, container, monitor, usePubServe);
// Show the debugger view.
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
DebuggerViewManager viewManager = DebuggerViewManager.getDefault();
if (viewManager.hasDebuggerView()) {
viewManager.openDebuggerView();
}
}
});
} catch (CoreException ce) {
displayError(ce);
}
return Status.OK_STATUS;
}
private void displayError(final CoreException exception) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
ErrorDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
"Error Connecting to " + host + ":" + port,
null,
exception.getStatus());
}
});
}
}
public enum ConnectionType {
CHROME(
"Chrome-based browser", //
"Connect to a Chrome-based browser", //
"To start Chrome with remote connections enabled, use the --remote-debugging-port=<port> command-line flag.", //
"localhost", "9222"),
COMMAND_LINE("Command-line VM", //
"Connect to the stand-alone Dart VM", //
"To start the command-line VM in debug mode, use the --debug[:<port>] command-line flag.", //
"localhost", "5858");
String label;
String message;
String helpMessage;
String hostDefault;
String portDefault;
ConnectionType(String label, String message, String helpMessage, String hostDefault,
String portDefault) {
this.label = label;
this.message = message;
this.helpMessage = helpMessage;
this.hostDefault = hostDefault;
this.portDefault = portDefault;
}
public void connection(String host, int port, IContainer container, boolean usePubServe) {
IRemoteConnectionDelegate connectionDelegate = null;
switch (this) {
case CHROME: {
connectionDelegate = new DartiumLaunchConfigurationDelegate(new ChromeTabChooser());
break;
}
case COMMAND_LINE: {
connectionDelegate = new DartServerLaunchConfigurationDelegate();
break;
}
default: {
throw new IllegalArgumentException();
}
}
if (connectionDelegate != null) {
Job job = new ConnectionJob(connectionDelegate, host, port, container, usePubServe);
job.schedule();
}
}
}
static class TabLabelProvider extends LabelProvider {
@Override
public Image getImage(Object element) {
return null;
}
@Override
public String getText(Object element) {
if (element instanceof ChromiumTabInfo) {
return ((ChromiumTabInfo) element).getTitle();
}
return null;
}
}
/**
* Open an instance of a RemoteConnectionDialog.
*
* @param workbench
*/
public static void show(IWorkbench workbench) {
RemoteConnectionDialog dialog = new RemoteConnectionDialog(
workbench.getActiveWorkbenchWindow().getShell());
dialog.open();
}
private Combo exceptionsCombo;
private Text hostText;
private Text portText;
private Label resourcePathLabel;
private Text instructionsLabel;
private Button usePubServeButton;
private Group pubGroup;
/**
* Create a new RemoteConnectionDialog with the given shell as its parent.
*
* @param shell
*/
public RemoteConnectionDialog(Shell shell) {
super(shell);
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Open Remote Connection");
}
@Override
protected Control createDialogArea(Composite parent) {
Composite contents = (Composite) super.createDialogArea(parent);
setTitle(getShell().getText());
setTitleImage(DartDebugUIPlugin.getImage("wiz/run_wiz.png"));
Composite composite = new Composite(contents, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(composite);
createDialogUI(composite);
return contents;
}
@Override
protected void okPressed() {
ConnectionType connection = getConnectionType();
String host = hostText.getText().trim();
String port = portText.getText().trim();
String resourcePath = resourcePathLabel.getText().trim();
boolean usePubServe = usePubServeButton.getSelection();
IDialogSettings settings = getDialogSettings();
settings.put("selected", connection.ordinal());
settings.put(connection.name() + ".host", host);
settings.put(connection.name() + ".port", port);
settings.put(connection.name() + ".resource", resourcePath);
settings.put(connection.name() + ".usePubServe", usePubServe);
int connectionPort;
try {
connectionPort = Integer.parseInt(port);
} catch (NumberFormatException nfe) {
ErrorDialog.openError(getShell(), "Invalid Port", null, new Status(
IStatus.ERROR,
DartDebugUIPlugin.PLUGIN_ID,
"\"" + port + "\" is an invalid port."));
return;
}
IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(resourcePath);
if (resource == null) {
ErrorDialog.openError(getShell(), "Invalid Source Project", null, new Status(
IStatus.ERROR,
DartDebugUIPlugin.PLUGIN_ID,
"\"" + resourcePath + "\" is an invalid resource path."));
return;
}
IContainer container = resource instanceof IContainer ? (IContainer) resource
: resource.getParent();
connection.connection(host, connectionPort, container, usePubServe);
super.okPressed();
}
private void createDialogUI(Composite parent) {
GridLayoutFactory.fillDefaults().numColumns(2).margins(12, 6).applyTo(parent);
Label label = new Label(parent, SWT.NONE);
label.setText("Connect to:");
exceptionsCombo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
exceptionsCombo.setItems(getConnectionLabels());
exceptionsCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleComboChanged();
}
});
// spacer
label = new Label(parent, SWT.NONE);
Group group = new Group(parent, SWT.NONE);
group.setText("Connection parameters");
GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
GridLayoutFactory.fillDefaults().numColumns(3).margins(12, 6).applyTo(group);
label = new Label(group, SWT.NONE);
label.setText("Host:");
hostText = new Text(group, SWT.SINGLE | SWT.BORDER);
GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(hostText);
label = new Label(group, SWT.NONE);
label.setText("Port:");
portText = new Text(group, SWT.SINGLE | SWT.BORDER);
GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(portText);
label = new Label(group, SWT.NONE);
label.setText("Project source:");
resourcePathLabel = new Label(group, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(
resourcePathLabel);
Button projectBrowseButton = new Button(group, SWT.PUSH);
projectBrowseButton.setText("Select Folder...");
GridDataFactory.swtDefaults().applyTo(projectBrowseButton);
projectBrowseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleSourceDirectoryBrowseButton();
}
});
label = new Label(parent, SWT.NONE);
instructionsLabel = new Text(parent, SWT.WRAP | SWT.READ_ONLY);
instructionsLabel.setBackground(parent.getBackground());
GridDataFactory.fillDefaults().grab(true, false).hint(100, -1).applyTo(instructionsLabel);
// spacer
label = new Label(parent, SWT.NONE);
label = new Label(parent, SWT.NONE);
label = new Label(parent, SWT.NONE);
pubGroup = new Group(parent, SWT.NONE);
pubGroup.setText("Pub settings");
GridDataFactory.fillDefaults().grab(true, false).applyTo(pubGroup);
GridLayoutFactory.fillDefaults().margins(12, 6).applyTo(pubGroup);
usePubServeButton = new Button(pubGroup, SWT.CHECK);
usePubServeButton.setText("Using pub to serve the application");
IDialogSettings settings = getDialogSettings();
int selected;
try {
selected = getDialogSettings().getInt("selected");
} catch (NumberFormatException nfe) {
selected = 0;
}
String pubSettings = settings.get(ConnectionType.values()[selected].name() + ".usePubServe");
boolean pubSelection = pubSettings != null ? Boolean.parseBoolean(pubSettings) : true;
usePubServeButton.setSelection(pubSelection);
GridDataFactory.fillDefaults().grab(true, false).applyTo(usePubServeButton);
Label message = new Label(pubGroup, SWT.NONE);
message.setText("(This information will be used to resolve breakpoints)");
message.setFont(Fonts.getItalicFont(message.getFont()));
exceptionsCombo.select(selected);
handleComboChanged();
}
private String[] getConnectionLabels() {
String[] labels = new String[ConnectionType.values().length];
for (int i = 0; i < labels.length; i++) {
labels[i] = ConnectionType.values()[i].label;
}
return labels;
}
private ConnectionType getConnectionType() {
return ConnectionType.values()[exceptionsCombo.getSelectionIndex()];
}
private IDialogSettings getDialogSettings() {
final String sectionName = "remoteConnectionSettings";
IDialogSettings settings = DartDebugUIPlugin.getDefault().getDialogSettings();
if (settings.getSection(sectionName) == null) {
IDialogSettings section = settings.addNewSection(sectionName);
for (ConnectionType connection : ConnectionType.values()) {
section.put(connection.name() + ".host", connection.hostDefault);
section.put(connection.name() + ".port", connection.portDefault);
}
}
return settings.getSection(sectionName);
}
private void handleComboChanged() {
ConnectionType connection = getConnectionType();
setMessage(connection.message);
instructionsLabel.setText(connection.helpMessage);
IDialogSettings settings = getDialogSettings();
hostText.setText(notNull(settings.get(connection.name() + ".host")));
portText.setText(notNull(settings.get(connection.name() + ".port")));
resourcePathLabel.setText(notNull(settings.get(connection.name() + ".resource")));
pubGroup.setVisible(connection == ConnectionType.CHROME);
}
private void handleSourceDirectoryBrowseButton() {
ContainerSelectionDialog dialog = new ContainerSelectionDialog(
getShell(),
null,
false,
"Select source location");
dialog.open();
Object[] results = dialog.getResult();
if ((results != null) && (results.length > 0)) {
String pathStr = ((IPath) results[0]).toString();
resourcePathLabel.setText(pathStr);
}
}
private String notNull(String str) {
return str == null ? "" : str;
}
}