/*   **********************************************************************  **
 **   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.dnd.FavoritesTreeDND;
import net.sourceforge.rssowl.controller.popup.FavoritesTreePopup;
import net.sourceforge.rssowl.controller.thread.AmphetaRateThread;
import net.sourceforge.rssowl.controller.thread.SettingsManager;
import net.sourceforge.rssowl.model.Category;
import net.sourceforge.rssowl.model.Favorite;
import net.sourceforge.rssowl.model.TreeItemData;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.i18n.ITranslatable;
import net.sourceforge.rssowl.util.search.SearchDefinition;
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.WidgetShop;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
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.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.GC;
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.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * This is the Tree that holds the categorys and its RSS favorites.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class FavoritesTree implements ITranslatable, IFontChangeable {
  private ToolItem aggregateAll;
  private ToolItem closeControllsToolItem;
  private MenuItem closeMenuItem;
  private boolean deletionInProgress;
  private Display display;
  private Composite favoritesTreeHolder;
  private boolean isClassicSort;
  private ToolItem markAllRead;
  private ToolItem reloadAll;
  private FavoritesTreeDND rssOwlFavoritesTreeDND;
  private FavoritesTreePopup rssOwlPopUpMenu;
  private ToolItem searchAll;
  private Menu topToolBarMenu;
  private ViewForm treeViewForm;
  private Composite treeViewFormCenterHeader;
  private Label treeViewFormHeaderLabel;
  private Composite treeViewFormLeftHeader;
  SashForm contentPane;
  EventManager eventManager;
  Tree favoritesTree;
  ToolItem linkTab;
  Composite renameEditorContainer;
  GUI rssOwlGui;
  Shell shell;
  MenuItem toggleToolBarMenuItem;
  TreeEditor treeEditor;

  /**
   * Instantiate a new FavoritesTree
   * 
   * @param display The display
   * @param shell The shell
   * @param contentPane The Sashform where to add the URL-Tree
   * @param rssOwlGui Access some methods of the main controller
   * @param eventManager The event manager
   */
  public FavoritesTree(Display display, Shell shell, SashForm contentPane, GUI rssOwlGui, EventManager eventManager) {
    this.contentPane = contentPane;
    this.display = display;
    this.shell = shell;
    this.rssOwlGui = rssOwlGui;
    this.eventManager = eventManager;
    isClassicSort = System.getProperties().containsKey(RSSOwlLoader.PROPERTY_CLASSIC_SORT);
    initComponents();

    /** The favorites tree supports basic Drag & Drop */
    rssOwlFavoritesTreeDND = new FavoritesTreeDND(this);
  }

  /**
   * Add a new category to the Tree
   * 
   * @param name Name of the category
   * @param parentCat The Parent category
   */
  public void addCategory(String name, Category parentCat) {
    Category rssOwlCategory = new Category(name, parentCat, false);
    parentCat.addCategory(rssOwlCategory);

    /** Update Selection Path */
    GlobalSettings.selectedTreeItem = rssOwlCategory.toCatPath();

    /** Request Save of Settings */
    SettingsManager.getInstance().requestSave();
  }

  /**
   * Add a new Favorite to the Tree
   * 
   * @param selectedCat The category to add this favorite into
   * @param link Link to the XML
   * @param title Desired Title of the Favorite
   * @param oldFavorite If this field is not NULL the oldFavorite is being
   * updated and given as parameter to clone its settings
   */
  public void addFavorite(Category selectedCat, String link, String title, Favorite oldFavorite) {

    /** Link must not be empty */
    if (link.length() == 0)
      return;

    /** Use link as title */
    if (title.length() == 0)
      title = link;

    /** Create a unique link */
    link = Category.createUniqueLink(link);

    /** Create a unique title */
    title = Category.createUniqueTitle(title);

    /** Set all parents expanded */
    Category tempCat = selectedCat;
    while (!tempCat.isRoot()) {
      tempCat.setExpanded(true);
      tempCat = tempCat.getParent();
    }

    /** Add Favorite */
    Favorite newFavorite = new Favorite(link, title, selectedCat);

    /** This is a new favorite */
    if (oldFavorite == null) {
      newFavorite.setCreationDate(System.currentTimeMillis());
      newFavorite.setLastVisitDate(0);
    }

    /** This is an updated favorite, clone settings */
    else {
      oldFavorite.clone(newFavorite);
    }

    /** Add to category */
    selectedCat.addFavorite(newFavorite);

    /** Update selected TreeItem with this new one */
    GlobalSettings.selectedTreeItem = selectedCat.toCatPath() + StringShop.CAT_TOKENIZER + newFavorite.getTitle();

    /** Request Save of Settings */
    SettingsManager.getInstance().requestSave();
  }

  /**
   * Add a new Favorite to the Tree
   * 
   * @param catPath Name of the Category
   * @param link Link to the XML
   * @param title Desired Title of the Favorite
   * @param oldFavorite If this field is not NULL the oldFavorite is being
   * updated and given as parameter to clone its settings
   */
  public void addFavorite(String catPath, String link, String title, Favorite oldFavorite) {
    addFavorite(getSelectedCat(catPath), link, title, oldFavorite);
  }

  /**
   * This Method will append the given unread-count to the TreeItem's name. If
   * the value is 0, just the Name is used as Text.
   * 
   * @param treeItem The TreeItem that is to be changed.
   * @param name The Name of the TreeItem.
   * @param unreadCount The Number to append to the Name.
   */
  public void appendUnreadCount(TreeItem treeItem, String name, int unreadCount) {
    StringBuffer strBuf = new StringBuffer(name);

    /** Only append in case not 0 */
    if (unreadCount > 0)
      strBuf.append(" (").append(unreadCount).append(")");

    /** Apply the Name in case different from the old Name */
    String newName = strBuf.toString();
    String oldName = treeItem.getText();

    if (!newName.equals(oldName))
      treeItem.setText(newName);
  }

  /**
   * Build the complete favorites Tree from the RSSOwlFavTree. The favtree
   * contains categorys with favorites. Do not heep the current Top-Item, but
   * show the last selected one.
   */
  public void buildFavoritesTree() {
    buildFavoritesTree(false);
  }

  /**
   * Build the complete favorites Tree from the RSSOwlFavTree. The favtree
   * contains categorys with favorites.
   * 
   * @param keepTopItem If TRUE, keep the current Top-Item by re-setting it
   * after Tree has been built.
   */
  public void buildFavoritesTree(boolean keepTopItem) {

    /** Get the selected element */
    String selectedItemTreePath = GlobalSettings.selectedTreeItem;

    /** Remember Top Item */
    String topItemTreePath = null;
    if (keepTopItem) {
      TreeItem topItem = favoritesTree.getTopItem();
      if (WidgetShop.isset(topItem))
        topItemTreePath = getTreePath(topItem, false);
    }

    /** Avoid flickering Scrollbar in this Case */
    if (keepTopItem)
      favoritesTree.setVisible(false);

    /** Remove all elements */
    deletionInProgress = true;
    favoritesTree.removeAll();
    deletionInProgress = false;

    /** Add sub categories */
    Hashtable subCats = Category.getRootCategory().getSubCategories();
    TreeSet sortedSubCatTitles = Category.getRootCategory().getSortedSubCatTitles();
    addCategoryOrFavorite(subCats, sortedSubCatTitles, favoritesTree, Category.getRootCategory());

    /** Restore the expanded state of the tree */
    restoreExpanded(favoritesTree.getItems(), Category.getRootCategory());

    /** Restore selection if given */
    if (selectedItemTreePath != null)
      restoreSelection(selectedItemTreePath);

    /** Restore the Top Item */
    if (keepTopItem && StringShop.isset(topItemTreePath)) {
      TreeItem topItemCandidate = getItem(topItemTreePath);

      if (WidgetShop.isset(topItemCandidate))
        favoritesTree.setTopItem(topItemCandidate);
      else if (favoritesTree.getSelectionCount() > 0)
        favoritesTree.showSelection();
    }

    /** Restore changes made for avoiding flicker in Scrollbar */
    if (keepTopItem) {
      favoritesTree.setVisible(true);
      favoritesTree.setFocus();
    }

    /** Notify that Selection has Changed */
    selectionChanged();
  }

  /**
   * Creates or updates the AmphetaRate feed
   */
  public void createAmphetaRateFeed() {

    /** Overwrite old AmphetaRate feed */
    if (Category.getRootCategory().getSortedSubCatTitles().contains("AmphetaRate"))
      Category.getRootCategory().removeCategory("AmphetaRate", Category.getRootCategory());

    /** Recreate category */
    addCategory("AmphetaRate", Category.getRootCategory());

    /** Add the global AmphetaRate latest best news feed */
    if (!Category.linkExists("http://amphetarate.sourceforge.net/dinka-get-rss.php?global=1" + AmphetaRateThread.PARAM_LIST)) {
      addFavorite("AmphetaRate", "http://amphetarate.sourceforge.net/dinka-get-rss.php?global=1" + AmphetaRateThread.PARAM_LIST, "Latest best news", null);
    }

    /** Create recommendation feed for old user */
    if (AmphetaRateThread.isOldUser()) {
      if (!Category.linkExists("http://amphetarate.sourceforge.net/dinka-get-rss.php?uid=" + GlobalSettings.amphetaRateUserID + AmphetaRateThread.PARAM_LIST)) {
        addFavorite("AmphetaRate", "http://amphetarate.sourceforge.net/dinka-get-rss.php?uid=" + GlobalSettings.amphetaRateUserID + AmphetaRateThread.PARAM_LIST, GUI.i18n.getTranslation("RECOMMENDED_ARTICLES"), null);
      }
    }

    /** Create recommendation feed for new user */
    else {
      if (!Category.linkExists("http://amphetarate.sourceforge.net/dinka-get-rss.php?alias=" + GlobalSettings.amphetaRateUsername + "&password=" + GlobalSettings.amphetaRatePassword + AmphetaRateThread.PARAM_LIST)) {
        addFavorite("AmphetaRate", "http://amphetarate.sourceforge.net/dinka-get-rss.php?alias=" + GlobalSettings.amphetaRateUsername + "&password=" + GlobalSettings.amphetaRatePassword + AmphetaRateThread.PARAM_LIST, GUI.i18n.getTranslation("RECOMMENDED_ARTICLES"), null);
      }
    }

    /** Update Favorites Tree */
    buildFavoritesTree();
  }

  /**
   * Let other objects access this controll (for example to focus on it)
   * 
   * @return The URL Tree
   */
  public Tree getFavoritesTree() {
    return favoritesTree;
  }

  /**
   * Get the DND class that allows drag and drop on this favorites tree
   * 
   * @return FavoritesTreeDND The dnd class of this favorite tree
   */
  public FavoritesTreeDND getRSSOwlFavoritesTreeDND() {
    return rssOwlFavoritesTreeDND;
  }

  /**
   * Get the selected category
   * 
   * @return Category Selected category
   */
  public Category getSelectedCat() {
    return getSelectedCat(null);
  }

  /**
   * Get the selected category from the given path
   * 
   * @param path Path to the category (e.g. "Test > Sub1 > Sub2")
   * @return Category Selected category
   */
  public Category getSelectedCat(String path) {
    Category selectedCat = Category.getRootCategory();
    String[] categorys = (path != null) ? path.split(StringShop.CAT_TOKENIZER) : getTreePath(true).split(StringShop.CAT_TOKENIZER);

    /** Foreach category in the path */
    for (int a = 0; a < categorys.length && selectedCat != null; a++)
      selectedCat = (Category) selectedCat.getSubCategories().get(categorys[a]);

    return selectedCat;
  }

  /**
   * Get the selected favorite
   * 
   * @return Favorite Selected Favorite
   */
  public Favorite getSelectedFav() {

    /** A TreeItem must be selected */
    if (getFavoritesTree().getSelectionCount() <= 0)
      return null;

    /** TreeItem data */
    TreeItemData data = (TreeItemData) getFavoritesTree().getSelection()[0].getData();
    return data.getFavorite();
  }

  /**
   * Check if RSSOwl contains favorites with unread news
   * 
   * @return boolean TRUE if there are favorites with unread news available
   */
  public boolean getTreeHasUnreadFavs() {
    TreeItem items[] = favoritesTree.getItems();
    for (int a = 0; a < items.length; a++) {
      TreeItemData data = (TreeItemData) items[a].getData();
      if (data.isStatusUnread())
        return true;
    }
    return false;
  }

  /**
   * Return the TreePath of the selected TreeItem
   * 
   * @param skipFavorite TRUE if a favorite does not count as selection in the
   * path
   * @return String The TreePath
   */
  public String getTreePath(boolean skipFavorite) {
    TreeItem selection[] = favoritesTree.getSelection();

    /** User has selected items */
    if (selection.length > 0) {
      TreeItem selectedTree = selection[0];
      return getTreePath(selectedTree, skipFavorite);
    }

    /** No selection, use the first item */
    else if (favoritesTree.getItemCount() > 0) {
      TreeItemData data = (TreeItemData) favoritesTree.getItems()[0].getData();
      return data.getName();
    }

    /** Empty tree */
    else
      return "";
  }

  /**
   * Return the TreePath of the selected TreeItem
   * 
   * @param selectedTree The selected TreeItem
   * @param skipFavorite TRUE if a favorite does not count as selection in the
   * path
   * @return String The TreePath
   */
  public String getTreePath(TreeItem selectedTree, boolean skipFavorite) {
    StringBuffer treePath = new StringBuffer("");
    ArrayList treePathVector = new ArrayList();

    /** TreeItem data */
    TreeItemData data = (TreeItemData) selectedTree.getData();

    /** TreeItem is category or blogroll */
    if (data.isCategory() || data.isBlogroll())
      treePathVector.add(data.getName());

    /** TreeItem is a favorite */
    else if (!skipFavorite)
      treePathVector.add(data.getName());

    /** Foreach parent item */
    while (selectedTree.getParentItem() != null) {
      TreeItemData parentData = (TreeItemData) selectedTree.getParentItem().getData();
      treePathVector.add(parentData.getName());
      selectedTree = selectedTree.getParentItem();
    }

    /** Create Treepath */
    if (treePathVector.size() > 0) {
      treePath = new StringBuffer((String) treePathVector.get(treePathVector.size() - 1));
      for (int a = treePathVector.size() - 2; a >= 0; a--)
        treePath.append((StringShop.CAT_TOKENIZER)).append(treePathVector.get(a));
    }

    return treePath.toString();
  }

  /**
   * Open the newsfeed if the user has clicked on a favorite. Open the category
   * if the user has clicked on a category.
   * 
   * @param isSingleClick if TRUE, the selection was made with a single click
   */
  public void handleTreeItemSelect(boolean isSingleClick) {
    TreeItem selectedTree[] = favoritesTree.getSelection();
    if (selectedTree.length > 0) {

      /** TreeItem data */
      TreeItemData data = (TreeItemData) selectedTree[0].getData();

      /**
       * This is a favorite - allow loading if RSSOwl is not loading an
       * aggregation at the same time.
       */
      if (!data.isCategory() && !data.isBlogroll()) {
        rssOwlGui.loadNewsFeed(Category.getLinkForTitle(data.getName()), SearchDefinition.NO_SEARCH, true, true, NewsTabFolder.DISPLAY_MODE_FOCUS);
        data.getFavorite().setLastVisitDate(System.currentTimeMillis());
      }

      /**
       * This is a category or blogroll. Do not expand the category or blogroll
       * in case the user has performed a single-click on them.
       */
      else if (!isSingleClick) {
        selectedTree[0].setExpanded(!selectedTree[0].getExpanded());

        /** Notify Listener */
        Event event = new Event();
        event.item = selectedTree[0];
        event.widget = favoritesTree;
        onTreeEvent(new TreeEvent(event), selectedTree[0].getExpanded());
      }
    }
  }

  /**
   * Toggles the visibility of the Tree ToolBar.
   * 
   * @param show If TRUE, show the Tree ToolBar.
   */
  public void setShowToolBar(boolean show) {

    /** Update Context Menu */
    toggleToolBarMenuItem.setSelection(show);

    /** Show the ToolBar if not yet visible */
    if (show && treeViewForm.getTopCenter() == null)
      treeViewForm.setTopCenter(treeViewFormCenterHeader, true);

    /** Hide the ToolBar if not yet hidden */
    else if (!show && treeViewForm.getTopCenter() != null)
      treeViewForm.setTopCenter(null, true);
  }

  /**
   * Disposes the Container for the Text Control that is used when the user is
   * going to Rename an Item of the Tree in place. This Method is called from
   * the DND Support to guarantee that the Rename-Box is closing as soon as Drag
   * is about to begin.
   */
  public void stopRenaming() {

    /** Container might already be disposed or NULL */
    if (WidgetShop.isset(renameEditorContainer)) {

      /** Dispose Text Container */
      renameEditorContainer.dispose();

      /** Restore printable accelerators */
      rssOwlGui.getRSSOwlMenu().updateAccelerators();
    }
  }

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

    /** Rebuild the favorites tree to cover bold fontstyle */
    buildFavoritesTree(true);
  }

  /** Update all controlls text with i18n */
  public void updateI18N() {
    treeViewFormHeaderLabel.setText(GUI.i18n.getTranslation("HEADER_RSS_FAVORITES"));
    closeControllsToolItem.setToolTipText(GUI.i18n.getTranslation("MENU_CLOSE"));
    markAllRead.setToolTipText(GUI.i18n.getTranslation("BUTTON_MARK_ALL_READ"));
    linkTab.setToolTipText(GUI.i18n.getTranslation("BUTTON_LINK_TAB"));
    aggregateAll.setToolTipText(GUI.i18n.getTranslation("BUTTON_AGGREGATE_ALL"));
    reloadAll.setToolTipText(GUI.i18n.getTranslation("BUTTON_RELOAD_ALL"));
    searchAll.setToolTipText(GUI.i18n.getTranslation("BUTTON_SEARCH_ALL"));
    toggleToolBarMenuItem.setText(GUI.i18n.getTranslation("MENU_TOOLBAR"));
    closeMenuItem.setText(GUI.i18n.getTranslation("MENU_CLOSE"));

    /** Mark Popup as required to Update and Update it */
    rssOwlPopUpMenu.setRequiresUpdate();
    onSelection();
  }

  /**
   * Update the icon on a TreeItem in dependance of the "isError" flag.
   * 
   * @param treeItem The TreeItem to update
   */
  public void updateTreeItemIcon(TreeItem treeItem) {

    /** User could have closed the GUI already */
    if (GUI.isAlive() && !treeItem.isDisposed()) {
      Favorite favorite = ((TreeItemData) treeItem.getData()).getFavorite();

      /** Operation only allowed on Favorites */
      if (favorite == null)
        return;

      /** Decide which image to use */
      Image img = PaintShop.iconLense;
      if (favorite.isErrorLoading())
        img = PaintShop.iconErrorLense;
      else if (favorite.getUnreadNewsCount() > 0)
        img = PaintShop.iconLenseUnread;

      treeItem.setImage(img);
    }
  }

  /**
   * Recursivly update all parent categories of the given TreeItem to set /
   * unset the unread status, which is indicated by a slightly different
   * category icon.
   * 
   * @param treeItem The starting point TreeItem
   */
  public void updateTreeReadStatus(TreeItem treeItem) {

    /** TreeItem must not be disposed */
    if (treeItem.isDisposed())
      return;

    /** Get Parent from given TreeItem */
    TreeItem parent = treeItem.getParentItem();

    /** Root reached, return */
    if (parent == null)
      return;

    /** Get all childs */
    TreeItem items[] = parent.getItems();

    boolean setUnreadIcon = false;
    boolean setUnreadFont = false;
    boolean setUnreadStatus = false;

    /** Foreach child */
    for (int a = 0; a < items.length; a++) {

      /** Data object */
      TreeItemData data = (TreeItemData) items[a].getData();

      /** Unread Favorite */
      if (data.isStatusUnread() && (data.isFavorite() || data.isBlogrollFavorite())) {
        setUnreadIcon = true;
        setUnreadFont = true;
        setUnreadStatus = true;
        break;
      }

      /** Unread Category or Blogroll */
      else if (data.isStatusUnread()) {
        setUnreadFont = true;
        setUnreadStatus = true;
      }
    }

    /** Parent Data object */
    TreeItemData data = (TreeItemData) parent.getData();

    /** Check wether an Image Update is required */
    boolean needsIconUpdate = false;
    if (data.isCategory())
      needsIconUpdate = parent.getImage().equals(setUnreadIcon ? PaintShop.iconFolder : PaintShop.iconFolderUnread);
    else if (data.isBlogroll())
      needsIconUpdate = parent.getImage().equals(setUnreadIcon ? PaintShop.iconFolderSubscribe : PaintShop.iconFolderSubscribeUnread);

    /** Update the image of this category */
    if (needsIconUpdate && data.isCategory())
      parent.setImage((setUnreadIcon == true) ? PaintShop.iconFolderUnread : PaintShop.iconFolder);
    else if (needsIconUpdate && data.isBlogroll())
      parent.setImage((setUnreadIcon == true) ? PaintShop.iconFolderSubscribeUnread : PaintShop.iconFolderSubscribe);

    /** Update the font style of the category */
    boolean needsFontUpdate = setUnreadStatus != data.isStatusUnread();
    if (needsFontUpdate)
      parent.setFont((setUnreadFont == true) ? FontShop.treeBoldFont : FontShop.treeFont);

    /** Update the data object of the parent */
    data.setStatusUnread(setUnreadStatus);

    /** Recursivly update parents until root is reached */
    updateTreeReadStatus(parent);
  }

  /**
   * Recursivly add Favorites and Categorys to the URL Tree.
   * 
   * @param favoritOrCategory contains favorites or categorys
   * @param sortedTitles Sorted Titles of favorits or categorys
   * @param parent The parent Widget of the new treeitems
   * @param rssOwlCategory Current category
   */
  private void addCategoryOrFavorite(Hashtable favoritOrCategory, TreeSet sortedTitles, Widget parent, Category rssOwlCategory) {
    Iterator sortedTitlesIt = sortedTitles.iterator();

    /** Foreach title */
    while (sortedTitlesIt.hasNext()) {
      String key = (String) sortedTitlesIt.next();

      /** This is a new category. */
      if (favoritOrCategory.get(key) instanceof Category) {
        Category rssOwlSubCategory = (Category) favoritOrCategory.get(key);
        TreeItem subParent;

        /** Category is a blogroll */
        if (rssOwlSubCategory.isBlogroll())
          subParent = addTreeItem(rssOwlSubCategory.getName(), PaintShop.iconFolderSubscribe, "", parent, rssOwlSubCategory.isBlogroll(), 0);

        /** Category is a normal one */
        else
          subParent = addTreeItem(rssOwlSubCategory.getName(), PaintShop.iconFolder, "", parent, rssOwlSubCategory.isBlogroll(), 0);

        /** Classic Sort: Sort Favorites over Categories */
        if (isClassicSort) {
          Hashtable subLinks = rssOwlSubCategory.getFavorites();
          TreeSet sortedLinkTitles = rssOwlSubCategory.getSortedLinkTitles();
          addCategoryOrFavorite(subLinks, sortedLinkTitles, subParent, rssOwlSubCategory);

          Hashtable subCats = rssOwlSubCategory.getSubCategories();
          TreeSet sortedSubCatTitles = rssOwlSubCategory.getSortedSubCatTitles();
          addCategoryOrFavorite(subCats, sortedSubCatTitles, subParent, rssOwlSubCategory);
        }

        /** Modern Sort: Sort Categories over Favorites */
        else {
          Hashtable subCats = rssOwlSubCategory.getSubCategories();
          TreeSet sortedSubCatTitles = rssOwlSubCategory.getSortedSubCatTitles();
          addCategoryOrFavorite(subCats, sortedSubCatTitles, subParent, rssOwlSubCategory);

          Hashtable subLinks = rssOwlSubCategory.getFavorites();
          TreeSet sortedLinkTitles = rssOwlSubCategory.getSortedLinkTitles();
          addCategoryOrFavorite(subLinks, sortedLinkTitles, subParent, rssOwlSubCategory);
        }

        /** Save the treeitem to this category */
        rssOwlSubCategory.setTreeItem(subParent);
      }

      /** This is a new favorite */
      else if (favoritOrCategory.get(Category.getLinkForTitle(key)) instanceof Favorite) {
        Favorite rssOwlFavorite = (Favorite) favoritOrCategory.get(Category.getLinkForTitle(key));
        TreeItem favoritesTreeItem;

        /** Set warning icon if the feed can not be loaded */
        if (rssOwlFavorite.isErrorLoading())
          favoritesTreeItem = addTreeItem(rssOwlFavorite.getTitle(), PaintShop.iconErrorLense, rssOwlCategory.getName(), parent, rssOwlCategory.isBlogroll(), 0);

        /** Set unread news are available Icon */
        else if (rssOwlFavorite.unreadNewsAvailable())
          favoritesTreeItem = addTreeItem(rssOwlFavorite.getTitle(), PaintShop.iconLenseUnread, rssOwlCategory.getName(), parent, rssOwlCategory.isBlogroll(), rssOwlFavorite.getUnreadNewsCount());

        /** Synchronizer showing when a Blogroll is synced */
        else if (rssOwlFavorite.isSynchronizer())
          favoritesTreeItem = addTreeItem(rssOwlFavorite.getTitle(), PaintShop.iconReload, rssOwlCategory.getName(), parent, rssOwlCategory.isBlogroll(), 0);

        /** Set default Icon */
        else
          favoritesTreeItem = addTreeItem(rssOwlFavorite.getTitle(), PaintShop.iconLense, rssOwlCategory.getName(), parent, rssOwlCategory.isBlogroll(), 0);

        /** Save the TreeItem to this Favorite */
        rssOwlFavorite.setTreeItem(favoritesTreeItem);

        /** Save the Favorite to this TreeItem */
        ((TreeItemData) favoritesTreeItem.getData()).setFavorite(rssOwlFavorite);
      }
    }
  }

  /**
   * Insert a new item into the favorites tree.
   * 
   * @param name Name of the Item
   * @param icon Icon (for example folder)
   * @param catName Name of the category
   * @param parent The parent of the new treeitem
   * @param isBlogroll TRUE if favorite is from a blogroll
   * @param unreadNewsCount The Number of unread News, or 0 if none.
   * @return TreeItem The new treeitem
   */
  private TreeItem addTreeItem(String name, Image icon, String catName, Widget parent, boolean isBlogroll, int unreadNewsCount) {
    TreeItem treeItem;
    boolean isParentTree = parent instanceof Tree;
    boolean indicateUnreadNews = unreadNewsCount > 0;

    /** Create a new TreeItem */
    if (isParentTree)
      treeItem = new TreeItem((Tree) parent, SWT.NONE);
    else
      treeItem = new TreeItem((TreeItem) parent, SWT.NONE);

    /** This is a category */
    if (catName.length() == 0) {

      /** Normal Category */
      if (!isBlogroll)
        treeItem.setData(TreeItemData.createCategory(name, indicateUnreadNews));

      /** Blogroll Category */
      else
        treeItem.setData(TreeItemData.createBlogroll(name, indicateUnreadNews));
    }

    /** This is a favorite */
    else {

      /** Normal Favorite */
      if (!isBlogroll)
        treeItem.setData(TreeItemData.createFavorite(name, indicateUnreadNews));

      /** Blogroll Favorite */
      else
        treeItem.setData(TreeItemData.createBlogrollFavorite(name, indicateUnreadNews));
    }

    /** Setup treeitem */
    treeItem.setText(name);
    treeItem.setImage(icon);

    /** Update item and parent items in case unread news are available */
    if (indicateUnreadNews) {
      treeItem.setFont(FontShop.treeBoldFont);
      appendUnreadCount(treeItem, name, unreadNewsCount);
      updateTreeReadStatus(treeItem);
    }

    return treeItem;
  }

  /**
   * Get the TreeItem from the given Tree Path.
   * 
   * @param path The Tree Path of the TreeItem to lookup.
   * @return TreeItem The TreeItem as defined by the given Path or NULL if none.
   */
  private TreeItem getItem(String path) {
    String[] itemNames = path.split(StringShop.CAT_TOKENIZER);
    TreeItem items[] = favoritesTree.getItems();
    TreeItem targetItem = null;

    /** Foreach Category */
    for (int i = 0; i < itemNames.length; i++) {
      String itemName = itemNames[i];
      boolean foundItem = false;

      /** Foreach Items in the currently selected TreeItem */
      for (int j = 0; j < items.length; j++) {
        TreeItem item = items[j];
        TreeItemData data = (TreeItemData) item.getData();

        /** The Item matches the current Category */
        if (data != null && data.getName().equals(itemName)) {
          targetItem = item;
          items = targetItem.getItems();
          foundItem = true;

          break;
        }
      }

      /** Return if one Item was not found */
      if (!foundItem)
        return null;
    }

    return targetItem;
  }

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

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

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

    /** Separate Toolbar from Top */
    treeViewForm.setTopCenterSeparate(true, false);

    /** ViewForm header - Left Side */
    treeViewFormLeftHeader = new Composite(treeViewForm, SWT.NONE);
    treeViewFormLeftHeader.setBackground(PaintShop.grayViewFormColor);
    treeViewFormLeftHeader.setLayout(LayoutShop.createGridLayout(1, 5, 5));

    /** Initialize the Context Menu to toggle the ToolBar */
    initTopContextMenu(treeViewFormLeftHeader);
    treeViewFormLeftHeader.setMenu(topToolBarMenu);

    /** Header label */
    treeViewFormHeaderLabel = new Label(treeViewFormLeftHeader, SWT.NONE);
    treeViewFormHeaderLabel.setBackground(PaintShop.grayViewFormColor);
    treeViewFormHeaderLabel.setText(GUI.i18n.getTranslation("HEADER_RSS_FAVORITES"));
    treeViewFormHeaderLabel.setFont(FontShop.headerFont);
    treeViewFormHeaderLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, true));
    treeViewFormHeaderLabel.setMenu(topToolBarMenu);

    /** Apply control to viewform */
    treeViewForm.setTopLeft(treeViewFormLeftHeader, false);

    /** ViewForm header - Right Side */
    Composite treeViewFormRightHeader = new Composite(treeViewForm, SWT.NONE);
    treeViewFormRightHeader.setLayout(LayoutShop.createGridLayout(1, 0, 0));
    treeViewFormRightHeader.setBackground(PaintShop.grayViewFormColor);

    /** ToolBar for closing the Controlls ViewForm */
    ToolBar closeControllsToolBar = new ToolBar(treeViewFormRightHeader, SWT.FLAT);
    closeControllsToolBar.setBackground(PaintShop.grayViewFormColor);
    closeControllsToolBar.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true));

    /** Close button */
    closeControllsToolItem = new ToolItem(closeControllsToolBar, SWT.PUSH);
    closeControllsToolItem.setImage(PaintShop.loadImage("/img/icons/cross.gif"));
    closeControllsToolItem.setToolTipText(GUI.i18n.getTranslation("MENU_CLOSE"));
    closeControllsToolItem.addDisposeListener(DisposeListenerImpl.getInstance());

    /** ToolBar with close button */
    closeControllsToolItem.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        rssOwlGui.setFavoritesMinimized(true, true);
      }
    });

    /** Apply toolbar to viewform */
    treeViewForm.setTopRight(treeViewFormRightHeader, false);

    /** ViewForm containing Tree Toolbar - Top Center Side */
    treeViewFormCenterHeader = new Composite(treeViewForm, SWT.NONE);
    treeViewFormCenterHeader.setLayout(LayoutShop.createGridLayout(1, 0, 0));
    treeViewFormCenterHeader.setBackground(PaintShop.grayToolBarColor);
    treeViewFormCenterHeader.setMenu(topToolBarMenu);

    /** Tree Toolbar */
    ToolBar treeViewFormBar = new ToolBar(treeViewFormCenterHeader, SWT.FLAT | SWT.WRAP);
    treeViewFormBar.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, true, false));
    treeViewFormBar.setBackground(PaintShop.grayToolBarColor);
    treeViewFormBar.setMenu(topToolBarMenu);

    /** Aggregate All button */
    aggregateAll = new ToolItem(treeViewFormBar, SWT.PUSH);
    aggregateAll.setImage(PaintShop.loadImage("/img/icons/aggregate_all_big.gif"));
    aggregateAll.addDisposeListener(DisposeListenerImpl.getInstance());
    aggregateAll.setToolTipText(GUI.i18n.getTranslation("BUTTON_AGGREGATE_ALL"));
    aggregateAll.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        eventManager.actionAggregateAllCategories();
      }
    });

    /** Reload All button */
    reloadAll = new ToolItem(treeViewFormBar, SWT.PUSH);
    reloadAll.setImage(PaintShop.loadImage("/img/icons/reload_all_big.gif"));
    reloadAll.addDisposeListener(DisposeListenerImpl.getInstance());
    reloadAll.setToolTipText(GUI.i18n.getTranslation("BUTTON_RELOAD_ALL"));
    reloadAll.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        eventManager.actionReloadAllCategories();
      }
    });

    /** Search All button */
    searchAll = new ToolItem(treeViewFormBar, SWT.PUSH);
    searchAll.setImage(PaintShop.loadImage("/img/icons/search_all_big.gif"));
    searchAll.addDisposeListener(DisposeListenerImpl.getInstance());
    searchAll.setToolTipText(GUI.i18n.getTranslation("BUTTON_SEARCH_ALL"));
    searchAll.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        eventManager.actionSearchInAllCategories();
      }
    });

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

    /** Mark All Read button */
    markAllRead = new ToolItem(treeViewFormBar, SWT.PUSH);
    markAllRead.setImage(PaintShop.loadImage("/img/icons/mark_read_big.gif"));
    markAllRead.addDisposeListener(DisposeListenerImpl.getInstance());
    markAllRead.setToolTipText(GUI.i18n.getTranslation("BUTTON_MARK_ALL_READ"));
    markAllRead.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        eventManager.actionMarkAllCategoriesRead();
      }
    });

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

    /** Link with displayed Feed */
    linkTab = new ToolItem(treeViewFormBar, SWT.CHECK);
    linkTab.setImage(PaintShop.loadImage("/img/icons/link_tab.gif"));
    linkTab.addDisposeListener(DisposeListenerImpl.getInstance());
    linkTab.setToolTipText(GUI.i18n.getTranslation("BUTTON_LINK_TAB"));
    linkTab.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {

        /** Update Settings */
        GlobalSettings.linkTreeWithTab = linkTab.getSelection();

        /** Run on current displayed Feed if given and set Focus */
        if (GlobalSettings.linkTreeWithTab) {
          rssOwlGui.getRSSOwlNewsTabFolder().linkSelectionToTree();
          favoritesTree.setFocus();
        }
      }
    });

    /** Apply toolbar to viewform */
    treeViewForm.setTopCenter(treeViewFormCenterHeader, false);

    /** Tree that displays the entered RSS Feeds */
    favoritesTree = new Tree(treeViewForm, SWT.NONE);
    favoritesTree.setFont(FontShop.treeFont);
    favoritesTree.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        onSelection();
      }
    });

    /** Listen for Mouse Click Event */
    favoritesTree.addMouseListener(new MouseAdapter() {
      public void mouseUp(MouseEvent e) {
        onMouseUp(e);
      }
    });

    /** Expand / Collapse TreeItem on Mouse double click */
    favoritesTree.addListener(SWT.MouseDoubleClick, new Listener() {
      public void handleEvent(Event event) {
        onMouseDoubleClick(event);
      }
    });

    /**
     * Suport DEL Key
     */
    favoritesTree.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        onKeyPressed(e);
      }
    });

    /** Save expanded / collapse state of the items */
    favoritesTree.addTreeListener(new TreeListener() {

      /**
       * Bug on Mac: A deleted category keeps its selection. Therefor a check
       * needs to be done if the selected category is null (=deleted).
       */
      public void treeCollapsed(TreeEvent e) {
        onTreeEvent(e, false);
      }

      /**
       * Bug on Mac: A deleted category keeps its selection. Therefor a check
       * needs to be done if the selected category is null (=deleted).
       */
      public void treeExpanded(TreeEvent e) {
        onTreeEvent(e, true);
      }
    });

    /** Apply Tree as content to ViewForm */
    treeViewForm.setContent(favoritesTree, true);

    /** Create the Popup Menu */
    rssOwlPopUpMenu = new FavoritesTreePopup(shell, this, rssOwlGui, eventManager);
    favoritesTree.setMenu(rssOwlPopUpMenu.getTreePopUpMenu());

    /** Fill the Menu dynamically on Show */
    rssOwlPopUpMenu.getTreePopUpMenu().addMenuListener(new MenuAdapter() {
      public void menuShown(MenuEvent e) {
        onPopupMenuShown();
      }
    });

    /** Create the TreeEditor to be used for "Rename" */
    treeEditor = new TreeEditor(favoritesTree);
  }

  /**
   * Initialize the Context Menu that toggles visibility of the Favorites Tree
   * ToolBar and allows to Close the Favorites Tree.
   * 
   * @param parent The Parent Control to create the Menu.
   */
  private void initTopContextMenu(Control parent) {

    /** Context Menu */
    topToolBarMenu = new Menu(parent);

    /** Set ToolBar Visibility */
    toggleToolBarMenuItem = new MenuItem(topToolBarMenu, SWT.CHECK);
    toggleToolBarMenuItem.setText(GUI.i18n.getTranslation("MENU_TOOLBAR"));
    toggleToolBarMenuItem.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        GlobalSettings.isTreeToolBarShown = toggleToolBarMenuItem.getSelection();
        setShowToolBar(GlobalSettings.isTreeToolBarShown);
      }
    });

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

    /** Close Tree */
    closeMenuItem = new MenuItem(topToolBarMenu, SWT.NONE);
    closeMenuItem.setText(GUI.i18n.getTranslation("MENU_CLOSE"));
    closeMenuItem.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        rssOwlGui.setFavoritesMinimized(true, true);
      }
    });
  }

  /**
   * Open an in-place Text editor field to rename the given TreeItem.
   * 
   * @param item The TreeItem to rename
   */
  private void rename(final TreeItem item) {

    /** Item must be set */
    if (!WidgetShop.isset(item))
      return;

    /** Get TreeItemData */
    final TreeItemData treeItemData = (TreeItemData) item.getData();

    /** Container for Edit Text Control */
    renameEditorContainer = new Composite(favoritesTree, SWT.NONE);
    renameEditorContainer.setBackground(display.getSystemColor(SWT.COLOR_BLACK));

    /** Editor Field */
    final Text text = new Text(renameEditorContainer, SWT.NONE);
    text.setFont(FontShop.treeFont);
    renameEditorContainer.addListener(SWT.Resize, new Listener() {
      public void handleEvent(Event e) {
        Rectangle rect = renameEditorContainer.getClientArea();
        text.setBounds(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);
      }
    });

    /** Item to Rename */
    final Category selectedCat = treeItemData.isCategory() ? getSelectedCat() : null;
    final Favorite selectedFav = treeItemData.isFavorite() ? getSelectedFav() : null;

    /** Listener for the Editor Field */
    Listener textListener = new Listener() {
      public void handleEvent(final Event e) {
        switch (e.type) {

          /** FocusOut: Set Text and dispose Editor Field */
          case SWT.FocusOut:
            if (treeItemData.isFavorite())
              renameFavorite(selectedFav, text.getText());
            else if (treeItemData.isCategory())
              renameCategory(selectedCat, text.getText());

            /** Dispose Text Container */
            renameEditorContainer.dispose();

            /** Restore printable accelerators */
            rssOwlGui.getRSSOwlMenu().updateAccelerators();
            break;

          /** FocusIn: Disable printable accelerators */
          case SWT.FocusIn:
            rssOwlGui.getRSSOwlMenu().updateAccelerators(true);
            break;

          /** Verify: Layout Tree Editor */
          case SWT.Verify:
            String newText = text.getText();
            String leftText = newText.substring(0, e.start);
            String rightText = newText.substring(e.end, newText.length());
            GC gc = new GC(text);
            Point size = gc.textExtent(leftText + e.text + rightText);
            gc.dispose();
            size = text.computeSize(size.x, SWT.DEFAULT);
            treeEditor.horizontalAlignment = SWT.LEFT;
            Rectangle itemRect = item.getBounds();
            Rectangle rect = favoritesTree.getClientArea();
            treeEditor.minimumWidth = Math.max(size.x, itemRect.width) + 2;
            int left = itemRect.x;
            int right = rect.x + rect.width;
            treeEditor.minimumWidth = Math.min(treeEditor.minimumWidth, right - left);
            treeEditor.minimumHeight = size.y + 2;
            treeEditor.layout();
            break;

          /** Traverse: */
          case SWT.Traverse:
            switch (e.detail) {
              case SWT.TRAVERSE_RETURN:
                if (treeItemData.isFavorite())
                  renameFavorite(selectedFav, text.getText());
                else if (treeItemData.isCategory())
                  renameCategory(selectedCat, text.getText());

                /** Restore printable accelerators */
                rssOwlGui.getRSSOwlMenu().updateAccelerators();

                /** Fall through */
              case SWT.TRAVERSE_ESCAPE:
                renameEditorContainer.dispose();
                e.doit = false;

                /** Restore printable accelerators */
                rssOwlGui.getRSSOwlMenu().updateAccelerators();
            }
            break;
        }
      }
    };

    /** Apply listener */
    text.addListener(SWT.FocusOut, textListener);
    text.addListener(SWT.FocusIn, textListener);
    text.addListener(SWT.Traverse, textListener);
    text.addListener(SWT.Verify, textListener);
    treeEditor.setEditor(renameEditorContainer, item);

    /** Set Text to Editor and Focus it */
    text.setText(treeItemData.getName());
    text.selectAll();
    text.setFocus();
  }

  /**
   * Recursivly restore expanded status for the TreeItems
   * 
   * @param items The TreeItems
   * @param rssOwlCategory Current working category
   */
  private void restoreExpanded(TreeItem items[], Category rssOwlCategory) {
    for (int i = 0; i < items.length; i++) {

      /** TreeItem data */
      TreeItemData data = (TreeItemData) items[i].getData();

      /** TreeItem is a category or blogroll */
      if (data.isCategory() || data.isBlogroll()) {
        Category rssOwlSubCategory = (Category) rssOwlCategory.getSubCategories().get(data.getName());
        items[i].setExpanded(rssOwlSubCategory.isExpanded());
        restoreExpanded(items[i].getItems(), rssOwlSubCategory);
      }
    }
  }

  /**
   * Restore selection of given treepath
   * 
   * @param path The path to the selected item
   */
  private void restoreSelection(String path) {
    String elements[] = path.split(StringShop.CAT_TOKENIZER);

    /** Tree could be empty */
    if (favoritesTree.getItemCount() <= 0)
      return;

    /** First item gets default selected */
    TreeItem selectedItem = favoritesTree.getItem(0);

    /** Get top element of path */
    TreeItem items[] = favoritesTree.getItems();
    for (int a = 0; a < items.length; a++) {
      TreeItemData data = (TreeItemData) items[a].getData();
      if (data.getName().equals(elements[0])) {
        selectedItem = items[a];
        break;
      }
    }

    /** Get child elements of top item */
    for (int a = 0; a < elements.length; a++) {
      items = selectedItem.getItems();
      for (int b = 0; b < items.length; b++) {
        TreeItemData data = (TreeItemData) items[b].getData();
        if (data.getName().equals(elements[a])) {
          selectedItem = items[b];
          break;
        }
      }
    }

    /** Set selection */
    if (selectedItem != null) {
      favoritesTree.setFocus();
      favoritesTree.setSelection(new TreeItem[] { selectedItem });
    }
  }

  /**
   * Get the current selected TreeItem's data.
   * 
   * @return TreeItemData The data of the selected item
   */
  TreeItemData getSelectedTreeItemData() {
    TreeItem selectedTreeItem[] = favoritesTree.getSelection();
    if (selectedTreeItem.length > 0)
      return (TreeItemData) selectedTreeItem[0].getData();
    return null;
  }

  /**
   * Called whenever a Key is pressed on the Tree
   * 
   * @param e The occuring KeyEvent
   */
  void onKeyPressed(KeyEvent e) {

    /** User has pressed DEL key (or Backspace on Mac) */
    if (e.keyCode == SWT.DEL || (GlobalSettings.isMac() && e.keyCode == SWT.BS))
      performDeletion();

    /** User has pressed ENTER key */
    else if (e.keyCode == SWT.CR)
      handleTreeItemSelect(false);
  }

  /**
   * The tree was doubleclicked with the mouse
   * 
   * @param event The occured event
   */
  void onMouseDoubleClick(Event event) {
    TreeItem selection[] = favoritesTree.getSelection();

    /** Check the region, where the event occured */
    if (selection.length > 0 && selection[0] != null) {

      /** Location of Event occurance */
      Rectangle clickedRect = event.getBounds();

      /** Get bounds of selected item */
      Rectangle selectedItemRect = selection[0].getBounds();

      /** Expand location to fit left image of Tree Item */
      Rectangle imgRect = selection[0].getImageBounds(0);
      if (imgRect != null)
        selectedItemRect.add(imgRect);

      /** Only handle event, if Mouse is in treeitem region */
      if (selectedItemRect.contains(clickedRect.x, clickedRect.y))
        handleTreeItemSelect(false);
    }

    /** There is no element selected */
    else {
      handleTreeItemSelect(false);
    }
  }

  /**
   * The favorites tree was selected with the Mouse
   * 
   * @param event The occured mouse event
   */
  void onMouseUp(MouseEvent event) {

    /** Bugfix: Popup not showing on Linux sometimes */
    if (GlobalSettings.isLinux()) {
      if (event.button != 1 && !favoritesTree.getMenu().isVisible())
        favoritesTree.getMenu().setVisible(true);
    }

    /**
     * The mouse on Mac does not provide a second mouse button. Display the
     * popup when the first mouse button is clicked while the ctrl key is
     * pressed
     */
    if (GlobalSettings.isMac()) {
      if (event.stateMask == (SWT.BUTTON1 | SWT.CTRL))
        favoritesTree.getMenu().setVisible(true);
    }

    /** Open the selection if single click open is set */
    if (event.button == 1 && !GlobalSettings.isDoubleClickOpen) {
      TreeItem selection[] = favoritesTree.getSelection();

      /**
       * Problems on Linux: The handling of a selected Tree differs to Windows.
       * The mouseUp() Method may be called even if the selection is gone.
       */
      if (selection.length <= 0)
        return;

      /** Get bounds of selected item */
      Rectangle selectedRect = selection[0].getBounds();

      /** Expand location to fit left image of Tree Item */
      Rectangle imgRect = selection[0].getImageBounds(0);
      if (imgRect != null)
        selectedRect.add(imgRect);

      /** Only handle event, if Mouse is over treeitem */
      if (selectedRect.contains(event.x, event.y))
        handleTreeItemSelect(true);
    }
  }

  /**
   * Called when the Context-Menu on the Tree is about to Show. Fill the Menu
   * dynamically with Items, based on the Type of the current Selection in the
   * Tree.
   */
  void onPopupMenuShown() {
    boolean wasMenuUpdated = false;

    /** Ensure that the Rename Box is disposed if open */
    stopRenaming();

    /** At least One selection in the Tree */
    if (favoritesTree.getSelectionCount() > 0) {

      /** TreeItem data */
      TreeItem selectedTreeItem = favoritesTree.getSelection()[0];
      TreeItemData data = (TreeItemData) selectedTreeItem.getData();

      /** Force update in case the Category is Empty */
      if (data.isCategory() && selectedTreeItem.getItemCount() == 0)
        rssOwlPopUpMenu.setRequiresUpdate();

      /** User has selected a category */
      if (data.isCategory())
        wasMenuUpdated = rssOwlPopUpMenu.updateMenu(FavoritesTreePopup.CAT_MENU, selectedTreeItem);

      /** User has selected a favorite */
      else if (data.isFavorite())
        wasMenuUpdated = rssOwlPopUpMenu.updateMenu(FavoritesTreePopup.FAV_MENU, selectedTreeItem);

      /** User has selected a blogroll category */
      else if (data.isBlogroll())
        wasMenuUpdated = rssOwlPopUpMenu.updateMenu(FavoritesTreePopup.BLOGROLL_CAT_MENU, selectedTreeItem);

      /** User has selected a blogroll favorite */
      else if (data.isBlogrollFavorite())
        wasMenuUpdated = rssOwlPopUpMenu.updateMenu(FavoritesTreePopup.BLOGROLL_FAV_MENU, selectedTreeItem);
    }

    /** No selection in the tree */
    else {
      wasMenuUpdated = rssOwlPopUpMenu.updateMenu(FavoritesTreePopup.EMPTY_TREE_MENU, null);
    }

    /** Init the Mnemonics */
    if (wasMenuUpdated)
      MenuManager.initMnemonics(rssOwlPopUpMenu.getTreePopUpMenu());
  }

  /**
   * The tree was selected
   */
  void onSelection() {

    /** Save selection path */
    GlobalSettings.selectedTreeItem = getTreePath(false);

    /** Notify that Selection has Changed */
    selectionChanged();
  }

  /**
   * Called whenever a TreeItem is expanded
   * 
   * @param e The occuring TreeEvent
   * @param expanded If TRUE the TreeItem was expanded
   */
  void onTreeEvent(TreeEvent e, boolean expanded) {

    /**
     * Bug on Mac: Upon disposal of TreeItems in the Tree, all expanded items
     * become collapsed first. Do not remeber that state then.
     */
    if (deletionInProgress || e.item.isDisposed())
      return;

    /** Retrieve the Tree Path */
    String treePath = getTreePath((TreeItem) e.item, true);

    /** Error retrieving the Tree Path */
    if (treePath == null)
      return;

    /** Set expanded state to Category */
    final Category selectedCat = getSelectedCat(treePath);
    if (selectedCat != null)
      selectedCat.setExpanded(expanded);

    /** Synchroniz an unsynced Blogroll it gets expanded */
    if (expanded) {

      /** Category is a Blogroll that is Unsynchronized */
      if (selectedCat != null && selectedCat.isBlogroll() && selectedCat.isUnSynchronized()) {
        display.timerExec(50, new Runnable() {
          public void run() {

            /** Synchronize Blogroll to retrieve Newsfeeds */
            eventManager.actionSynchronizeBlogroll(selectedCat);
          }
        });
      }
    }
  }

  /** Delete selected cat or fav or sub */
  void performDeletion() {

    /** TreeItem data */
    TreeItemData data = null;
    if (favoritesTree.getSelection().length > 0)
      data = (TreeItemData) favoritesTree.getSelection()[0].getData();

    /** Check for the type of TreeItem */
    if (data != null) {

      /** Delete favorite */
      if (data.isFavorite())
        eventManager.actionDeleteFavorite();

      /** Delete category */
      else if (data.isCategory())
        eventManager.actionDeleteCategory();

      /** Delete blogroll */
      else if (data.isBlogroll() || data.isBlogrollFavorite())
        eventManager.actionDeleteBlogroll();
    }

    /** Notify that Selection has Changed */
    selectionChanged();

    /** Request Save of Settings */
    SettingsManager.getInstance().requestSave();
  }

  /**
   * Rename the given Category using the new title
   * 
   * @param category The category to rename
   * @param newTitle The new title of the category
   */
  void renameCategory(Category category, String newTitle) {

    /** New Title not set, title exists or no change at all */
    if (!StringShop.isset(newTitle) || category.getParent().getSubCategories().containsKey(newTitle) || category.getName().equals(newTitle))
      return;

    /** Edit Category and rebuild the Tree */
    category.getParent().editCategory(category.getName(), newTitle);

    /** Update Selection */
    GlobalSettings.selectedTreeItem = category.toCatPath();

    /** Build Tree */
    buildFavoritesTree();

    /** Request Save of Settings */
    SettingsManager.getInstance().requestSave();
  }

  /**
   * Rename the given Favorite using the new title
   * 
   * @param favorite The favorite to rename
   * @param newTitle The new title of the favorite
   */
  void renameFavorite(Favorite favorite, String newTitle) {

    /** New Title not set, title exists or no change at all */
    if (!StringShop.isset(newTitle) || Category.titleExists(newTitle) || favorite.getTitle().equals(newTitle))
      return;

    /** Create a new favorite and rebuild the Tree */
    Favorite newFavorite = new Favorite(favorite.getUrl(), newTitle, favorite.getRSSOwlCategory());
    favorite.clone(newFavorite);
    favorite.getRSSOwlCategory().editFavorite(favorite, newFavorite);

    /** Update Selection */
    GlobalSettings.selectedTreeItem = favorite.getRSSOwlCategory().toCatPath() + StringShop.CAT_TOKENIZER + newTitle;

    /** Build Tree */
    buildFavoritesTree();

    /** Request Save of Settings */
    SettingsManager.getInstance().requestSave();
  }

  /**
   * Rename the selected TreeItem (in place)
   */
  void renameSelectedItem() {

    /** Selection must be given */
    if (favoritesTree.getSelectionCount() <= 0)
      return;

    /** Rename the selection */
    rename(favoritesTree.getSelection()[0]);
  }

  /** Update on Selection Changed */
  void selectionChanged() {
    TreeItem selection[] = favoritesTree.getSelection();

    /** At least One selection in the tree */
    if (selection.length > 0) {

      /** TreeItem data */
      TreeItem selectedTreeItem = selection[0];
      TreeItemData data = (TreeItemData) selectedTreeItem.getData();

      /** User has selected a category */
      if (data.isCategory())
        MenuManager.notifyState(MenuManager.TREE_SELECTION_CATEGORY);

      /** User has selected a blogroll */
      else if (data.isBlogroll())
        MenuManager.notifyState(MenuManager.TREE_SELECTION_BLOGROLL_CAT);

      /** User has selected a favorite */
      else if (data.isFavorite())
        MenuManager.notifyState(MenuManager.TREE_SELECTION_FAVORITE);

      /** User has selected a blogroll favorite */
      else if (data.isBlogrollFavorite())
        MenuManager.notifyState(MenuManager.TREE_SELECTION_BLOGROLL_FAV);

      /** Try to Link Selection to TabFolder */
      if (GlobalSettings.isDoubleClickOpen && GlobalSettings.linkTreeWithTab && !GlobalSettings.displaySingleTab) {
        NewsTabFolder newsTabFolder = rssOwlGui.getRSSOwlNewsTabFolder();
        CTabItem feedTabItem = newsTabFolder.getFeedTabItem(data.getName());

        /** Set selection to this item */
        if (WidgetShop.isset(feedTabItem)) {
          newsTabFolder.getNewsHeaderTabFolder().setSelection(feedTabItem);
          newsTabFolder.updateTabFolderState();
        }
      }
    }

    /** No selection in the tree */
    else {
      MenuManager.notifyState(MenuManager.TREE_SELECTION_EMPTY);
    }
  }

  /**
   * Set the checed state of the linkTab ToolItem.
   * 
   * @param checked If TRUE, show the Item checked.
   */
  void setLinkWithTabChecked(boolean checked) {
    if (linkTab.getSelection() != checked)
      linkTab.setSelection(checked);
  }
}