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);
}
}
}