package org.limewire.ui.swing.browser; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.GradientPaint; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import javax.swing.JLabel; import net.miginfocom.swing.MigLayout; import org.jdesktop.swingx.JXBusyLabel; import org.jdesktop.swingx.JXCollapsiblePane; import org.jdesktop.swingx.JXPanel; import org.limewire.ui.swing.components.ColoredBusyLabel; import org.limewire.ui.swing.painter.GenericBarPainter; import org.limewire.ui.swing.util.I18n; import org.limewire.ui.swing.util.PainterUtils; import org.limewire.ui.swing.util.SwingUtils; import org.limewire.util.OSUtils; import org.mozilla.browser.MozillaException; import org.mozilla.browser.MozillaExecutor; import org.mozilla.browser.MozillaPanel; import org.mozilla.browser.XPCOMUtils; import org.mozilla.browser.impl.ChromeAdapter; import org.mozilla.interfaces.nsIBaseWindow; import org.mozilla.interfaces.nsIHistoryEntry; import org.mozilla.interfaces.nsIRequest; import org.mozilla.interfaces.nsISHistory; import org.mozilla.interfaces.nsISupports; import org.mozilla.interfaces.nsIURI; import org.mozilla.interfaces.nsIWebNavigation; import org.mozilla.interfaces.nsIWebProgress; import org.mozilla.interfaces.nsIWebProgressListener; import org.mozilla.xpcom.Mozilla; /** * Extension to Mozilla's browser that adds the correct listeners. */ public class Browser extends MozillaPanel { private final Listener listener = new Listener(); private volatile boolean lastRequestFailed = true; private volatile boolean requestInProgress; private final VisibilityMode loadStatus; private JXCollapsiblePane loadingPane; public Browser() { super(); loadStatus = VisibilityMode.FORCED_HIDDEN; } public Browser(boolean attachNewBrowserOnCreation, VisibilityMode toolbarVisMode, VisibilityMode statusbarVisMode) { super(null, attachNewBrowserOnCreation, toolbarVisMode, statusbarVisMode); loadStatus = VisibilityMode.FORCED_HIDDEN; } public Browser(VisibilityMode toolbarVisMode, VisibilityMode statusbarVisMode) { super(toolbarVisMode, statusbarVisMode); loadStatus = VisibilityMode.FORCED_HIDDEN; } public Browser(VisibilityMode toolbarVisMode, VisibilityMode statusbarVisMode, VisibilityMode loadingMode) { super(toolbarVisMode, statusbarVisMode); this.loadStatus = loadingMode; } /** * Returns an Iterable of HistoryEntry items that can be used * to see what's previously been loaded in the browser. * */ public Iterable<HistoryEntry> getHistory(final int maxEntries, final AtomicReference<Integer> currentPosition) { assert !OSUtils.isMacOSX() : "The history menu is not working on Mac OS X. Calls to retrieve it go into native code and never return"; try { return MozillaExecutor.mozSyncExec(new Callable<Iterable<HistoryEntry>>() { @Override public Iterable<HistoryEntry> call() throws Exception { nsIWebNavigation nav = XPCOMUtils.qi(getChromeAdapter().getWebBrowser(), nsIWebNavigation.class); nsISHistory history = nav.getSessionHistory(); List<HistoryEntry> entries = new ArrayList<HistoryEntry>(maxEntries); if(history != null) { for(int i = history.getCount() - 1; entries.size() < maxEntries && i >= 0; i--) { nsIHistoryEntry entry = history.getEntryAtIndex(i, false); entries.add(new HistoryEntry(i, entry)); } if(currentPosition != null) { currentPosition.set(history.getIndex()); } } return entries; } }); } catch (MozillaException e) { return Collections.emptyList(); } } /** * Navigates to a specific item in the history. */ public void loadHistoryEntry(final HistoryEntry entry) { MozillaExecutor.mozAsyncExec(new Runnable() { @Override public void run() { nsIWebNavigation nav = XPCOMUtils.qi(getChromeAdapter().getWebBrowser(), nsIWebNavigation.class); nav.gotoIndex(entry.getIndex()); } }); } @Override protected void createChrome() { super.createChrome(); loadingPane = new JXCollapsiblePane(); if(loadStatus != VisibilityMode.FORCED_HIDDEN) { loadingPane.setLayout(new BorderLayout()); JXBusyLabel busySpinner = new ColoredBusyLabel(new Dimension(12, 12)); busySpinner.setBusy(true); busySpinner.setOpaque(false); JLabel busyLabel = new JLabel(I18n.tr("Loading...")); busyLabel.setFont(new Font("DIALOG", Font.PLAIN, 12)); busyLabel.setForeground(new Color(0x313131)); busyLabel.setOpaque(false); JXPanel loadingPaneInner = new JXPanel(new MigLayout("insets 6, gap 4, aligny center")); loadingPaneInner.setBackgroundPainter(new GenericBarPainter<JXCollapsiblePane>( new GradientPaint(0,0,new Color(0xc8c8c8),0,1, new Color(0xf9f9f9)), new Color(0x696969), new Color(0xebebeb), PainterUtils.TRANSPARENT, PainterUtils.TRANSPARENT)); loadingPane.setOpaque(false); loadingPaneInner.setOpaque(true); loadingPaneInner.add(busySpinner, "gaptop 1"); loadingPaneInner.add(busyLabel); loadingPane.add(loadingPaneInner, BorderLayout.CENTER); add(loadingPane, BorderLayout.SOUTH); } } public boolean isRequestInProgress() { return requestInProgress; } /** Returns true if the last request is currently in progress or succeeded. */ public boolean isLastRequestSuccessful() { return !lastRequestFailed; } @Override public void onSetTitle(String title) { // Don't set it! } // overridden to remove LimeDomListener @Override public void onDetachBrowser() { if (getChromeAdapter() != null) { BrowserUtils.removeDomListener(getChromeAdapter()); getChromeAdapter().getWebBrowser().removeWebBrowserListener(listener, nsIWebProgressListener.NS_IWEBPROGRESSLISTENER_IID); } super.onDetachBrowser(); } // overridden for browser initialization that can not be done earlier @Override public void onAttachBrowser(final ChromeAdapter chromeAdapter, ChromeAdapter parentChromeAdapter) { super.onAttachBrowser(chromeAdapter, parentChromeAdapter); BrowserUtils.addDomListener(chromeAdapter); chromeAdapter.getWebBrowser().addWebBrowserListener(listener, nsIWebProgressListener.NS_IWEBPROGRESSLISTENER_IID); SwingUtils.invokeNowOrLater(new Runnable() { @Override public void run() { addKeyListener(new MozillaKeyListener(chromeAdapter)); addComponentListener(new VisibilityListener()); } }); } public void showLoadingPanel() { loadingPane.setCollapsed(false); } public void pageLoadStarted() { lastRequestFailed = false; requestInProgress = true; if(loadStatus == VisibilityMode.DEFAULT) { SwingUtils.invokeNowOrLater(new Runnable() { @Override public void run() { loadingPane.setCollapsed(false); } }); } } public void pageLoadStopped(boolean failed) { lastRequestFailed = failed; requestInProgress = false; if(loadStatus == VisibilityMode.DEFAULT) { SwingUtils.invokeNowOrLater(new Runnable() { @Override public void run() { loadingPane.setCollapsed(true); } }); } } private class VisibilityListener extends ComponentAdapter { @Override public void componentHidden(ComponentEvent e) { setBrowserVisibility(false); } @Override public void componentShown(ComponentEvent e) { setBrowserVisibility(true); } } private void setBrowserVisibility(final boolean isVisible) { // Mozilla is not threadsafe so most mozilla methods must be called on // the // mozilla thread. This is asynchronous so as not to tie up the EDT. MozillaExecutor.mozAsyncExec(new Runnable() { public void run() { ChromeAdapter chromeAdapter = getChromeAdapter(); if (chromeAdapter != null) { nsIBaseWindow baseWindow = XPCOMUtils.qi(chromeAdapter.getWebBrowser(), nsIBaseWindow.class); baseWindow.setVisibility(isVisible); } } }); } private class Listener implements nsIWebProgressListener { @Override public void onLocationChange(nsIWebProgress webProgress, nsIRequest request, nsIURI location) { } @Override public void onProgressChange(nsIWebProgress webProgress, nsIRequest request, int curSelfProgress, int maxSelfProgress, int curTotalProgress, int maxTotalProgress) { } @Override public void onSecurityChange(nsIWebProgress webProgress, nsIRequest request, long state) { } @Override public void onStateChange(nsIWebProgress webProgress, nsIRequest request, long stateFlags, long status) { if ((stateFlags & nsIWebProgressListener.STATE_IS_NETWORK) != 0 && (stateFlags & nsIWebProgressListener.STATE_START) != 0) { pageLoadStarted(); } if ((stateFlags & nsIWebProgressListener.STATE_IS_NETWORK) != 0 && (stateFlags & nsIWebProgressListener.STATE_STOP) != 0) { pageLoadStopped(status != 0); } } @Override public void onStatusChange(nsIWebProgress webProgress, nsIRequest request, long status, String message) { } @Override public nsISupports queryInterface(String iid) { return Mozilla.queryInterface(this, iid); } } }