blob: d874e8e3ad5d072e491b23851bd99db765d5cae5 [file] [log] [blame]
/*
* 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;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.dev.DevModeBase.HostedModeBaseOptions;
import com.google.gwt.dev.WebServerPanel.RestartAction;
import com.google.gwt.dev.shell.ShellMainWindow;
import com.google.gwt.dev.ui.DevModeUI;
import com.google.gwt.dev.ui.DoneCallback;
import com.google.gwt.dev.ui.DoneEvent;
import com.google.gwt.dev.ui.RestartServerCallback;
import com.google.gwt.dev.ui.RestartServerEvent;
import com.google.gwt.dev.util.collect.HashMap;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JTabbedPane;
import javax.swing.WindowConstants;
/**
* Implements the Swing UI for development mode.
*/
public class SwingUI extends DevModeUI {
/**
* Module handle for Swing UI.
*/
protected class SwingModuleHandle implements ModuleHandle {
private final ModulePanel tab;
/**
* Create an immutable module handle.
* @param tab the module panel associated with this instance
*/
public SwingModuleHandle(ModulePanel tab) {
this.tab = tab;
}
public TreeLogger getLogger() {
return tab.getLogger();
}
/**
* @return the ModulePanel associated with this module instance.
*/
public ModulePanel getTab() {
return tab;
}
public void unload() {
if (tab != null) {
tab.disconnect();
}
}
}
/**
* Interface to group activities related to adding and deleting tabs.
*/
protected 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);
}
protected static final String PACKAGE_PATH = SwingUI.class.getPackage(
).getName().replace('.', '/').concat("/shell/");
private static final Object sessionCounterLock = new Object();
private static int sessionCounter = 0;
/**
* Loads an image from the classpath in this package.
*/
static ImageIcon loadImageIcon(String name) {
return loadImageIcon(name, true);
}
/**
* Loads an image from the classpath, optionally prepending this package.
*
* @param name name of an image file.
* @param prependPackage true if {@link #PACKAGE_PATH} should be prepended to
* this name.
* @return an ImageIcon instance to use -- in the event of an error loading
* the requested image, a blank ImageIcon is returned
*/
static ImageIcon loadImageIcon(String name, boolean prependPackage) {
ClassLoader cl = SwingUI.class.getClassLoader();
if (prependPackage) {
name = PACKAGE_PATH + name;
}
URL url = (name == null) ? null : cl.getResource(name);
if (url != null) {
ImageIcon image = new ImageIcon(url);
return image;
} else {
// Bad image.
return new ImageIcon();
}
}
private final HostedModeBaseOptions options;
private final Map<DevelModeTabKey, ModuleTabPanel> tabPanels = new HashMap<
DevelModeTabKey, ModuleTabPanel>();
private ShellMainWindow mainWnd;
private JFrame frame;
private JTabbedPane tabs;
private WebServerPanel webServerLog;
private TreeLogger topLogger;
/**
* Create a Swing UI instance.
*
* @param options parsed command-line options
*/
public SwingUI(HostedModeBaseOptions options) {
this.options = options;
}
@Override
public ModuleHandle getModuleLogger(final String userAgent,
final String remoteSocket, final String url, final String tabKey,
final String moduleName, final String sessionKey, final String agentTag,
final byte[] agentIcon, final TreeLogger.Type logLevel) {
// TODO(jat): add support for closing an active module
ModuleHandle handle = invokeAndGet(new Callable<ModuleHandle>() {
public ModuleHandle call() throws Exception {
ModuleTabPanel tabPanel = findModuleTab(userAgent, remoteSocket,
url, tabKey, moduleName, agentIcon);
ModulePanel tab = tabPanel.addModuleSession(logLevel, moduleName,
sessionKey, options.getLogFile(String.format("%s-%s-%d.log",
moduleName, agentTag, getNextSessionCounter(
options.getLogDir()))));
// TODO: Switch to a wait cursor?
return new SwingModuleHandle(tab);
}
});
TreeLogger logger = handle.getLogger();
TreeLogger branch = logger.branch(TreeLogger.INFO, "Loading module "
+ moduleName);
if (logger.isLoggable(TreeLogger.INFO)) {
if (url != null) {
branch.log(TreeLogger.INFO, "Top URL: " + url);
}
branch.log(TreeLogger.INFO, "User agent: " + userAgent);
}
if (logger.isLoggable(TreeLogger.TRACE)) {
branch.log(TreeLogger.TRACE, "Remote socket: " + remoteSocket);
}
if (branch.isLoggable(TreeLogger.DEBUG)) {
if (tabKey != null) {
branch.log(TreeLogger.DEBUG, "Tab key: " + tabKey);
}
if (sessionKey != null) {
branch.log(TreeLogger.DEBUG, "Session key: " + sessionKey);
}
}
return handle;
}
@Override
public TreeLogger getTopLogger() {
return topLogger;
}
@Override
public TreeLogger getWebServerLogger(String serverName, byte[] serverIcon) {
if (webServerLog == null) {
RestartAction restartAction = null;
final RestartServerCallback callback = getCallback(
RestartServerEvent.getType());
if (callback != null) {
restartAction = new RestartAction() {
public void restartServer(TreeLogger logger) {
callback.onRestartServer(logger);
}
};
}
webServerLog = new WebServerPanel(options.getPort(), getLogLevel(),
options.getLogFile("webserver.log"), restartAction);
Icon serverIconImage = null;
if (serverIcon != null) {
serverIconImage = new ImageIcon(serverIcon);
}
tabs.insertTab(serverName, serverIconImage, webServerLog, null, 1);
}
return webServerLog.getLogger();
}
@Override
public void initialize(final Type logLevel) {
super.initialize(logLevel);
invokeAndWait(new Runnable() {
public void run() {
ImageIcon gwtIcon16 = loadImageIcon("icon16.png");
ImageIcon gwtIcon24 = loadImageIcon("icon24.png");
ImageIcon gwtIcon32 = loadImageIcon("icon32.png");
ImageIcon gwtIcon48 = loadImageIcon("icon48.png");
ImageIcon gwtIcon64 = loadImageIcon("icon64.png");
ImageIcon gwtIcon128 = loadImageIcon("icon128.png");
frame = new JFrame("GWT Development Mode");
tabs = new JTabbedPane();
if (options.alsoLogToFile()) {
options.getLogDir().mkdirs();
}
mainWnd = new ShellMainWindow(logLevel, options.getLogFile("main.log"));
topLogger = mainWnd.getLogger();
tabs.addTab("Development Mode", gwtIcon24, mainWnd,
"GWT Development Mode");
frame.getContentPane().add(tabs);
frame.setSize(950, 700);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
DoneCallback callback = getCallback(DoneEvent.getType());
if (callback != null) {
callback.onDone();
}
}
});
setIconImages(topLogger, gwtIcon48, gwtIcon32, gwtIcon64, gwtIcon128,
gwtIcon16);
frame.setVisible(true);
}
});
maybeInitializeOsXApplication();
}
@Override
public void moduleLoadComplete(final boolean success) {
EventQueue.invokeLater(new Runnable() {
public void run() {
mainWnd.moduleLoadComplete(success);
}
});
}
@Override
public void setStartupUrls(final Map<String, URL> urls) {
invokeAndWait(new Runnable() {
public void run() {
mainWnd.setStartupUrls(urls);
}
});
}
@Override
public void setWebServerSecure(TreeLogger serverLogger) {
if (webServerLog != null && serverLogger == webServerLog.getLogger()) {
EventQueue.invokeLater(new Runnable() {
public void run() {
// TODO(jat): if the web server has an icon, should combine with the
// secure icon or perhaps switch to a different one.
ImageIcon secureIcon = loadImageIcon("secure24.png");
tabs.setIconAt(1, secureIcon);
}
});
}
}
protected int getNextSessionCounter(File logdir) {
synchronized (sessionCounterLock) {
if (sessionCounter == 0 && logdir != null) {
// first time only, figure out the "last" session count already in use
for (String filename : logdir.list()) {
if (filename.matches("^[A-Za-z0-9_$]*-[a-z]*-[0-9]*.log$")) {
String substring = filename.substring(filename.lastIndexOf('-') + 1,
filename.length() - 4);
int number = Integer.parseInt(substring);
if (number > sessionCounter) {
sessionCounter = number;
}
}
}
}
//
return ++sessionCounter;
}
}
private ModuleTabPanel findModuleTab(String userAgent, String remoteSocket,
String url, String tabKey, String moduleName, byte[] agentIcon) {
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,
agentIcon, 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;
}
/**
* Invoke a Callable on the UI thread, wait for it to finish, and return the
* result.
*
* @param <T> return type
* @param callable wrapper of the method to run on the UI thread
* @return the return value of callable.call()
* @throws RuntimeException if an error occurs
*/
private <T> T invokeAndGet(Callable<T> callable) {
FutureTask<T> task = new FutureTask<T>(callable);
try {
EventQueue.invokeAndWait(task);
return task.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Invoke a Runnable on the UI thread and wait for it to finish.
*
* @param runnable
* @throws RuntimeException if an error occurs
*/
private void invokeAndWait(Runnable runnable) {
try {
EventQueue.invokeAndWait(runnable);
} catch (InterruptedException e) {
throw new RuntimeException("Error running on Swing UI thread", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error running on Swing UI thread", e);
}
}
/**
* This method contains code that will call certain Apple extensions to make
* the DevMode app integrate into the system UI a bit better. These methods
* are called reflectively so that we don't have to build gwt-dev against a
* no-op stub library.
*/
private void maybeInitializeOsXApplication() {
Throwable ex;
try {
Class<?> applicationClass = Class.forName("com.apple.eawt.Application");
topLogger.log(TreeLogger.SPAM, "Got Application class, on OS X");
Object application = applicationClass.getMethod("getApplication").invoke(
null);
assert application != null : "application";
// Remove the about menu entry
applicationClass.getMethod("removeAboutMenuItem").invoke(application);
// Remove the preferences menu entry
applicationClass.getMethod("removePreferencesMenuItem").invoke(
application);
// Make the Dock icon pretty
applicationClass.getMethod("setDockIconImage", Image.class).invoke(
application, loadImageIcon("icon128.png").getImage());
return;
} catch (ClassNotFoundException e) {
// Nothing to do here, this is expected on non-Apple JVMs.
return;
} catch (RuntimeException e) {
ex = e;
} catch (IllegalAccessException e) {
ex = e;
} catch (InvocationTargetException e) {
ex = e;
} catch (NoSuchMethodException e) {
ex = e;
}
topLogger.log(TreeLogger.WARN, "Unable to initialize some OS X UI support",
ex);
}
/**
* Set the images for the frame. On JDK 1.5, only the last icon supplied is
* used for all needs.
*
* @param logger logger to use for warnings
* @param icons one or more icons
*/
private void setIconImages(TreeLogger logger, ImageIcon... icons) {
if (icons.length == 0) {
return;
}
Exception caught = null;
try {
// if this fails, we fall back to the JDK 1.5 method
Method method = frame.getClass().getMethod("setIconImages", List.class);
List<Image> imageList = new ArrayList<Image>();
for (ImageIcon icon : icons) {
Image image = icon.getImage();
if (image != null) {
imageList.add(image);
}
}
method.invoke(frame, imageList);
return;
} catch (SecurityException e) {
caught = e;
} catch (IllegalArgumentException e) {
caught = e;
} catch (NoSuchMethodException e) {
// ignore, expected on JDK 1.5
} catch (IllegalAccessException e) {
caught = e;
} catch (InvocationTargetException e) {
caught = e;
}
if (caught != null) {
logger.log(TreeLogger.WARN, "Unexpected exception setting icon images",
caught);
}
frame.setIconImage(icons[icons.length - 1].getImage());
}
}