/*   **********************************************************************  **
 **   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;

import net.sourceforge.rssowl.controller.dialog.SearchDialog;
import net.sourceforge.rssowl.controller.dnd.NewsTabFolderDND;
import net.sourceforge.rssowl.controller.panel.BrowserPanel;
import net.sourceforge.rssowl.controller.panel.ErrorPanel;
import net.sourceforge.rssowl.controller.panel.NewsfeedPanel;
import net.sourceforge.rssowl.controller.panel.UpdatePanel;
import net.sourceforge.rssowl.controller.panel.WelcomePanel;
import net.sourceforge.rssowl.controller.thread.AggregationLoader;
import net.sourceforge.rssowl.dao.NewsfeedFactoryException;
import net.sourceforge.rssowl.model.Category;
import net.sourceforge.rssowl.model.Channel;
import net.sourceforge.rssowl.model.Favorite;
import net.sourceforge.rssowl.model.NewsItem;
import net.sourceforge.rssowl.model.TabItemData;
import net.sourceforge.rssowl.model.TableItemData;
import net.sourceforge.rssowl.util.DateParser;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.document.DocumentGenerator;
import net.sourceforge.rssowl.util.i18n.ITranslatable;
import net.sourceforge.rssowl.util.search.ParsedSearch;
import net.sourceforge.rssowl.util.search.SearchDefinition;
import net.sourceforge.rssowl.util.shop.BrowserShop;
import net.sourceforge.rssowl.util.shop.FileShop;
import net.sourceforge.rssowl.util.shop.FontShop;
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.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.TitleEvent;
import org.eclipse.swt.browser.TitleListener;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeItem;

import com.lowagie.text.DocumentException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Vector;

/**
 * This is the TabFolder that holds the opened feeds displaying a table with all
 * news header and some informations about the channel.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class NewsTabFolder implements ITranslatable, IFontChangeable {

  /** Display Mode: Focus Feed */
  public static final int DISPLAY_MODE_FOCUS = 0;

  /** Display Mode: Focus Feed that is on Tabposition 1 */
  public static final int DISPLAY_MODE_FOCUS_FIRST = 2;

  /** Display Mode: Do not Focus Feed */
  public static final int DISPLAY_MODE_NO_FOCUS = 1;

  /** Display Mode: Like DISPLAY_MODE_FOCUS. Select first News automatically. */
  public static final int DISPLAY_MODE_SELECT_NEWS = 3;

  /** Display Mode: Like DISPLAY_MODE_FOCUS. Select unread News automatically. */
  public static final int DISPLAY_MODE_SELECT_UNREAD_NEWS = 4;

  /** Maximum number of history Items in the Menu */
  private static final int MAX_HISTORY_ITEMS = 25;

  /** Maximum number of chars for a TabItem Title */
  private static final int MAX_TAB_TITLE_LENGTH = 40;

  /** Tab State: Displaying a browser */
  private static final int TAB_STATE_BROWSER = 1;

  /** Tab State: TabFolder empty */
  private static final int TAB_STATE_EMPTY = 0;

  /** Tab State: Displaying an error */
  private static final int TAB_STATE_ERROR = 2;

  /** Tab State: Displaying a feed */
  private static final int TAB_STATE_FEED = 4;

  /** Tab State: Displaying a message */
  private static final int TAB_STATE_MESSAGE = 3;

  private MenuItem close;
  private MenuItem closeAll;
  private Display display;
  private Vector lastOpenedFeeds;
  private int lastTabState;
  private Composite newsHeaderTabFolderHolder;
  private NewsTabFolderDND rssOwlNewsTabFolderDND;
  private MenuItem tabPositionMenu;
  MenuItem closeAllKeepCurrent;
  MenuItem closeAllKeepFeeds;
  SashForm contentPane;
  EventManager eventManager;
  ArrayList historyList;
  boolean mouseInTab;
  CTabFolder newsHeaderTabFolder;
  ViewForm newsHeaderViewForm;
  GUI rssOwlGui;
  Shell shell;
  Menu tabFolderMenu;
  MenuItem tabPositionBottom;
  MenuItem tabPositionTop;

  /**
   * Instantiate a new NewsTabFolder holding the opened news feeds and the news
   * header.
   * 
   * @param display The display
   * @param shell The shell
   * @param contentPane The Sashform where to add the ViewForm
   * @param rssOwlGui Access some methods of the main controller
   * @param eventManager The event manager
   */
  public NewsTabFolder(Display display, Shell shell, SashForm contentPane, GUI rssOwlGui, EventManager eventManager) {
    this.display = display;
    this.shell = shell;
    this.contentPane = contentPane;
    this.rssOwlGui = rssOwlGui;
    this.eventManager = eventManager;
    this.lastOpenedFeeds = new Vector();
    lastTabState = -1;
    historyList = new ArrayList();
    initComponents();

    /** The tabfolder supports basic Drag & Drop */
    rssOwlNewsTabFolderDND = new NewsTabFolderDND(newsHeaderTabFolder, rssOwlGui.getRSSOwlFavoritesTree().getRSSOwlFavoritesTreeDND());
  }

  /**
   * Add the URL of a feed to the list of feeds that RSSOwl shall open after
   * restart.
   * 
   * @param url The URL of the feed
   */
  public void addFeedToLastOpened(String url) {

    /** Only store one URL in case TabFolder is not used */
    if (GlobalSettings.displaySingleTab)
      lastOpenedFeeds.clear();

    /** Only add if not yet existing */
    if (!lastOpenedFeeds.contains(url))
      lastOpenedFeeds.add(url);
  }

  /**
   * Close the given tabItem
   * 
   * @param tabItem Selected tabItem
   */
  public void closeTab(CTabItem tabItem) {
    int index = newsHeaderTabFolder.getSelectionIndex();
    boolean isTabSelected = (newsHeaderTabFolder.getSelection() != null && newsHeaderTabFolder.getSelection().equals(tabItem));

    /** Tab may be closed or disposed already, return in that case */
    if (!WidgetShop.isset(tabItem))
      return;

    /** Set isWelcomeShown state */
    if (tabItem.getText() != null && tabItem.getText().equals(GUI.i18n.getTranslation("TAB_WELCOME")))
      GlobalSettings.isWelcomeShown = false;

    /** Remove feed from last opened */
    TabItemData data = (tabItem.getData() != null) ? (TabItemData) tabItem.getData() : null;
    if (data != null && data.isFeed() && data.getUrl() != null)
      lastOpenedFeeds.remove(data.getUrl());

    /** Mark feed read if set so */
    if (GlobalSettings.markFeedReadOnTabClose && data != null && data.isFeed()) {

      /** Get the rsschannel from the closed tab */
      Channel channel = data.getChannel();

      /** Mark feed read, but do not update table since the tab is closing */
      if (channel != null)
        eventManager.actionMarkAllNewsRead(channel, false);
    }

    /** Dispose TabItemData Object */
    if (data != null)
      data.dispose();

    /** Dispose the CTabItem's control */
    if (tabItem.getControl() != null)
      tabItem.getControl().dispose();

    /** Dispose tabitem */
    tabItem.dispose();

    /**
     * Set selection to the next item to the right if that is possible and the
     * Tab that was closed was selected in the TabFolder.
     */
    if (isTabSelected && index >= 0 && index < newsHeaderTabFolder.getItemCount())
      newsHeaderTabFolder.setSelection(index);

    /** Update TabFolder state */
    updateTabFolderState();

    /** Restore the initial TabFolder if neccessary */
    if (newsHeaderTabFolder.getMaximized() && newsHeaderTabFolder.getItemCount() == 0) {

      /** Restore View */
      if (GUI.isAlive())
        onRestore();
    }
  }

  /**
   * Create a new Tab for a popup. This method is ONLY called from another
   * internal browser, when either the user has clicked on a link that uses
   * "target=_blank" or a JS popup.
   * 
   * @return Browser The new browser instance that will display the popup
   */
  public Browser createBrowserPopupTab() {

    /** Create a new CTabItem with a Browser */
    final CTabItem tabItem = (GlobalSettings.displaySingleTab && newsHeaderTabFolder.getItemCount() > 0) ? newsHeaderTabFolder.getItem(0) : new CTabItem(newsHeaderTabFolder, SWT.NONE);

    /** Dispose old TabItem */
    if ((GlobalSettings.displaySingleTab && newsHeaderTabFolder.getItemCount() > 0 && WidgetShop.isset(newsHeaderTabFolder.getItem(0))))
      if (WidgetShop.isset(newsHeaderTabFolder.getItem(0).getControl()))
        newsHeaderTabFolder.getItem(0).getControl().dispose();

    /** Create New */
    BrowserPanel rssOwlBrowser = new BrowserPanel(rssOwlGui, newsHeaderTabFolder, tabItem, true, true);
    tabItem.setControl(rssOwlBrowser.getBrowserPanel());
    tabItem.setImage(PaintShop.iconWorld);
    tabItem.setData(TabItemData.createBrowserData(rssOwlBrowser));

    /**
     * This method might have been called from a browser instance that was
     * closed triggering an "onUnLoad" JavaScript popup. In case RSSOwl is in
     * process of closing, do not focus the browser-tab, just return it now.
     */
    if (!GUI.isAlive())
      return rssOwlBrowser.getBrowser();

    /**
     * Set selection to the popup if grabFocus is TRUE or there is no tabitem
     * selected in the tabfolder
     */
    if (focusTab() || (newsHeaderTabFolder.getSelectionIndex() == -1)) {
      newsHeaderTabFolder.setSelection(tabItem);

      /** Update TabFolder state */
      updateTabFolderState();
    }

    /** Set the title of the webpage as tabtitle */
    if (WidgetShop.isset(rssOwlBrowser.getBrowser())) {
      rssOwlBrowser.getBrowser().addTitleListener(new TitleListener() {
        public void changed(TitleEvent event) {

          /** Replace '&' with '&&' to avoid mnemonic display */
          if (!StringShop.isWhiteSpaceOrEmpty(event.title) && !URLShop.ABOUT_BLANK.equals(event.title))
            tabItem.setText(StringShop.pointTrim(event.title, MAX_TAB_TITLE_LENGTH, true));

          /** In case the title is not given */
          else
            tabItem.setText(GUI.i18n.getTranslation("NO_TITLE"));
        }
      });
    }

    return rssOwlBrowser.getBrowser();
  }

  /**
   * Display a new browser tab in the tabfolder
   * 
   * @param url URL of the website to display
   */
  public void displayBrowserTab(String url) {
    CTabItem tabItem;

    /** Use first Tab to show error if displaySingleTab is TRUE */
    if (GlobalSettings.displaySingleTab && newsHeaderTabFolder.getItemCount() > 0)
      tabItem = newsHeaderTabFolder.getItem(0);

    /** Create TabItem to load error into */
    else
      tabItem = new CTabItem(newsHeaderTabFolder, SWT.NONE);

    tabItem.setText(StringShop.pointTrim(url, MAX_TAB_TITLE_LENGTH, true));
    tabItem.setImage(PaintShop.iconWorld);

    /** Create browser tab and open url */
    createBrowserTab(tabItem, url);
  }

  /**
   * Display the selected newsfeed in the TabFolder
   * 
   * @param rssChannel The newsfeed to display
   * @param url URL / Path to the RSS XML
   * @param searchDefinition Pattern and Scope of the Search.
   * @param reselectNews Wether to reselect an already selected news
   * @param displayMode One of the supported displaymodes
   */
  public void displayNewsfeed(Channel rssChannel, String url, SearchDefinition searchDefinition, boolean reselectNews, int displayMode) {
    String selectedNews = null;
    int columnWidths[] = null;

    /** Return in case application was closed */
    if (!GUI.isAlive() || newsHeaderTabFolder.isDisposed())
      return;

    /** Remember wether Focus is set on Browser in NewsText Area */
    boolean isFocusOnNewsTextBrowser = false;
    boolean autoSelectNews = (displayMode == DISPLAY_MODE_SELECT_NEWS || displayMode == DISPLAY_MODE_SELECT_UNREAD_NEWS);
    BrowserPanel panel = GUI.rssOwlGui.getRSSOwlNewsText().getBrowserPanel();
    if (autoSelectNews && panel != null && WidgetShop.isset(panel.getBrowser()))
      isFocusOnNewsTextBrowser = panel.getBrowser().isFocusControl();

    /** Check if the news feed is already opened in the TabFolder */
    CTabItem feedTabItem = getFeedTabItem(url);

    /** Check if the channel is from an aggregation and opened */
    if (feedTabItem == null && rssChannel.isAggregatedCat())
      feedTabItem = getTabItem(rssChannel.getAggregatedCategory());

    /** Check if a feed is reloaded and the table width needs to be restored */
    if (WidgetShop.isset(feedTabItem) && feedTabItem.getData() != null) {
      TabItemData tabData = (TabItemData) feedTabItem.getData();
      Table newsHeaderTable = tabData.getNewsHeaderTable();

      /** Selected News */
      if (reselectNews && WidgetShop.isset(newsHeaderTable) && newsHeaderTable.getSelectionCount() > 0)
        selectedNews = newsHeaderTable.getSelection()[0].getText(1);

      /** TableColumn widths */
      if (WidgetShop.isset(newsHeaderTable) && newsHeaderTable.getColumnCount() > 0) {
        columnWidths = new int[newsHeaderTable.getColumnCount()];
        for (int a = 0; a < columnWidths.length; a++)
          columnWidths[a] = newsHeaderTable.getColumn(a).getWidth();
      }
    }

    /** If displaySingleTab is True, always use first Tab if available */
    if (GlobalSettings.displaySingleTab && feedTabItem == null && newsHeaderTabFolder.getItemCount() > 0) {
      feedTabItem = newsHeaderTabFolder.getItem(0);

      /** Treat this Tab as if being closed and mark it read if set so */
      TabItemData data = (TabItemData) feedTabItem.getData();
      if (GlobalSettings.markFeedReadOnTabClose && data != null && data.isFeed())
        eventManager.actionMarkAllNewsRead(data.getChannel(), false);
    }

    boolean feedIsOpened = (feedTabItem != null);

    /** Dispose control of old tabitem */
    if (feedIsOpened && feedTabItem != null)
      feedTabItem.getControl().dispose();

    /** Create a new tabitem */
    else
      feedTabItem = new CTabItem(getNewsHeaderTabFolder(), SWT.NONE);

    /** Get a suitable title */
    String title = StringShop.isset(url) ? url : GUI.i18n.getTranslation("NO_TITLE");
    if (Category.getTitleForLink(title) != null)
      title = Category.getTitleForLink(title);
    else if (rssChannel != null && StringShop.isset(rssChannel.getTitle()))
      title = rssChannel.getTitle();

    /** Set title to the Tab if title changed */
    if (!feedTabItem.getText().equals(title))
      feedTabItem.setText(title);

    /** See if search is performed */
    boolean performSearch = StringShop.isset(searchDefinition.getPattern());

    /** Run the search if set */
    if (performSearch && rssChannel != null)
      rssChannel.peformSearch(ParsedSearch.parseFrom(searchDefinition));

    /** Create a new NewsfeedPanel containing all news */
    NewsfeedPanel newsfeedPanel = new NewsfeedPanel(rssOwlGui, feedTabItem, rssChannel, url, title, searchDefinition.getPattern(), selectedNews, columnWidths);

    /** Apply control to tabitem */
    feedTabItem.setControl(newsfeedPanel.getNewsfeedPanel());

    /** Select this TabItem in the TabFolder if needed */
    boolean isFirstTabItem = (newsHeaderTabFolder.getItemCount() == 1);
    boolean focusTab = focusTab();

    /** Case 1: Focus is requested and allowed */
    if ((displayMode == DISPLAY_MODE_FOCUS && focusTab))
      newsHeaderTabFolder.setSelection(feedTabItem);

    /** Case 2: Focus on first Tab is requested and this is the first Tab */
    else if (displayMode == DISPLAY_MODE_FOCUS_FIRST && isFirstTabItem && focusTab)
      newsHeaderTabFolder.setSelection(feedTabItem);

    /** Case 3: Always Focus this Tab if TabFolder is empty */
    else if (newsHeaderTabFolder.getSelectionIndex() == -1)
      newsHeaderTabFolder.setSelection(feedTabItem);

    /** Case 4: Focus is requested and the Feed is already opened */
    else if (displayMode == DISPLAY_MODE_FOCUS && feedIsOpened)
      newsHeaderTabFolder.setSelection(feedTabItem);

    /** Case 5: Select the first News of the Feed */
    else if ((displayMode == DISPLAY_MODE_SELECT_NEWS && focusTab))
      newsHeaderTabFolder.setSelection(feedTabItem);

    /** Case 6: Select the first unread News of the Feed */
    else if ((displayMode == DISPLAY_MODE_SELECT_UNREAD_NEWS && focusTab))
      newsHeaderTabFolder.setSelection(feedTabItem);

    /** Update TabItem status */
    updateTabItemStatus(feedTabItem);

    /** Update TabFolder state */
    updateTabFolderState();

    /** Link with the Tree if News is to be auto-selected */
    if (autoSelectNews && !GlobalSettings.linkTreeWithTab)
      linkSelectionToTree();

    /**
     * Also update the unread status in the favorite again (if this is a
     * favorite)
     */
    if (Category.getFavPool().containsKey(url) && rssChannel != null) {
      Favorite rssOwlFavorite = (Favorite) Category.getFavPool().get(url);
      rssOwlFavorite.updateReadStatus(rssChannel.getUnreadNewsCount());
    }

    /** Select the next News automatically if set in display mode */
    boolean channelContainsItems = rssChannel != null && rssChannel.getItemCount() > 0;
    if (channelContainsItems && displayMode == DISPLAY_MODE_SELECT_NEWS)
      eventManager.actionGotoNextNews();
    else if (channelContainsItems && displayMode == DISPLAY_MODE_SELECT_UNREAD_NEWS)
      eventManager.actionGotoNextUnreadNews();

    /** Force Focus to stay in the Browser in case of these Display-Modes */
    if (isFocusOnNewsTextBrowser && panel != null && WidgetShop.isset(panel.getBrowser()) && !panel.getBrowser().isFocusControl())
      panel.getBrowser().setFocus();

    /** Remember in History */
    addHistoryItem(url, title);
  }

  /**
   * Check wether new tabs should get focus. Either the settings tells so, or
   * only one tabitem is visible in the tabfolder. That item will always get
   * focus.
   * 
   * @return boolean TRUE if the new tab should gain focus
   */
  public boolean focusTab() {

    /** Case: Not using tabs */
    if (GlobalSettings.displaySingleTab)
      return true;

    /** Case: Using tabs */
    return (GlobalSettings.focusNewTabs || newsHeaderTabFolder.getItemCount() == 1);
  }

  /**
   * Get the tabitem showing the given Feed or NULL.
   * 
   * @param title The title of the Feed
   * @return CTabItem The Tab or NULL
   */
  public CTabItem getFeedTabItem(String title) {

    /** Title must be set */
    if (!StringShop.isset(title))
      return null;

    /** For each tabitem */
    CTabItem[] tabItems = newsHeaderTabFolder.getItems();
    for (int a = 0; a < tabItems.length; a++) {

      /** The tabitem could have bene closed meanwhile */
      if (WidgetShop.isset(tabItems[a])) {
        TabItemData tabData = (TabItemData) tabItems[a].getData();

        /** Only handle Tabs showing Feeds/Errors */
        if (tabData != null && (tabData.isFeed() || tabData.isError())) {

          /** Check the plain tabitem title */
          String tabTitle = tabItems[a].getText();
          if (title.equals(tabTitle) || title.equals(Category.getLinkForTitle(tabTitle)))
            return tabItems[a];

          /** TabItem Data */
          if (tabData.getUrl() != null && tabData.getUrl().equals(title))
            return tabItems[a];
        }
      }
    }
    return null;
  }

  /**
   * Get the Vector with the last opened newsfeeds.
   * 
   * @return Returns the lastOpenedFeeds.
   */
  public Vector getLastOpenedFeeds() {
    return lastOpenedFeeds;
  }

  /**
   * Let other objects access this controll (for example to focus on it)
   * 
   * @return The tabfolder holding the feeds and news header
   */
  public CTabFolder getNewsHeaderTabFolder() {
    return newsHeaderTabFolder;
  }

  /**
   * Get the DND class that allows drag and drop on this tabfolder
   * 
   * @return RSSOwlTabFolderDND The dnd class of this tabfolder
   */
  public NewsTabFolderDND getRSSOwlNewsTabFolderDND() {
    return rssOwlNewsTabFolderDND;
  }

  /**
   * Get Channel from the selected Tab
   * 
   * @return Channel The Channel displayed in the tab
   */
  public Channel getSelectedChannel() {

    /** A tabitem must be selected in the folder */
    if (newsHeaderTabFolder.getSelectionIndex() != -1)
      return getChannel(newsHeaderTabFolder.getSelection());

    return null;
  }

  /**
   * Get the tabitem for the given category or NULL.
   * 
   * @param aggregatedCategory The category that is aggregated
   * @return CTabItem The Tab or NULL
   */
  public CTabItem getTabItem(Category aggregatedCategory) {
    CTabItem[] tabItems = newsHeaderTabFolder.getItems();

    /** For each tabitem */
    for (int a = 0; a < tabItems.length; a++) {

      /** The tabitem could have bene closed meanwhile */
      if (WidgetShop.isset(tabItems[a])) {

        /** Retrieve TabItem Data Object */
        TabItemData tabItemData = (TabItemData) tabItems[a].getData();

        /** Check if tabitem is showing the aggregated category */
        if (tabItemData.showsCategory(aggregatedCategory))
          return tabItems[a];
      }
    }
    return null;
  }

  /**
   * Check if a tab with the given title of the Aggregation is opened in the
   * tabfolder.
   * 
   * @param title The title of the Aggregation to look for
   * @return boolean TRUE if the tab is opened
   */
  public boolean isAggregationOpened(String title) {
    CTabItem[] tabitems = getNewsHeaderTabFolder().getItems();

    /** Check all opened tabs */
    for (int a = 0; a < tabitems.length; a++) {
      TabItemData tabData = (TabItemData) tabitems[a].getData();

      /** Check this Aggregation */
      if (tabData.isAggregatedCat()) {
        String tabTitle = tabitems[a].getText();
        if (title.equals(tabTitle) || title.equals(Category.getLinkForTitle(tabTitle)))
          return true;
      }
    }
    return false;
  }

  /**
   * Check if a tab with the given URL is opened in the tabfolder.
   * 
   * @param url The URL of the Feed
   * @return boolean TRUE if the tab is opened
   */
  public boolean isFeedOpened(String url) {
    CTabItem[] tabitems = getNewsHeaderTabFolder().getItems();

    /** Check all opened tabs */
    for (int a = 0; a < tabitems.length; a++) {
      TabItemData tabData = (TabItemData) tabitems[a].getData();

      /** Check this Feed */
      if (tabData.isFeed() && StringShop.isset(url) && url.equals(tabData.getUrl()))
        return true;
    }
    return false;
  }

  /**
   * Load a URL in a new TabItem.
   * 
   * @param url URL of the News
   */
  public void loadURLInTab(String url) {

    /** Open a new browser window if user has chosen it */
    if (GlobalSettings.openNewBrowserWindow) {
      displayBrowserTab(url);
    }

    /** Open in current browser window */
    else {
      int a;
      CTabItem items[] = newsHeaderTabFolder.getItems();
      for (a = 0; a < items.length; a++) {
        TabItemData data = (TabItemData) items[a].getData();
        if (data != null && data.isBrowser()) {
          Browser openedBrowser = data.getRSSOwlBrowserPanel().getBrowser();

          /** Browser must not be disposed */
          if (!openedBrowser.isDisposed()) {
            data.getRSSOwlBrowserPanel().openUrl(url);
            items[a].setText(StringShop.pointTrim(url, MAX_TAB_TITLE_LENGTH, true));

            /** Update selection if set so */
            if (focusTab()) {
              newsHeaderTabFolder.setSelection(items[a]);
              updateTabFolderState();
            }
          }

          /** If browser was disposed, open new tab with browser */
          else {
            displayBrowserTab(url);
          }
          break;
        }
      }

      /** There was no opened browser window. Open a new one */
      if (a == items.length)
        displayBrowserTab(url);
    }
  }

  /**
   * Reload the feed in the selected tab
   */
  public void reloadFeed() {
    CTabItem tabItem = newsHeaderTabFolder.getSelection();
    reloadFeed(tabItem);
  }

  /** Open search dialog and perform search on the selected tab */
  public void searchInSelectedFeed() {

    /** New search dialog */
    SearchDialog rssOwlSearchDialog = new SearchDialog(shell, GUI.i18n.getTranslation("SEARCH_DIALOG_TITLE"), GUI.i18n.getTranslation("SEARCH_DIALOG_MESSAGE"));

    /** User has pressed the OK button */
    if (rssOwlSearchDialog.open() == Window.OK && StringShop.isset(rssOwlSearchDialog.getValue().getPattern()))
      searchInSelectedFeed(rssOwlSearchDialog.getValue(), true);
  }

  /**
   * Search in visible Tab for the given String
   * 
   * @param searchDefinition Pattern and Scope of the Search.
   * @param reselectNews Wether to reselect a selected news
   */
  public void searchInSelectedFeed(SearchDefinition searchDefinition, boolean reselectNews) {

    /** No Selection available */
    if (newsHeaderTabFolder.getSelectionIndex() < 0)
      return;

    /** TabItem Data */
    TabItemData data = (TabItemData) newsHeaderTabFolder.getSelection().getData();

    /** Get URL */
    String url = data.getUrl();
    if (url == null && data.getTitle() != null)
      url = Category.getLinkForTitle(data.getTitle());

    /** Perform search */
    rssOwlGui.loadNewsFeed(url, searchDefinition, true, reselectNews, NewsTabFolder.DISPLAY_MODE_FOCUS);
  }

  /**
   * Display an error as a new tab in the tab folder
   * 
   * @param tabItem The tabitem to load the error into
   * @param e The exception that occured
   */
  public void showErrorTab(CTabItem tabItem, NewsfeedFactoryException e) {

    /** Create a new Error Panel */
    ErrorPanel errorPanel = new ErrorPanel(rssOwlGui, display, eventManager, newsHeaderTabFolder, e);

    /** Update Font */
    tabItem.setFont(FontShop.headerFont);

    /** Apply controll to tabitem */
    tabItem.setControl(errorPanel.getPanel());
  }

  /**
   * Display an warning as a new tab in the tab folder
   * 
   * @param url URL / Path that caused the exception
   * @param e The exception that occured
   */
  public void showErrorTab(String url, NewsfeedFactoryException e) {
    CTabItem tabItem;

    /** Use first Tab to show error if displaySingleTab is TRUE */
    if (GlobalSettings.displaySingleTab && newsHeaderTabFolder.getItemCount() > 0)
      tabItem = newsHeaderTabFolder.getItem(0);

    /** Create TabItem to load error into */
    else
      tabItem = new CTabItem(newsHeaderTabFolder, SWT.NONE);

    tabItem.setData(TabItemData.createErrorData(url));
    tabItem.setImage(PaintShop.iconError);
    tabItem.setFont(FontShop.headerFont);

    /** Set tab title */
    if (Category.getTitleForLink(url) != null)
      tabItem.setText(Category.getTitleForLink(url));
    else
      tabItem.setText(StringShop.escapeAmpersands(url));

    /** Create error tab */
    showErrorTab(tabItem, e);

    /** Set selection to this item if needed */
    if ((focusTab()) || (newsHeaderTabFolder.getSelectionIndex() == -1))
      newsHeaderTabFolder.setSelection(tabItem);

    /** Update TabFolder state */
    updateTabFolderState();
  }

  /**
   * Display the License in a new tab
   * 
   * @param inS Source of the license file
   * @param icon The icon to use for the tab
   */
  public void showLicenseTab(InputStream inS, Image icon) {
    String result = FileShop.getContent(inS);
    showMessageTab(result, "EPL - Eclipse Public License", icon);
  }

  /**
   * Show information about new RSSOwl version in th UpdatePanel. It offers a
   * link to download new version and shows its changelog.
   * 
   * @param title Title of the TabItem
   * @param message Message containing the Changelog
   */
  public void showUpdateAvailableTab(String title, String message) {
    CTabItem tabItem;

    /** User might have closed RSSOwl already */
    if (!GUI.isAlive() || newsHeaderTabFolder.isDisposed())
      return;

    /** Use first Tab to show error if displaySingleTab is TRUE */
    if (GlobalSettings.displaySingleTab && newsHeaderTabFolder.getItemCount() > 0)
      tabItem = newsHeaderTabFolder.getItem(0);

    /** Create TabItem to load error into */
    else
      tabItem = new CTabItem(newsHeaderTabFolder, SWT.NONE);

    tabItem.setText(title);
    tabItem.setData(TabItemData.createMessageData(title));
    tabItem.setImage(PaintShop.loadImage("/img/icons/update.gif"));

    /** Create a new Update Panel */
    UpdatePanel updatePanel = new UpdatePanel(display, newsHeaderTabFolder, title, message);

    /** Apply controll to tabitem */
    tabItem.setControl(updatePanel.getPanel());

    /** Show message */
    newsHeaderTabFolder.setSelection(tabItem);

    /** Update TabFolder state */
    updateTabFolderState();
  }

  /**
   * @see net.sourceforge.rssowl.controller.IFontChangeable#updateFonts()
   */
  public void updateFonts() {
    newsHeaderTabFolder.setFont(FontShop.headerFont);

    /** Update Newsheader Tables if visible */
    CTabItem tabItems[] = newsHeaderTabFolder.getItems();
    for (int i = 0; i < tabItems.length; i++) {
      CTabItem tabItem = tabItems[i];
      tabItem.setFont(FontShop.headerFont);
      TabItemData tabItemData = (TabItemData) tabItem.getData();

      /** Update Fonts on TabItem */
      if (tabItemData.getChannel() != null && tabItemData.getChannel().containsUnreadNews())
        tabItem.setFont(FontShop.headerBoldFont);
      else
        tabItem.setFont(FontShop.headerFont);

      /** The Tab is containing a Newsheader Table */
      if (WidgetShop.isset(tabItemData.getNewsHeaderTable())) {
        Table newsTable = tabItemData.getNewsHeaderTable();
        newsTable.setFont(FontShop.tableFont);

        /** For each TableItem update Font */
        TableItem tableItems[] = newsTable.getItems();
        for (int j = 0; j < tableItems.length; j++) {
          TableItem tableItem = tableItems[j];
          TableItemData tableItemData = (TableItemData) tableItem.getData();
          tableItem.setFont(tableItemData.isNewsRead() ? FontShop.tableFont : FontShop.tableBoldFont);
        }

        /** Pack all Columns */
        TableColumn tableColumns[] = newsTable.getColumns();
        for (int j = 0; j < tableColumns.length; j++) {
          tableColumns[j].pack();
        }
      }
    }
  }

  /** Update all controlls text with i18n */
  public void updateI18N() {
    close.setText(GUI.i18n.getTranslation("MENU_CLOSE"));
    closeAll.setText(GUI.i18n.getTranslation("MENU_CLOSE_ALL"));
    closeAllKeepCurrent.setText(GUI.i18n.getTranslation("POP_KEEP_CURRENT"));
    closeAllKeepFeeds.setText(GUI.i18n.getTranslation("POP_KEEP_NEWSFEEDS"));
    tabPositionBottom.setText(GUI.i18n.getTranslation("POP_TAB_POS_BOTTOM"));
    tabPositionTop.setText(GUI.i18n.getTranslation("POP_TAB_POS_TOP"));
    tabPositionMenu.setText(GUI.i18n.getTranslation("POP_TAB_POSITION"));

    /** Init the Mnemonics */
    MenuManager.initMnemonics(tabFolderMenu);
  }

  /**
   * This method updates the lastOpenedFeeds Vector.
   */
  public void updateLastOpenedFeeds() {

    /** Clear lastOpenedFeeds Vector */
    lastOpenedFeeds.clear();
    CTabItem[] items = newsHeaderTabFolder.getItems();

    /** Foreach tabitem */
    for (int a = 0; a < items.length; a++) {
      TabItemData data = (TabItemData) items[a].getData();
      if (data.isFeed() && !data.isAggregatedCat())
        addFeedToLastOpened(data.getUrl());
    }
  }

  /**
   * Update the state of the TabFolder and tell the MenuManager. This method is
   * called whenever the selection in the tabfolder changes, for example when a
   * new tab is opened or another one was closed, revealing the tab that is left
   * of it.
   */
  public void updateTabFolderState() {

    /** Get TabItem data object */
    TabItemData data = null;
    if (newsHeaderTabFolder.getSelection() != null)
      data = (TabItemData) newsHeaderTabFolder.getSelection().getData();

    /** Tell that more than one tab is opened */
    if (newsHeaderTabFolder.getItemCount() > 1)
      MenuManager.notifyState(MenuManager.MORE_THAN_ONE_TAB_OPENED);

    /** Tell that only one tab or less is opened */
    else
      MenuManager.notifyState(MenuManager.ONE_ZERO_TAB_OPENED);

    /** TabFolder is not containing any Tabs */
    if (data == null)
      announceTabFolderState(data, TAB_STATE_EMPTY);

    /** TabFolder is showing a Message Tab */
    else if (data.isMessage())
      announceTabFolderState(data, TAB_STATE_MESSAGE);

    /** TabFolder is showing a Browser Tab */
    else if (data.isBrowser())
      announceTabFolderState(data, TAB_STATE_BROWSER);

    /** TabFolder is showing an Error Tab */
    else if (data.isError())
      announceTabFolderState(data, TAB_STATE_ERROR);

    /** TabFolder is showing a Feed */
    else if (data.isFeed())
      announceTabFolderState(data, TAB_STATE_FEED);

    /** Update state of 3D Border outside the ViewForm if neccessary */
    updateViewFormBorder();
  }

  /**
   * Update the state of the given TabItem to reflect wether unread news are
   * available in the Channel that is displayed.
   * 
   * @param tabItem The TabItem to update the status
   */
  public void updateTabItemStatus(CTabItem tabItem) {

    /** TabItem is not set or does not display a newsfeed */
    if (!WidgetShop.isset(tabItem) || !(tabItem.getData() instanceof TabItemData) || GlobalSettings.displaySingleTab)
      return;

    /** Retrieve the TabItemData for the given TabItem */
    TabItemData tabData = (TabItemData) tabItem.getData();

    /** TabItem is not showing a newsfeed */
    if (!tabData.isFeed() && !tabData.isAggregatedCat())
      return;

    /** Retrieve the displayed Channel */
    Channel displayedChannel = tabData.getChannel();

    /**
     * Check if update is required:
     * <p>- No Icon is set on the CTabItem
     * <p>- An error Icon is set on the CTabItem
     * <p>- An read Icon is set although Feed contains unread news
     * <p>- An unread Icon is set although Feed contains only read news
     * <p>- The Tab shows the icon for an aggregation, but only a single feed
     * is displayed
     */
    boolean isWrongIcon = !PaintShop.isset(tabItem.getImage()) || tabItem.getImage().equals(PaintShop.iconError);
    boolean isOutOfSync = !isWrongIcon && (tabItem.getImage().equals(PaintShop.iconRead) && displayedChannel.containsUnreadNews() || tabItem.getImage().equals(PaintShop.iconUnread) && !displayedChannel.containsUnreadNews());
    boolean needsUpdate = isWrongIcon || isOutOfSync || (tabData.isShowsUnreadNews() != displayedChannel.containsUnreadNews()) || (tabData.isAggregatedCat() != displayedChannel.isAggregatedCat());

    /** The TabItem does not require to be changed */
    if (!needsUpdate)
      return;

    /** Update TabItem image */
    if (displayedChannel.isAggregatedCat())
      tabItem.setImage(displayedChannel.containsUnreadNews() ? PaintShop.iconFolderUnread : PaintShop.iconFolder);
    else
      tabItem.setImage(displayedChannel.containsUnreadNews() ? PaintShop.iconUnread : PaintShop.iconRead);

    /** Update Font */
    tabItem.setFont(displayedChannel.containsUnreadNews() ? FontShop.headerBoldFont : FontShop.headerFont);

    /** Update state */
    tabData.setShowsUnreadNews(displayedChannel.containsUnreadNews());

    /** Layout TabFolder */
    newsHeaderTabFolder.layout();
  }

  /**
   * Add a new Entry to the History List.
   * 
   * @param url The URL of the Newsfeed to add.
   * @param title The Title of the Newsfeed to add.
   */
  private void addHistoryItem(String url, String title) {
    StringBuffer strBuf = new StringBuffer();
    strBuf.append(url).append(StringShop.AUTH_TOKENIZER).append(title);
    String historyItem = strBuf.toString();
    int entryIndex = historyList.indexOf(historyItem);

    /** Item present but out of range. Re-Add to first index. */
    if (entryIndex >= 0 && entryIndex >= MAX_HISTORY_ITEMS) {
      historyList.remove(historyItem);
      historyList.add(0, historyItem);
    }

    /** Item not yet present, so add it */
    else if (entryIndex < 0)
      historyList.add(0, historyItem);
  }

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

    /** Composite holding the CTabFolder */
    newsHeaderTabFolderHolder = new Composite(contentPane, SWT.NONE);
    newsHeaderTabFolderHolder.setLayout(new FillLayout());

    /** ViewForm holding the Composite that holds the CTabFolder */
    int style = GlobalSettings.isMac() ? SWT.BORDER | SWT.FLAT : SWT.BORDER;
    newsHeaderViewForm = new ViewForm(newsHeaderTabFolderHolder, style);

    /** News Header Tabs */
    newsHeaderTabFolder = new CTabFolder(newsHeaderViewForm, GlobalSettings.displaySingleTab || !GlobalSettings.showTabCloseButton ? SWT.NONE : SWT.CLOSE);
    newsHeaderTabFolder.setFont(FontShop.headerFont);
    newsHeaderTabFolder.setSimple(GlobalSettings.simpleTabs);
    newsHeaderTabFolder.setSelectionBackground((GlobalSettings.displaySingleTab) ? PaintShop.grayViewFormColor : display.getSystemColor(SWT.COLOR_WHITE));
    newsHeaderTabFolder.setTabPosition((GlobalSettings.tabPositionIsTop == true) ? SWT.TOP : SWT.BOTTOM);
    newsHeaderTabFolder.setMRUVisible(true);
    newsHeaderTabFolder.setMaximizeVisible(true);

    /**
     * A multiple of the tab height that specifies the minimum width to which a
     * tab will be compressed before scrolling arrows are used to navigate the
     * tabs.
     */
    newsHeaderTabFolder.setMinimumCharacters(10);

    /** Listen for the tabitem that becomes displayed */
    newsHeaderTabFolder.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {

        /** Notify about new TabFolder State */
        updateTabFolderState();

        /** Pass Focus into the Content of the Tab if displaying a Feed */
        if (WidgetShop.isset(e.item)) {
          TabItemData data = (TabItemData) e.item.getData();
          if (data.isFeed() && WidgetShop.isset(data.getNewsHeaderTable()))
            data.getNewsHeaderTable().setFocus();
        }
      }
    });

    /** Listen for Mouse Up Event */
    newsHeaderTabFolder.addMouseListener(new MouseAdapter() {
      public void mouseDown(MouseEvent e) {

        /** Close Tab if Middle Mouse Button was pressed */
        if (!GlobalSettings.displaySingleTab && e.button == 2) {
          CTabItem item = newsHeaderTabFolder.getItem(new Point(e.x, e.y));

          /** Close */
          if (WidgetShop.isset(item))
            closeTab(item);
        }
      }
    });

    /** Listen for the close operation */
    newsHeaderTabFolder.addCTabFolder2Listener(new CTabFolder2Adapter() {
      public void close(CTabFolderEvent event) {
        event.doit = false;
        closeTab((CTabItem) event.item);
      }

      public void maximize(CTabFolderEvent event) {
        onMaximize();
      }

      public void restore(CTabFolderEvent event) {
        onRestore();
      }
    });

    /** Set Popup Menu on TabFolder */
    initTabFolderPopup();

    /** Apply content to ViewForm */
    newsHeaderViewForm.setContent(newsHeaderTabFolder, true);
  }

  /**
   * Init the Popupmenu on the TabFolder
   */
  private void initTabFolderPopup() {
    mouseInTab = false;
    tabFolderMenu = new Menu(newsHeaderTabFolder);

    /** Menu Listener */
    tabFolderMenu.addMenuListener(new MenuAdapter() {
      public void menuShown(MenuEvent e) {

        /**
         * Do not show the menu if the tabfolder is empty or no tabitem is
         * selected. Also disable tabfolder menu in case no tabs are used.
         */
        if (GlobalSettings.displaySingleTab || newsHeaderTabFolder.getItemCount() <= 0 || newsHeaderTabFolder.getSelectionIndex() == -1) {
          tabFolderMenu.setVisible(false);
          return;
        }

        /** This action does not work if only one item is shown */
        if (newsHeaderTabFolder.getItemCount() == 1)
          closeAllKeepCurrent.setEnabled(false);
        else if (!closeAllKeepCurrent.getEnabled())
          closeAllKeepCurrent.setEnabled(true);

        /** This action does not work if only feeds or no feeds are shown */
        if (isOnlyFeedsOpen() || isNoFeedOpen())
          closeAllKeepFeeds.setEnabled(false);
        else if (!closeAllKeepFeeds.getEnabled())
          closeAllKeepFeeds.setEnabled(true);
      }
    });

    /** Mouse Listener */
    newsHeaderTabFolder.addMouseListener(new MouseAdapter() {

      /**
       * Close selected tab on doubleclick
       */
      public void mouseDoubleClick(MouseEvent e) {
        onMouseDoubleClick(e);
      }
    });

    /** Close selected tab */
    close = new MenuItem(tabFolderMenu, SWT.POP_UP);
    close.setText(GUI.i18n.getTranslation("MENU_CLOSE"));
    close.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {

        /** Bug on Linux: Tab popup is showing even on no selection */
        if (newsHeaderTabFolder.getSelection() == null)
          return;

        closeCurrent();
      }
    });

    /** Separator */
    new MenuItem(tabFolderMenu, SWT.SEPARATOR);

    /** Close all tabs, keep current */
    closeAllKeepCurrent = new MenuItem(tabFolderMenu, SWT.POP_UP);
    closeAllKeepCurrent.setText(GUI.i18n.getTranslation("POP_KEEP_CURRENT"));
    closeAllKeepCurrent.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        closeAll(true, false);
      }
    });

    /** Close all tabs, keep newsfeeds */
    closeAllKeepFeeds = new MenuItem(tabFolderMenu, SWT.POP_UP);
    closeAllKeepFeeds.setText(GUI.i18n.getTranslation("POP_KEEP_NEWSFEEDS"));
    closeAllKeepFeeds.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        closeAll(false, true);
      }
    });

    /** Close all tabs */
    closeAll = new MenuItem(tabFolderMenu, SWT.POP_UP);
    closeAll.setText(GUI.i18n.getTranslation("MENU_CLOSE_ALL"));
    closeAll.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        closeAll();
      }
    });

    /** Separator */
    new MenuItem(tabFolderMenu, SWT.SEPARATOR);

    /** Tab position: Top / Bottom */
    tabPositionMenu = new MenuItem(tabFolderMenu, SWT.CASCADE);
    tabPositionMenu.setText(GUI.i18n.getTranslation("POP_TAB_POSITION"));

    Menu selectTabPosition = new Menu(shell, SWT.DROP_DOWN);
    tabPositionMenu.setMenu(selectTabPosition);

    /** Position: Top */
    tabPositionTop = new MenuItem(selectTabPosition, SWT.RADIO);
    tabPositionTop.setText(GUI.i18n.getTranslation("POP_TAB_POS_TOP"));
    tabPositionTop.setSelection(GlobalSettings.tabPositionIsTop);
    tabPositionTop.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (tabPositionTop.getSelection()) {
          newsHeaderTabFolder.setTabPosition(SWT.TOP);
          GlobalSettings.tabPositionIsTop = true;
          newsHeaderTabFolder.layout();
        }
      }
    });

    /** Position: Bottom */
    tabPositionBottom = new MenuItem(selectTabPosition, SWT.RADIO);
    tabPositionBottom.setText(GUI.i18n.getTranslation("POP_TAB_POS_BOTTOM"));
    tabPositionBottom.setSelection(!GlobalSettings.tabPositionIsTop);
    tabPositionBottom.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (tabPositionBottom.getSelection()) {
          newsHeaderTabFolder.setTabPosition(SWT.BOTTOM);
          GlobalSettings.tabPositionIsTop = false;
          newsHeaderTabFolder.layout();
        }
      }
    });

    /** Set the radio selection */
    selectTabPosition.addMenuListener(new MenuAdapter() {
      public void menuShown(MenuEvent e) {
        tabPositionTop.setSelection(GlobalSettings.tabPositionIsTop);
        tabPositionBottom.setSelection(!GlobalSettings.tabPositionIsTop);
      }
    });

    /** Init the Mnemonics */
    MenuManager.initMnemonics(tabFolderMenu);

    /** Apply menu */
    newsHeaderTabFolder.setMenu(tabFolderMenu);
  }

  /**
   * Announce the state of the TabFolder. Possible states are an empty tabfolder
   * or a tab displaying a message, error, browser or newsfeed.
   * 
   * @param data The TabItemData model object
   * @param state One of the five states of the TabFolder
   */
  void announceTabFolderState(TabItemData data, int state) {
    switch (state) {

      /**
       * CTabFolder is not containing any CTabItems
       */
      case TAB_STATE_EMPTY:

        /** No action if state did not change */
        if (lastTabState == MenuManager.TAB_EMPTY)
          return;

        /** Restore previous size of tabfolder */
        setMaximized(false);

        /** Tell MenuManager about the new state */
        MenuManager.notifyState(MenuManager.TAB_EMPTY);
        lastTabState = MenuManager.TAB_EMPTY;

        /** Restore printable accelerators in case focus is not in Input Field */
        Control focusControl = display.getFocusControl();
        if (!WidgetShop.isset(focusControl) || (!(focusControl instanceof Text) && !(focusControl instanceof Combo)))
          rssOwlGui.getRSSOwlMenu().updateAccelerators();

        /** Clear newstext viewform */
        MenuManager.notifyState(MenuManager.NEWS_TEXT_EMPTY);
        rssOwlGui.getRSSOwlNewsText().clearNewstext();

        /** Hide TabFolder is Visible */
        if (newsHeaderTabFolder.getVisible())
          newsHeaderTabFolder.setVisible(false);

        break;

      /**
       * CTabFolder is displaying a Message
       */
      case TAB_STATE_MESSAGE:

        /** No action if state did not change */
        if (lastTabState == MenuManager.TAB_NO_NEWS)
          return;

        /** Maximize size of tabfolder */
        setMaximized(true);

        /** Tell MenuManager about the new state */
        MenuManager.notifyState(MenuManager.TAB_NO_NEWS);
        lastTabState = MenuManager.TAB_NO_NEWS;

        /** Clear newstext viewform */
        MenuManager.notifyState(MenuManager.NEWS_TEXT_EMPTY);
        rssOwlGui.getRSSOwlNewsText().clearNewstext();

        /** Show TabFolder if Hidden */
        if (!newsHeaderTabFolder.getVisible())
          newsHeaderTabFolder.setVisible(true);

        break;

      /**
       * CTabFolder is displaying an Error
       */
      case TAB_STATE_ERROR:

        /** Select the displyed TreeItem this Feed is associated to, if given */
        if (GlobalSettings.linkTreeWithTab)
          linkSelectionToTree();

        /** No action if state did not change */
        if (lastTabState == MenuManager.TAB_ERROR)
          return;

        /** Restore previous size of tabfolder */
        setMaximized(false);

        /** Tell MenuManager about the new state */
        MenuManager.notifyState(MenuManager.TAB_ERROR);
        lastTabState = MenuManager.TAB_ERROR;

        /** Restore printable accelerators in case focus is not in Input Field */
        focusControl = display.getFocusControl();
        if (!WidgetShop.isset(focusControl) || (!(focusControl instanceof Text) && !(focusControl instanceof Combo)))
          rssOwlGui.getRSSOwlMenu().updateAccelerators();

        /** Clear newstext viewform */
        MenuManager.notifyState(MenuManager.NEWS_TEXT_EMPTY);
        rssOwlGui.getRSSOwlNewsText().clearNewstext();

        /** Show TabFolder if Hidden */
        if (!newsHeaderTabFolder.getVisible())
          newsHeaderTabFolder.setVisible(true);

        break;

      /**
       * CTabFolder is displaying the Internal Browser
       */
      case TAB_STATE_BROWSER:

        /** Give Focus to the Browser to support scrolling */
        BrowserPanel browserPanel = data.getRSSOwlBrowserPanel();
        if (browserPanel != null && WidgetShop.isset(browserPanel.getBrowser())) {

          /** Avoid moving Focus into Browser if about:blank is URL */
          if (!URLShop.ABOUT_BLANK.equals(browserPanel.getBrowser().getUrl()))
            browserPanel.getBrowser().setFocus();

          /** Else move Focus into Text-Input and select all */
          else if (WidgetShop.isset(browserPanel.getLocation())) {
            browserPanel.getLocation().setFocus();
            browserPanel.getLocation().selectAll();
          }
        }

        /** No action if state did not change */
        if (lastTabState == MenuManager.TAB_NO_NEWS)
          return;

        /** Maximize size of tabfolder */
        setMaximized(true);

        /** Tell MenuManager about the new state */
        MenuManager.notifyState(MenuManager.TAB_NO_NEWS);
        lastTabState = MenuManager.TAB_NO_NEWS;

        /** Clear newstext viewform */
        MenuManager.notifyState(MenuManager.NEWS_TEXT_EMPTY);
        rssOwlGui.getRSSOwlNewsText().clearNewstext();

        /** Remove accelerators that are printable */
        rssOwlGui.getRSSOwlMenu().updateAccelerators(true);

        /** Show TabFolder if Hidden */
        if (!newsHeaderTabFolder.getVisible())
          newsHeaderTabFolder.setVisible(true);

        break;

      /**
       * CTabFolder is displaying a Newsfeed
       */
      case TAB_STATE_FEED:

        /** Restore previous size of tabfolder */
        setMaximized(false);

        /** Show TabFolder if Hidden */
        if (!newsHeaderTabFolder.getVisible())
          newsHeaderTabFolder.setVisible(true);

        /** Tell MenuManager about the new state */
        MenuManager.notifyState(MenuManager.TAB_FILLED_WITH_NEWS);
        lastTabState = MenuManager.TAB_FILLED_WITH_NEWS;

        /** Restore printable accelerators in case focus is not in Input Field */
        focusControl = display.getFocusControl();
        if (!WidgetShop.isset(focusControl) || (!(focusControl instanceof Text) && !(focusControl instanceof Combo)))
          rssOwlGui.getRSSOwlMenu().updateAccelerators();

        /** Select the displyed TreeItem this Feed is associated to, if given */
        if (GlobalSettings.linkTreeWithTab)
          linkSelectionToTree();

        /** Get Table from newsfeed tab holding the news */
        Table newsFeedTable = null;

        /** The table maybe null in the case a search did not brought results */
        if (WidgetShop.isset(data.getNewsHeaderTable()))
          newsFeedTable = data.getNewsHeaderTable();

        /** Display the selected news of this tab if a news is selected */
        if (WidgetShop.isset(newsFeedTable) && newsFeedTable.getSelectionIndex() >= 0) {

          /** Tell MenuManager that a newsheader is selected */
          MenuManager.notifyState(MenuManager.NEWS_HEADER_SELECTED);

          /** Get selected news item */
          String feedUrl = data.getUrl();
          String newsTitle = newsFeedTable.getSelection()[0].getText(1);

          NewsItem rssNewsItem = rssOwlGui.getRSSOwlNewsTabFolder().getSelectedNewsItem(newsTitle);

          /** NewsItem must not be null */
          if (rssNewsItem == null)
            return;

          /** Avoid multiple browser load bugs. */
          if (GlobalSettings.useBrowserForNewsText || (!GlobalSettings.directOpenEachNews && !GlobalSettings.directOpenNews) || !GlobalSettings.directOpenEachNews && rssNewsItem.getDescription() != null) {

            /** Display News */
            rssOwlGui.getRSSOwlNewsText().displayNews(feedUrl, newsTitle);

            /** Tell MenuManager that newstext is filled */
            MenuManager.notifyState(MenuManager.NEWS_TEXT_FILLED);
          }
        }

        /** Empty search tab or empty channel */
        else if (newsFeedTable == null || newsFeedTable.isDisposed()) {
          MenuManager.notifyState(MenuManager.EMPTY_SEARCH_TAB);
          rssOwlGui.getRSSOwlNewsText().clearNewstext();
          MenuManager.notifyState(MenuManager.NEWS_TEXT_EMPTY);
        }

        /** No news selected, clear newstext viewform */
        else {
          rssOwlGui.getRSSOwlNewsText().clearNewstext();
          MenuManager.notifyState(MenuManager.NEWS_TEXT_EMPTY);
        }
    }
  }

  /** Close all opened tabs - Block Link feature if enabled */
  void closeAll() {
    boolean linkTreeWithTab = GlobalSettings.linkTreeWithTab;
    GlobalSettings.linkTreeWithTab = false;
    closeAll(false, false);
    GlobalSettings.linkTreeWithTab = linkTreeWithTab;
  }

  /**
   * Close all tabs but keep newsfeeds
   * 
   * @param keepCurrent TRUE if the current tab should remain opened
   * @param keepFeeds TRUE if newsfeeds should remain opened
   */
  void closeAll(boolean keepCurrent, boolean keepFeeds) {
    CTabItem items[] = newsHeaderTabFolder.getItems();
    int selectedIndex = newsHeaderTabFolder.getSelectionIndex();

    /** For all tabitems */
    for (int a = 0; a < items.length; a++) {

      /** Case: Keep Current */
      if (keepCurrent && a != selectedIndex)
        closeTab(items[a]);

      /** Case: Keep Newsfeeds */
      else if (keepFeeds && items[a].getData() != null && !((TabItemData) items[a].getData()).isFeed())
        closeTab(items[a]);

      /** Case: Keep None */
      else if (!keepCurrent && !keepFeeds)
        closeTab(items[a]);
    }
  }

  /** Close current opened tab */
  void closeCurrent() {
    if (newsHeaderTabFolder.getSelection() != null)
      closeTab(newsHeaderTabFolder.getSelection());
  }

  /**
   * Create the browser in the given Tab and open the given URL.
   * 
   * @param tabItem The TabItem to add the browser
   * @param url The URL to open
   */
  void createBrowserTab(final CTabItem tabItem, String url) {
    try {
      BrowserPanel rssOwlBrowser = new BrowserPanel(rssOwlGui, newsHeaderTabFolder, tabItem, true, true);
      tabItem.setControl(rssOwlBrowser.getBrowserPanel());
      tabItem.setData(TabItemData.createBrowserData(rssOwlBrowser));
      rssOwlBrowser.openUrl(url);

      /** Focus tab if needed */
      if ((focusTab()) || (newsHeaderTabFolder.getSelectionIndex() == -1)) {
        newsHeaderTabFolder.setSelection(tabItem);

        /** Update TabFolder state */
        updateTabFolderState();
      }

      /** Set the title of the webpage as tabtitle */
      rssOwlBrowser.getBrowser().addTitleListener(new TitleListener() {
        public void changed(TitleEvent event) {

          /** Replace '&' with '&&' to avoid mnemonic display */
          if (!StringShop.isWhiteSpaceOrEmpty(event.title) && !URLShop.ABOUT_BLANK.equals(event.title))
            tabItem.setText(StringShop.pointTrim(event.title, MAX_TAB_TITLE_LENGTH, true));

          /** In case the title is not given */
          else
            tabItem.setText(GUI.i18n.getTranslation("NO_TITLE"));
        }
      });
    }

    /** If OS fails to build internal browser, dont use it! */
    catch (SWTError e) {
      GUI.logger.log("createBrowserTab()", e);
      tabItem.dispose();
      GlobalSettings.openBrowserExtern = true;
      MessageBoxFactory.showError(shell, BrowserShop.createBrowserError(e));
      eventManager.actionOpenFAQOnBrowser();
      BrowserShop.openLink(url);
    }
  }

  /**
   * Export the selected RSS feed to PDF / RTF or HTML into the given
   * targetFile.
   * 
   * @param targetFile The target file to export the news into
   * @param format PDF, RTF or HTML
   * @param newsTitle If given, just export one newsitem
   */
  void exportToDocument(File targetFile, int format, String newsTitle) {

    /** Get selected Channel */
    Channel rssChannel = rssOwlGui.getRSSOwlNewsTabFolder().getSelectedChannel();

    /** Channel was found */
    if (rssChannel != null) {

      /** TabItem Data */
      TabItemData data = (TabItemData) newsHeaderTabFolder.getSelection().getData();

      /** Get the newsitem order from the Table */
      ArrayList tableNewsItemOrder = new ArrayList();

      /** Retrieve data from newsheader table */
      if (newsTitle == null && WidgetShop.isset(data.getNewsHeaderTable())) {
        Table newsHeaderTable = data.getNewsHeaderTable();
        int itemCount = newsHeaderTable.getItemCount();

        for (int a = 0; a < itemCount; a++)
          tableNewsItemOrder.add(newsHeaderTable.getItem(a).getText(1));
      }

      /** User has selected a single news to export */
      else if (newsTitle != null) {
        tableNewsItemOrder.add(newsTitle);
      }

      /** File must not be NULL */
      if (targetFile != null) {
        try {

          /** Create the Document */
          new DocumentGenerator(rssChannel, new FileOutputStream(targetFile), format).createDocument(tableNewsItemOrder);
        }

        /** An error occured, log and show it */
        catch (FileNotFoundException e) {
          GUI.logger.log("exportToDocument()", e);
          MessageBoxFactory.showError(shell, e);
        }

        /** An error occured, log and show it */
        catch (DocumentException e) {
          GUI.logger.log("exportToDocument()", e);
          MessageBoxFactory.showError(shell, e);
        }

        /** An error occured, log and show it */
        catch (IOException e) {
          GUI.logger.log("exportToDocument()", e);
          MessageBoxFactory.showError(shell, e);
        }
      }
    }
  }

  /**
   * Export the selected RSS feed to PDF / RTF or HTML. Ask the filepath from
   * the user and suggest a filename.
   * 
   * @param format PDF, RTF or HTML
   * @param newsTitle If given, just export one newsitem
   */
  void exportToDocument(int format, String newsTitle) {

    /** TabItem Data */
    TabItemData data = (TabItemData) newsHeaderTabFolder.getSelection().getData();

    /** Build the current Date */
    String curDate = DateParser.dateToFileName(DateParser.formatDate());

    /** Suggest a filename */
    String file;

    /** Suggest channel title as file name */
    if (newsTitle == null)
      file = StringShop.createFileName(data.getTitle());

    /** Suggest news title as file name */
    else
      file = StringShop.createFileName(newsTitle);

    /** If filename only contains of special chars */
    if (file.matches("[_]+"))
      file = "rssowl";

    /** Title for Operation */
    String title;
    if (format == DocumentGenerator.RTF_FORMAT)
      title = GUI.i18n.getTranslation("MENU_GENERATE_RTF");
    else if (format == DocumentGenerator.HTML_FORMAT)
      title = GUI.i18n.getTranslation("MENU_GENERATE_HTML");
    else
      title = GUI.i18n.getTranslation("MENU_GENERATE_PDF");

    String fileName = file + "_rss_" + curDate + "." + DocumentGenerator.formatToString(format);
    String selectedFileName = FileShop.getSavePath(fileName, DocumentGenerator.formatToString(format), title);

    /** User has aborted */
    if (!StringShop.isset(selectedFileName))
      return;

    /** Export into Document using the created Filename */
    exportToDocument(new File(selectedFileName), format, newsTitle);
  }

  /**
   * Fill the History as MenuItems into the given Menu.
   * 
   * @param parent The Menu to fill in the History.
   */
  void fillHistory(Menu parent) {
    boolean isHistoryEmpty = historyList.size() == 0;

    /** Foreach History Item create a MenuItem until Max. is reached */
    for (int i = 0; i < historyList.size() && i < MAX_HISTORY_ITEMS; i++) {
      String historyItem[] = ((String) historyList.get(i)).split(StringShop.AUTH_TOKENIZER);
      String url = historyItem[0];
      String title = historyItem[1];

      /** Append the number of unread News to the Title and show correct Image */
      StringBuffer menuItemText = new StringBuffer(title);
      Image menuItemImage = null;
      Channel channel = rssOwlGui.getFeedCacheManager().getCachedNewsfeed(url);
      if (channel != null) {
        int unreadNewsCount = channel.getUnreadNewsCount();
        boolean hasUnreadNews = unreadNewsCount > 0;

        /** Text */
        if (hasUnreadNews)
          menuItemText.append(" (").append(unreadNewsCount).append(")");

        /** Image for Aggregation with Unread News */
        if (channel.isAggregatedCat() && hasUnreadNews)
          menuItemImage = PaintShop.iconFolderUnread;

        /** Image for Aggregation */
        else if (channel.isAggregatedCat())
          menuItemImage = PaintShop.iconFolder;

        /** Image for Feed with Unread News */
        else if (hasUnreadNews)
          menuItemImage = PaintShop.iconLenseUnread;

        /** Image for Feed */
        else
          menuItemImage = PaintShop.iconLense;
      }

      /** Create Menu Item */
      MenuItem historyMenuItem = new MenuItem(parent, SWT.PUSH);
      historyMenuItem.setText(menuItemText.toString());
      historyMenuItem.setData(url);
      if (!GlobalSettings.isMac() && PaintShop.isset(menuItemImage))
        historyMenuItem.setImage(menuItemImage);
      historyMenuItem.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
          if (WidgetShop.isset(e.widget))
            eventManager.actionOpenFeed((String) e.widget.getData());
        }
      });
    }

    /** Add Separator in case History is not empty */
    if (!isHistoryEmpty)
      new MenuItem(parent, SWT.SEPARATOR);

    /** Provide an item to clear the History */
    MenuItem clearHistory = new MenuItem(parent, SWT.PUSH);
    clearHistory.setText(GUI.i18n.getTranslation("POP_CLEAR_HISTORY"));
    clearHistory.setEnabled(!isHistoryEmpty);
    clearHistory.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        historyList.clear();
      }
    });
  }

  /**
   * Get Channel from the given Tab
   * 
   * @param tabItem The TabItem to get the Channel from
   * @return Channel The Channel displayed in the tab
   */
  Channel getChannel(CTabItem tabItem) {

    /** Get TabItem data object */
    TabItemData data = (TabItemData) tabItem.getData();

    /** Try to get the channel from the Tab Model object */
    if (data.getChannel() != null)
      return data.getChannel();

    /** Try to find the channel via getUrl() Method */
    else if (rssOwlGui.getFeedCacheManager().isNewsfeedCached(data.getUrl(), false))
      return rssOwlGui.getFeedCacheManager().getCachedNewsfeed(data.getUrl());

    /** Try to find the channel via getTitle() Method */
    else if (data.getTitle() != null && Category.getLinkForTitle(data.getTitle()) != null)
      return rssOwlGui.getFeedCacheManager().getCachedNewsfeed(Category.getLinkForTitle(data.getTitle()));

    return null;
  }

  /**
   * Get the selected newsitem
   * 
   * @return NewsItem The selected newsitem
   */
  NewsItem getSelectedNewsItem() {

    TabItemData data = (TabItemData) newsHeaderTabFolder.getSelection().getData();
    if (data != null && data.getNewsHeaderTable() != null) {

      /** Get out what title is selected in the newsheader table */
      TableItem tableSelection[] = data.getNewsHeaderTable().getSelection();

      /** Return the newsitem that is connected to the title */
      if (tableSelection.length == 1)
        return getSelectedNewsItem(tableSelection[0].getText(1));
    }
    return null;
  }

  /**
   * Get the selected newsitem from the given title
   * 
   * @param newsTitle Title of the selected news
   * @return NewsItem The newsitem for that title
   */
  NewsItem getSelectedNewsItem(String newsTitle) {
    Channel rssChannel = getSelectedChannel();

    /** Retrieve newsitem from channel */
    if (rssChannel != null)
      return (NewsItem) rssChannel.getItems().get(newsTitle);

    return null;
  }

  /** Goto next tab */
  void gotoNextTab() {
    int selection = newsHeaderTabFolder.getSelectionIndex();
    int newSelection = -1;

    /** Goto next Tab */
    if (newsHeaderTabFolder.getItemCount() > selection + 1)
      newSelection = selection + 1;

    /** Wrap to first Tab */
    else if (newsHeaderTabFolder.getItemCount() > 1)
      newSelection = 0;

    /** Set the New Selection */
    if (newSelection >= 0) {
      newsHeaderTabFolder.showItem(newsHeaderTabFolder.getItem(newSelection));
      newsHeaderTabFolder.setSelection(newsHeaderTabFolder.getItem(newSelection));

      /** Update TabFolder state */
      updateTabFolderState();

      /** Pass Focus into the Content of the Tab if displaying a Feed */
      CTabItem selectedItem = newsHeaderTabFolder.getSelection();
      if (WidgetShop.isset(selectedItem)) {
        TabItemData data = (TabItemData) selectedItem.getData();
        if (data.isFeed() && WidgetShop.isset(data.getNewsHeaderTable()))
          data.getNewsHeaderTable().setFocus();
      }
    }
  }

  /** Goto previous tab */
  void gotoPreviousTab() {
    int selection = newsHeaderTabFolder.getSelectionIndex();
    int newSelection = -1;

    /** Goto previous Tab */
    if (selection > 0)
      newSelection = selection - 1;

    /** Wrap to last Tab */
    else if (selection == 0 && newsHeaderTabFolder.getItemCount() > 1)
      newSelection = newsHeaderTabFolder.getItemCount() - 1;

    /** Set the New Selection */
    if (newSelection >= 0) {
      newsHeaderTabFolder.showItem(newsHeaderTabFolder.getItem(newSelection));
      newsHeaderTabFolder.setSelection(newsHeaderTabFolder.getItem(newSelection));

      /** Update TabFolder state */
      updateTabFolderState();

      /** Pass Focus into the Content of the Tab if displaying a Feed */
      CTabItem selectedItem = newsHeaderTabFolder.getSelection();
      if (WidgetShop.isset(selectedItem)) {
        TabItemData data = (TabItemData) selectedItem.getData();
        if (data.isFeed() && WidgetShop.isset(data.getNewsHeaderTable()))
          data.getNewsHeaderTable().setFocus();
      }
    }
  }

  /**
   * Check if no Feed is currently opened in the TabFolder.
   * 
   * @return TRUE if no Feed is open.
   */
  boolean isNoFeedOpen() {
    int itemCount = newsHeaderTabFolder.getItemCount();
    for (int a = 0; a < itemCount; a++)
      if (((TabItemData) newsHeaderTabFolder.getItem(a).getData()).isFeed())
        return false;
    return true;
  }

  /**
   * Check if only Feeds are currently opened in the TabFolder.
   * 
   * @return TRUE if only Feeds are open.
   */
  boolean isOnlyFeedsOpen() {
    int itemCount = newsHeaderTabFolder.getItemCount();

    for (int a = 0; a < itemCount; a++) {
      TabItemData data = ((TabItemData) newsHeaderTabFolder.getItem(a).getData());
      if (data != null && !data.isFeed())
        return false;
    }

    return true;
  }

  /**
   * Select the TreeItem in the Favorites Tree that is associated with the
   * current Selection in the TabFolder. A TreeItem is associated for a
   * displayed Favorite or Aggregation.
   */
  void linkSelectionToTree() {

    /** Return if TabFolder disposed, or no Selection */
    if (!WidgetShop.isset(newsHeaderTabFolder) || newsHeaderTabFolder.getSelectionIndex() == -1)
      return;

    /** Retrieve selected TabItem Data Object */
    TabItemData data = (TabItemData) newsHeaderTabFolder.getSelection().getData();

    /** Return if selection is not a Newsfeed or Error */
    if (!data.isFeed() && !data.isError())
      return;

    /** TreeItem to select and show */
    TreeItem treeItem;

    /** Resolve TreeItem from displayed Category */
    if (data.isAggregatedCat())
      treeItem = data.getAggregatedCategory().getTreeItem();

    /** Resolve TreeItem from displayed Favorite */
    else {

      /** Return if there is no Favorite with the given URL */
      if (!Category.getFavPool().containsKey(data.getUrl()))
        return;

      Favorite displayedFavorite = (Favorite) Category.getFavPool().get(data.getUrl());
      treeItem = displayedFavorite.getTreeItem();
    }

    /** Return if the TreeItem is not set */
    if (!WidgetShop.isset(treeItem))
      return;

    /** Show and Select the Item */
    rssOwlGui.getRSSOwlFavoritesTree().getFavoritesTree().showItem(treeItem);
    rssOwlGui.getRSSOwlFavoritesTree().getFavoritesTree().setSelection(new TreeItem[] { treeItem });
  }

  /**
   * Called whenever the CTabFolder becomes maximized.
   */
  void onMaximize() {
    newsHeaderTabFolder.setMaximized(true);
    rssOwlGui.setFavoritesMinimized(true, false);
    rssOwlGui.getRSSOwlQuickview().setShowQuickview(false, false);
    rssOwlGui.getRSSOwlQuickview().setShowToolBar(false, false);
  }

  /**
   * Called whenever the tabfolder is doubleclicked
   * 
   * @param e The occuring MouseEvent
   */
  void onMouseDoubleClick(MouseEvent e) {

    /** Check if and what keys are pressed */
    int stateMask = e.stateMask;
    boolean ctrlPressed = (stateMask & SWT.CTRL) != 0 || (stateMask & SWT.COMMAND) != 0;
    boolean shiftPressed = (stateMask == SWT.SHIFT);
    boolean keyPressed = (ctrlPressed || shiftPressed);

    /** Only listen for right-clicks and if a Tab is selected */
    if (newsHeaderTabFolder.getItemCount() == 0 || newsHeaderTabFolder.getSelectionIndex() == -1) {
      mouseInTab = false;
      return;
    }

    /** Rectangle of selected tab */
    Rectangle selectedTabBounds = newsHeaderTabFolder.getSelection().getBounds();

    /** Check if mouse is located on a tabitem */
    mouseInTab = (e.x > selectedTabBounds.x && e.x < selectedTabBounds.width + selectedTabBounds.x);

    /** Close current selected tab */
    if (mouseInTab && !keyPressed)
      closeCurrent();

    /** Close others if Ctrl is pressed */
    else if (mouseInTab && ctrlPressed)
      closeAll(true, false);

    /** Close feeds if Shift is pressed */
    else if (mouseInTab && shiftPressed)
      closeAll(false, true);
  }

  /**
   * Called whenever the CTabFolder becomes restored.
   */
  void onRestore() {
    newsHeaderTabFolder.setMaximized(false);
    rssOwlGui.setFavoritesMinimized(!GlobalSettings.isFavoritesTreeShown, false);
    rssOwlGui.getRSSOwlQuickview().setShowQuickview(GlobalSettings.isQuickviewShown, false);
    rssOwlGui.getRSSOwlQuickview().setShowToolBar(GlobalSettings.isToolBarShown, false);
  }

  /**
   * Reload the given feed
   * 
   * @param anItem the feed to reload
   */
  void reloadFeed(CTabItem anItem) {

    /** TabItem Data */
    TabItemData data = (TabItemData) anItem.getData();

    /** TabItem holds an aggregated category */
    if (data.isAggregatedCat()) {

      /** Un-Cache feed */
      rssOwlGui.getFeedCacheManager().unCacheNewsfeed(anItem.getText(), false);

      /** Reload aggregated category */
      AggregationLoader rssMultiLoader = new AggregationLoader(data.getChannel().getAggregatedFavorites(), data.getAggregatedCategory(), rssOwlGui, data.getAggregatedCategory().toCatPath(true));
      rssMultiLoader.setReload(true);
      rssMultiLoader.loadFavorites(true);
    }

    /** TabItem holds a normal feed */
    else {
      String feedUrl = data.getUrl();
      if (feedUrl == null && Category.getLinkForTitle(data.getTitle()) != null)
        feedUrl = Category.getLinkForTitle(data.getTitle());

      /** Reload feed */
      rssOwlGui.reloadNewsFeed(feedUrl);
    }
  }

  /**
   * Maximize the Newsheader Tabfolder if maximized is TRUE, otherwise restore
   * the normal state.
   * 
   * @param maximize TRUE if maximized
   */
  void setMaximized(boolean maximize) {
    boolean isMaximized = (contentPane.getMaximizedControl() != null);
    if (isMaximized != maximize)
      contentPane.setMaximizedControl((maximize == true) ? newsHeaderTabFolderHolder : null);
  }

  /**
   * Display a message in a new tab
   * 
   * @param message Message String
   * @param title Title String
   * @param icon The image icon to display
   */
  void showMessageTab(String message, String title, Image icon) {
    CTabItem tabItem;

    /** User might have closed RSSOwl already */
    if (!GUI.isAlive() || newsHeaderTabFolder.isDisposed())
      return;

    /** Use first Tab to show error if displaySingleTab is TRUE */
    if (GlobalSettings.displaySingleTab && newsHeaderTabFolder.getItemCount() > 0)
      tabItem = newsHeaderTabFolder.getItem(0);

    /** Create TabItem to load error into */
    else
      tabItem = new CTabItem(newsHeaderTabFolder, SWT.NONE);

    tabItem.setText(title);
    tabItem.setData(TabItemData.createMessageData(title));
    tabItem.setImage(icon);

    /** Composite holding the message */
    Composite messageHolder = new Composite(newsHeaderTabFolder, SWT.NONE);
    messageHolder.setLayout(LayoutShop.createFillLayout(10, 10));
    messageHolder.setBackground(display.getSystemColor(SWT.COLOR_WHITE));

    /** Text control displaying the message */
    StyledText messageText = new StyledText(messageHolder, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
    messageText.setText(message);
    messageText.setCaret(null);
    messageText.setFont(FontShop.textFont);
    messageText.setBackground(display.getSystemColor(SWT.COLOR_WHITE));

    /** Apply controll to tabitem */
    tabItem.setControl(messageHolder);

    /** Show message */
    newsHeaderTabFolder.setSelection(tabItem);

    /** Update TabFolder state */
    updateTabFolderState();
  }

  /**
   * Show the Welcome Tab
   */
  void showWelcomeTab() {
    CTabItem tabItem;

    /** User might have closed RSSOwl already */
    if (!GUI.isAlive() || newsHeaderTabFolder.isDisposed())
      return;

    /** Use first Tab to show error if displaySingleTab is TRUE */
    if (GlobalSettings.displaySingleTab && newsHeaderTabFolder.getItemCount() > 0)
      tabItem = newsHeaderTabFolder.getItem(0);

    /** Create TabItem to load error into */
    else
      tabItem = new CTabItem(newsHeaderTabFolder, SWT.NONE);

    tabItem.setText(GUI.i18n.getTranslation("TAB_WELCOME"));
    tabItem.setData(TabItemData.createMessageData(GUI.i18n.getTranslation("TAB_WELCOME")));
    tabItem.setImage(PaintShop.iconInfo);

    /** Create a new Welcome Panel */
    WelcomePanel welcomePanel = new WelcomePanel(display, eventManager, newsHeaderTabFolder);

    /** Apply controll to tabitem */
    tabItem.setControl(welcomePanel.getPanel());

    /** Show message */
    newsHeaderTabFolder.setSelection(tabItem);

    /** Update TabFolder state */
    updateTabFolderState();

    /** Maximize View for Welcome Tab */
    onMaximize();
  }

  /**
   * Update state of 3D Border outside the ViewForm if neccessary
   */
  void updateViewFormBorder() {
    if (!newsHeaderViewForm.isDisposed() && !newsHeaderTabFolder.isDisposed())
      newsHeaderViewForm.set3DBorderState(newsHeaderTabFolder.getItemCount() != 0);
  }

  /**
   * View the current opened feed as PDF / RTF or HTML document. This method
   * will launch the associated progra to the given document format. The
   * document is created using a temp file, that is deleted after exit.
   * 
   * @param format PDF, RTF or HTML
   */
  void viewNewsInDocument(int format) {
    boolean success = true;
    try {

      /** Create a temp file that contains the document */
      File tempFile = File.createTempFile(DocumentGenerator.formatToString(format), "." + DocumentGenerator.formatToString(format), new File(GlobalSettings.TEMP_DIR));

      /** Export document into temp file */
      exportToDocument(tempFile, format, null);

      /** Decide how to view Document */
      switch (format) {

        /** View in PDF */
        case DocumentGenerator.PDF_FORMAT:
          success = Program.launch(tempFile.toString());
          break;

        /** View in RTF */
        case DocumentGenerator.RTF_FORMAT:
          success = Program.launch(tempFile.toString());
          break;

        /** View in HTML */
        case DocumentGenerator.HTML_FORMAT:
          BrowserShop.openLinkInTab("file://" + tempFile.toString());
          break;
      }

      /** Give out status if Launch failed */
      if (!success)
        MessageBoxFactory.showMessage(shell, SWT.ICON_WARNING, GUI.i18n.getTranslation("MESSAGEBOX_TITLE_ATTENTION"), StringShop.printf(GUI.i18n.getTranslation("MESSAGEBOX_LAUNCH_FAILED"), new String[] { "%FORMAT%" }, new String[] { DocumentGenerator.formatToString(format).toUpperCase() }));
    } catch (IOException e) {
      GUI.logger.log("viewNewsInDocument()", e);
      MessageBoxFactory.showError(shell, e);
    }
  }
}