/*
GNU GENERAL PUBLIC LICENSE
Copyright (C) 2006 The Lobo Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
verion 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Contact info: lobochief@users.sourceforge.net
*/
package org.lobobrowser.gui;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Window;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Box;
import javax.swing.JMenu;
import javax.swing.SwingUtilities;
import org.eclipse.jdt.annotation.NonNull;
import org.lobobrowser.clientlet.ClientletResponse;
import org.lobobrowser.clientlet.ComponentContent;
import org.lobobrowser.main.ExtensionManager;
import org.lobobrowser.ua.NavigationEntry;
import org.lobobrowser.ua.NavigatorEvent;
import org.lobobrowser.ua.NavigatorEventType;
import org.lobobrowser.ua.NavigatorFrame;
import org.lobobrowser.ua.NavigatorProgressEvent;
import org.lobobrowser.ua.NavigatorWindow;
import org.lobobrowser.ua.NavigatorWindowEvent;
import org.lobobrowser.ua.NavigatorWindowListener;
import org.lobobrowser.ua.ParameterInfo;
import org.lobobrowser.ua.RequestType;
import org.lobobrowser.ua.TargetType;
import org.lobobrowser.ua.UserAgent;
import org.lobobrowser.util.EventDispatch2;
import org.lobobrowser.util.Urls;
/**
* Default implementation of the {@link NavigatorWindow} interface.
*/
public class NavigatorWindowImpl implements NavigatorWindow, WindowCallback {
private static final Logger logger = Logger.getLogger(NavigatorWindowImpl.class.getName());
private static final int HGAP = 4;
private static final int VGAP = 2;
private final FramePanel framePanel;
private final Properties requestedProperties;
private final String windowId;
private final AbstractBrowserWindow browserWindow;
private final Map<String, JMenu> menusById = new HashMap<>();
private final Collection<JMenu> menus = new LinkedList<>();
// private final Collection<JMenuItem> sharedMenuItems = new
// LinkedList<JMenuItem>();
private final Collection<Component> addressBarComponents = new LinkedList<>();
private final Collection<Component> sharedToolbarComponents = new LinkedList<>();
private final Collection<Component> statusBarComponents = new LinkedList<>();
private final Collection<Component> toolBars = new LinkedList<>();
private volatile boolean launched = false;
// private volatile boolean disposingProgressWindow = false;
private static volatile WindowFactory windowFactory = DefaultWindowFactory.getInstance();
/**
* Changes the {@link WindowFactory} that is used to create browser windows.
*/
public static void setWindowFactory(final WindowFactory wf) {
windowFactory = wf;
}
/**
* Constructs a PlatformWindowContextImpl. It starts out by showing a progress
* window. Later a new browser window is obtained given the windowId, or
* created.
*/
public NavigatorWindowImpl(final NavigatorFrame openerFrame, final String windowId, final Properties properties) {
this.requestedProperties = properties;
this.windowId = windowId;
final WindowFactory wf = windowFactory;
if (wf == null) {
throw new IllegalStateException("Global WindowFactory is null.");
}
final AbstractBrowserWindow window = wf.getExistingWindow(windowId);
FramePanel framePanel = null;
if (window != null) {
framePanel = window.getTopFramePanel();
if (framePanel == null) {
throw new IllegalStateException("Window with ID " + windowId + " exists but its top frame is null.");
}
} else {
framePanel = FramePanelFactorySource.getInstance().getActiveFactory().createFramePanel(windowId);
framePanel.setOpenerFrame(openerFrame);
}
this.framePanel = framePanel;
// Starts out as progress window.
// We allow documents to override window properties, but
// it can also be the case that such methods as alert() are
// invoked while the document loads.
if (window != null) {
this.browserWindow = window;
this.launched = true;
} else {
final AbstractBrowserWindow newWindow = wf.createWindow(this.windowId, properties, this);
this.browserWindow = newWindow;
}
}
public boolean isClosed() {
return !this.browserWindow.isDisplayable();
}
public FramePanel getFramePanel() {
return this.framePanel;
}
private void showWindow() {
final AbstractBrowserWindow window = this.browserWindow;
if (!window.isVisible()) {
window.setVisible(true);
}
window.toFront();
}
void resetAsNavigator(final Properties overridingProperties) {
// Invoke in GUI thread
if (this.launched) {
return;
}
this.launched = true;
final AbstractBrowserWindow window = this.browserWindow;
// Come up with combination properties object
if (overridingProperties != null) {
Properties original = this.requestedProperties;
if (original == null) {
original = new Properties();
}
original.putAll(overridingProperties);
final WindowFactory wf = windowFactory;
if (wf == null) {
throw new IllegalStateException("Global WindowFactory is null.");
}
wf.overrideProperties(window, original);
}
// Initialize title
final NavigationEntry currentEntry = this.getCurrentNavigationEntry();
if (currentEntry != null) {
String title = currentEntry.getTitle();
if (title == null) {
title = Urls.getNoRefForm(currentEntry.getUrl());
}
window.setTitle(title);
}
showWindow();
}
public void close() {
final Window window = this.browserWindow;
if (window != null) {
window.dispose();
}
}
/**
* @param windowFeatures
* Window features formatted as in the window.open() method of
* Javascript.
*/
public static NavigatorWindowImpl createFromWindowFeatures(final NavigatorFrame openerFrame, final String windowId,
final String windowFeatures) {
// Transform into properties file format.
return new NavigatorWindowImpl(openerFrame, windowId, getPropertiesFromWindowFeatures(windowFeatures));
}
public static Properties getPropertiesFromWindowFeatures(final String windowFeatures) {
final String lineBreak = System.getProperty("line.separator");
final StringBuffer buffer = new StringBuffer();
final StringTokenizer tok = new StringTokenizer(windowFeatures, ",");
while (tok.hasMoreTokens()) {
final String token = tok.nextToken();
buffer.append(token);
buffer.append(lineBreak);
}
final Properties props = new Properties();
final byte[] bytes = buffer.toString().getBytes();
final ByteArrayInputStream in = new ByteArrayInputStream(bytes);
try {
props.load(in);
} catch (final IOException ioe) {
// impossible
logger.log(Level.SEVERE, "unexpected", ioe);
}
return props;
}
public void navigate(final String urlOrPath) throws java.net.MalformedURLException {
this.framePanel.navigate(urlOrPath);
}
public void navigate(final @NonNull URL url, final String method, final ParameterInfo paramInfo) {
this.framePanel.navigate(url, method, paramInfo, TargetType.SELF, RequestType.PROGRAMMATIC);
}
public void handleError(final NavigatorFrame frame, final ClientletResponse response, final Throwable exception,
final RequestType requestType) {
ExtensionManager.getInstance().handleError(frame, response, exception, requestType);
// Also inform as if document rendering.
this.handleDocumentRendering(frame, response, null);
}
private volatile NavigatorFrame latestAccessedFrame = null;
public void handleDocumentAccess(final NavigatorFrame frame, final ClientletResponse response) {
final NavigatorWindowEvent event = new NavigatorWindowEvent(this, NavigatorEventType.DOCUMENT_ACCESSED, frame, response,
response.getRequestType());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
EVENT.fireEvent(event);
}
});
}
public void handleDocumentAccess(final NavigatorFrame frame, final ClientletResponse response, final boolean okToAddToNavigationList) {
this.handleDocumentAccess(frame, response);
}
public boolean canCopy() {
return this.framePanel.canCopy();
}
public boolean canReload() {
return this.framePanel.canReload();
}
public boolean copy() {
return this.framePanel.copy();
}
public UserAgent getUserAgent() {
return org.lobobrowser.request.UserAgentImpl.getInstance();
}
public void dispose() {
this.browserWindow.dispose();
}
public boolean reload() {
this.framePanel.reload();
return true;
}
public boolean stop() {
org.lobobrowser.request.RequestEngine.getInstance().cancelAllRequests();
return true;
}
public void handleDocumentRendering(final NavigatorFrame frame, final ClientletResponse response, final ComponentContent content) {
if (SwingUtilities.isEventDispatchThread()) {
this.handleDocumentRenderingImpl(frame, response, content);
} else {
SwingUtilities.invokeLater(() -> NavigatorWindowImpl.this.handleDocumentRenderingImpl(frame, response, content));
}
}
private static String getWindowTitle(final ClientletResponse response, final ComponentContent content) {
String title = content == null ? null : content.getTitle();
if (title == null) {
title = response == null ? "No response" : Urls.getNoRefForm(response.getResponseURL());
}
return title;
}
private void handleDocumentRenderingImpl(final NavigatorFrame frame, final ClientletResponse response, final ComponentContent content) {
if (frame == this.framePanel) {
final String title = getWindowTitle(response, content);
final Object window = this.browserWindow;
if (window instanceof Frame) {
((Frame) window).setTitle(title);
}
}
final RequestType requestType = response == null ? null : response.getRequestType();
final NavigatorWindowEvent event = new NavigatorWindowEvent(this, NavigatorEventType.DOCUMENT_RENDERING, frame, response, requestType);
latestAccessedFrame = event.getNavigatorFrame();
if (!EVENT.fireEvent(event)) {
logger.warning("handleDocumentRendering(): Did not deliver event to any window: " + event);
}
}
public void updateProgress(final NavigatorProgressEvent event) {
SwingUtilities.invokeLater(() -> EVENT.fireEvent(event));
}
public boolean back() {
final NavigatorFrame frame = this.latestAccessedFrame;
if (frame != null) {
if (frame.back()) {
return true;
}
if (frame == this.framePanel) {
return false;
}
}
return this.framePanel.back();
}
public boolean forward() {
final NavigatorFrame frame = this.latestAccessedFrame;
if (frame != null) {
if (frame.forward()) {
return true;
}
if (frame == this.framePanel) {
return false;
}
}
return this.framePanel.forward();
}
public boolean canForward() {
final NavigatorFrame frame = this.latestAccessedFrame;
if (frame != null) {
if (frame.canForward()) {
return true;
}
if (frame == this.framePanel) {
return false;
}
}
return this.framePanel.canForward();
}
public boolean canBack() {
final NavigatorFrame frame = this.latestAccessedFrame;
if (frame != null) {
if (frame.canBack()) {
return true;
}
if (frame == this.framePanel) {
return false;
}
}
return this.framePanel.canBack();
}
public void toFront() {
this.browserWindow.toFront();
}
public void toBack() {
this.browserWindow.toBack();
}
public NavigatorFrame getTopFrame() {
return this.framePanel;
}
public void statusUpdated(final NavigatorFrame clientletFrame, final String value) {
final NavigatorWindowEvent event = new NavigatorWindowEvent(NavigatorWindowImpl.this, NavigatorEventType.STATUS_UPDATED,
clientletFrame, value, RequestType.NONE);
SwingUtilities.invokeLater(() -> EVENT.fireEvent(event));
}
public void defaultStatusUpdated(final NavigatorFrame clientletFrame, final String value) {
final NavigatorWindowEvent event = new NavigatorWindowEvent(NavigatorWindowImpl.this, NavigatorEventType.STATUS_UPDATED,
clientletFrame, value, RequestType.NONE);
SwingUtilities.invokeLater(() -> EVENT.fireEvent(event));
}
private String status;
private String defaultStatus;
public void setStatus(final NavigatorFrame frame, final String value) {
String actualStatus;
synchronized (this) {
if (!java.util.Objects.equals(this.status, value)) {
this.status = value;
actualStatus = value == null ? this.defaultStatus : value;
final NavigatorWindowEvent event = new NavigatorWindowEvent(this, NavigatorEventType.STATUS_UPDATED, frame, actualStatus,
RequestType.NONE);
SwingUtilities.invokeLater(() -> EVENT.fireEvent(event));
}
}
}
public void setDefaultStatus(final NavigatorFrame frame, final String value) {
synchronized (this) {
this.defaultStatus = value;
if (this.status == null) {
final String actualStatus = this.defaultStatus;
final NavigatorWindowEvent event = new NavigatorWindowEvent(this, NavigatorEventType.STATUS_UPDATED, frame, actualStatus,
RequestType.NONE);
SwingUtilities.invokeLater(() -> EVENT.fireEvent(event));
}
}
}
public String getStatus() {
synchronized (this) {
return this.status;
}
}
public String getDefaultStatus() {
synchronized (this) {
return this.defaultStatus;
}
}
public void addAddressBarComponent(final Component addressBar) {
synchronized (this) {
this.addressBarComponents.add(addressBar);
}
}
public void addMenu(final String menuId, final JMenu menu) {
final Map<String, JMenu> map = this.menusById;
synchronized (this) {
if (map.containsKey(menuId)) {
throw new IllegalArgumentException("Menu " + menuId + " already exists.");
}
this.menusById.put(menuId, menu);
this.menus.add(menu);
}
}
public JMenu getMenu(final String menuId) {
synchronized (this) {
return this.menusById.get(menuId);
}
}
public void addSharedToolBarComponent(final Component toolBarComponent) {
synchronized (this) {
this.sharedToolbarComponents.add(toolBarComponent);
}
}
public void addStatusBarComponent(final Component statusBarComponent) {
synchronized (this) {
this.statusBarComponents.add(statusBarComponent);
}
}
public void addToolBar(final Component toolBar) {
synchronized (this) {
this.toolBars.add(toolBar);
}
}
// public void addItemToSharedMenu(JMenuItem menuItem) {
// synchronized(this) {
// this.sharedMenuItems.add(menuItem);
// }
// }
private final EventDispatch2 EVENT = new LocalEventDispatch();
public void addNavigatorWindowListener(final NavigatorWindowListener listener) {
EVENT.addListener(listener);
}
public void removeNavigatorWindowListener(final NavigatorWindowListener listener) {
EVENT.removeListener(listener);
}
public static class LocalEventDispatch extends EventDispatch2 {
@Override
protected void dispatchEvent(final EventListener listener, final EventObject event) {
final NavigatorEvent ne = (NavigatorEvent) event;
final NavigatorWindowListener nwl = (NavigatorWindowListener) listener;
switch (ne.getEventType()) {
case DOCUMENT_ACCESSED:
nwl.documentAccessed((NavigatorWindowEvent) ne);
break;
case DOCUMENT_RENDERING:
nwl.documentRendering((NavigatorWindowEvent) ne);
break;
case PROGRESS_UPDATED:
nwl.progressUpdated((NavigatorProgressEvent) ne);
break;
case STATUS_UPDATED:
nwl.statusUpdated((NavigatorWindowEvent) ne);
break;
case DEFAULT_STATUS_UPDATED:
nwl.defaultStatusUpdated((NavigatorWindowEvent) ne);
break;
default:
break;
}
}
}
public Collection<Component> getAddressBarComponents() {
return addressBarComponents;
}
public Collection<JMenu> getMenus() {
return this.menus;
}
// public Collection<JMenuItem> getSharedMenuItems() {
// return sharedMenuItems;
// }
public Collection<Component> getSharedToolbarComponents() {
return sharedToolbarComponents;
}
public Collection<Component> getStatusBarComponents() {
return statusBarComponents;
}
public Collection<Component> getToolBars() {
return toolBars;
}
public Object getComponentLock() {
return this;
}
public Component createGlueComponent(final Component wrappedComponent, final boolean usingMaxSize) {
return new FillerComponent(wrappedComponent, usingMaxSize);
}
public Component createGap() {
return Box.createRigidArea(new Dimension(HGAP, VGAP));
}
public boolean goTo(final NavigationEntry entry) {
return this.framePanel.goTo(entry);
}
public List<NavigationEntry> getBackNavigationEntries() {
return this.framePanel.getBackNavigationEntries();
}
public List<NavigationEntry> getForwardNavigationEntries() {
return this.framePanel.getForwardNavigationEntries();
}
public NavigationEntry getCurrentNavigationEntry() {
return this.framePanel.getCurrentNavigationEntry();
}
public boolean hasSource() {
return this.framePanel.hasSource();
}
public Window getAwtWindow() {
return this.browserWindow;
}
}