/* 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 version 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.Container; import java.awt.Dimension; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.EventListener; import java.util.EventObject; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JPanel; 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.main.PlatformInit; import org.lobobrowser.ua.NavigationEntry; import org.lobobrowser.ua.NavigationListener; 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.RequestType; import org.lobobrowser.ua.UserAgent; import org.lobobrowser.util.EventDispatch2; import org.lobobrowser.util.Urls; /** * A <code>BrowserPanel</code> contains a {@link FramePanel} along with optional * address bar, toolbars, menu bar and status bar. * <p> * Invoke {@link #navigate(String)} to load a document into the top frame of the * <code>BrowserPanel</code>. * * @see PlatformInit#init(boolean, boolean) */ public class BrowserPanel extends JPanel implements NavigatorWindow, BrowserWindow, WindowCallback { private static final long serialVersionUID = -861661039719372331L; private static final Logger logger = Logger.getLogger(BrowserPanel.class.getName()); // private final boolean hasAddressBar; private final boolean hasToolBar; // private final boolean hasStatusBar; private final JMenuBar menuBar; private final FramePanel framePanel; private final AddressBarPanel addressBarPanel; private final SharedToolBarPanel sharedToolBarPanel; private final StatusBarPanel statusBarPanel; /** * Constructs a <code>BrowserPanel</code> with toolbars, an address bar, a * status bar, but no menu bar. */ public BrowserPanel() { this(null, true, true, true); } /** * Constructs a <code>BrowserPanel</code> with a menu bar, toolbars, an * address bar and a status bar. * * @param menuBar * A <code>JMenuBar</code> instance presumably set on a JFrame. */ public BrowserPanel(final JMenuBar menuBar) { this(menuBar, true, true, true); } /** * Constructs a <code>BrowserPanel</code> with optional menu bar, toolbars, an * address bar and a status bar. * * @param menuBar * A <code>JMenuBar</code> instance presumably set on a JFrame. * @param hasAddressBar * Whether the panel has an address bar. * @param hasToolBar * Whether the panel has toolbars. * @param hasStatusBar * Whether the panel has a status bar. */ public BrowserPanel(final JMenuBar menuBar, final boolean hasAddressBar, final boolean hasToolBar, final boolean hasStatusBar) { this.hasToolBar = hasToolBar; // this.hasAddressBar = hasAddressBar; // this.hasStatusBar = hasStatusBar; this.menuBar = menuBar; final String windowId = "BrowserPanel." + System.identityHashCode(this); final FramePanel framePanel = FramePanelFactorySource.getInstance().getActiveFactory().createFramePanel(windowId); this.framePanel = framePanel; final Container contentPane = this; contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); if (hasAddressBar) { final AddressBarPanel abp = new AddressBarPanel(); this.addressBarPanel = abp; contentPane.add(abp); } else { this.addressBarPanel = null; } if (hasToolBar) { final SharedToolBarPanel stbp = new SharedToolBarPanel(); this.sharedToolBarPanel = stbp; contentPane.add(stbp); } else { this.sharedToolBarPanel = null; } contentPane.add(new FillerComponent(framePanel, false)); if (hasStatusBar) { final StatusBarPanel statusBar = new StatusBarPanel(); this.statusBarPanel = statusBar; contentPane.add(statusBar); } else { this.statusBarPanel = null; } ExtensionManager.getInstance().initExtensionsWindow(this); } /** * Invoke this method when the window that owns the <code>BrowserPanel</code> * is about to close. This should be done in order to inform extensions about * the window closing. */ public void windowClosing() { ExtensionManager.getInstance().shutdownExtensionsWindow(this); } /** * Navigates to the URL or path provided. * * @param urlOrPath * An absolute URL or file path. */ public void navigate(final String urlOrPath) throws java.net.MalformedURLException { this.framePanel.navigate(urlOrPath); } /** * Navigates to the URL provided. * * @param url * A URL. */ public void navigate(final @NonNull URL url) throws java.net.MalformedURLException { this.framePanel.navigate(url); } public void addAddressBarComponent(final Component addressBarComponent) { final AddressBarPanel abp = this.addressBarPanel; if (abp != null) { abp.add(addressBarComponent); } } private final Map<String, JMenu> menuesById = new HashMap<>(1); public void addMenu(final String menuId, final JMenu menu) { final JMenuBar menuBar = this.menuBar; if (menuBar != null) { synchronized (this.menuesById) { this.menuesById.put(menuId, menu); } menuBar.add(menu); } } private final EventDispatch2 EVENT = new LocalEventDispatch(); public void addNavigatorWindowListener(final NavigatorWindowListener listener) { EVENT.addListener(listener); } public void addSharedToolBarComponent(final Component toolBarComponent) { final SharedToolBarPanel stbp = this.sharedToolBarPanel; if (stbp != null) { stbp.add(toolBarComponent); } } public void addStatusBarComponent(final Component statusBarComponent) { final StatusBarPanel sbp = this.statusBarPanel; if (sbp != null) { sbp.add(statusBarComponent); } } public void addToolBar(final Component toolBar) { if (this.hasToolBar) { this.add(toolBar); } } private volatile NavigatorFrame latestAccessedFrame = null; 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 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 boolean canCopy() { return this.framePanel.canCopy(); } 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 canReload() { return this.framePanel.canReload(); } public boolean copy() { return this.framePanel.copy(); } private static final int HGAP = 4; private static final int VGAP = 2; public Component createGap() { return Box.createRigidArea(new Dimension(HGAP, VGAP)); } public Component createGlueComponent(final Component wrappedComponent, final boolean usingMaxSize) { return new FillerComponent(wrappedComponent, usingMaxSize); } private boolean closeWindowOnDispose = true; /** * Returns a value indicating whether the parent window is closed when the * current <code>BrowserPanel</code> is disposed. * * @see #setCloseWindowOnDispose(boolean) */ public boolean isCloseWindowOnDispose() { return closeWindowOnDispose; } /** * Sets a flag indicating whether the parent window should be closed when the * current <code>BrowserPanel</code> is disposed. The * <code>BrowserPanel</code> is normally disposed by the standard File/Exit * menu and equivalent actions. * <p> * The default value of the flag is <code>true</code>. * * @param closeWindowOnDispose * A boolean value. * @see #dispose() */ public void setCloseWindowOnDispose(final boolean closeWindowOnDispose) { this.closeWindowOnDispose = closeWindowOnDispose; } /** * Disposes the current <code>BrowserPanel</code>. This method is normally * activated by the standard File/Exit menu. * * @see #setCloseWindowOnDispose(boolean) */ public void dispose() { if (this.closeWindowOnDispose) { final java.awt.Window awtFrame = this.getAwtWindow(); if (awtFrame != null) { awtFrame.dispose(); } } } 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 java.awt.Window getAwtWindow() { Container parent = this.getParent(); while ((parent != null) && !(parent instanceof java.awt.Window)) { parent = parent.getParent(); } return (java.awt.Window) parent; } public List<NavigationEntry> getBackNavigationEntries() { return this.framePanel.getBackNavigationEntries(); } public NavigationEntry getCurrentNavigationEntry() { return this.framePanel.getCurrentNavigationEntry(); } public List<NavigationEntry> getForwardNavigationEntries() { return this.framePanel.getForwardNavigationEntries(); } public JMenu getMenu(final String menuId) { synchronized (this.menuesById) { return this.menuesById.get(menuId); } } public NavigatorFrame getTopFrame() { return this.framePanel; } public UserAgent getUserAgent() { return org.lobobrowser.request.UserAgentImpl.getInstance(); } public boolean goTo(final NavigationEntry entry) { return this.framePanel.goTo(entry); } public boolean hasSource() { return this.framePanel.hasSource(); } public boolean reload() { this.framePanel.reload(); return true; } public void removeNavigatorWindowListener(final NavigatorWindowListener listener) { EVENT.removeListener(listener); } public boolean stop() { org.lobobrowser.request.RequestEngine.getInstance().cancelAllRequests(); return true; } public FramePanel getTopFramePanel() { return this.framePanel; } public WindowCallback getWindowCallback() { return this; } private String defaultStatus; private String status; public String getDefaultStatus() { synchronized (this) { return this.defaultStatus; } } public String getStatus() { synchronized (this) { return this.status; } } public void handleDocumentAccess(final NavigatorFrame frame, final ClientletResponse response) { final NavigatorWindowEvent event = new NavigatorWindowEvent(this, NavigatorEventType.DOCUMENT_ACCESSED, frame, response, response.getRequestType()); SwingUtilities.invokeLater(() -> EVENT.fireEvent(event)); } public void handleDocumentRendering(final NavigatorFrame frame, final ClientletResponse response, final ComponentContent content) { if (SwingUtilities.isEventDispatchThread()) { this.handleDocumentRenderingImpl(frame, response, content); } else { SwingUtilities.invokeLater(() -> BrowserPanel.this.handleDocumentRenderingImpl(frame, response, content)); } } protected static String getWindowTitle(final ClientletResponse response, final ComponentContent content) { String title = content == null ? null : content.getTitle(); if (title == null) { title = response == null ? "" : Urls.getNoRefForm(response.getResponseURL()); } return title; } private String documentTitle; /** * Gets the recommended title of the document currently in the top frame. */ public String getDocumentTitle() { return documentTitle; } public void setDocumentTitle(final String documentTitle) { this.documentTitle = documentTitle; } private void handleDocumentRenderingImpl(final NavigatorFrame frame, final ClientletResponse response, final ComponentContent content) { if (frame == this.framePanel) { final String title = BrowserPanel.getWindowTitle(response, content); this.setDocumentTitle(title); } final NavigatorWindowEvent event = new NavigatorWindowEvent(this, NavigatorEventType.DOCUMENT_RENDERING, frame, response, response.getRequestType()); this.latestAccessedFrame = event.getNavigatorFrame(); if (!EVENT.fireEvent(event)) { logger.warning("handleDocumentRendering(): Did not deliver event to any window: " + event); } } private static ExtensionManager getSafeExtensionManager() { return AccessController.doPrivileged(new PrivilegedAction<ExtensionManager>() { public ExtensionManager run() { return ExtensionManager.getInstance(); } }); } public void handleError(final NavigatorFrame frame, final ClientletResponse response, final Throwable exception, final RequestType requestType) { getSafeExtensionManager().handleError(frame, response, exception, requestType); // Also inform as if document rendering. this.handleDocumentRendering(frame, response, null); } public void setDefaultStatus(final NavigatorFrame frame, final String defaultStatus) { synchronized (this) { this.defaultStatus = defaultStatus; 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 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 updateProgress(final NavigatorProgressEvent event) { SwingUtilities.invokeLater(() -> EVENT.fireEvent(event)); } /** * Gets an object that is used to represent the current frame content. For * example, if the frame is currently showing HTML, this method will probably * return an instance of <code>org.w3c.dom.html2.HTMLDocument</code>. */ public Object getContentObject() { return this.framePanel.getContentObject(); } /** * Gets a mime type that goes with the object returned by * {@link FramePanel#getContentObject()}. This is not necessarily the same as * the mime type declared in the headers of the response that produced the * current content. */ public String getCurrentMimeType() { return this.framePanel.getCurrentMimeType(); } 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; } } } /** * Adds a listener of navigation events, applicable only to the top frame. * * @param listener * The listener. * @see FramePanel#addNavigationListener(NavigationListener) * @see FramePanelFactorySource */ public void addNavigationListener(final NavigationListener listener) { this.framePanel.addNavigationListener(listener); } /** * Removes a listener of navigation events previously added with * {@link #addNavigationListener(NavigationListener)}. * * @param listener * The listener. */ public void removeNavigationListener(final NavigationListener listener) { this.framePanel.addNavigationListener(listener); } /** * Adds a listener of content events. * * @param listener * The listener. * @see #getComponentContent() */ public void addContentListener(final ContentListener listener) { this.framePanel.addContentListener(listener); } public void removeContentListener(final ContentListener listener) { this.framePanel.removeContentListener(listener); } /** * Adds a listener of response events. * * @param listener * The listener. */ public void addResponseListener(final ResponseListener listener) { this.framePanel.addResponseListener(listener); } public void removeResponseListener(final ResponseListener listener) { this.framePanel.removeResponseListener(listener); } /** * Gets the component content currently set in the frame. */ public ComponentContent getComponentContent() { return this.framePanel.getComponentContent(); } }