Add various ways of launching a browser for HelpInfo links, add support for
changing the prefix for those links and better formatting of them, cleanup
comments.
Patch by: jat
Review by: rjrjr
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6691 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/TreeLogger.java b/dev/core/src/com/google/gwt/core/ext/TreeLogger.java
index c433524..e934823 100644
--- a/dev/core/src/com/google/gwt/core/ext/TreeLogger.java
+++ b/dev/core/src/com/google/gwt/core/ext/TreeLogger.java
@@ -37,6 +37,13 @@
}
/**
+ * @return the prefix to go before the link.
+ */
+ public String getPrefix() {
+ return "More info: ";
+ }
+
+ /**
* @return a URL containing extra information about the problem, or null if
* none.
*/
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index 99e1a5d..90932c3 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -17,6 +17,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.TreeLogger.HelpInfo;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
import com.google.gwt.dev.cfg.ModuleDef;
@@ -921,11 +922,12 @@
protected void launchURL(String url) throws UnableToCompleteException {
/*
- * TODO(jat): properly support launching arbitrary browsers; waiting on
- * Freeland's work with BrowserScanner and the trunk merge to get it.
+ * TODO(jat): properly support launching arbitrary browsers -- need some
+ * UI API tweaks to support that.
*/
+ URL parsedUrl = null;
try {
- URL parsedUrl = new URL(url);
+ parsedUrl = new URL(url);
String path = parsedUrl.getPath();
String query = parsedUrl.getQuery();
String hash = parsedUrl.getRef();
@@ -939,8 +941,9 @@
if (hash != null) {
path += '#' + hash;
}
- url = new URL(parsedUrl.getProtocol(), parsedUrl.getHost(),
- parsedUrl.getPort(), path).toExternalForm();
+ parsedUrl = new URL(parsedUrl.getProtocol(), parsedUrl.getHost(),
+ parsedUrl.getPort(), path);
+ url = parsedUrl.toExternalForm();
} catch (MalformedURLException e) {
getTopLogger().log(TreeLogger.ERROR, "Invalid URL " + url, e);
throw new UnableToCompleteException();
@@ -948,8 +951,24 @@
System.err.println("Using a browser with the GWT Development Plugin, please browse to");
System.err.println("the following URL:");
System.err.println(" " + url);
+ final URL helpInfoUrl = parsedUrl;
getTopLogger().log(TreeLogger.INFO,
- "Waiting for browser connection to " + url, null);
+ "Waiting for browser connection to " + url, null, new HelpInfo() {
+ @Override
+ public String getAnchorText() {
+ return "Launch default browser";
+ }
+
+ @Override
+ public String getPrefix() {
+ return "";
+ }
+
+ @Override
+ public URL getURL() {
+ return helpInfoUrl;
+ }
+ });
}
/**
diff --git a/dev/core/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java b/dev/core/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
index 5d29208..b6a6c22 100644
--- a/dev/core/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
+++ b/dev/core/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
@@ -20,6 +20,7 @@
import com.google.gwt.dev.shell.CloseButton;
import com.google.gwt.dev.shell.CloseButton.Callback;
import com.google.gwt.dev.shell.log.SwingTreeLogger.LogEvent;
+import com.google.gwt.dev.util.BrowserLauncher;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.dev.util.log.CompositeTreeLogger;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
@@ -249,8 +250,6 @@
String regexFilter;
- private boolean autoScroll;
-
private final JEditorPane details;
private final TreeLogger logger;
@@ -273,6 +272,13 @@
private boolean disconnected = false;
+ /**
+ * Create a Swing-based logger panel, with a tree section and a detail
+ * section.
+ *
+ * @param maxLevel
+ * @param logFile
+ */
public SwingLoggerPanel(TreeLogger.Type maxLevel, File logFile) {
super(new BorderLayout());
regexFilter = "";
@@ -425,6 +431,9 @@
});
}
+ /**
+ * Collapse all tree nodes.
+ */
@SuppressWarnings("unchecked")
public void collapseAll() {
Enumeration<DefaultMutableTreeNode> children = root.postorderEnumeration();
@@ -437,12 +446,18 @@
tree.invalidate();
}
+ /**
+ * Show that the client connected to this logger has disconnected.
+ */
public void disconnected() {
disconnected = true;
tree.setBackground(DISCONNECTED_COLOR);
tree.repaint();
}
+ /**
+ * Expand all tree nodes.
+ */
@SuppressWarnings("unchecked")
public void expandAll() {
Enumeration<DefaultMutableTreeNode> children = root.postorderEnumeration();
@@ -455,10 +470,9 @@
tree.invalidate();
}
- public boolean getAutoScroll() {
- return autoScroll;
- }
-
+ /**
+ * @return the TreeLogger for this panel
+ */
public TreeLogger getLogger() {
return logger;
}
@@ -467,9 +481,14 @@
EventType eventType = event.getEventType();
if (eventType == HyperlinkEvent.EventType.ACTIVATED) {
URL url = event.getURL();
- // TODO(jat): how best to display the URL? We could either figure out
- // how to run the user's browser, create a mini-browser in Swing, or just
- // re-use the details pane as we do here, but this is rather poor.
+ try {
+ BrowserLauncher.browse(url.toExternalForm());
+ return;
+ } catch (Exception e) {
+ // if anything fails, fall-through to failsafe implementation
+ }
+ // As a last resort, just use the details pane to display the HTML, but
+ // this is rather poor.
try {
details.setPage(url);
} catch (IOException e) {
@@ -478,6 +497,9 @@
}
}
+ /**
+ * @param node
+ */
public void notifyChange(DefaultMutableTreeNode node) {
treeModel.nodeChanged(node);
}
@@ -488,10 +510,6 @@
details.setText("");
}
- public void setAutoScroll(boolean autoScroll) {
- this.autoScroll = autoScroll;
- }
-
/**
* Sets a callback for handling a close request, which also makes the close
* button visible.
diff --git a/dev/core/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java b/dev/core/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
index dc29218..30aef66 100644
--- a/dev/core/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
+++ b/dev/core/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
@@ -179,8 +179,9 @@
if (anchorText == null) {
anchorText = url.toExternalForm();
}
+ String prefix = helpInfo.getPrefix();
if (url != null) {
- sb.append("\nMore info: <a href=\"");
+ sb.append("<p>" + prefix + "<a href=\"");
sb.append(url.toString());
sb.append("\">");
sb.append(anchorText);
diff --git a/dev/core/src/com/google/gwt/dev/util/BrowserLauncher.java b/dev/core/src/com/google/gwt/dev/util/BrowserLauncher.java
new file mode 100644
index 0000000..55bb6e2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/BrowserLauncher.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.gwt.dev.util;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * Provides a platform and JDK-independent method of launching a browser
+ * given a URI.
+ *
+ * <p>Portions derived from public-domain code at
+ * <pre>http://www.centerkey.com/java/browser/</pre>
+ */
+public class BrowserLauncher {
+
+ /**
+ * A browser launcher that uses JDK 1.6 Desktop.browse support.
+ */
+ private static class Jdk16Launcher extends ReflectiveLauncher {
+
+ /**
+ * Create a Jdk16Launcher if supported.
+ *
+ * @throws UnsupportedOperationException if not supported
+ */
+ public Jdk16Launcher() throws UnsupportedOperationException {
+ try {
+ Class<?> desktopClass = Class.forName("java.awt.Desktop");
+ browseMethod = desktopClass.getMethod("browse", URI.class);
+ Method factory = desktopClass.getMethod("getDesktop");
+ browseObject = factory.invoke(null);
+ return;
+ } catch (ClassNotFoundException e) {
+ // not on JDK 1.6, try other methods
+ } catch (NoSuchMethodException e) {
+ // not on JDK 1.6, try other methods
+ } catch (SecurityException e) {
+ // ignore, try other methods
+ } catch (IllegalArgumentException e) {
+ // ignore, try other methods
+ } catch (IllegalAccessException e) {
+ // ignore, try other methods
+ } catch (InvocationTargetException e) {
+ // ignore, try other methods
+ }
+ throw new UnsupportedOperationException("no JDK 1.6 Desktop.browse");
+ }
+
+ @Override
+ protected Object convertUrl(String url) throws URISyntaxException, MalformedURLException {
+ return new URL(url).toURI();
+ }
+ }
+
+ private interface Launcher {
+ void browse(String url) throws IOException, URISyntaxException;
+ }
+
+ /**
+ * Launch the default browser on Mac via FileManager openURL.
+ */
+ private static class MacLauncher extends ReflectiveLauncher {
+
+ public MacLauncher() throws UnsupportedOperationException {
+ Throwable caught = null;
+ try {
+ Class<?> fileManager = Class.forName("com.apple.eio.FileManager");
+ browseMethod = fileManager.getMethod("openURL", String.class);
+ browseObject = null;
+ return;
+ } catch (SecurityException e) {
+ caught = e;
+ } catch (ClassNotFoundException e) {
+ caught = e;
+ } catch (NoSuchMethodException e) {
+ caught = e;
+ }
+ throw new UnsupportedOperationException("Can't get openURL", caught);
+ }
+ }
+
+ /**
+ * Interface for launching a URL in a browser, which uses reflection.
+ *
+ * <p>Subclass must set browseObject and browseMethod appropriately.
+ */
+ private abstract static class ReflectiveLauncher implements Launcher {
+
+ protected Object browseObject;
+ protected Method browseMethod;
+
+ public void browse(String url) throws IOException, URISyntaxException {
+ Object arg = convertUrl(url);
+ Throwable caught = null;
+ try {
+ browseMethod.invoke(browseObject, arg);
+ return;
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+ caught = e;
+ } catch (IllegalAccessException e) {
+ caught = e;
+ }
+ throw new RuntimeException("Unexpected exception", caught);
+ }
+
+ /**
+ * Convert the URL into another form if required. The default
+ * implementation simply returns the unmodified string.
+ *
+ * @param url URL in string form
+ * @return the URL in the form needed for browseMethod
+ * @throws URISyntaxException
+ * @throws MalformedURLException
+ */
+ protected Object convertUrl(String url) throws URISyntaxException,
+ MalformedURLException {
+ return url;
+ }
+ }
+
+ /**
+ * Launch a browser by searching for a browser executable on the path.
+ */
+ private static class UnixExecBrowserLauncher implements Launcher {
+
+ private static final String[] browsers = {
+ "firefox", "opera", "konqueror", "chrome", "chromium", "epiphany",
+ "seamonkey", "mozilla", "netscape", "galeon", "kazehakase",
+ };
+
+ private String browserExecutable;
+
+ /**
+ * Creates a launcher by searching for a suitable browser executable.
+ * Assumes the presence of the "which" command.
+ *
+ * @throws UnsupportedOperationException if no suitable browser can be
+ * found.
+ */
+ public UnixExecBrowserLauncher() throws UnsupportedOperationException {
+ for (String browser : browsers) {
+ try {
+ Process process = Runtime.getRuntime().exec(new String[] { "which",
+ browser});
+ if (process.waitFor() == 0) {
+ browserExecutable = browser;
+ return;
+ }
+ } catch (IOException e) {
+ // ignore, try next one
+ } catch (InterruptedException e) {
+ // ignore, try next one
+ }
+ }
+ throw new UnsupportedOperationException("no suitable browser found");
+ }
+
+ public void browse(String url) throws IOException, URISyntaxException {
+ Runtime.getRuntime().exec(new String[] { browserExecutable, url });
+ // TODO(jat): do we need to wait for it to exit and check exit status?
+ // That would be best for Firefox, but bad for some of the other browsers.
+ }
+ }
+
+ /**
+ * Launch the default browser on Windows via the URL protocol handler.
+ */
+ private static class WindowsLauncher implements Launcher {
+
+ public void browse(String url) throws IOException, URISyntaxException {
+ Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
+ // TODO(jat): do we need to wait for it to exit and check exit status?
+ }
+ }
+
+ private static Launcher launcher;
+
+ /**
+ * Browse to a given URI.
+ *
+ * @param url
+ * @throws IOException
+ * @throws URISyntaxException
+ */
+ public static void browse(String url) throws IOException, URISyntaxException {
+ if (launcher == null) {
+ findLauncher();
+ }
+ launcher.browse(url);
+ }
+
+ /**
+ * Main method so this can be run from the command line for testing.
+ *
+ * @param args URL to launch
+ * @throws URISyntaxException
+ * @throws IOException
+ */
+ public static void main(String[] args) throws IOException,
+ URISyntaxException {
+ if (args.length == 0) {
+ System.err.println("Usage: BrowserLauncher url...");
+ System.exit(1);
+ }
+ for (String url : args) {
+ browse(url);
+ }
+ }
+
+ /**
+ * Initialize launcher to an appropriate one for the current platform/JDK.
+ */
+ private static void findLauncher() {
+ try {
+ launcher = new Jdk16Launcher();
+ return;
+ } catch (UnsupportedOperationException e) {
+ // ignore and try other methods
+ }
+ String osName = System.getProperty("os.name");
+ if (osName.startsWith("Mac OS")) {
+ launcher = new MacLauncher();
+ } else if (osName.startsWith("Windows")) {
+ launcher = new WindowsLauncher();
+ } else {
+ launcher = new UnixExecBrowserLauncher();
+ // let UnsupportedOperationException escape to caller
+ }
+ }
+}