/*   **********************************************************************  **
 **   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.forms.Hyperlink;
import net.sourceforge.rssowl.controller.forms.HyperlinkAdapter;
import net.sourceforge.rssowl.controller.forms.HyperlinkEvent;
import net.sourceforge.rssowl.controller.panel.BrowserPanel;
import net.sourceforge.rssowl.controller.thread.AmphetaRateThread;
import net.sourceforge.rssowl.model.Enclosure;
import net.sourceforge.rssowl.model.NewsItem;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.i18n.ITranslatable;
import net.sourceforge.rssowl.util.shop.BrowserShop;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.LayoutDataShop;
import net.sourceforge.rssowl.util.shop.LayoutShop;
import net.sourceforge.rssowl.util.shop.PaintShop;
import net.sourceforge.rssowl.util.shop.RegExShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.URLShop;
import net.sourceforge.rssowl.util.shop.WidgetShop;
import net.sourceforge.rssowl.util.shop.XMLShop;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.MouseMoveListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
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.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.jdom.Text;

import java.util.Iterator;
import java.util.Vector;

/**
 * This is the ViewForm where a newsitem is displayed with Title, URL to the
 * news and the description.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class NewsText implements ITranslatable, IFontChangeable {
  private BrowserPanel browserPanel;
  private SashForm contentSash;
  private MenuItem copyItemUrl;
  private Composite descriptionHolder;
  private boolean isShowingNews;
  private Menu linkMenu;
  private Composite newsContentHolder;
  private ViewForm newsContentViewForm;
  private Composite newsContentViewFormHeader;
  private Composite newsTextHolder;
  private MenuItem openExternalItem;
  private MenuItem printNews;
  private MenuItem rateBad;
  private MenuItem rateFantastic;
  private MenuItem rateGood;
  private MenuItem rateModerate;
  private MenuItem rateVeryBad;
  private boolean requiresUpdate;
  private MenuItem selectAllItem;
  private MenuItem useBrowserForNewsText;
  MenuItem copyItem;
  Display display;
  EventManager eventManager;
  boolean isReset;
  int lastRate;
  StyledText newsText;
  Menu newsTextMenu;
  StyledText newsTextTitle;
  ToolBar newsTextToolBar;
  Hyperlink newsTextUrl;
  MenuItem openInBrowserItem;
  Composite rateComposite;
  Menu rateDropdown;
  ToolItem rateToolItem;
  NewsItem rssNewsItem;
  GUI rssOwlGui;
  Shell shell;
  Composite urlTitleHolder;
  int x;
  int y;

  /**
   * Instantiate a new NewsText to display a newsitem
   * 
   * @param display The display
   * @param shell The shell
   * @param contentSash The Sashform where to add the ViewForm
   * @param rssOwlGui Access some methods of the main controller
   * @param eventManager The event manager
   */
  public NewsText(Display display, Shell shell, SashForm contentSash, GUI rssOwlGui, EventManager eventManager) {
    this.display = display;
    this.shell = shell;
    this.contentSash = contentSash;
    this.rssOwlGui = rssOwlGui;
    this.eventManager = eventManager;
    isReset = true;
    isShowingNews = false;
    requiresUpdate = true;
    lastRate = AmphetaRateThread.RATE_MODERATE;
    initComponents();
  }

  /**
   * Get the BrowserPanel of the Newstext panel in case internal browser view is
   * activated for newstext
   * 
   * @return BrowserPanel The browserpanel for the newstext
   */
  public BrowserPanel getBrowserPanel() {
    return browserPanel;
  }

  /**
   * Let other objects access this controll (for example to focus on it)
   * 
   * @return NewsTextTitle StyledText
   */
  public StyledText getNewsTextTitle() {
    return newsTextTitle;
  }

  /**
   * Get the currently viewed newsitem's text to print.
   * 
   * @return String Text to print
   */
  public String getPrintableNewstext() {
    String printableText = "";

    /** Create printable description */
    if (rssNewsItem != null)
      printableText = rssNewsItem.toPrintable();

    return printableText;
  }

  /**
   * Set the NewsText-ToolBar visible or invisible based on the given argument.
   * 
   * @param visible Set to TRUE if the Toolbar is to be visible.
   */
  public void setNewsTextToolBarVisible(boolean visible) {
    if (WidgetShop.isset(newsTextToolBar))
      newsTextToolBar.setVisible(visible);
  }

  /**
   * Explicitly marks the newstext as required to update. This is called when
   * the user has clicked into a news.
   */
  public void setRequiresUpdate() {
    requiresUpdate = true;
  }

  /**
   * @see net.sourceforge.rssowl.controller.IFontChangeable#updateFonts()
   */
  public void updateFonts() {

    /** Update StyledText font */
    if (WidgetShop.isset(newsText)) {
      newsText.setFont(FontShop.textFont);
    }

    /** Update Browser Font */
    else if (browserPanel != null && rssNewsItem != null && WidgetShop.isset(browserPanel.getBrowser())) {
      rssNewsItem.setRequiresViewUpdate(true);
      displayNews(rssNewsItem);
    }

    newsTextTitle.setFont(FontShop.textBoldFont);
    newsTextUrl.setFont(FontShop.textFont);
  }

  /** Update all controlls text with i18n */
  public void updateI18N() {
    rateFantastic.setText(GUI.i18n.getTranslation("RATE_FANTASTIC"));
    rateGood.setText(GUI.i18n.getTranslation("RATE_GOOD"));
    rateModerate.setText(GUI.i18n.getTranslation("RATE_MODERATE"));
    rateBad.setText(GUI.i18n.getTranslation("RATE_BAD"));
    rateVeryBad.setText(GUI.i18n.getTranslation("RATE_VERY_BAD"));
    rateToolItem.setToolTipText(GUI.i18n.getTranslation("TOOLTIP_RATE"));
    copyItemUrl.setText(GUI.i18n.getTranslation("POP_COPY"));

    if (openExternalItem != null)
      openExternalItem.setText(GUI.i18n.getTranslation("POP_OPEN_EXTERN"));

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

    /** Update non-browser controlls */
    if (!GlobalSettings.useBrowserForNewsText) {

      /** Only update if newstext was created before and was not disposed */
      if (WidgetShop.isset(newsText)) {
        updateAccelerators();
        openInBrowserItem.setText(GUI.i18n.getTranslation("POP_OPEN_IN_BROWSER"));

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

  /** Update color of Link Label */
  public void updateLinkColor() {
    newsTextUrl.setForeground(PaintShop.linkColor);
  }

  /**
   * Create the browser panel to display newstext in the internal browser
   * 
   * @param onStartup If TRUE this method is called on startup of RSSOwl
   */
  private void createBrowserPanel(boolean onStartup) {
    try {
      browserPanel = new BrowserPanel(rssOwlGui, descriptionHolder, false);
      browserPanel.getBrowserPanel().setLayoutData(LayoutDataShop.createFormData(0, 0, 0, 0));
    }

    /** If OS fails to build internal browser, dont use it! */
    catch (SWTError e) {
      GUI.logger.log("displayNewsInBrowser()", e);

      /** Set browser to null explicitly */
      if (browserPanel != null) {
        browserPanel.dispose();
        browserPanel = null;
      }

      /** Display the news in the styled text */
      GlobalSettings.useBrowserForNewsText = false;

      /** Reset MenuItem and ToolItem */
      rssOwlGui.getRSSOwlMenu().setUseBrowserForNewsText(GlobalSettings.useBrowserForNewsText);

      /** Show info to the user if not called from startup */
      if (!onStartup) {
        MessageBoxFactory.showError(shell, BrowserShop.createBrowserError(e));
        eventManager.actionOpenFAQOnBrowser();
      }

      /** Create styled text and display news */
      createStyledText();

      /** Recreate the Newstext using StyledText */
      rebuildNewsText();
    }
  }

  /**
   * Create the StyledText to display the newstext. This method is called
   * whenever the user switches from the internal browser view to the StyledText
   * view.
   */
  private void createStyledText() {

    /** StyledText to display the news description */
    newsText = new StyledText(descriptionHolder, SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
    newsText.setLayoutData(LayoutDataShop.createFormData(8, 0, 8, -1));
    newsText.setFont(FontShop.textFont);
    newsText.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
    newsText.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
    newsText.setCaret(null);

    /** Remove selection from Title and URL if selected */
    newsText.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (newsTextTitle != null)
          newsTextTitle.setSelection(0);
      }
    });

    /** Listen for the mouseposition and check if the mouse is over an URL * */
    newsText.addMouseMoveListener(new MouseMoveListener() {
      public void mouseMove(MouseEvent e) {
        onMouseMove(e);
      }
    });

    /** Handle Mouse Up Evnt */
    newsText.addMouseListener(new MouseAdapter() {
      public void mouseUp(MouseEvent e) {
        onMouseUp(e);
      }
    });

    /** A popupmenu for the StyledText */
    newsTextMenu = new Menu(newsText);

    /** Display newstext in internal browser if possible */
    if (GlobalSettings.useInternalBrowser()) {
      useBrowserForNewsText = new MenuItem(newsTextMenu, SWT.NONE);
      if (!GlobalSettings.isMac()) {
        useBrowserForNewsText.setImage(PaintShop.loadImage("/img/icons/browserview.gif"));
        useBrowserForNewsText.addDisposeListener(DisposeListenerImpl.getInstance());
      }
      useBrowserForNewsText.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
          eventManager.actionSetBrowserView(true);
        }
      });

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

    /** Copy selection */
    copyItem = new MenuItem(newsTextMenu, SWT.NONE);
    if (!GlobalSettings.isMac())
      copyItem.setImage(PaintShop.iconCopy);
    copyItem.addSelectionListener(new SelectionAdapter() {

      /** Copy selection to clipboard */
      public void widgetSelected(SelectionEvent e) {
        if (!newsText.getSelectionText().equals("")) {
          newsText.copy();
        }
      }
    });

    /** Select all text of the StyledText */
    selectAllItem = new MenuItem(newsTextMenu, SWT.NONE);
    selectAllItem.addSelectionListener(new SelectionAdapter() {

      /** Selection the whole text */
      public void widgetSelected(SelectionEvent e) {
        newsText.selectAll();
      }
    });

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

    /** Printing is only supported on Windows and Mac */
    if (GlobalSettings.usePrinting()) {

      /** Print news */
      printNews = new MenuItem(newsTextMenu, SWT.NONE);
      if (!GlobalSettings.isMac())
        printNews.setImage(PaintShop.iconPrint);
      printNews.addSelectionListener(new SelectionAdapter() {

        /** Print news */
        public void widgetSelected(SelectionEvent e) {
          eventManager.actionPrintNews();
        }
      });

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

    /** Open the selected text in the browser */
    openInBrowserItem = new MenuItem(newsTextMenu, SWT.NONE);
    openInBrowserItem.setText(GUI.i18n.getTranslation("POP_OPEN_IN_BROWSER"));
    openInBrowserItem.addSelectionListener(new SelectionAdapter() {

      /** Open the selection in browser */
      public void widgetSelected(SelectionEvent e) {
        eventManager.actionOpenURL(newsText.getSelectionText());
      }
    });

    /** Dynamically Enable / Disable MenuItems */
    newsTextMenu.addMenuListener(new MenuAdapter() {
      public void menuShown(MenuEvent e) {

        /** First check Widget */
        if (!WidgetShop.isset(newsText))
          return;

        /** User has selected text */
        if (newsText.getSelectionText().equals("")) {
          copyItem.setEnabled(false);
          openInBrowserItem.setEnabled(false);
        }

        /** User has not selected text */
        else {
          copyItem.setEnabled(true);
          openInBrowserItem.setEnabled(true);
        }
      }
    });

    /** Set Accelerators */
    updateAccelerators();

    /** Set Mnemonics */
    MenuManager.initMnemonics(newsTextMenu);

    /** Apply menu */
    newsText.setMenu(newsTextMenu);
  }

  /**
   * Display news in the ViewForm
   * 
   * @param rssNewsItem The newsitem to display
   */
  private void displayNews(NewsItem rssNewsItem) {

    /** Return in case the news is already showing and an update is not req. */
    if (!requiresUpdate && isShowingNews(rssNewsItem))
      return;

    /** This is a new news to display or no news was displayed */
    this.rssNewsItem = rssNewsItem;

    /** Update flags */
    isShowingNews = true;
    requiresUpdate = false;
    this.rssNewsItem.setRequiresViewUpdate(false);

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

    /** Get Link */
    String link = rssNewsItem.getLink();
    if (!StringShop.isset(link) && StringShop.isset(rssNewsItem.getGuid()) && RegExShop.isValidURL(rssNewsItem.getGuid()))
      link = rssNewsItem.getGuid();

    /** If this newsItem has no description and the user chose to directOpen */
    if (!StringShop.isset(rssNewsItem.getDescription()) && GlobalSettings.directOpenNews && StringShop.isset(link))
      displayNewsLinkInBrowser(link);

    /** User chose to open each news in Browser */
    else if (GlobalSettings.directOpenEachNews && StringShop.isset(link))
      displayNewsLinkInBrowser(link);

    /** Display news in the internal browser */
    else if (GlobalSettings.useBrowserForNewsText)
      displayNewsInBrowser();

    /** Display news in a StyledText */
    else
      displayNewsInStyledText();

    /** Show Rate Dropdown */
    // rateComposite.setVisible(true);

    /** Layout components */
    descriptionHolder.layout();
  }

  /**
   * Display the selected news in the internal browser. This will display Title
   * and URL of the news as usual and show the description (plus possible
   * enclosures, comments, source) in the internal browser.
   */
  private void displayNewsInBrowser() {

    /** Display title and link of the news */
    displayNewsMetaInfo();

    /** Create the browserPanel if not yet done */
    if (browserPanel == null)
      createBrowserPanel(false);

    /** Let the browser display the rest of the news */
    if (browserPanel != null)
      browserPanel.displayNews(rssNewsItem);

    /** Browser creation failed, display news in StyledText */
    else
      displayNews(rssNewsItem);
  }

  /**
   * Display selected news in a StyledText
   */
  private void displayNewsInStyledText() {

    /** Create the StyledText if not yet done */
    if (newsText == null || newsText.isDisposed())
      createStyledText();

    /** Display title and link of the news */
    displayNewsMetaInfo();

    /** Update Links in Copy Menu */
    int items = newsTextMenu.getItemCount();
    int linksBegin = 4 + ((GlobalSettings.useInternalBrowser()) ? 2 : 0) + ((GlobalSettings.usePrinting()) ? 2 : 0);
    for (int i = linksBegin; i < items; i++)
      newsTextMenu.getItem(linksBegin).dispose();

    /** Description of the news */
    if (StringShop.isset(rssNewsItem.getDescription())) {
      newsText.setText(XMLShop.stripSimpleHTMLTags(Text.normalizeString(rssNewsItem.getDescription())));

      /** Add the Links from the description to the popupmenu */
      if (rssNewsItem.getLinkList() != null && rssNewsItem.getLinkList().size() > 0) {

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

        Iterator linkListIt = rssNewsItem.getLinkList().iterator();

        /** Foreach link in the text */
        while (linkListIt.hasNext()) {

          /** Add a menuitem foreach URL from the text */
          final MenuItem linkFromText = new MenuItem(newsTextMenu, SWT.NONE);
          linkFromText.setText((String) linkListIt.next());
          if (!GlobalSettings.isMac()) {
            linkFromText.setImage(PaintShop.iconWorld);
          }
          linkFromText.addSelectionListener(new SelectionAdapter() {

            /** URL openes after click on it */
            public void widgetSelected(SelectionEvent e) {
              eventManager.actionOpenURL(linkFromText.getText(), linkFromText.getText().equals(rssNewsItem.getOrigurl()));
            }
          });
        }
      }
    }

    /** Selected news has no description */
    else {
      newsText.setText(GUI.i18n.getTranslation("NEWS_NO_DESCRIPTION"));
    }

    /** Vector containing all StyleRanges for the Newstext */
    Vector styleRanges = new Vector();

    /** Paint clickable links in selected link-color */
    if (rssNewsItem.getLinkList() != null)
      styleRanges.addAll(WidgetShop.calculateStyleRanges(newsText, new Vector(rssNewsItem.getLinkList()), PaintShop.linkColor, display.getSystemColor(SWT.COLOR_WHITE), SWT.NORMAL, false, true));

    /** Highlight words if serch was performed */
    if (rssNewsItem.getHighlightWords() != null)
      styleRanges.addAll(WidgetShop.calculateStyleRanges(newsText, rssNewsItem.getHighlightWords(), display.getSystemColor(SWT.COLOR_BLACK), PaintShop.syntaxHighlightColor, 0, rssNewsItem.isSearchCaseSensitive(), false));

    /** Enclosures */
    if (rssNewsItem.getEnclosures() != null && rssNewsItem.getEnclosures().size() > 0) {
      String enclosureTitle = "\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_ENCLOSURE") + ":\n";
      newsText.append(enclosureTitle);

      /** Highlight */
      styleRanges.addAll(WidgetShop.calculateStyleRanges(newsText, StringShop.toVector(new String[] { GUI.i18n.getTranslation("NEWS_ITEM_INFO_ENCLOSURE") + ":" }), display.getSystemColor(SWT.COLOR_BLACK), display.getSystemColor(SWT.COLOR_WHITE), SWT.BOLD, true, false));

      /** Give out all enclosures */
      for (int a = 0; a < rssNewsItem.getEnclosures().size(); a++) {
        Enclosure enclosure = (Enclosure) rssNewsItem.getEnclosures().get(a);
        newsText.append("\n" + enclosure.getUrl() + " (" + enclosure.getType() + " / " + enclosure.getLength(true) + ")");
      }
    }

    /** Source of the news */
    if (rssNewsItem.getSource() != null) {
      String source = "\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_SOURCE") + ":  " + rssNewsItem.getSource();
      newsText.append(source);

      /** Highlight */
      styleRanges.addAll(WidgetShop.calculateStyleRanges(newsText, StringShop.toVector(new String[] { GUI.i18n.getTranslation("NEWS_ITEM_INFO_SOURCE") + ":" }), display.getSystemColor(SWT.COLOR_BLACK), display.getSystemColor(SWT.COLOR_WHITE), SWT.BOLD, true, false));
    }

    /** Comments to this news */
    if (rssNewsItem.getComments() != null && rssNewsItem.getComments().size() > 0) {
      newsText.append("\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_COMMENTS") + ":\n");

      /** Highlight */
      styleRanges.addAll(WidgetShop.calculateStyleRanges(newsText, StringShop.toVector(new String[] { GUI.i18n.getTranslation("NEWS_ITEM_INFO_COMMENTS") + ":" }), display.getSystemColor(SWT.COLOR_BLACK), display.getSystemColor(SWT.COLOR_WHITE), SWT.BOLD, true, false));

      /** Give out all comments */
      for (int a = 0; a < rssNewsItem.getComments().size(); a++)
        if (rssNewsItem.getComments().get(a) != null && !rssNewsItem.getComments().get(a).equals(""))
          newsText.append("\n" + rssNewsItem.getComments().get(a));
    }

    /** Add StyleRanges */
    WidgetShop.highlightText(newsText, styleRanges);
  }

  /**
   * Display the Link of the News using the internal browser.
   * 
   * @param link The valid link of the Newsitem to display.
   */
  private void displayNewsLinkInBrowser(String link) {

    /** Show the link inside the newstext panel */
    if (GlobalSettings.useBrowserForNewsText) {

      /** Display title and link of the news */
      displayNewsMetaInfo();

      /** Create the browserPanel if not yet done */
      if (browserPanel == null)
        createBrowserPanel(false);

      /** Let the browser display the rest of the news */
      if (browserPanel != null) {

        /** Reset to empty page */
        browserPanel.getBrowser().setUrl(URLShop.ABOUT_BLANK);

        /** Set visibility */
        if (!browserPanel.getBrowserPanel().isVisible())
          browserPanel.getBrowserPanel().setVisible(true);

        /** Load Link */
        browserPanel.setBlockNavigation(false);
        browserPanel.getBrowser().setUrl(link);
      }

      /** Browser creation failed, display news in StyledText */
      else
        displayNews(rssNewsItem);
    }

    /** Show the link inside a new Tab or external */
    else {
      eventManager.actionOpenURL(link);
    }
  }

  /**
   * Display Title and link of the news
   */
  private void displayNewsMetaInfo() {

    /** Title of the news */
    if (rssNewsItem.getTitle() != null) {
      newsTextTitle.setText(rssNewsItem.getTitle());

      /** Highlight words if serch was performed */
      if (rssNewsItem.getHighlightWords() != null)
        WidgetShop.highlightText(newsTextTitle, rssNewsItem.getHighlightWords(), display.getSystemColor(SWT.COLOR_BLACK), PaintShop.syntaxHighlightColor, 0, rssNewsItem.isSearchCaseSensitive(), false);
    }

    /** Title somehow not given */
    else {
      newsTextTitle.setText(GUI.i18n.getTranslation("NO_TITLE"));
    }

    /** Link of the news */
    if (rssNewsItem.getLink() != null)
      newsTextUrl.setText(rssNewsItem.getLink());

    /** Try the guid as link */
    else if (rssNewsItem.getGuid() != null && RegExShop.isValidURL(rssNewsItem.getGuid()))
      newsTextUrl.setText(rssNewsItem.getGuid());

    /** Clear newsTextUrl in any other case */
    else
      newsTextUrl.setText("");

    /** Remove tooltip if URL is not given */
    if (newsTextUrl.getText().equals("")) {
      newsTextUrl.setToolTipText(null);
      newsTextUrl.setVisible(false);
    }

    /** Set tooltip and cursor if URL is given */
    else {
      newsTextUrl.setVisible(true);
    }

    /** Layout the ViewForm */
    newsContentViewForm.layout(false);

    /** Show AmphetaRate status label */
    String rateLabelMsg = "";
    if (AmphetaRateThread.ratedNews.size() > 0) {
      Integer rating = (Integer) AmphetaRateThread.ratedNews.get(rssNewsItem.toAmphetaRateURL());

      /** News was rated before, show rating level */
      if (rating != null)
        rateLabelMsg = GUI.i18n.getTranslation("LABEL_NEWS_RATED") + ": " + AmphetaRateThread.getTranslatedRatingLevel(rating.intValue());
    }

    /** Update Statusline Rate Label */
    rssOwlGui.getRSSOwlStatusLine().updateRateLabel(rateLabelMsg);
  }

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

    /** Composite holding the View of a news */
    newsContentHolder = new Composite(contentSash, SWT.NONE);
    newsContentHolder.setLayout(new FillLayout());

    /** ViewForm holding the news text */
    int style = GlobalSettings.isMac() ? SWT.BORDER | SWT.FLAT : SWT.BORDER;
    newsContentViewForm = new ViewForm(newsContentHolder, style);
    newsContentViewForm.setTopCenterSeparate(true, false);

    /** Composite holding URL, Title and Rate Button */
    newsContentViewFormHeader = new Composite(newsContentViewForm, SWT.NONE);
    newsContentViewFormHeader.setLayout(LayoutShop.createGridLayout(2, 0, 0));
    newsContentViewFormHeader.setBackground(PaintShop.grayViewFormColor);

    /** Holder for Title and URL */
    urlTitleHolder = new Composite(newsContentViewFormHeader, SWT.NONE);
    urlTitleHolder.setBackground(PaintShop.grayViewFormColor);
    urlTitleHolder.setLayout(LayoutShop.createGridLayout(1, 7, 8));
    urlTitleHolder.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 2));

    /** Display the title of the news text */
    newsTextTitle = new StyledText(urlTitleHolder, SWT.READ_ONLY | SWT.WRAP);
    newsTextTitle.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
    ((GridData) newsTextTitle.getLayoutData()).horizontalIndent = 1;
    newsTextTitle.setBackground(PaintShop.grayViewFormColor);
    newsTextTitle.setFont(FontShop.textBoldFont);
    newsTextTitle.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
    newsTextTitle.setCaret(null);

    /** Remove selection from URL and description if selected */
    newsTextTitle.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (WidgetShop.isset(newsText))
          newsText.setSelection(0);
      }
    });

    /**
     * This composite is used to display the Hyperlink widget. It is needed
     * because of a bug that will overflow the text on the right-end out of the
     * parent widget.
     */
    Composite urlHolder = new Composite(urlTitleHolder, SWT.WRAP);
    urlHolder.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
    urlHolder.setLayout(LayoutShop.createGridLayout(1, 0, 0));
    urlHolder.setBackground(PaintShop.grayViewFormColor);

    /** URL of the news text as Hyperlink Widget */
    newsTextUrl = new Hyperlink(urlHolder, SWT.WRAP | SWT.NO_FOCUS);
    newsTextUrl.setBackground(PaintShop.grayViewFormColor);
    newsTextUrl.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
    newsTextUrl.setForeground(PaintShop.linkColor);
    newsTextUrl.setFont(FontShop.textFont);
    newsTextUrl.setUnderlined(true);

    /** We have to init the link with a String */
    newsTextUrl.setText("");

    /** Allow to Drag the Link */
    final DragSource linkDragSource = new DragSource(newsTextUrl, DND.DROP_MOVE | DND.DROP_COPY);
    linkDragSource.setTransfer(new Transfer[] { TextTransfer.getInstance() });
    linkDragSource.addDragListener(new DragSourceAdapter() {

      /** Set URL Data Slot */
      public void dragSetData(DragSourceEvent event) {
        if (StringShop.isset(newsTextUrl.getText()))
          event.data = newsTextUrl.getText();
      }

      /** Dragging has begun */
      public void dragStart(DragSourceEvent event) {
        event.doit = StringShop.isset(newsTextUrl.getText());
      }
    });

    /** Dispose Drag Support on Link Dispose */
    newsTextUrl.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        linkDragSource.dispose();
      }
    });

    /** Popup menue for the newsTextUrl component */
    linkMenu = new Menu(newsTextTitle);
    copyItemUrl = new MenuItem(linkMenu, SWT.NONE);
    copyItemUrl.setText(GUI.i18n.getTranslation("POP_COPY"));
    if (!GlobalSettings.isMac())
      copyItemUrl.setImage(PaintShop.iconCopy);
    copyItemUrl.addSelectionListener(new SelectionAdapter() {

      /** Copy URL to clipboard */
      public void widgetSelected(SelectionEvent e) {
        eventManager.actionCopyText(newsTextUrl);
      }
    });

    /** Offer the user the possibility to open the link external */
    if (GlobalSettings.useInternalBrowser() && !GlobalSettings.openBrowserExtern) {

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

      openExternalItem = new MenuItem(linkMenu, SWT.NONE);
      openExternalItem.setText(GUI.i18n.getTranslation("POP_OPEN_EXTERN"));
      openExternalItem.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
          if (StringShop.isset(newsTextUrl.getText()))
            eventManager.actionOpenURLExternal(newsTextUrl.getText());
        }
      });
    }

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

    /** Apply menu */
    newsTextUrl.setMenu(linkMenu);

    /** Open URL after click */
    newsTextUrl.addHyperlinkListener(new HyperlinkAdapter() {
      public void linkActivated(HyperlinkEvent e) {
        boolean openExternal = (e.getStateMask() & SWT.CTRL) != 0 || (e.getStateMask() & SWT.COMMAND) != 0;
        if (StringShop.isset(newsTextUrl.getText())) {
          if (openExternal)
            eventManager.actionOpenURLExternal(newsTextUrl.getText());
          else
            eventManager.actionOpenURL(newsTextUrl.getText());
        }
      }
    });

    /** Composite for the rate-dropdown and the info textfield */
    rateComposite = new Composite(newsContentViewFormHeader, SWT.NONE);
    rateComposite.setLayout(LayoutShop.createGridLayout(2, 0, GlobalSettings.isWindows() ? 4 : 0));
    rateComposite.setBackground(PaintShop.grayViewFormColor);
    rateComposite.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING));

    /** Toolbar that holds the rate-dropdown */
    newsTextToolBar = new ToolBar(rateComposite, SWT.FLAT);
    newsTextToolBar.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING));
    newsTextToolBar.setBackground(PaintShop.grayViewFormColor);

    /** Dropdown to rate a newstext */
    rateDropdown = new Menu(newsContentViewForm);

    /** AmphetaRate Level: Fantastic */
    rateFantastic = new MenuItem(rateDropdown, SWT.RADIO);
    rateFantastic.setText(GUI.i18n.getTranslation("RATE_FANTASTIC"));
    rateFantastic.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (((MenuItem) e.getSource()).getSelection()) {
          lastRate = AmphetaRateThread.RATE_FANTASTIC;
          eventManager.actionRateNews(rssNewsItem, lastRate);
        }
      }
    });

    /** AmphetaRate Level: Good */
    rateGood = new MenuItem(rateDropdown, SWT.RADIO);
    rateGood.setText(GUI.i18n.getTranslation("RATE_GOOD"));
    rateGood.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (((MenuItem) e.getSource()).getSelection()) {
          lastRate = AmphetaRateThread.RATE_GOOD;
          eventManager.actionRateNews(rssNewsItem, lastRate);
        }
      }
    });

    /** AmphetaRate Level: Moderate */
    rateModerate = new MenuItem(rateDropdown, SWT.RADIO);
    rateModerate.setText(GUI.i18n.getTranslation("RATE_MODERATE"));
    rateModerate.setSelection(true);
    rateModerate.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (((MenuItem) e.getSource()).getSelection()) {
          lastRate = AmphetaRateThread.RATE_MODERATE;
          eventManager.actionRateNews(rssNewsItem, lastRate);
        }
      }
    });

    /** AmphetaRate Level: Bad */
    rateBad = new MenuItem(rateDropdown, SWT.RADIO);
    rateBad.setText(GUI.i18n.getTranslation("RATE_BAD"));
    rateBad.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (((MenuItem) e.getSource()).getSelection()) {
          lastRate = AmphetaRateThread.RATE_BAD;
          eventManager.actionRateNews(rssNewsItem, lastRate);
        }
      }
    });

    /** AmphetaRate Level: Very Bad */
    rateVeryBad = new MenuItem(rateDropdown, SWT.RADIO);
    rateVeryBad.setText(GUI.i18n.getTranslation("RATE_VERY_BAD"));
    rateVeryBad.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (((MenuItem) e.getSource()).getSelection()) {
          lastRate = AmphetaRateThread.RATE_VERY_BAD;
          eventManager.actionRateNews(rssNewsItem, lastRate);
        }
      }
    });

    /** ToolItem to rate */
    rateToolItem = new ToolItem(newsTextToolBar, SWT.DROP_DOWN);
    rateToolItem.setImage(PaintShop.loadImage("/img/icons/rate.gif"));
    rateToolItem.setToolTipText(GUI.i18n.getTranslation("TOOLTIP_RATE"));
    rateToolItem.addDisposeListener(DisposeListenerImpl.getInstance());

    /** This listener will place the popup menue to the right place */
    rateToolItem.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event event) {

        /** Make popupmenue visible */
        if (event.detail == SWT.ARROW) {
          Rectangle rect = rateToolItem.getBounds();
          Point pt = new Point(rect.x, rect.y + rect.height);
          pt = newsTextToolBar.toDisplay(pt);
          rateDropdown.setLocation(pt.x, pt.y);
          rateDropdown.setVisible(true);
        }

        /** Perform a rating on the news */
        else {
          rateNews(rssNewsItem, lastRate);
        }
      }
    });

    /** Apply Header to ViewForm */
    newsContentViewForm.setTopCenter(newsContentViewFormHeader, false);

    /** TextArea that displays the news text */
    newsTextHolder = new Composite(newsContentViewForm, SWT.NONE);
    newsTextHolder.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
    newsTextHolder.setLayout(LayoutShop.createGridLayout(1, 0, 0));

    /** Holder for the main text of the news */
    descriptionHolder = new Composite(newsTextHolder, SWT.NONE);
    descriptionHolder.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
    descriptionHolder.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_BOTH, 1));
    descriptionHolder.setLayout(new FormLayout());

    /** Apply control to viewform */
    newsContentViewForm.setContent(newsTextHolder, true);

    /** Newstext is empty at begin */
    MenuManager.notifyState(MenuManager.NEWS_TEXT_EMPTY);

    /** Create the browserPanel to display news if set so */
    if (GlobalSettings.useBrowserForNewsText)
      createBrowserPanel(true);

    /** Create the styledtext to display news if set so */
    else
      createStyledText();
  }

  /**
   * Check if the given NewsItem is currently being displayed
   * 
   * @param newsItem The newsitem to check if being displayed
   * @return boolean TRUE in case the newsitem is currently displayed
   */
  private boolean isShowingNews(NewsItem newsItem) {
    return isShowingNews && rssNewsItem != null && rssNewsItem.sameAs(newsItem) && !rssNewsItem.getRequiresViewUpdate();
  }

  /**
   * Rebuild the newstext area
   */
  private void rebuildNewsText() {
    newsContentHolder.dispose();
    initComponents();
    contentSash.layout();
  }

  /** Update the accelerators on the menuitems */
  private void updateAccelerators() {
    if (GlobalSettings.usePrinting())
      rssOwlGui.getRSSOwlMenu().updateAccelerator(printNews, "TOOLTIP_PRINT", true, false);
    if (GlobalSettings.useInternalBrowser())
      rssOwlGui.getRSSOwlMenu().updateAccelerator(useBrowserForNewsText, "MENU_BROWSER_FOR_NEWSTEXT", false, false);
    rssOwlGui.getRSSOwlMenu().updateAccelerator(selectAllItem, "MENU_EDIT_SELECT_ALL", false, false);
    rssOwlGui.getRSSOwlMenu().updateAccelerator(copyItem, "MENU_EDIT_COPY", false, false);
  }

  /**
   * Get the word from the position where the mouse is and check if the word is
   * an URL. If an URL was found the setData() method of the StyledText is
   * called and the URL is inserted as String. Otherwise the setData() method is
   * called with NULL as argument.
   * 
   * @param text StyledText to check the word
   */
  void checkWord(StyledText text) {
    int offset = text.getOffsetAtLocation(new Point(x, y));
    int start = offset;
    int end = offset;

    boolean leftTerminatorFound = false;
    boolean rightTerminatorFound = false;

    /** Dont search if char is whitespace */
    if (text.getText(start, start).equals(" "))
      return;

    /** Search for the whole word */
    while (true) {

      /** Left URL Terminating Symbol Search */
      if (!leftTerminatorFound) {

        /** Check if left whitespace was found */
        if (StringShop.isTerminating(text.getText(start, start))) {
          leftTerminatorFound = true;
          start++;
        }

        /** End search, because first char is reached */
        else if (start == 0)
          leftTerminatorFound = true;

        /** Search at the next left position */
        else
          start--;
      }

      /** Right URL Terminating Symbol Search */
      if (!rightTerminatorFound) {

        /** Check if right whitespace was found */
        if (StringShop.isTerminating(text.getText(end, end))) {
          rightTerminatorFound = true;
          end--;
        }

        /** End search, because the last char is reached */
        else if (end == text.getText().length() - 1)
          rightTerminatorFound = true;

        /** Search at the next right position */
        else
          end++;
      }

      /** Complete word was found, end this search */
      if (leftTerminatorFound && rightTerminatorFound)
        break;
    }

    /** Get the word from the position where the mouse is */
    String curWord = text.getText(start, end).trim();

    /** Check if the word is a link */
    if (!curWord.equals("") && rssNewsItem.getLinkList() != null && rssNewsItem.getLinkList().contains(curWord)) {
      text.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
      text.setData(curWord);

      /** This is a link from AmphetaRate recommendation feed */
      if (curWord.equals(rssNewsItem.getOrigurl()))
        text.setToolTipText(GUI.i18n.getTranslation("BUTTON_ADDTO_FAVORITS"));

      /** this styledtext needs reset */
      isReset = false;
    }

    /** The word is not a link */
    else {
      if (!isReset) {
        text.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
        text.setData(null);
        text.setToolTipText(null);

        /** this styledtext is reseted */
        isReset = true;
      }
    }
  }

  /** Empty the newstext viewform */
  void clearNewstext() {

    /** Set Flag */
    isShowingNews = false;

    /** Clear newstext title */
    newsTextTitle.setText("");
    newsTextTitle.setStyleRange(null);

    /** Clear newstext url */
    newsTextUrl.setText("");
    newsTextUrl.setVisible(false);

    /** Remove newsitem */
    rssNewsItem = null;

    /** Clear rate info message */
    rssOwlGui.getRSSOwlStatusLine().updateRateLabel("");

    /** Clear browserPanel */
    if (GlobalSettings.useBrowserForNewsText && browserPanel != null)
      browserPanel.hideNoControlsBrowser();

    /** Clear newstext */
    else if (!GlobalSettings.useBrowserForNewsText && newsText != null)
      newsText.dispose();

    /** Hide Rate Dropdown */
    rateComposite.setVisible(false);
  }

  /**
   * Display the selected news in the newstext-viewform
   * 
   * @param urlOrTitle The URL / Path or Title of the RSS XML
   * @param newsTitle Title of the selected news
   */
  void displayNews(String urlOrTitle, String newsTitle) {
    NewsItem rssNewsItem = rssOwlGui.getRSSOwlNewsTabFolder().getSelectedNewsItem(newsTitle);

    /** Return if the selected news item is not available */
    if (rssNewsItem == null)
      return;

    /** Set newsfeed XML Url to this newsitem if it is not yet set */
    if (rssNewsItem.getNewsfeedXmlUrl() == null)
      rssNewsItem.setNewsfeedXmlUrl(urlOrTitle);

    /** Display the news in the NewsText ViewForm */
    displayNews(rssNewsItem);
  }

  /**
   * Called whenever the mouse is moving
   * 
   * @param e The occuring MouseEvent
   */
  void onMouseMove(MouseEvent e) {

    /** Save position */
    x = e.x;
    y = e.y;

    try {
      checkWord(newsText);
    }

    /** Mouse out of text range */
    catch (IllegalArgumentException f) {

      /** Reset cursor, data and tooltip text */
      if (!isReset) {
        newsText.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
        newsText.setData(null);
        newsText.setToolTipText(null);
      }

      /** Update flag */
      isReset = true;
    }
  }

  /**
   * Called whenever the mouse button was released
   * 
   * @param e The occuring MouseEvent
   */
  void onMouseUp(MouseEvent e) {

    /** Open URL after click if mouse is hovering above an URL */
    if (e.button == 1 && newsText.getSelectionCount() == 0 && newsText.getData() != null)
      eventManager.actionOpenURL((String) newsText.getData(), ((String) newsText.getData()).equals(rssNewsItem.getOrigurl()));

    /**
     * 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 (e.stateMask == (SWT.BUTTON1 | SWT.CTRL) && WidgetShop.isset(newsTextMenu))
        newsTextMenu.setVisible(true);
    }
  }

  /**
   * AmphetaRate has been Disabled for now!
   * 
   * @param rssNewsItem The newsitem to rate
   * @param rateLevel The rating level
   */
  void rateNews(NewsItem rssNewsItem, int rateLevel) {
    return;

    //    /** User has set an AmphetaRate account */
    //    if (AmphetaRateThread.isOldUser() || !GlobalSettings.amphetaRateUsername.equals("")) {
    //      boolean rateSuccessfull = AmphetaRateThread.rate(rssNewsItem, rateLevel);
    //
    //      /** Wake up submit thread to submit rating */
    //      rssOwlGui.getRSSOwlAmphetaRate().wakeUp();
    //
    //      /** Get the translated rating level */
    //      String rate = AmphetaRateThread.getTranslatedRatingLevel(rateLevel);
    //
    //      /** Rating was successfull */
    //      if (rateSuccessfull)
    //        rssOwlGui.getRSSOwlStatusLine().updateRateLabel(GUI.i18n.getTranslation("LABEL_NEWS_RATED") + ": " + rate);
    //
    //      /** Rating was not successfull */
    //      else
    //        rssOwlGui.getRSSOwlStatusLine().updateRateLabel("");
    //    }
    //
    //    /** User has not yet set an AmphetaRate user ID */
    //    else {
    //      MessageBoxFactory.showMessage(shell, SWT.ICON_WARNING, GUI.i18n.getTranslation("POP_RATE_NEWS"), GUI.i18n.getTranslation("DIALOG_ID_ATTENTION"));
    //
    //      /** Prompt for AmphetaRate User ID */
    //      PreferencesDialog.lastOpenedPropertyPage = 8;
    //      new PreferencesDialog(shell, GUI.i18n.getTranslation("MENU_PREFERENCES"), rssOwlGui).open();
    //    }
  }

  /**
   * Update the newstext composite to either show Internal Browser or StyledText
   * for the newstext
   */
  void updateNewsTextComposite() {

    /** Create the internal browserPanel */
    if (GlobalSettings.useBrowserForNewsText) {
      try {
        browserPanel = new BrowserPanel(rssOwlGui, descriptionHolder, false);
        browserPanel.getBrowserPanel().setLayoutData(LayoutDataShop.createFormData(0, 0, 0, 0));

        /** Dispose styledtext */
        if (newsText != null)
          newsText.dispose();
      }

      /** If OS fails to build internal browser, dont use it! */
      catch (SWTError e) {
        GUI.logger.log("updateNewsTextComposite()", e);
        GlobalSettings.useBrowserForNewsText = false;
        rssOwlGui.getRSSOwlMenu().useBrowserForNewsText.setSelection(GlobalSettings.useBrowserForNewsText);
        MessageBoxFactory.showError(shell, BrowserShop.createBrowserError(e));
        eventManager.actionOpenFAQOnBrowser();
        rebuildNewsText();
      }
    }

    /** Create the StyledText */
    else {

      /** Dispose the browserPanel if its not null */
      if (browserPanel != null)
        browserPanel.dispose();

      /** Create styledtext */
      createStyledText();
    }

    /** Layout update */
    newsTextHolder.update();
    newsTextHolder.layout();

    /** Set Flag */
    isShowingNews = false;

    /** Display current newsitem */
    if (this.rssNewsItem != null)
      displayNews(this.rssNewsItem);
  }
}