/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2003-2006 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License 1.0 which accompanies this     **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl - initial API and implementation (bpasero@rssowl.org)         **
 **                                                                          **
 **  **********************************************************************  */

package net.sourceforge.rssowl.controller.panel;

import net.sourceforge.rssowl.controller.DisposeListenerImpl;
import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.controller.MenuManager;
import net.sourceforge.rssowl.controller.ViewForm;
import net.sourceforge.rssowl.model.NewsItem;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.LayoutDataShop;
import net.sourceforge.rssowl.util.shop.LayoutShop;
import net.sourceforge.rssowl.util.shop.PaintShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.URLShop;
import net.sourceforge.rssowl.util.shop.WidgetShop;

import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.CloseWindowListener;
import org.eclipse.swt.browser.LocationAdapter;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.OpenWindowListener;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;
import org.eclipse.swt.browser.VisibilityWindowAdapter;
import org.eclipse.swt.browser.WindowEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

/**
 * This is the Web-Browser support for RSSOwl. It loads a Browser in a Composite
 * and opens URLs.
 * <p>
 * Important Note: Dynamically disable/enable JavaScript in the Browser widget
 * on Windows with the following modifications:<br>
 * <br>
 * BrowserPanel.java:<br>
 * <br>
 * Line 276:<br>
 * <code>browser.setScriptDisabled(false);</code><br>
 * Line 550:<br>
 * <code>browser.setScriptDisabled(!URLShop.isScriptAllowed(event.location));</code>
 * <br>
 * Browser.java:<br>
 * <code>
 * public void setScriptDisabled(boolean disabled) {
 *   if (site != null)
 *     ((WebSite) site).disableScript = disabled;
 * }
 * </code>
 * <br>
 * WebSite.java:<br>
 * <code>boolean disableScript = false;</code><br>
 * Line 417:<br>
 * <code>
 * if (disableScript && dwAction == 5120 //0x1400 - URLACTION_SCRIPT_RUN//) {
 *   policy = Browser.URLPOLICY_DISALLOW;
 * }
 * </code>
 * </p>
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class BrowserPanel {

  /** JavaScript command to print the current website of the Browser */
  private static final String JAVA_SCRIPT_PRINT = "window.print();";

  private Composite browserPanel;
  private ToolItem itemDiscovery;
  private ToolItem itemHome;
  private ToolItem itemNewTab;
  private ToolItem itemReload;
  private ToolItem itemStop;
  private Composite parent;
  private boolean showControls;
  private boolean showNewTabControl;
  boolean blockNavigation;
  Browser browser;
  ToolItem itemBack;
  ToolItem itemForward;
  Text location;
  CTabItem parentTabItem;
  GUI rssOwlGui;
  String saveHome;
  Label status;
  Label statusProgressBar;

  /**
   * Instantiate a new BrowserPanel
   * 
   * @param rssOwlGui The maincontroller
   * @param parent The parent composite
   * @param showControls TRUE if the controls should be visible
   */
  public BrowserPanel(GUI rssOwlGui, Composite parent, boolean showControls) {
    this(rssOwlGui, parent, null, showControls, false);
  }

  /**
   * Instantiate a new BrowserPanel
   * 
   * @param rssOwlGui The maincontroller
   * @param parent The parent composite
   * @param parentTabItem The parent CTabItem containing the Browser if
   * available
   * @param showControls TRUE if the controls should be visible
   * @param showNewTabControl TRUE if the "Open new Tab" button should be
   * visible
   */
  public BrowserPanel(GUI rssOwlGui, Composite parent, CTabItem parentTabItem, boolean showControls, boolean showNewTabControl) {
    this.parent = parent;
    this.rssOwlGui = rssOwlGui;
    this.showControls = showControls;
    this.showNewTabControl = showNewTabControl;
    this.parentTabItem = parentTabItem;

    /** Display browser with controls */
    if (showControls)
      initComponents();

    /** Display browser without controls */
    else
      initComponentsNoControls();
  }

  /**
   * Display HTML formatted news
   * 
   * @param rssNewsItem News to display
   */
  public void displayNews(NewsItem rssNewsItem) {

    /** Convert non ASCII chars to Unicode before sending it to the browser */
    setText(StringShop.unicodeToEntities(rssNewsItem.toHTML()));
  }

  /**
   * Dispose the browser
   */
  public void dispose() {
    try {

      /** Explicitly hide Browser Panel and Browser */
      if (GUI.isAlive() && WidgetShop.isset(browser))
        browser.setVisible(false);
      if (GUI.isAlive() && WidgetShop.isset(browserPanel))
        browserPanel.setVisible(false);

      /** Dispose browser */
      browser.dispose();

      /** Explicitly force GC to clean browser */
      browser = null;

      /** Dispose browser panel */
      browserPanel.dispose();
    }

    /**
     * History has shown that disposing the Browser can have strange results,
     * for instance events being sent out from the Browser although its
     * disposed. Therefor this paranoia-code is used to get around this issue.
     */
    catch (Exception e) {
      GUI.logger.log(e.getMessage());
    }

    /** Explicitly force GC to clean browser */
    finally {
      browser = null;
    }
  }

  /**
   * Get the Browser widget
   * 
   * @return Browser The browser widget
   */
  public Browser getBrowser() {
    return browser;
  }

  /**
   * Get the browser panel
   * 
   * @return Composite The browser panel
   */
  public Composite getBrowserPanel() {
    return browserPanel;
  }

  /**
   * Get the Location Text-Input field.
   * 
   * @return Text The location text-input field.
   */
  public Text getLocation() {
    return location;
  }

  /**
   * Hide the Newstext Browser Panel
   */
  public void hideNoControlsBrowser() {
    browserPanel.setVisible(false);
  }

  /**
   * Open a new URL in the browser
   * 
   * @param url URL of the webpage
   */
  public void openUrl(String url) {
    saveHome = url;
    location.setText(url);

    /** Set the URL as Tooltip to the Home Button and enable it */
    if (showControls) {
      itemHome.setEnabled(true);
      itemHome.setToolTipText(url);
    }

    /** Re-Create Browser if it was disposed already */
    if (!WidgetShop.isset(browser)) {
      if (showControls)
        initBrowser();
      else
        initBrowserNoControls();
    }

    /** Launch URL in Browser */
    browser.setUrl(url);
  }

  /**
   * Print the Browser using the JavaScript print() method
   * 
   * @return boolean TRUE in case of success, FALSE otherwise
   */
  public boolean print() {
    if (WidgetShop.isset(browser))
      return browser.execute(JAVA_SCRIPT_PRINT);
    return false;
  }

  /**
   * Set TRUE to block navigation in the browser widget. Any selected links will
   * open in the external browser if set in Settings. Set FALSE to allow
   * navigation in the browser. When setting text to the browser, blockNav has
   * to be FALSE to display the text!
   * 
   * @param blockNav The blockNav to set.
   */
  public void setBlockNavigation(boolean blockNav) {
    this.blockNavigation = blockNav;
  }

  /**
   * Sets the CTabItem this BrowserPanel is part of. This method is only called
   * when the initial TabItem was moved to a different TabItem.
   * 
   * @param tabItem The CTabItem this Table is part of.
   */
  public void setTabItem(CTabItem tabItem) {
    parentTabItem = tabItem;
  }

  /** Init the browser */
  private void initBrowser() {

    /** ViewForm containing the Browser */
    ViewForm browserViewForm = new ViewForm(browserPanel, SWT.BORDER | SWT.FLAT);
    browserViewForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

    /** Create the Browser widget */
    browser = new Browser(browserViewForm, SWT.NONE);

    /** Add Feed-Protocol Support */
    setupFeedProtocolSupport(browser);

    /** Store a reference to the TabItem for closing popups */
    browser.setData(parentTabItem);

    /** Apply content to ViewForm */
    browserViewForm.setContent(browser, true);

    /** Container for Status Label and Progress Bar */
    Composite bottomContainer = new Composite(browserPanel, SWT.NONE);
    bottomContainer.setLayout(LayoutShop.createGridLayout(2, 2, 1));
    bottomContainer.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
    bottomContainer.setBackground(PaintShop.grayViewFormColor);

    /** Status label */
    status = new Label(bottomContainer, SWT.NONE);
    status.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
    status.setBackground(PaintShop.grayViewFormColor);

    /** Label displaying the progress bar */
    statusProgressBar = new Label(bottomContainer, SWT.NONE);
    statusProgressBar.setFont(FontShop.dialogFont);
    statusProgressBar.setImage(PaintShop.getProgressIcon(0));
    statusProgressBar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));

    /** Status Text Listener */
    browser.addStatusTextListener(new StatusTextListener() {
      public void changed(StatusTextEvent event) {
        onStatusTextChange(event);
      }
    });

    /** Progress Listener */
    browser.addProgressListener(new ProgressListener() {
      public void changed(ProgressEvent event) {
        onProgressChange(event);

        /** Set text for new browser tabs */
        if (!StringShop.isset(location.getText()) && !location.isDisposed()) {
          String url = ((Browser) event.widget).getUrl();
          location.setText(URLShop.ABOUT_BLANK.equals(url) ? "" : url);
        }
      }

      /** Reset progress bar on completion */
      public void completed(ProgressEvent event) {
        onProgressCompletion();

        if (!location.isDisposed()) {
          String url = ((Browser) event.widget).getUrl();
          location.setText(URLShop.ABOUT_BLANK.equals(url) ? "" : url);

          /** Pass Focus into Location Field if about:blank is URL */
          if (URLShop.ABOUT_BLANK.equals(url))
            location.setFocus();
        }
      }
    });

    /** Listen for open events in case displaySingleTabs is FALSE */
    if (!GlobalSettings.displaySingleTab) {
      browser.addOpenWindowListener(new OpenWindowListener() {
        public void open(WindowEvent event) {

          /** Tell event to use this browser if application still alive */
          if (GUI.isAlive() && WidgetShop.isset(browser))
            event.browser = rssOwlGui.getRSSOwlNewsTabFolder().createBrowserPopupTab();
        }
      });
    }

    /** Listen for close events */
    browser.addCloseWindowListener(new CloseWindowListener() {
      public void close(WindowEvent event) {
        if (WidgetShop.isset(parentTabItem))
          rssOwlGui.getRSSOwlNewsTabFolder().closeTab(parentTabItem);
      }
    });

    /** Location Listener */
    browser.addLocationListener(new LocationAdapter() {
      public void changed(LocationEvent event) {
        updateNavToolItems();
      }
    });

    /** Visibility Listener to close popups - Mozilla does this for free on Linux */
    if (!GlobalSettings.isLinux()) {
      browser.addVisibilityWindowListener(new VisibilityWindowAdapter() {
        public void show(WindowEvent event) {
          final WindowEvent fEvent = event;

          /** Block most common popups if set */
          if (GlobalSettings.blockPopups && (!event.addressBar || !event.menuBar || !event.statusBar || !event.toolBar)) {

            /** Close the TabItem showing the popup */
            if (GUI.isAlive()) {
              GUI.display.asyncExec(new Runnable() {
                public void run() {
                  if (GUI.isAlive() && WidgetShop.isset(fEvent.widget) && WidgetShop.isWidget(fEvent.widget.getData()))
                    rssOwlGui.getRSSOwlNewsTabFolder().closeTab(((CTabItem) fEvent.widget.getData()));
                }
              });
            }
          }
        }
      });
    }

    /** Update the Edit Menu */
    browser.addListener(SWT.Activate, new Listener() {
      public void handleEvent(Event event) {
        MenuManager.handleEditMenuState();
      }
    });
  }

  /** Create the browser to use with no controls */
  private void initBrowserNoControls() {

    /** Browser Margin */
    Composite browserMargin = new Composite(browserPanel, SWT.NONE);
    browserMargin.setLayout(LayoutShop.createGridLayout(1, 0, 0));
    browserMargin.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

    /** Create a new Browser widget */
    browser = new Browser(browserMargin, SWT.NONE);

    /** Add Feed-Protocol Support */
    setupFeedProtocolSupport(browser);

    /** GridData used by the browser widget */
    browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

    /** ViewForm for Status Container */
    ViewForm statusViewForm = new ViewForm(browserPanel, SWT.BORDER | SWT.FLAT);
    statusViewForm.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
    statusViewForm.setBackground(PaintShop.grayViewFormColor);
    statusViewForm.setOnlyBorderTop(true);

    /** Container for Status Text and Progress Bar */
    Composite statusContainer = new Composite(statusViewForm, SWT.NONE);
    statusContainer.setBackground(PaintShop.grayViewFormColor);
    statusContainer.setLayout(LayoutShop.createGridLayout(2, 2, 2));
    statusViewForm.setTopLeft(statusContainer, true);

    /** Status label */
    status = new Label(statusContainer, SWT.NONE);
    status.setBackground(PaintShop.grayViewFormColor);
    status.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));

    /** Label displaying the progress bar */
    statusProgressBar = new Label(statusContainer, SWT.NONE);
    statusProgressBar.setFont(FontShop.dialogFont);
    statusProgressBar.setImage(PaintShop.getProgressIcon(0));
    statusProgressBar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));

    /** Status Text Listener */
    browser.addStatusTextListener(new StatusTextListener() {
      public void changed(StatusTextEvent event) {
        if (StringShop.isset(event.text) && !URLShop.ABOUT_BLANK.equals(event.text))
          status.setText(StringShop.escapeAmpersands(event.text));
        else
          status.setText(GUI.i18n.getTranslation("LABEL_READY"));
      }
    });

    /** Progress Listener */
    browser.addProgressListener(new ProgressListener() {
      public void changed(ProgressEvent event) {
        onProgressChange(event);
      }

      /** Reset progress bar on completion */
      public void completed(ProgressEvent event) {
        onProgressCompletion();
      }
    });

    /** Visibility Listener to close popups - Mozilla does this for free on Linux */
    if (!GlobalSettings.isLinux()) {
      browser.addVisibilityWindowListener(new VisibilityWindowAdapter() {
        public void show(WindowEvent event) {
          final WindowEvent fEvent = event;

          /** Block most common popups if set */
          if (GlobalSettings.blockPopups && (!event.addressBar || !event.menuBar || !event.statusBar || !event.toolBar)) {

            /** Close the TabItem showing the popup */
            if (GUI.isAlive()) {
              GUI.display.asyncExec(new Runnable() {
                public void run() {
                  if (GUI.isAlive() && WidgetShop.isset(fEvent.widget) && WidgetShop.isWidget(fEvent.widget.getData()))
                    rssOwlGui.getRSSOwlNewsTabFolder().closeTab(((CTabItem) fEvent.widget.getData()));
                }
              });
            }
          }
        }
      });
    }

    /** Listen for open events */
    browser.addOpenWindowListener(new OpenWindowListener() {
      public void open(WindowEvent event) {

        /** Tell event to use this browser if application still alive */
        if (GUI.isAlive() && WidgetShop.isset(browser))
          event.browser = rssOwlGui.getRSSOwlNewsTabFolder().createBrowserPopupTab();
      }
    });

    /**
     * The location listener is added to the browser to block the navigation in
     * that widget if "blockNav" is set to TRUE and the settings tell to use an
     * external Browser. The URL is then opened externally.This is kind of a
     * Hack and only supported on Windows. For more information:
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=89091
     */
    browser.addLocationListener(new LocationAdapter() {

      /**
       * Workaround to reset the blockNav flag back to TRUE after a news was
       * displayed. The changed() event is triggered, when the loading of the
       * page is done. Since this event can be called more than once for
       * embedded IFrames or Frames in general, we need to check the event.top
       * field, which indicates that the Top-Frame has finished loading.
       */
      public void changed(LocationEvent event) {
        if (event.top)
          setBlockNavigation(true);
      }

      /**
       * If the blockNav flag is set to TRUE, stop loading and show the link in
       * a new tab or open it external.
       */
      public void changing(LocationEvent event) {

        /**
         * Bug on Mac: Safari puts out links from images as event.location
         * resulting in RSSOwl to open a browser although its not necessary
         * Workaround is to disable this feature on Mac until its fixed. (see
         * Bug #1068304).
         */
        if (GlobalSettings.isMac())
          return;

        /**
         * Feature on Linux not required at all, since Mozilla is not a
         * problematic Browser.
         */
        if (GlobalSettings.isLinux())
          return;

        /** Only proceed if navigation should be blocked */
        if (!blockNavigation)
          return;

        /** This Hack is only supported for external Browser usage */
        if (!GlobalSettings.openBrowserExtern)
          return;

        /** Analyze the event.location */
        if (!StringShop.isset(event.location) || event.location.equals(URLShop.ABOUT_BLANK) || event.location.startsWith(URLShop.FEED_PROTOCOL_SHORT))
          return;

        /** Finally, cancel event and open URL external */
        event.doit = false;
        rssOwlGui.getEventManager().actionOpenURLExternal(event.location);
      }
    });

    /** Update the Edit Menu and Accelerators */
    browser.addListener(SWT.Activate, new Listener() {
      public void handleEvent(Event event) {
        MenuManager.handleEditMenuState();
      }
    });
  }

  /** Init all components */
  private void initComponents() {

    /** Browser Panel */
    browserPanel = new Composite(parent, SWT.NONE);
    browserPanel.setLayout(LayoutShop.createGridLayout(1, 0, 0, 2));
    browserPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    browserPanel.setBackground(PaintShop.grayViewFormColor);

    /** ViewForm for ToolBar and Location Text field */
    ViewForm toolBarViewForm = new ViewForm(browserPanel, SWT.BORDER | SWT.FLAT);
    toolBarViewForm.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
    toolBarViewForm.setBackground(PaintShop.grayViewFormColor);

    /** Composite containing ToolBar and Location Text field */
    Composite topContainer = new Composite(toolBarViewForm, SWT.NONE);
    topContainer.setLayout(LayoutShop.createGridLayout(2, 3, 1));
    topContainer.setBackground(PaintShop.grayViewFormColor);

    /** Apply content to ViewForm */
    toolBarViewForm.setTopLeft(topContainer, false);

    /** Toolbar */
    ToolBar toolbar = new ToolBar(topContainer, SWT.FLAT);
    toolbar.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
    toolbar.setBackground(PaintShop.grayViewFormColor);

    /** Open new, blank browser tab */
    if (showNewTabControl) {
      itemNewTab = new ToolItem(toolbar, SWT.PUSH);
      itemNewTab.setToolTipText(GUI.i18n.getTranslation("TOOLTIP_OPEN_TAB"));
      itemNewTab.setImage(PaintShop.loadImage("/img/icons/new_browser_tab.gif"));
      itemNewTab.setDisabledImage(PaintShop.loadImage("/img/icons/new_browser_tab_disabled.gif"));
      itemNewTab.addDisposeListener(DisposeListenerImpl.getInstance());
      itemNewTab.setEnabled(!GlobalSettings.displaySingleTab);
      itemNewTab.addListener(SWT.Selection, new Listener() {
        public void handleEvent(Event event) {
          rssOwlGui.getRSSOwlNewsTabFolder().displayBrowserTab(URLShop.ABOUT_BLANK);
        }
      });

      /** Separator */
      new ToolItem(toolbar, SWT.SEPARATOR);
    }

    /** Navigate back in history */
    itemBack = new ToolItem(toolbar, SWT.PUSH);
    itemBack.setToolTipText(GUI.i18n.getTranslation("BROWSER_BACK"));
    itemBack.setImage(PaintShop.iconBackward);
    itemBack.setDisabledImage(PaintShop.loadImage("/img/icons/backward_disabled.gif"));
    itemBack.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        itemBack.getDisabledImage().dispose();
      }
    });
    itemBack.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {
        if (WidgetShop.isset(browser))
          browser.back();
      }
    });

    /** Navigate forward in history */
    itemForward = new ToolItem(toolbar, SWT.PUSH);
    itemForward.setToolTipText(GUI.i18n.getTranslation("BROWSER_FORWARD"));
    itemForward.setImage(PaintShop.iconForward);
    itemForward.setDisabledImage(PaintShop.loadImage("/img/icons/forward_disabled.gif"));
    itemForward.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        itemForward.getDisabledImage().dispose();
      }
    });
    itemForward.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {
        if (WidgetShop.isset(browser))
          browser.forward();
      }
    });

    /** Stop loading */
    itemStop = new ToolItem(toolbar, SWT.PUSH);
    itemStop.setToolTipText(GUI.i18n.getTranslation("BROWSER_STOP"));
    itemStop.setImage(PaintShop.iconStop);
    itemStop.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {
        if (WidgetShop.isset(browser))
          browser.stop();
      }
    });

    /** Reload webpage */
    itemReload = new ToolItem(toolbar, SWT.PUSH);
    itemReload.setToolTipText(GUI.i18n.getTranslation("MENU_RELOAD"));
    itemReload.setImage(PaintShop.iconReloadBrowser);
    itemReload.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {
        if (WidgetShop.isset(browser))
          browser.refresh();
      }
    });

    /** Navigate to home url */
    itemHome = new ToolItem(toolbar, SWT.PUSH);
    itemHome.setImage(PaintShop.loadImage("/img/icons/home.gif"));
    itemHome.setDisabledImage(PaintShop.loadImage("/img/icons/home_disabled.gif"));
    itemHome.addDisposeListener(DisposeListenerImpl.getInstance());
    itemHome.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {
        if (WidgetShop.isset(browser))
          browser.setUrl(saveHome);
      }
    });

    /** Separator */
    new ToolItem(toolbar, SWT.SEPARATOR);

    /** Search for Newsfeeds on Website */
    itemDiscovery = new ToolItem(toolbar, SWT.PUSH);
    itemDiscovery.setToolTipText(GUI.i18n.getTranslation("MENU_FEED_DISCOVERY"));
    itemDiscovery.setImage(PaintShop.loadImage("/img/icons/feed_discovery_browse.gif"));
    itemDiscovery.addDisposeListener(DisposeListenerImpl.getInstance());
    itemDiscovery.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {
        rssOwlGui.getEventManager().actionDiscoverFeeds(location.getText());
      }
    });

    /** Set back, forward and home disabled */
    itemBack.setEnabled(false);
    itemForward.setEnabled(false);
    itemHome.setEnabled(false);

    /** Input field to enter a URL */
    location = new Text(topContainer, SWT.BORDER);
    location.setFont(FontShop.dialogFont);
    location.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

    /** Browse to url after enter */
    location.addListener(SWT.DefaultSelection, new Listener() {
      public void handleEvent(Event e) {
        if (StringShop.isset(location.getText())) {
          browser.setUrl(location.getText());
          browser.setFocus();
        }
      }
    });

    /** Setup Drop Support */
    WidgetShop.setupDropSupport(location, new Runnable() {
      public void run() {
        browser.setUrl(location.getText());
        browser.setFocus();
      }
    });

    /** Tweak Text Widget */
    WidgetShop.tweakTextWidget(location);

    /** Setup the browser to use */
    initBrowser();
  }

  /** Init components without the controls */
  private void initComponentsNoControls() {

    /** Browser Panel */
    browserPanel = new Composite(parent, SWT.NONE);
    browserPanel.setLayout(LayoutShop.createGridLayout(1, 0, 0, 0, 0, false));

    /** The parent uses FormLayout, so use FormData for Layout */
    browserPanel.setLayoutData(LayoutDataShop.createFormData(0, 0, 0, 0));

    /** Create the browser to be used with no controls */
    initBrowserNoControls();
  }

  /**
   * Set HTML text to the browser
   * 
   * @param text HTML code
   */
  private void setText(String text) {

    /** We have to allow navigation to display the news */
    setBlockNavigation(false);

    /** Re-Create Browser if it was disposed already */
    if (!WidgetShop.isset(browser)) {
      if (showControls)
        initBrowser();
      else
        initBrowserNoControls();
    }

    /** Set Text to display in Browser */
    browser.setText(text);

    /** Might be hidden */
    if (!browserPanel.isVisible())
      browserPanel.setVisible(true);
  }

  /**
   * Add Support for the Feed Protocol to the given Browser.<br>
   * <br>
   * Listen for changing Location and intercept the new location the Browser is
   * about to navigate to. In case the new Location uses the Feed Protocol URI
   * Syntax, parse the Feed's URL out of it and open the "New Favorite" dialog.
   * 
   * @param browser The Browser to add the Feed Protocol Support.
   */
  private void setupFeedProtocolSupport(Browser browser) {
    browser.addLocationListener(new LocationAdapter() {
      public void changing(LocationEvent event) {

        /**
         * The Browser is navigating to a new Location. Check if the new
         * Location is using the Feed-Protocol URI Syntax. If this is TRUE,
         * parse the Feed's Link out of the URI and open the "New Favorite"
         * Dialog.
         */
        if (event.location != null && event.location.startsWith(URLShop.FEED_PROTOCOL_SHORT)) {
          String feedUrl = event.location;

          /** The Long URI Syntax Format is used */
          if (feedUrl.startsWith(URLShop.FEED_PROTOCOL_LONG))
            feedUrl = feedUrl.substring(URLShop.FEED_PROTOCOL_LONG.length());

          /** The Short URI Syntax Format is used */
          else
            feedUrl = feedUrl.substring(URLShop.FEED_PROTOCOL_SHORT.length());

          /** Handle the Link and cancel navigation */
          if (StringShop.isset(feedUrl)) {
            rssOwlGui.getEventManager().actionHandleSuppliedLink(feedUrl);
            event.doit = false;
          }
        }
      }
    });
  }

  /**
   * Called whenever the Progress is changing
   * 
   * @param event The ProgressEvent
   */
  void onProgressChange(ProgressEvent event) {

    /** Return in case no progress was made yet */
    if (event.total == 0)
      return;

    /** Calculate ratio */
    int ratio = event.current * 100 / event.total;

    /** Set the Progress bar in dependance of the ratio */
    if (ratio == 100)
      statusProgressBar.setImage(PaintShop.getProgressIcon(100));
    else if (ratio >= 80)
      statusProgressBar.setImage(PaintShop.getProgressIcon(80));
    else if (ratio >= 60)
      statusProgressBar.setImage(PaintShop.getProgressIcon(60));
    else if (ratio >= 40)
      statusProgressBar.setImage(PaintShop.getProgressIcon(40));
    else
      statusProgressBar.setImage(PaintShop.getProgressIcon(20));

    /** Update the Progress Bar */
    statusProgressBar.update();
  }

  /**
   * Called whenever the Progress is completed
   */
  void onProgressCompletion() {
    statusProgressBar.setImage(PaintShop.getProgressIcon(0));

    /** Update the Progress Bar */
    statusProgressBar.update();
  }

  /**
   * Called whenever the Status Text is changing
   * 
   * @param event The StatusTextEvent
   */
  void onStatusTextChange(StatusTextEvent event) {
    status.setText(StringShop.isset(event.text) ? StringShop.escapeAmpersands(event.text) : GUI.i18n.getTranslation("LABEL_READY"));
    updateNavToolItems();
  }

  /**
   * Update "Back" and "Forward" tool items
   */
  void updateNavToolItems() {
    if (WidgetShop.isset(itemBack))
      itemBack.setEnabled(browser.isBackEnabled());

    if (WidgetShop.isset(itemForward))
      itemForward.setEnabled(browser.isForwardEnabled());
  }
}