Change the way tabs work in the OOPHM UI:
- If the browser plugin supports stable tab identifiers across refreshes,
that is used to choose which tabs are created in the UI, with one OOPHM
tab corresponding to one browser tab. If the browser plugin does not
support tab identifiers (and none currently do), then the combination of
user agent, the base URL (ignoring query params and hash locations), and
the host of the connecting browser are used to select a tab. In this case,
multiple tabs in the same browser will be treated as several simultaneously
open sessions within one tab (see below).
- When a tab is reloaded or where the plugin does not support tab identifiers,
a session, identified by the timestamp of when it was created, groups
modules on the same page together. A reload will generate a new session,
with the logs still avaialble by selecting the session in a dropdown box.
Within a session, individual instances of modules (which may be the same
module loaded multiple times, including dynamically adding/dropping
modules) are identified in a separate dropdown box. Disconnected modules
and sessions with no active modules are identified in the dropdown boxes
by gray italic text.
- The usual case of only one module on a page means that sessions will
represent refreshes of the page (once tab identifiers are supported, until
then they also represent multiple instances of the application running in
the same browser) and there will be no module dropdown.
- Due to limitations from passing control through code shared with legacy
hosted mode, there is still no way to close a module while it is still
connected -- the browser will have to disconnect before it can be closed.
Patch by: jat
Review by: rjrjr
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5996 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/HostedModeBase.java b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
index 9d1d7cc..1496a6d 100644
--- a/dev/core/src/com/google/gwt/dev/HostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
@@ -599,6 +599,8 @@
return false;
}
options.setPort(resultPort);
+ getTopLogger().log(TreeLogger.INFO, "Started web server on port "
+ + resultPort);
}
return true;
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
index a9bcbd6..b1b7294 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
@@ -259,9 +259,6 @@
*/
private ModuleDef doLoadModule(TreeLogger logger, String moduleName)
throws UnableToCompleteException {
- logger = logger.branch(TreeLogger.TRACE, "Loading module '" + moduleName
- + "'", null);
-
if (!ModuleDef.isValidModuleName(moduleName)) {
logger.log(TreeLogger.ERROR, "Invalid module name: '" + moduleName + "'",
null);
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserWidget.java b/dev/core/src/com/google/gwt/dev/shell/BrowserWidget.java
index 612cf1a..50ed0e1 100644
--- a/dev/core/src/com/google/gwt/dev/shell/BrowserWidget.java
+++ b/dev/core/src/com/google/gwt/dev/shell/BrowserWidget.java
@@ -110,6 +110,7 @@
public void widgetDefaultSelected(SelectionEvent e) {
}
+ @SuppressWarnings("deprecation")
public void widgetSelected(SelectionEvent evt) {
if (evt.widget == backButton) {
browser.back();
@@ -168,12 +169,6 @@
}
}
- /**
- * The version number that should be passed into gwtOnLoad. Must match the
- * version in hosted.html.
- */
- private static final String EXPECTED_GWT_ONLOAD_VERSION = "2.0";
-
public static void launchExternalBrowser(TreeLogger logger, String location) {
String browserCmd = System.getenv("GWT_EXTERNAL_BROWSER");
if (browserCmd != null) {
@@ -328,19 +323,20 @@
* Initializes and attaches module space to this browser widget. Called by
* subclasses in response to calls from JavaScript.
*
+ * @param moduleSpaceLogger logger to use for attaching the ModuleSpace
* @param space ModuleSpace instance to initialize
*/
- protected final void attachModuleSpace(TreeLogger logger, ModuleSpace space)
- throws UnableToCompleteException {
+ protected final void attachModuleSpace(TreeLogger moduleSpaceLogger,
+ ModuleSpace space) throws UnableToCompleteException {
Object key = space.getKey();
loadedModules.put(key, space);
- logger.log(TreeLogger.SPAM, "Loading module " + space.getModuleName()
- + " (id " + key.toString() + ")", null);
+ moduleSpaceLogger.log(TreeLogger.SPAM, "Loading module "
+ + space.getModuleName() + " (id " + key.toString() + ")", null);
// Let the space do its thing.
//
- space.onLoad(logger);
+ space.onLoad(moduleSpaceLogger);
// Enable the compile button since we successfully loaded.
//
@@ -401,30 +397,6 @@
+ key.toString() + ")", null);
}
- /**
- * Validate that the supplied hosted.html version matches.
- *
- * This is to detect cases where users upgrade to a new version but forget to
- * update the generated hosted.html file.
- *
- * @param version version supplied by hosted.html file
- * @return true if the version is valid, false otherwise
- */
- protected boolean validHostedHtmlVersion(String version) {
- if (!EXPECTED_GWT_ONLOAD_VERSION.equals(version)) {
- getHost().getLogger().log(
- TreeLogger.ERROR,
- "Invalid version number \"" + version
- + "\" passed to external.gwtOnLoad(), expected \""
- + EXPECTED_GWT_ONLOAD_VERSION
- + "\"; your hosted mode bootstrap file may be out of date; "
- + "if you are using -noserver try recompiling and redeploying "
- + "your app");
- return false;
- }
- return true;
- }
-
private Composite buildLocationBar(Composite parent) {
Color white = new Color(null, 255, 255, 255);
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java b/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
index 41e7a27..2fce44c 100644
--- a/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/BrowserWidgetHost.java
@@ -58,6 +58,9 @@
* can't distinguish tabs or null if using an old browser plugin
* @param sessionKey unique session key, may be null for old browser plugins
* @param remoteEndpoint
+ *
+ * TODO(jat): change remoteEndpoint to be a BrowserChannelServer instance
+ * when we remove the SWT implementation
*/
ModuleSpaceHost createModuleSpaceHost(TreeLogger logger, String moduleName,
String userAgent, String url, String tabKey, String sessionKey,
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedHtmlVersion.java b/dev/core/src/com/google/gwt/dev/shell/HostedHtmlVersion.java
new file mode 100644
index 0000000..84f6954
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedHtmlVersion.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2008 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.shell;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+/**
+ * Holds the expected version number for the hosted.html file and a check
+ * for it.
+ */
+public class HostedHtmlVersion {
+ /**
+ * The version number that should be passed into gwtOnLoad. Must match the
+ * version in hosted.html.
+ */
+ private static final String EXPECTED_GWT_ONLOAD_VERSION = "2.0";
+
+ /**
+ * Validate that the supplied hosted.html version matches.
+ *
+ * This is to detect cases where users upgrade to a new version but forget to
+ * update the generated hosted.html file.
+ *
+ * @param logger to report errors on
+ * @param version version supplied by hosted.html file
+ * @return true if the version is valid, false otherwise
+ */
+ public static boolean validHostedHtmlVersion(TreeLogger logger,
+ String version) {
+ if (!EXPECTED_GWT_ONLOAD_VERSION.equals(version)) {
+ logger.log(TreeLogger.ERROR,
+ "Invalid version number \"" + version
+ + "\" passed to external.gwtOnLoad(), expected \""
+ + EXPECTED_GWT_ONLOAD_VERSION
+ + "\"; your hosted mode bootstrap file may be out of date; "
+ + "if you are using -noserver try recompiling and redeploying "
+ + "your app");
+ return false;
+ }
+ return true;
+ }
+
+ // prevent instantiation
+ private HostedHtmlVersion() {
+ }
+}
diff --git a/dev/linux/src/com/google/gwt/dev/shell/moz/BrowserWidgetMoz.java b/dev/linux/src/com/google/gwt/dev/shell/moz/BrowserWidgetMoz.java
index 8f0392e..cd847ae 100644
--- a/dev/linux/src/com/google/gwt/dev/shell/moz/BrowserWidgetMoz.java
+++ b/dev/linux/src/com/google/gwt/dev/shell/moz/BrowserWidgetMoz.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.shell.BrowserWidget;
import com.google.gwt.dev.shell.BrowserWidgetHost;
+import com.google.gwt.dev.shell.HostedHtmlVersion;
import com.google.gwt.dev.shell.LowLevel;
import com.google.gwt.dev.shell.ModuleSpace;
import com.google.gwt.dev.shell.ModuleSpaceHost;
@@ -45,7 +46,7 @@
TreeLogger logger = getHost().getLogger().branch(TreeLogger.DEBUG,
"Loading an instance of module '" + moduleName + "'");
try {
- if (!validHostedHtmlVersion(version)) {
+ if (!HostedHtmlVersion.validHostedHtmlVersion(logger, version)) {
return false;
}
diff --git a/dev/oophm/overlay/com/google/gwt/dev/GWTShell.java b/dev/oophm/overlay/com/google/gwt/dev/GWTShell.java
index 93d8604..1c9e35c 100644
--- a/dev/oophm/overlay/com/google/gwt/dev/GWTShell.java
+++ b/dev/oophm/overlay/com/google/gwt/dev/GWTShell.java
@@ -30,7 +30,6 @@
import com.google.gwt.util.tools.ArgHandlerExtra;
import java.io.File;
-import java.net.URL;
import java.util.Set;
import javax.swing.ImageIcon;
@@ -179,21 +178,6 @@
}
/**
- * Loads an image from the classpath in this package.
- */
- static ImageIcon loadImageIcon(String name) {
- ClassLoader cl = GWTShell.class.getClassLoader();
- URL url = cl.getResource(PACKAGE_PATH + name);
- if (url != null) {
- ImageIcon image = new ImageIcon(url);
- return image;
- } else {
- // Bad image.
- return new ImageIcon();
- }
- }
-
- /**
* Hiding super field because it's actually the same object, just with a
* stronger type.
*/
diff --git a/dev/oophm/overlay/com/google/gwt/dev/HostedMode.java b/dev/oophm/overlay/com/google/gwt/dev/HostedMode.java
index 3a8147e..f22a94f 100644
--- a/dev/oophm/overlay/com/google/gwt/dev/HostedMode.java
+++ b/dev/oophm/overlay/com/google/gwt/dev/HostedMode.java
@@ -362,12 +362,14 @@
servletValidator = ServletValidator.create(getTopLogger(), webXml);
}
+ TreeLogger branch = getTopLogger().branch(TreeLogger.INFO, "Loading modules");
for (String moduleName : options.getModuleNames()) {
- TreeLogger loadLogger = getTopLogger().branch(TreeLogger.DEBUG,
- "Bootstrap link for command-line module '" + moduleName + "'");
+ TreeLogger moduleBranch = branch.branch(TreeLogger.INFO, moduleName);
try {
- ModuleDef module = loadModule(loadLogger, moduleName, false);
- validateServletTags(loadLogger, servletValidator, module, webXml);
+ ModuleDef module = loadModule(moduleBranch, moduleName, false);
+ validateServletTags(moduleBranch, servletValidator, module, webXml);
+ TreeLogger loadLogger = moduleBranch.branch(TreeLogger.DEBUG,
+ "Bootstrap link for command-line module '" + moduleName + "'");
link(loadLogger, module);
} catch (UnableToCompleteException e) {
// Already logged.
diff --git a/dev/oophm/src/com/google/gwt/dev/DevelModeTabKey.java b/dev/oophm/src/com/google/gwt/dev/DevelModeTabKey.java
new file mode 100644
index 0000000..f78b592
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/DevelModeTabKey.java
@@ -0,0 +1,117 @@
+/**
+ * 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;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Represents what a composite unique key for individual tabs in the
+ * development mode window.
+ */
+class DevelModeTabKey {
+ private final String remoteHost;
+ private final String tabKey;
+ private final String url;
+ private final String userAgent;
+
+ /**
+ * Create a key.
+ *
+ * @param userAgent user agent string (not null)
+ * @param url top-level URL (may be null for old clients)
+ * @param tabKey opaque identifier for a browser tab - must be unique
+ * within a particular browser (user agent + remoteSocket) or an
+ * empty string if not support (may be null for old clients)
+ * @param remoteHost host portion of endpoint identifier of browser
+ * (not null)
+ * @throws IllegalArgumentException if userAgent or remoteHost is null
+ */
+ public DevelModeTabKey(String userAgent, String url, String tabKey,
+ String remoteHost) {
+ if (url == null) {
+ url = "";
+ }
+ if (tabKey == null) {
+ tabKey = "";
+ }
+ if (userAgent == null) {
+ throw new IllegalArgumentException("userAgent cannot be null");
+ }
+ if (remoteHost == null) {
+ throw new IllegalArgumentException("remoteHost cannot be null");
+ }
+ this.userAgent = userAgent;
+ try {
+ // Strip off the query part and the hash part
+ // TODO(jat): is it correct to strip off the query part?
+ URL fullUrl = new URL(url);
+ StringBuilder buf = new StringBuilder();
+ buf.append(fullUrl.getProtocol()).append(':');
+ if (fullUrl.getAuthority() != null && fullUrl.getAuthority().length() > 0) {
+ buf.append("//").append(fullUrl.getAuthority());
+ }
+ if (fullUrl.getPath() != null) {
+ buf.append(fullUrl.getPath());
+ }
+ url = buf.toString();
+ } catch (MalformedURLException e) {
+ // use URL as-is if it appears to be malformed
+ }
+ this.url = url;
+ this.tabKey = tabKey;
+ this.remoteHost = remoteHost;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DevelModeTabKey other = (DevelModeTabKey) obj;
+ return tabKey.equals(other.tabKey) && url.equals(other.url)
+ && userAgent.equals(other.userAgent)
+ && remoteHost.equals(other.remoteHost);
+ }
+
+ public String getRemoteSocket() {
+ return remoteHost;
+ }
+
+ public String getTabKey() {
+ return tabKey;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
+ }
+
+ @Override
+ public int hashCode() {
+ return remoteHost.hashCode() * 7 + tabKey.hashCode() * 11
+ + url.hashCode() * 13 + userAgent.hashCode() * 17;
+ }
+}
\ No newline at end of file
diff --git a/dev/oophm/src/com/google/gwt/dev/Disconnectable.java b/dev/oophm/src/com/google/gwt/dev/Disconnectable.java
new file mode 100644
index 0000000..ef42887
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/Disconnectable.java
@@ -0,0 +1,32 @@
+/**
+ * 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;
+
+/**
+ * Interface that represents something that is disconnectable.
+ */
+public interface Disconnectable {
+
+ /**
+ * Request disconnect.
+ */
+ void disconnect();
+
+ /**
+ * @return true if disconnected.
+ */
+ boolean isDisconnected();
+}
\ No newline at end of file
diff --git a/dev/oophm/src/com/google/gwt/dev/ModulePanel.java b/dev/oophm/src/com/google/gwt/dev/ModulePanel.java
index fcf905c..f20ec84 100644
--- a/dev/oophm/src/com/google/gwt/dev/ModulePanel.java
+++ b/dev/oophm/src/com/google/gwt/dev/ModulePanel.java
@@ -15,79 +15,37 @@
*/
package com.google.gwt.dev;
-import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.dev.ModuleTabPanel.Session;
import com.google.gwt.dev.shell.log.SwingLoggerPanel;
+import com.google.gwt.dev.shell.log.SwingLoggerPanel.CloseHandler;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import java.awt.BorderLayout;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import javax.swing.ImageIcon;
import javax.swing.JButton;
-import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
-import javax.swing.JTabbedPane;
/**
* A panel which represents a single module session.
*/
-public class ModulePanel extends JPanel {
-
- /**
- * A tab component with a close button, derived from Swing
- * TabComponentsDemoProject.
- */
- private class ClosedTabComponent extends JPanel {
-
- public ClosedTabComponent() {
- super(new FlowLayout(FlowLayout.LEFT, 0, 0));
- setOpaque(false);
- JButton button = new JButton(closeIcon);
- button.setBorderPainted(false);
- button.setPreferredSize(new Dimension(closeIcon.getIconWidth(),
- closeIcon.getIconHeight()));
- button.setToolTipText("Close this tab");
- add(button);
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- synchronized (tabs) {
- tabs.remove(ModulePanel.this);
- }
- }
- });
- }
- }
-
- @SuppressWarnings("deprecation")
- private static ImageIcon firefoxIcon = GWTShell.loadImageIcon("firefox24.png");
-
- @SuppressWarnings("deprecation")
- private static ImageIcon ieIcon = GWTShell.loadImageIcon("ie24.png");
-
- @SuppressWarnings("deprecation")
- private static ImageIcon safariIcon = GWTShell.loadImageIcon("safari24.png");
-
- @SuppressWarnings("deprecation")
- private static ImageIcon closeIcon = GWTShell.loadImageIcon("close.png");
+public class ModulePanel extends JPanel implements Disconnectable {
private SwingLoggerPanel loggerPanel;
- private final JTabbedPane tabs;
+ private Session session;
+
+ private boolean disconnected;
- private JPanel topPanel;
-
- public ModulePanel(Type maxLevel, String moduleName, String userAgent,
- String remoteSocket, final JTabbedPane tabs) {
+ public ModulePanel(Type maxLevel, String moduleName,
+ Session session) {
super(new BorderLayout());
- this.tabs = tabs;
- topPanel = new JPanel();
- topPanel.add(new JLabel("Module: " + moduleName));
+ this.session = session;
if (false) {
+ JPanel topPanel = new JPanel();
JButton compileButton = new JButton("Compile (not yet implemented)");
compileButton.setEnabled(false);
compileButton.addActionListener(new ActionListener() {
@@ -97,48 +55,42 @@
}
});
topPanel.add(compileButton);
+ add(topPanel, BorderLayout.NORTH);
}
- add(topPanel, BorderLayout.NORTH);
loggerPanel = new SwingLoggerPanel(maxLevel);
add(loggerPanel);
- AbstractTreeLogger logger = loggerPanel.getLogger();
- ImageIcon browserIcon = null;
- String lcAgent = userAgent.toLowerCase();
- if (lcAgent.contains("msie")) {
- browserIcon = ieIcon;
- } else if (lcAgent.contains("webkit") || lcAgent.contains("safari")) {
- browserIcon = safariIcon;
- } else if (lcAgent.contains("firefox")) {
- browserIcon = firefoxIcon;
- }
- String shortModuleName = moduleName;
- int lastDot = shortModuleName.lastIndexOf('.');
- if (lastDot >= 0) {
- shortModuleName = shortModuleName.substring(lastDot + 1);
- }
- synchronized (tabs) {
- tabs.addTab(shortModuleName, browserIcon, this, moduleName + " from "
- + remoteSocket + " on " + userAgent);
- }
- TreeLogger branch = logger.branch(TreeLogger.INFO, "Request for module "
- + moduleName);
- branch.log(TreeLogger.INFO, "User agent: " + userAgent);
- branch.log(TreeLogger.INFO, "Remote host: " + remoteSocket);
+ session.addModule(moduleName, this);
}
+ /* (non-Javadoc)
+ * @see com.google.gwt.dev.Disconnectable#disconnect()
+ */
public void disconnect() {
- topPanel.add(new ClosedTabComponent());
- synchronized (tabs) {
- int index = tabs.indexOfComponent(this);
- if (index > -1) {
- tabs.setTitleAt(index, "Disconnected");
- tabs.setIconAt(index, null);
- }
- }
- loggerPanel.disconnected();
+ setDisconnected();
}
public AbstractTreeLogger getLogger() {
return loggerPanel.getLogger();
}
+
+ /* (non-Javadoc)
+ * @see com.google.gwt.dev.Disconnectable#isDisconnected()
+ */
+ public boolean isDisconnected() {
+ return disconnected;
+ }
+
+ /**
+ * Called by ModuleTabPanel when the user forces the close.
+ */
+ void setDisconnected() {
+ disconnected = true;
+ loggerPanel.disconnected();
+ // TODO(jat): allow closing open connections once we do away with SWT
+ loggerPanel.setCloseHandler(new CloseHandler() {
+ public void onCloseRequest(SwingLoggerPanel loggerPanelToClose) {
+ session.disconnectModule(ModulePanel.this);
+ }
+ });
+ }
}
diff --git a/dev/oophm/src/com/google/gwt/dev/ModuleTabPanel.java b/dev/oophm/src/com/google/gwt/dev/ModuleTabPanel.java
new file mode 100644
index 0000000..63dc067
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/ModuleTabPanel.java
@@ -0,0 +1,486 @@
+/**
+ * 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;
+
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.dev.OophmHostedModeBase.TabPanelCollection;
+import com.google.gwt.dev.shell.Icons;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.ImageIcon;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.plaf.basic.BasicComboBoxRenderer;
+
+/**
+ * A panel which contains all modules in one browser tab.
+ */
+public class ModuleTabPanel extends JPanel {
+
+ /**
+ * A session has a unique session key within a module tab panel, and is
+ * identified to the user by the timestamp it was first seen.
+ *
+ * <p>Within a session, there will be one or more modules, each with their
+ * own ModulePanel.
+ */
+ public class Session {
+
+ private final long createTimestamp;
+
+ /**
+ * Map from display names in the dropdown box to module panels.
+ */
+ private final Map<String, ModulePanel> displayNameToModule;
+
+ private final IdentityHashMap<ModulePanel, SessionModule> moduleSessionMap;
+
+ private SessionModule lastSelectedModule;
+
+ /**
+ * Map of module names to the number of times that module has been seen.
+ */
+ private final Map<String, Integer> moduleCounts;
+
+ /**
+ * List, in display order, of entries in the module dropdown box.
+ */
+ private final List<SessionModule> modules;
+
+ private final String sessionKey;
+
+ public Session(String sessionKey) {
+ this.sessionKey = sessionKey;
+ createTimestamp = System.currentTimeMillis();
+ displayNameToModule = new HashMap<String, ModulePanel>();
+ moduleSessionMap = new IdentityHashMap<ModulePanel, SessionModule>();
+ modules = new ArrayList<SessionModule>();
+ moduleCounts = new HashMap<String, Integer>();
+ }
+
+ public synchronized void addModule(String moduleName,
+ ModulePanel panel) {
+ Integer moduleCount = moduleCounts.get(moduleName);
+ if (moduleCount == null) {
+ moduleCount = 0;
+ }
+ moduleCounts.put(moduleName, moduleCount + 1);
+ String shortModuleName = getShortModuleName(moduleName);
+ if (moduleCount > 0) {
+ shortModuleName += " (" + moduleCount + ")";
+ }
+ SessionModule sessionModule = SessionModule.create(sessionKey,
+ panel, shortModuleName);
+ modules.add(sessionModule);
+ displayNameToModule.put(shortModuleName, panel);
+ moduleSessionMap.put(panel, sessionModule);
+ // add this item with the key we will use with cardLayout later
+ deckPanel.add(panel, sessionModule.getStringKey());
+ if (this == currentSession) {
+ moduleDropdown.addItem(sessionModule);
+ if (moduleDropdown.getItemCount() > 1) {
+ moduleDropdownPanel.setEnabled(true);
+ moduleDropdownPanel.setVisible(true);
+ }
+ selectModule(sessionModule);
+ }
+ }
+
+ public void buildModuleDropdownContents() {
+ if (this == currentSession) {
+ moduleDropdown.removeAllItems();
+ SessionModule firstModule = null;
+ for (SessionModule sessionModule : modules) {
+ moduleDropdown.addItem(sessionModule);
+ if (firstModule == null) {
+ firstModule = sessionModule;
+ }
+ }
+ if (moduleDropdown.getItemCount() > 1) {
+ moduleDropdownPanel.setEnabled(true);
+ moduleDropdownPanel.setVisible(true);
+ } else {
+ moduleDropdownPanel.setEnabled(false);
+ moduleDropdownPanel.setVisible(false);
+ }
+ if (lastSelectedModule != null) {
+ selectModule(lastSelectedModule);
+ } else if (firstModule != null) {
+ selectModule(firstModule);
+ }
+ }
+ }
+
+ public void disconnectModule(ModulePanel modulePanel) {
+ /*
+ * TODO(jat): for now, only disconnected modules can be closed. When
+ * SWT is ripped out and we can pass OOPHM-specific classes through
+ * BrowseWidgetHost.createModuleSpaceHost, we will need to be able
+ * to shutdown the connection from here if it is not already
+ * disconnected.
+ */
+ SessionModule sessionModule = moduleSessionMap.get(modulePanel);
+ moduleSessionMap.remove(modulePanel);
+ deckPanel.remove(modulePanel);
+ modules.remove(sessionModule);
+ switch (modules.size()) {
+ case 0: // we just closed the last module in this session
+ closeSession(this);
+ break;
+ case 1: // only one module left, hide dropdown
+ moduleDropdownPanel.setEnabled(false);
+ moduleDropdownPanel.setVisible(false);
+ break;
+ default:
+ if (lastSelectedModule == sessionModule) {
+ // if we closed the active module, switch to the most recent remaining
+ lastSelectedModule = modules.get(modules.size() - 1);
+ }
+ buildModuleDropdownContents();
+ break;
+ }
+ }
+
+ public Collection<String> getActiveModules() {
+ ArrayList<String> activeModules = new ArrayList<String>();
+ for (SessionModule sessionModule : modules) {
+ String displayName = sessionModule.toString();
+ Disconnectable module = sessionModule.getModulePanel();
+ if (!module.isDisconnected()) {
+ activeModules.add(displayName);
+ }
+ }
+ return Collections.unmodifiableList(activeModules);
+ }
+
+ public String getDisplayName() {
+ return DateFormat.getDateTimeInstance().format(new Date(createTimestamp));
+ }
+
+ public String getSessionKey() {
+ return sessionKey;
+ }
+
+ public boolean hasActiveModules() {
+ for (SessionModule sessionModule : modules) {
+ Disconnectable module = sessionModule.getModulePanel();
+ if (!module.isDisconnected()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getDisplayName();
+ }
+
+ private String getShortModuleName(String moduleName) {
+ int idx = moduleName.lastIndexOf('.');
+ if (idx < 0) {
+ return moduleName;
+ } else {
+ return moduleName.substring(idx + 1);
+ }
+ }
+
+ private void selectModule(SessionModule sessionModule) {
+ cardLayout.show(deckPanel, sessionModule.getStringKey());
+ lastSelectedModule = sessionModule;
+ moduleDropdown.setSelectedItem(sessionModule);
+ }
+ }
+
+ /**
+ * Renderer used to show entries in the session dropdown box.
+ */
+ private static class SessionRenderer extends BasicComboBoxRenderer {
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ // the superclass just returns this, so we don't save the result and
+ // cast it back to a label
+ super.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ if (value instanceof Session) {
+ Session session = (Session) value;
+ if (!session.hasActiveModules()) {
+ setForeground(DISCONNECTED_DROPDOWN_COLOR);
+ setFont(getFont().deriveFont(Font.ITALIC));
+ }
+ // TODO(jat): set font to bold/etc if new modules were added
+ }
+ return this;
+ }
+ }
+
+ /**
+ * Renderer used to show entries in the module dropdown box.
+ */
+ private static class SessionModuleRenderer extends BasicComboBoxRenderer {
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ // the superclass just returns this, so we don't save the result and
+ // cast it back to a label
+ super.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ if (value instanceof SessionModule) {
+ SessionModule sessionModule = (SessionModule) value;
+ if (sessionModule.getModulePanel().isDisconnected()) {
+ setForeground(DISCONNECTED_DROPDOWN_COLOR);
+ setFont(getFont().deriveFont(Font.ITALIC));
+ }
+ // TODO(jat): set font to bold/etc if the window has new messages
+ }
+ return this;
+ }
+ }
+
+ public static final Color DISCONNECTED_DROPDOWN_COLOR = Color.decode("0x808080");
+
+ private CardLayout cardLayout;
+
+ private Session currentSession;
+
+ private JPanel deckPanel;
+
+ private JComboBox moduleDropdown;
+
+ private JComboBox sessionDropdown;
+
+ private final Map<String, Session> sessions = new HashMap<String, Session>();
+
+ private final TabPanelCollection tabPanelCollection;
+
+ private JPanel topPanel;
+
+ private JPanel sessionDropdownPanel;
+
+ private JPanel moduleDropdownPanel;
+
+ /**
+ * Create a panel which will be a top-level tab in the OOPHM UI. Each of
+ * these tabs will contain one or more sessions, and within that one or
+ * more module instances.
+ *
+ * @param userAgent
+ * @param remoteSocket
+ * @param url
+ * @param tabPanelCollection
+ * @param moduleName used just for the tab name in the event that the plugin
+ * is an older version that doesn't supply the url
+ */
+ public ModuleTabPanel(String userAgent, String remoteSocket, String url,
+ TabPanelCollection tabPanelCollection, String moduleName) {
+ super(new BorderLayout());
+ this.tabPanelCollection = tabPanelCollection;
+ topPanel = new JPanel();
+ sessionDropdownPanel = new JPanel();
+ sessionDropdownPanel.add(new JLabel("Session: "));
+ sessionDropdown = new JComboBox();
+ sessionDropdown.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ Session session = (Session) sessionDropdown.getSelectedItem();
+ selectSession(session);
+ }
+
+ });
+ sessionDropdown.setRenderer(new SessionRenderer());
+ sessionDropdownPanel.add(sessionDropdown);
+ sessionDropdownPanel.setEnabled(false);
+ sessionDropdownPanel.setVisible(false);
+ topPanel.add(sessionDropdownPanel);
+ moduleDropdownPanel = new JPanel();
+ moduleDropdownPanel.add(new JLabel("Module: "));
+ moduleDropdown = new JComboBox();
+ moduleDropdown.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ SessionModule sessionModule = (SessionModule)
+ moduleDropdown.getSelectedItem();
+ if (sessionModule != null) {
+ // may be null when removeAllItems is called
+ currentSession.selectModule(sessionModule);
+ }
+ }
+ });
+ moduleDropdown.setRenderer(new SessionModuleRenderer());
+ moduleDropdownPanel.add(moduleDropdown);
+ moduleDropdownPanel.setEnabled(false);
+ moduleDropdownPanel.setVisible(false);
+ topPanel.add(moduleDropdownPanel);
+ add(topPanel, BorderLayout.NORTH);
+ deckPanel = new JPanel();
+ cardLayout = new CardLayout();
+ deckPanel.setLayout(cardLayout);
+ add(deckPanel);
+ ImageIcon browserIcon = chooseBrowserIcon(userAgent);
+
+ // Construct the tab title and tooltip
+ String tabTitle = url;
+ if (tabTitle == null) {
+ int idx = moduleName.lastIndexOf('.');
+ tabTitle = moduleName.substring(idx + 1);
+ url = "";
+ } else {
+ try {
+ URL parsedUrl = new URL(url);
+ tabTitle = getTabTitle(parsedUrl);
+ // rebuild the URL omitting query params and the hash
+ StringBuilder buf = new StringBuilder();
+ buf.append(parsedUrl.getProtocol()).append(':');
+ if (parsedUrl.getAuthority() != null
+ && parsedUrl.getAuthority().length() > 0) {
+ buf.append("//").append(parsedUrl.getAuthority());
+ }
+ if (parsedUrl.getPath() != null) {
+ buf.append(parsedUrl.getPath());
+ }
+ buf.append(' '); // space for tooltip below
+ url = buf.toString();
+ } catch (MalformedURLException e1) {
+ // Ignore and just use the full URL
+ }
+ }
+
+ tabPanelCollection.addTab(this, browserIcon, tabTitle, url + "from "
+ + remoteSocket + " on " + userAgent);
+ }
+
+ public synchronized ModulePanel addModuleSession(Type maxLevel,
+ String moduleName,
+ String sessionKey) {
+ Session session = findOrCreateSession(sessionKey);
+
+ ModulePanel panel = new ModulePanel(maxLevel, moduleName, session);
+ return panel;
+ }
+
+ private synchronized void addSession(Session session) {
+ sessionDropdown.addItem(session);
+ sessionDropdown.setSelectedItem(session);
+ if (sessionDropdown.getItemCount() > 1) {
+ sessionDropdownPanel.setEnabled(true);
+ sessionDropdownPanel.setVisible(true);
+ }
+ selectSession(session);
+ }
+
+ /**
+ * Choose an icon appropriate for this browser, or null if none.
+ *
+ * @param userAgent User-Agent string from browser
+ * @return icon or null if none
+ */
+ private ImageIcon chooseBrowserIcon(String userAgent) {
+ ImageIcon browserIcon = null;
+ String lcAgent = userAgent.toLowerCase();
+ if (lcAgent.contains("msie")) {
+ browserIcon = Icons.getIE24();
+ } else if (lcAgent.contains("chrome")) {
+ browserIcon = Icons.getChrome24();
+ } else if (lcAgent.contains("webkit") || lcAgent.contains("safari")) {
+ browserIcon = Icons.getSafari24();
+ } else if (lcAgent.contains("firefox")) {
+ browserIcon = Icons.getFirefox24();
+ }
+ return browserIcon;
+ }
+
+ private synchronized void closeSession(Session session) {
+ sessionDropdown.removeItem(session);
+ sessions.remove(session.getSessionKey());
+ switch (sessionDropdown.getItemCount()) {
+ case 0: // last session closed, close tab
+ tabPanelCollection.removeTab(this);
+ return;
+ case 1: // one session left, remove dropdown
+ sessionDropdownPanel.setEnabled(false);
+ sessionDropdownPanel.setVisible(false);
+ break;
+ default: // more than 1 left, do nothing
+ break;
+ }
+ if (session == currentSession) {
+ selectSession((Session) sessionDropdown.getItemAt(
+ sessionDropdown.getItemCount() - 1));
+ }
+ }
+
+ /**
+ * Return the proper Session object for this session, creating it if needed.
+ *
+ * @param sessionKey unique key for this session
+ * @return Session instance
+ */
+ private synchronized Session findOrCreateSession(String sessionKey) {
+ Session session = sessions.get(sessionKey);
+ if (session == null) {
+ session = new Session(sessionKey);
+ sessions.put(sessionKey, session);
+ addSession(session);
+ }
+ return session;
+ }
+
+ private String getTabTitle(URL parsedUrl) {
+ String tabTitle = parsedUrl.getPath();
+ if (tabTitle.length() > 0) {
+ int startIdx = tabTitle.lastIndexOf('/');
+ int lastIdx = tabTitle.length();
+ if (tabTitle.endsWith(".html")) {
+ lastIdx -= 5;
+ } else if (tabTitle.endsWith(".htm")) {
+ lastIdx -= 4;
+ }
+ tabTitle = tabTitle.substring(startIdx + 1, lastIdx);
+ } else {
+ tabTitle = "/";
+ }
+ return tabTitle;
+ }
+
+ private void selectSession(Session session) {
+ currentSession = session;
+ if (session != null) {
+ // can be null when last session removed
+ session.buildModuleDropdownContents();
+ }
+ }
+}
diff --git a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
index 98390f3..bc3f169 100644
--- a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
+++ b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
@@ -27,6 +27,7 @@
import com.google.gwt.dev.shell.OophmSessionHandler;
import com.google.gwt.dev.shell.ShellMainWindow;
import com.google.gwt.dev.shell.ShellModuleSpaceHost;
+import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.util.tools.ArgHandlerString;
@@ -39,6 +40,7 @@
import java.net.URL;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
@@ -46,11 +48,68 @@
import javax.swing.WindowConstants;
/**
- * The main executable class for hosted mode shells based on SWT.
+ * Base class for OOPHM hosted mode shells.
*/
abstract class OophmHostedModeBase extends HostedModeBase {
/**
+ * Interface to group activities related to adding and deleting tabs.
+ */
+ public interface TabPanelCollection {
+
+ /**
+ * Add a new tab containing a ModuleTabPanel.
+ *
+ * @param tabPanel
+ * @param icon
+ * @param title
+ * @param tooltip
+ */
+ void addTab(ModuleTabPanel tabPanel, ImageIcon icon, String title,
+ String tooltip);
+
+ /**
+ * Remove the tab containing a ModuleTabpanel.
+ *
+ * @param tabPanel
+ */
+ void removeTab(ModuleTabPanel tabPanel);
+ }
+
+ abstract static class ArgProcessor extends HostedModeBase.ArgProcessor {
+ public ArgProcessor(OophmHostedModeBaseOptions options, boolean forceServer) {
+ super(options, forceServer);
+ registerHandler(new ArgHandlerPortHosted(options));
+ }
+ }
+
+ interface OophmHostedModeBaseOptions extends HostedModeBaseOptions,
+ OptionPortHosted {
+ }
+
+ /**
+ * Concrete class to implement all shell options.
+ */
+ static class OophmHostedModeBaseOptionsImpl extends HostedModeBaseOptionsImpl
+ implements OophmHostedModeBaseOptions {
+ private int portHosted;
+
+ public int getPortHosted() {
+ return portHosted;
+ }
+
+ public void setPortHosted(int port) {
+ portHosted = port;
+ }
+ }
+
+ interface OptionPortHosted {
+ int getPortHosted();
+
+ void setPortHosted(int portHosted);
+ }
+
+ /**
* Handles the -portHosted command line flag.
*/
private static class ArgHandlerPortHosted extends ArgHandlerString {
@@ -96,43 +155,11 @@
return true;
}
}
-
- abstract static class ArgProcessor extends HostedModeBase.ArgProcessor {
- public ArgProcessor(OophmHostedModeBaseOptions options, boolean forceServer) {
- super(options, forceServer);
- registerHandler(new ArgHandlerPortHosted(options));
- }
- }
-
- interface OophmHostedModeBaseOptions extends HostedModeBaseOptions,
- OptionPortHosted {
- }
-
- /**
- * Concrete class to implement all shell options.
- */
- static class OophmHostedModeBaseOptionsImpl extends HostedModeBaseOptionsImpl
- implements OophmHostedModeBaseOptions {
- private int portHosted;
-
- public int getPortHosted() {
- return portHosted;
- }
-
- public void setPortHosted(int port) {
- portHosted = port;
- }
- }
-
- interface OptionPortHosted {
- int getPortHosted();
-
- void setPortHosted(int portHosted);
- }
-
+
private class OophmBrowserWidgetHostImpl extends BrowserWidgetHostImpl {
private final Map<ModuleSpaceHost, ModulePanel> moduleTabs = new IdentityHashMap<ModuleSpaceHost, ModulePanel>();
-
+ private final Map<DevelModeTabKey, ModuleTabPanel> tabPanels = new HashMap<DevelModeTabKey, ModuleTabPanel>();
+
@Override
public ModuleSpaceHost createModuleSpaceHost(TreeLogger logger,
BrowserWidget widget, String moduleName)
@@ -144,23 +171,38 @@
String moduleName, String userAgent, String url, String tabKey,
String sessionKey, String remoteSocket)
throws UnableToCompleteException {
+ if (sessionKey == null) {
+ // if we don't have a unique session key, make one up
+ sessionKey = randomString();
+ }
TreeLogger logger = mainLogger;
TreeLogger.Type maxLevel = TreeLogger.INFO;
if (mainLogger instanceof AbstractTreeLogger) {
maxLevel = ((AbstractTreeLogger) mainLogger).getMaxDetail();
}
- // TODO(jat): collect different sessions into the same tab, with a
- // dropdown to select individual module's logs
- ModulePanel tab;
+ ModuleTabPanel tabPanel = null;
+ ModulePanel tab = null;
if (!isHeadless()) {
- tab = new ModulePanel(maxLevel, moduleName, userAgent, remoteSocket,
- tabs);
+ tabPanel = findModuleTab(userAgent, remoteSocket, url, tabKey,
+ moduleName);
+ tab = tabPanel.addModuleSession(maxLevel, moduleName, sessionKey);
logger = tab.getLogger();
+ TreeLogger branch = logger.branch(TreeLogger.INFO, "Loading module "
+ + moduleName);
+ if (url != null) {
+ branch.log(TreeLogger.INFO, "Top URL: " + url);
+ }
+ branch.log(TreeLogger.INFO, "User agent: " + userAgent);
+ branch.log(TreeLogger.TRACE, "Remote socket: " + remoteSocket);
+ if (tabKey != null) {
+ branch.log(TreeLogger.DEBUG, "Tab key: " + tabKey);
+ }
+ if (sessionKey != null) {
+ branch.log(TreeLogger.DEBUG, "Session key: " + sessionKey);
+ }
// Switch to a wait cursor.
frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- } else {
- tab = null;
}
try {
@@ -190,16 +232,71 @@
}
public void unloadModule(ModuleSpaceHost moduleSpaceHost) {
- ModulePanel tab = moduleTabs.remove(moduleSpaceHost);
+ Disconnectable tab = moduleTabs.remove(moduleSpaceHost);
if (tab != null) {
tab.disconnect();
}
}
+
+ private ModuleTabPanel findModuleTab(String userAgent, String remoteSocket,
+ String url, String tabKey, String moduleName) {
+ int hostEnd = remoteSocket.indexOf(':');
+ if (hostEnd < 0) {
+ hostEnd = remoteSocket.length();
+ }
+ String remoteHost = remoteSocket.substring(0, hostEnd);
+ final DevelModeTabKey key = new DevelModeTabKey(userAgent, url, tabKey,
+ remoteHost);
+ ModuleTabPanel moduleTabPanel = tabPanels.get(key);
+ if (moduleTabPanel == null) {
+ moduleTabPanel = new ModuleTabPanel(userAgent, remoteSocket, url,
+ new TabPanelCollection() {
+ public void addTab(ModuleTabPanel tabPanel, ImageIcon icon,
+ String title, String tooltip) {
+ synchronized (tabs) {
+ tabs.addTab(title, icon, tabPanel, tooltip);
+ tabPanels.put(key, tabPanel);
+ }
+ }
+
+ public void removeTab(ModuleTabPanel tabPanel) {
+ synchronized (tabs) {
+ tabs.remove(tabPanel);
+ tabPanels.remove(key);
+ }
+ }
+ }, moduleName);
+ }
+ return moduleTabPanel;
+ }
}
protected static final String PACKAGE_PATH = OophmHostedModeBase.class.getPackage().getName().replace(
'.', '/').concat("/shell/");
+ private static final Random RNG = new Random();
+
+ /**
+ * Produce a random string that has low probability of collisions.
+ *
+ * <p>In this case, we use 16 characters, each drawn from a pool of 94,
+ * so the number of possible values is 94^16, leading to an expected number
+ * of values used before a collision occurs as sqrt(pi/2) * 94^8 (treated the
+ * same as a birthday attack), or a little under 10^16.
+ *
+ * <p>This algorithm is also implemented in hosted.html, though it is not
+ * technically important that they match.
+ *
+ * @return a random string
+ */
+ protected static String randomString() {
+ StringBuilder buf = new StringBuilder(16);
+ for (int i = 0; i < 16; ++i) {
+ buf.append((char) RNG.nextInt('~' - '!' + 1) + '!');
+ }
+ return buf.toString();
+ }
+
/**
* Loads an image from the classpath in this package.
*/
diff --git a/dev/oophm/src/com/google/gwt/dev/SessionModule.java b/dev/oophm/src/com/google/gwt/dev/SessionModule.java
new file mode 100644
index 0000000..7b25dbc
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/SessionModule.java
@@ -0,0 +1,128 @@
+/**
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class used in the module drop-down box so that we can have a unique
+ * object in the dropdown. It is important that identity is maintained, such
+ * that if sessionKey, modulePanel, and moduleDisplayName are the same the
+ * same SessionModule instance is returned so == means the same as equals.
+ */
+final class SessionModule {
+
+ /**
+ * Used to cache instances so we always return the same SessionModule
+ * instance for the same key values.
+ */
+ private static final Map<SessionModule, SessionModule> instanceCache
+ = new HashMap<SessionModule, SessionModule>();
+
+ /**
+ * Return a SessionModule instance for a given sessionKey, modulePanel, and
+ * moduleDisplayName, re-using an existing instance if it exists.
+ *
+ * @param sessionKey
+ * @param modulePanel
+ * @param moduleDisplayName
+ * @return unique SessionModule instance matching sessionKey and
+ * moduleDisplayName
+ */
+ public static SessionModule create(String sessionKey,
+ Disconnectable modulePanel, String moduleDisplayName) {
+ SessionModule sessionModule = new SessionModule(sessionKey, modulePanel,
+ moduleDisplayName);
+ if (instanceCache.containsKey(sessionModule)) {
+ return instanceCache.get(sessionModule);
+ }
+ instanceCache.put(sessionModule, sessionModule);
+ return sessionModule;
+ }
+
+ // @NotNull
+ private final Disconnectable modulePanel;
+
+ // @NotNull
+ private final String moduleDisplayName;
+
+ // @NotNull
+ private final String sessionKey;
+
+ private SessionModule(String sessionKey, Disconnectable modulePanel,
+ String moduleDisplayName) {
+ if (sessionKey == null) {
+ throw new IllegalArgumentException("sessionKey cannot be null");
+ }
+ if (modulePanel == null) {
+ throw new IllegalArgumentException("modulePanel cannot be null");
+ }
+ if (moduleDisplayName == null) {
+ throw new IllegalArgumentException("moduleDisplayName cannot be null");
+ }
+ this.sessionKey = sessionKey;
+ this.modulePanel = modulePanel;
+ this.moduleDisplayName = moduleDisplayName;
+ }
+
+ // Even though we guarantee identity, we still need equals implemented
+ // so we can do so.
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ SessionModule other = (SessionModule) obj;
+ return sessionKey.equals(other.sessionKey)
+ && modulePanel.equals(other.modulePanel)
+ && moduleDisplayName.equals(other.moduleDisplayName);
+ }
+
+ public String getModuleDisplayName() {
+ return moduleDisplayName;
+ }
+
+ public Disconnectable getModulePanel() {
+ return modulePanel;
+ }
+
+ /**
+ * @return a unique key representing the session and the module name
+ * within that session.
+ */
+ public String getStringKey() {
+ return sessionKey + moduleDisplayName;
+ }
+
+ @Override
+ public int hashCode() {
+ return sessionKey.hashCode() + 31 * moduleDisplayName.hashCode();
+ }
+
+ /**
+ * @return a string suitable for human display only, which consists of the
+ * module's short name, possibly including a disambiguator for multiple
+ * instances of the module name within the same session.
+ */
+ @Override
+ public String toString() {
+ return moduleDisplayName;
+ }
+}
\ No newline at end of file
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
index dd59992..9ec6599 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/BrowserChannelServer.java
@@ -39,6 +39,8 @@
private String userAgent;
+ private int protocolVersion = -1;
+
public BrowserChannelServer(TreeLogger initialLogger, Socket socket,
SessionHandler handler) throws IOException {
super(socket);
@@ -65,6 +67,13 @@
}
/**
+ * @return the negotiated protocol version, or -1 if not yet negotiated.
+ */
+ public int getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ /**
* @param ccl
* @param jsthis
* @param methodName
@@ -285,6 +294,9 @@
}
moduleName = oldLoadModule.getModuleName();
userAgent = oldLoadModule.getUserAgent();
+ protocolVersion = 1;
+ logger.log(TreeLogger.WARN, "Connection from old browser plugin -- "
+ + "please upgrade to a later version for full functionality");
break;
case CHECK_VERSIONS:
String connectError = null;
@@ -296,14 +308,22 @@
|| maxVersion < BROWSERCHANNEL_PROTOCOL_VERSION) {
connectError = "No supported protocol version in range " + minVersion
+ " - " + maxVersion;
+ } else {
+ if (!HostedHtmlVersion.validHostedHtmlVersion(logger,
+ hostedHtmlVersion)) {
+ new FatalErrorMessage(this,
+ "Invalid hosted.html version - check log window").send();
+ return;
+ }
}
- // TODO(jat): verify hosted.html version
if (connectError != null) {
- logger.log(TreeLogger.ERROR, "Connection error " + connectError, null);
+ logger.log(TreeLogger.ERROR, "Connection error: " + connectError,
+ null);
new FatalErrorMessage(this, connectError).send();
return;
}
- new ProtocolVersionMessage(this, BROWSERCHANNEL_PROTOCOL_VERSION).send();
+ protocolVersion = BROWSERCHANNEL_PROTOCOL_VERSION;
+ new ProtocolVersionMessage(this, protocolVersion).send();
type = Message.readMessageType(getStreamFromOtherSide());
// Optionally allow client to request switch of transports. Inband is
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/CloseButton.java b/dev/oophm/src/com/google/gwt/dev/shell/CloseButton.java
new file mode 100644
index 0000000..dd927d0
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/shell/CloseButton.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2008 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.shell;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+
+/**
+ * Represents a close button, shrink-wrapped to exactly fit the size of the
+ * close icon.
+ */
+public class CloseButton extends JPanel {
+
+ /**
+ * Callback interface for clicking on the close button.
+ */
+ public interface Callback {
+
+ /**
+ * Called when the close button is clicked.
+ */
+ void onCloseRequest();
+ }
+
+ private Callback callback;
+
+ /**
+ * Create a close button.
+ *
+ * @param toolTipText text to use for tooltip if non-null
+ */
+ public CloseButton(String toolTipText) {
+ super(new FlowLayout(FlowLayout.LEFT, 0, 0));
+ setOpaque(false);
+ ImageIcon closeIcon = Icons.getClose();
+ JButton button = new JButton(closeIcon);
+ button.setBorderPainted(false);
+ button.setPreferredSize(new Dimension(closeIcon.getIconWidth(),
+ closeIcon.getIconHeight()));
+ if (toolTipText != null) {
+ button.setToolTipText(toolTipText);
+ }
+ add(button);
+ button.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (callback != null) {
+ callback.onCloseRequest();
+ }
+ }
+ });
+ }
+
+ /**
+ * Set the callback for when this button is clicked.
+ *
+ * @param callback
+ */
+ public void setCallback(Callback callback) {
+ this.callback = callback;
+ }
+}
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/Icons.java b/dev/oophm/src/com/google/gwt/dev/shell/Icons.java
new file mode 100644
index 0000000..c5f4da5
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/shell/Icons.java
@@ -0,0 +1,115 @@
+/*
+ * 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.shell;
+
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+
+import java.net.URL;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+/**
+ * Purely static class which holds references to all the icons needed in the
+ * development mode shell.
+ */
+public class Icons {
+
+ // log level icons
+ private static final Icon LOG_ITEM_ERROR = loadIcon(
+ AbstractTreeLogger.class, "log-item-error.gif");
+ private static final Icon LOG_ITEM_WARNING = loadIcon(
+ AbstractTreeLogger.class, "log-item-warning.gif");
+ private static final Icon LOG_ITEM_INFO = loadIcon(
+ AbstractTreeLogger.class, "log-item-info.gif");
+ private static final Icon LOG_ITEM_DEBUG = loadIcon(
+ AbstractTreeLogger.class, "log-item-debug.gif");
+ private static final Icon LOG_ITEM_TRACE = loadIcon(
+ AbstractTreeLogger.class, "log-item-trace.gif");
+ private static final Icon LOG_ITEM_SPAM = loadIcon(
+ AbstractTreeLogger.class, "log-item-spam.gif");
+
+ // browser icons
+ private static final ImageIcon CHROME_24 = loadIcon("chrome24.png");
+ private static final ImageIcon FIREFOX_24 = loadIcon("firefox24.png");
+ private static final ImageIcon IE_24 = loadIcon("ie24.png");
+ private static final ImageIcon SAFARI_24 = loadIcon("safari24.png");
+
+ private static final ImageIcon CLOSE = loadIcon("close.png");
+
+ public static ImageIcon getChrome24() {
+ return CHROME_24;
+ }
+
+ public static ImageIcon getClose() {
+ return CLOSE;
+ }
+
+ public static ImageIcon getFirefox24() {
+ return FIREFOX_24;
+ }
+
+ public static ImageIcon getIE24() {
+ return IE_24;
+ }
+
+ public static Icon getLogItemDebug() {
+ return LOG_ITEM_DEBUG;
+ }
+
+ public static Icon getLogItemError() {
+ return LOG_ITEM_ERROR;
+ }
+
+ public static Icon getLogItemInfo() {
+ return LOG_ITEM_INFO;
+ }
+
+ public static Icon getLogItemSpam() {
+ return LOG_ITEM_SPAM;
+ }
+
+ public static Icon getLogItemTrace() {
+ return LOG_ITEM_TRACE;
+ }
+
+ public static Icon getLogItemWarning() {
+ return LOG_ITEM_WARNING;
+ }
+
+ public static ImageIcon getSafari24() {
+ return SAFARI_24;
+ }
+
+ private static ImageIcon loadIcon(Class<?> clazz, String name) {
+ URL url = clazz.getResource(name);
+ if (url != null) {
+ ImageIcon image = new ImageIcon(url);
+ return image;
+ } else {
+ // Bad image.
+ return new ImageIcon();
+ }
+ }
+
+ private static ImageIcon loadIcon(String name) {
+ return loadIcon(Icons.class, name);
+ }
+
+ // prevent instantiation
+ private Icons() {
+ }
+}
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java b/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
index ff73041..1eeae51 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/OophmSessionHandler.java
@@ -154,30 +154,34 @@
}
@Override
- public TreeLogger loadModule(TreeLogger logger, BrowserChannel channel,
- String moduleName, String userAgent, String url, String tabKey,
- String sessionKey) {
+ public TreeLogger loadModule(TreeLogger loadModuleLogger,
+ BrowserChannel channel, String moduleName, String userAgent, String url,
+ String tabKey, String sessionKey) {
+ logger = loadModuleLogger;
try {
// Attach a new ModuleSpace to make it programmable.
//
+ // TODO(jat): pass serverChannel to createModuleSpaceHost instead
+ // of the remote endpoint when we remove SWT
BrowserChannelServer serverChannel = (BrowserChannelServer) channel;
- ModuleSpaceHost msh = host.createModuleSpaceHost(logger, moduleName,
- userAgent, url, tabKey, sessionKey, channel.getRemoteEndpoint());
- this.logger = logger = msh.getLogger();
+ ModuleSpaceHost msh = host.createModuleSpaceHost(loadModuleLogger,
+ moduleName, userAgent, url, tabKey, sessionKey,
+ channel.getRemoteEndpoint());
+ logger = msh.getLogger();
ModuleSpace moduleSpace = new ModuleSpaceOOPHM(msh, moduleName,
serverChannel);
moduleMap.put(serverChannel, moduleSpace);
- moduleSpace.onLoad(logger);
+ moduleSpace.onLoad(loadModuleLogger);
} catch (Throwable e) {
// We do catch Throwable intentionally because there are a ton of things
// that can go wrong trying to load a module, including Error-derived
// things like NoClassDefFoundError.
//
- logger.log(TreeLogger.ERROR, "Failed to load module '" + moduleName
+ this.logger.log(TreeLogger.ERROR, "Failed to load module '" + moduleName
+ "' from user agent '" + userAgent + "' at "
+ channel.getRemoteEndpoint(), e);
}
- return logger;
+ return this.logger;
}
@Override
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/chrome24.png b/dev/oophm/src/com/google/gwt/dev/shell/chrome24.png
new file mode 100644
index 0000000..be03337
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/shell/chrome24.png
Binary files differ
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
index fe459f5..c24a316 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
@@ -17,6 +17,8 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
+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.log.AbstractTreeLogger;
@@ -38,7 +40,6 @@
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
-import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
@@ -71,6 +72,20 @@
*/
public class SwingLoggerPanel extends JPanel implements TreeSelectionListener {
+ /**
+ * Callback interface for optional close button behavior.
+ */
+ public interface CloseHandler {
+
+ /**
+ * Called when the close button has been clicked on the tree logger
+ * and any confirmation needed has been handled.
+ *
+ * @param loggerPanel SwingTreeLogger instance being closed
+ */
+ void onCloseRequest(SwingLoggerPanel loggerPanel);
+ }
+
private class FindBox extends JPanel {
private JTextField searchField;
@@ -101,11 +116,12 @@
prevMatch();
}
});
- JButton closeButton = new JButton(closeIcon);
- closeButton.setBorderPainted(false);
- closeButton.setPreferredSize(new Dimension(closeIcon.getIconWidth(),
- closeIcon.getIconHeight()));
- closeButton.setToolTipText("Close this search box");
+ CloseButton closeButton = new CloseButton("Close this search box");
+ closeButton.setCallback(new Callback() {
+ public void onCloseRequest() {
+ hideFindBox();
+ }
+ });
top.add(closeButton);
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(key, "find-cancel");
@@ -125,11 +141,6 @@
}
};
getActionMap().put("find-cancel", closeFindBox);
- closeButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- hideFindBox();
- }
- });
add(top, BorderLayout.NORTH);
searchStatus = new JLabel("Type search text and press Enter");
searchStatus.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 0));
@@ -203,9 +214,10 @@
private static class TreeRenderer extends DefaultTreeCellRenderer {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
- boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+ boolean sel, boolean expanded, boolean leaf, int row,
+ boolean componentHasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row,
- hasFocus);
+ componentHasFocus);
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object userObject = node.getUserObject();
if (userObject instanceof LogEvent) {
@@ -216,8 +228,6 @@
}
}
- private static ImageIcon closeIcon = SwingTreeLogger.tryLoadImage("icon-close.png");
-
private static final Color DISCONNECTED_COLOR = Color.decode("0xFFDDDD");
// package protected for SwingTreeLogger to access
@@ -248,6 +258,12 @@
private JScrollPane treeView;
+ private CloseButton closeLogger;
+
+ private CloseHandler closeHandler;
+
+ private boolean disconnected = false;
+
public SwingLoggerPanel(TreeLogger.Type maxLevel) {
super(new BorderLayout());
regexFilter = "";
@@ -255,23 +271,26 @@
// TODO(jat): how to make the topPanel properly layout items
// when the window is resized
topPanel = new JPanel();
+ topPanel.setLayout(new BorderLayout());
+ JPanel logButtons = new JPanel();
JButton expandButton = new JButton("Expand All");
expandButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
expandAll();
}
});
- topPanel.add(expandButton);
- JButton collapsButton = new JButton("Collapse All");
- collapsButton.addActionListener(new ActionListener() {
+ logButtons.add(expandButton);
+ JButton collapseButton = new JButton("Collapse All");
+ collapseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
collapseAll();
}
});
- topPanel.add(collapsButton);
+ logButtons.add(collapseButton);
+ topPanel.add(logButtons, BorderLayout.CENTER);
// TODO(jat): temporarily avoid showing parts that aren't implemented.
if (false) {
- topPanel.add(new JLabel("Filter Log Messages: "));
+ logButtons.add(new JLabel("Filter Log Messages: "));
levelComboBox = new JComboBox();
for (TreeLogger.Type type : TreeLogger.Type.instances()) {
if (type.compareTo(maxLevel) > 0) {
@@ -288,14 +307,14 @@
}
});
regexField = new JTextField(20);
- topPanel.add(regexField);
+ logButtons.add(regexField);
JButton applyRegexButton = new JButton("Apply Regex");
applyRegexButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setRegexFilter(regexField.getText());
}
});
- topPanel.add(applyRegexButton);
+ logButtons.add(applyRegexButton);
JButton clearRegexButton = new JButton("Clear Regex");
clearRegexButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
@@ -303,8 +322,20 @@
setRegexFilter("");
}
});
- topPanel.add(clearRegexButton);
+ logButtons.add(clearRegexButton);
}
+ closeLogger = new CloseButton("Close this log window");
+ closeLogger.setCallback(new Callback() {
+ // TODO(jat): add support for closing active session when SWT is removed
+ public void onCloseRequest() {
+ if (disconnected && closeHandler != null) {
+ closeHandler.onCloseRequest(SwingLoggerPanel.this);
+ }
+ }
+ });
+ closeLogger.setEnabled(false);
+ closeLogger.setVisible(false);
+ topPanel.add(closeLogger, BorderLayout.EAST);
add(topPanel, BorderLayout.NORTH);
root = new DefaultMutableTreeNode();
treeModel = new DefaultTreeModel(root);
@@ -384,6 +415,7 @@
}
public void disconnected() {
+ disconnected = true;
tree.setBackground(DISCONNECTED_COLOR);
tree.repaint();
}
@@ -422,6 +454,18 @@
this.autoScroll = autoScroll;
}
+ /**
+ * Sets a callback for handling a close request, which also makes the close
+ * button visible.
+ *
+ * @param handler
+ */
+ public void setCloseHandler(CloseHandler handler) {
+ closeHandler = handler;
+ closeLogger.setEnabled(true);
+ closeLogger.setVisible(true);
+ }
+
public void valueChanged(TreeSelectionEvent e) {
if (e.isAddedPath()) {
TreePath path = e.getPath();
@@ -446,6 +490,19 @@
JOptionPane.INFORMATION_MESSAGE);
}
+ /**
+ * Ask the user for confirmation to close the current logger.
+ *
+ * @return true if the user confirmed the request
+ */
+ protected boolean confirmClose() {
+ int response = JOptionPane.showConfirmDialog(null,
+ "Close the logger for the currently displayed module",
+ "Close this Logger", JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+ return response != JOptionPane.YES_OPTION;
+ }
+
protected ArrayList<DefaultMutableTreeNode> doFind(String search) {
@SuppressWarnings("unchecked")
Enumeration<DefaultMutableTreeNode> children = root.preorderEnumeration();
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
index d82458c..520769c 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
@@ -16,6 +16,7 @@
package com.google.gwt.dev.shell.log;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.shell.Icons;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import org.jdesktop.swingworker.SwingWorker;
@@ -27,7 +28,6 @@
import java.util.concurrent.ExecutionException;
import javax.swing.Icon;
-import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
@@ -89,7 +89,7 @@
}
/**
- * @return
+ * @return full text of log event.
*/
public String getFullText() {
StringBuffer sb = new StringBuffer();
@@ -130,28 +130,30 @@
}
/**
- * @param treeRenderer
+ * Set the properties of a label to match this log entry.
+ *
+ * @param treeLabel label to set properties for.
*/
public void setDisplayProperties(JLabel treeLabel) {
Icon image = null;
if (type == TreeLogger.ERROR) {
treeLabel.setForeground(Color.RED);
- image = imageError;
+ image = Icons.getLogItemError();
} else if (type == TreeLogger.WARN) {
treeLabel.setForeground(WARN_COLOR);
- image = imageWarning;
+ image = Icons.getLogItemWarning();
} else if (type == TreeLogger.INFO) {
treeLabel.setForeground(Color.BLACK);
- image = imageInfo;
+ image = Icons.getLogItemInfo();
} else if (type == TreeLogger.TRACE) {
treeLabel.setForeground(Color.DARK_GRAY);
- image = imageTrace;
+ image = Icons.getLogItemTrace();
} else if (type == TreeLogger.DEBUG) {
treeLabel.setForeground(DEBUG_COLOR);
- image = imageDebug;
+ image = Icons.getLogItemDebug();
} else if (type == TreeLogger.SPAM) {
treeLabel.setForeground(SPAM_COLOR);
- image = imageSpam;
+ image = Icons.getLogItemSpam();
} else {
// Messages without icons, ie ALL
treeLabel.setForeground(Color.BLACK);
@@ -209,34 +211,6 @@
}
}
- // These don't get disposed, but they do last for the entire process, so
- // not a big deal.
- //
- private static final ImageIcon imageDebug = tryLoadImage("log-item-debug.gif");
- private static final ImageIcon imageError = tryLoadImage("log-item-error.gif");
- private static final ImageIcon imageInfo = tryLoadImage("log-item-info.gif");
- // private static final ImageIcon imageLink = tryLoadImage("log-link.gif");
- private static final ImageIcon imageSpam = tryLoadImage("log-item-spam.gif");
- private static final ImageIcon imageTrace = tryLoadImage("log-item-trace.gif");
- private static final ImageIcon imageWarning = tryLoadImage("log-item-warning.gif");
-
- // package protected to allow SwingLoggerPanel access
- // TODO(jat): reorganize
- static ImageIcon tryLoadImage(String simpleName) {
- URL url = SwingTreeLogger.class.getResource(simpleName);
- if (url != null) {
- try {
- ImageIcon image = new ImageIcon(url);
- return image;
- } finally {
- }
- } else {
- // Bad image.
- //
- return null;
- }
- }
-
// package protected so SwingLoggerPanel can access
DefaultMutableTreeNode treeNode;
@@ -245,7 +219,7 @@
/**
* Constructs the top-level TreeItemLogger.
*
- * @param treePanel
+ * @param panel
*/
public SwingTreeLogger(SwingLoggerPanel panel) {
this.panel = panel;
diff --git a/dev/oophm/test/com/google/gwt/dev/DevelModeTabKeyTest.java b/dev/oophm/test/com/google/gwt/dev/DevelModeTabKeyTest.java
new file mode 100644
index 0000000..0971e47
--- /dev/null
+++ b/dev/oophm/test/com/google/gwt/dev/DevelModeTabKeyTest.java
@@ -0,0 +1,83 @@
+/**
+ * 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;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for DevelModeTabKey.
+ */
+public class DevelModeTabKeyTest extends TestCase {
+
+ public void testConstructor() {
+ DevelModeTabKey key = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html", "tabkey1", "host:9999");
+ assertEquals("ua1", key.getUserAgent());
+ assertEquals("http://example.org/foo.html", key.getUrl());
+ assertEquals("tabkey1", key.getTabKey());
+ assertEquals("host:9999", key.getRemoteSocket());
+ key = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html?param=value#hash", "tabkey1", "host:9999");
+ assertEquals("http://example.org/foo.html", key.getUrl());
+
+ // check acceptance/rejection of nulls
+ try {
+ key = new DevelModeTabKey(null,
+ "http://example.org/foo.html", "tabkey1", "host:9999");
+ fail("Expected exception on null userAgent");
+ } catch (IllegalArgumentException expected) {
+ }
+ key = new DevelModeTabKey("ua1",
+ null, "tabkey1", "host:9999");
+ assertEquals("", key.getUrl());
+ key = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html", null, "host:9999");
+ assertEquals("", key.getTabKey());
+ try {
+ key = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html", "tabkey1", null);
+ fail("Expected exception on null remoteHost");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testEquals() {
+ DevelModeTabKey key1 = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html", "tabkey1", "host:9999");
+ DevelModeTabKey key2 = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html", "tabkey1", "host:9999");
+ assertEquals(key1, key2);
+
+ // query parameters and the history token don't matter
+ key2 = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html?param=value#hash", "tabkey1", "host:9999");
+ assertEquals(key1, key2);
+
+ // various mismatches
+ key2 = new DevelModeTabKey("ua2",
+ "http://example.org/foo.html?param=value#hash", "tabkey1", "host:9999");
+ assertFalse(key1.equals(key2));
+ key2 = new DevelModeTabKey("ua1",
+ "http://example.org:80/foo.html", "tabkey1", "host:9999");
+ assertFalse(key1.equals(key2));
+ key2 = new DevelModeTabKey("ua1",
+ "http://example.org/foo.html", "tabkey2", "host:9999");
+ assertFalse(key1.equals(key2));
+ key2 = new DevelModeTabKey("ua1",
+ "http://example.org:80/foo.html", "tabkey1", "host:9998");
+ assertFalse(key1.equals(key2));
+ }
+}
diff --git a/dev/oophm/test/com/google/gwt/dev/SessionModuleTest.java b/dev/oophm/test/com/google/gwt/dev/SessionModuleTest.java
new file mode 100644
index 0000000..8d018fc
--- /dev/null
+++ b/dev/oophm/test/com/google/gwt/dev/SessionModuleTest.java
@@ -0,0 +1,67 @@
+/**
+ * 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;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for SessionModule.
+ */
+public class SessionModuleTest extends TestCase {
+
+ public static final Disconnectable mockDisc1 = new Disconnectable() {
+ public void disconnect() {
+ }
+
+ public boolean isDisconnected() {
+ return false;
+ }
+ };
+
+ public static final Disconnectable mockDisc2 = new Disconnectable() {
+ public void disconnect() {
+ }
+
+ public boolean isDisconnected() {
+ return false;
+ }
+ };
+
+ public void testInstanceCache() {
+ String sessionKey1 = "session1";
+ String moduleName1 = "module1";
+ SessionModule sm1 = SessionModule.create(sessionKey1, mockDisc1,
+ moduleName1);
+ SessionModule sm2 = SessionModule.create(sessionKey1, mockDisc1,
+ moduleName1);
+ assertSame(sm1, sm2);
+ String sessionKey2 = "session" + "1"; // make sure it is a new string
+ sm2 = SessionModule.create(sessionKey2, mockDisc1,
+ moduleName1);
+ assertSame(sm1, sm2);
+ sessionKey2 = "session21";
+ sm2 = SessionModule.create(sessionKey2, mockDisc1,
+ moduleName1);
+ assertNotSame(sm1, sm2);
+ sm2 = SessionModule.create(sessionKey1, mockDisc2,
+ moduleName1);
+ assertNotSame(sm1, sm2);
+ String moduleName2 = "module2";
+ sm2 = SessionModule.create(sessionKey1, mockDisc1,
+ moduleName2);
+ assertNotSame(sm1, sm2);
+ }
+}