blob: 95b5ddd800b67d1662cafb4769efe11fe42b38de [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.Type;
import com.google.gwt.dev.SwingUI.TabPanelCollection;
import com.google.gwt.dev.shell.WrapLayout;
import com.google.gwt.dev.util.BrowserInfo;
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.io.File;
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 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;
}
}
/**
* 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;
}
}
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 agentIconBytes
* @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,
byte[] agentIconBytes, TabPanelCollection tabPanelCollection,
String moduleName) {
super(new BorderLayout());
this.tabPanelCollection = tabPanelCollection;
topPanel = new JPanel();
sessionDropdownPanel = new JPanel(new WrapLayout());
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(new WrapLayout());
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);
cardLayout = new CardLayout();
deckPanel = new JPanel(cardLayout);
add(deckPanel);
// 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
}
}
ImageIcon browserIcon = null;
if (agentIconBytes != null) {
browserIcon = new ImageIcon(agentIconBytes);
}
String shortName = BrowserInfo.getShortName(userAgent);
if (browserIcon == null) {
if (shortName != null) {
tabTitle += " (" + shortName + ")";
}
}
tabPanelCollection.addTab(this, browserIcon, tabTitle, url + "from "
+ remoteSocket + " on " + userAgent);
}
public synchronized ModulePanel addModuleSession(Type maxLevel,
String moduleName,
String sessionKey,
File logFile) {
Session session = findOrCreateSession(sessionKey);
ModulePanel panel = new ModulePanel(maxLevel, moduleName, session, logFile);
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);
}
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();
}
}
}