Package org.rssowl.ui.internal.editors.feed

Source Code of org.rssowl.ui.internal.editors.feed.NewsBrowserLabelProvider

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2009 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 v1.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 Development Team - initial API and implementation             **
**                                                                          **
**  **********************************************************************  */

package org.rssowl.ui.internal.editors.feed;

import static org.rssowl.ui.internal.ILinkHandler.HANDLER_PROTOCOL;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.ARCHIVE_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.ATTACHMENTS_MENU_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.ATTACHMENT_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.COLLAPSE_GROUP_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.COLLAPSE_NEWS_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.DELETE_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.EXPAND_NEWS_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.GROUP_MENU_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.LABELS_MENU_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.NEWS_MENU_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.NEXT_PAGE_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.RELATED_NEWS_MENU_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.SHARE_NEWS_MENU_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.TOGGLE_READ_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.TOGGLE_STICKY_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.TRANSFORM_HANDLER_ID;

import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.ui.PlatformUI;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
import org.rssowl.core.persist.IAttachment;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INews.State;
import org.rssowl.core.persist.IPerson;
import org.rssowl.core.persist.ISource;
import org.rssowl.core.persist.reference.NewsBinReference;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.persist.reference.SearchMarkReference;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.DateUtils;
import org.rssowl.core.util.ExpandingReader;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.URIUtils;
import org.rssowl.ui.internal.Activator;
import org.rssowl.ui.internal.Application;
import org.rssowl.ui.internal.ApplicationServer;
import org.rssowl.ui.internal.EntityGroup;
import org.rssowl.ui.internal.FolderNewsMark.FolderNewsMarkReference;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.PageLatch;
import org.rssowl.ui.internal.util.CBrowser;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* @author bpasero
*/
public class NewsBrowserLabelProvider extends LabelProvider {

  /* Date Formatter for News */
  private DateFormat fDateFormat = OwlUI.getLongDateFormat();

  /* Time Formatter for News */
  private DateFormat fTimeFormat = OwlUI.getShortTimeFormat();

  /* Potential Image Tags */
  private final Set<String> fImageTags = new HashSet<String>(Arrays.asList(new String[] { "img", "map", "area" })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

  /* Potential Media Tags */
  private final Set<String> fMediaTags = new HashSet<String>(Arrays.asList(new String[] { "applet", "embed", "frame", "frameset", "iframe", "object" })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$

  /* Image and Media Tags */
  private final Set<String> fImageAndMediaTags;

  /* Windows only: Mark of the Web */
  private static final String IE_MOTW = "<!-- saved from url=(0014)about:internet -->"; //$NON-NLS-1$

  /* Dynamic HTML in Content */
  enum Dynamic {
    NEWS("newsitem"), //$NON-NLS-1$
    GROUP("group"), //$NON-NLS-1$
    GROUP_NOTE("groupNote"), //$NON-NLS-1$
    TITLE("title"), //$NON-NLS-1$
    TITLE_LINK("titleLink"), //$NON-NLS-1$
    SUBTITLE_LINK("subtitleLink"), //$NON-NLS-1$
    SUBLINE("subline"), //$NON-NLS-1$
    DELETE("delete"), //$NON-NLS-1$
    TOGGLE_READ_LINK("toggleRead"), //$NON-NLS-1$
    TOGGLE_READ_IMG("toggleReadImg"), //$NON-NLS-1$
    TINY_TOGGLE_STICKY_LINK("tinyToggleStickyLink"), //$NON-NLS-1$
    TOGGLE_STICKY_LINK("toggleStickyLink"), //$NON-NLS-1$
    TOGGLE_STICKY_IMG("toggleStickyImg"), //$NON-NLS-1$
    TOGGLE_GROUP_LINK("toggleGroupLink"), //$NON-NLS-1$
    TOGGLE_GROUP_IMG("toggleGroupImg"), //$NON-NLS-1$
    LABELS_MENU_LINK("labelsMenuLink"), //$NON-NLS-1$
    ARCHIVE_LINK("archiveLink"), //$NON-NLS-1$
    SHARE_MENU_LINK("shareMenuLink"), //$NON-NLS-1$
    GROUP_MENU_LINK("groupMenuLink"), //$NON-NLS-1$
    NEWS_MENU_LINK("newsMenuLink"), //$NON-NLS-1$
    HEADER("header"), //$NON-NLS-1$
    CONTENT("content"), //$NON-NLS-1$
    FOOTER("footer"), //$NON-NLS-1$
    FOOTER_STICKY_LINK("footerStickyLink"), //$NON-NLS-1$
    FOOTER_ARCHIVE_LINK("footerArchiveLink"), //$NON-NLS-1$
    FOOTER_LABEL_MENU_LINK("footerLabelMenuLink"), //$NON-NLS-1$
    FOOTER_SHARE_MENU_LINK("footerShareMenuLink"), //$NON-NLS-1$
    FIND_RELATED_MENU_LINK("findRelatedMenuLink"), //$NON-NLS-1$
    FOOTER_NEWS_MENU_LINK("footerNewsMenuLink"), //$NON-NLS-1$
    ATTACHMENTS_MENU_LINK("attachmentsMenuLink"), //$NON-NLS-1$
    ATTACHMENT_LINK("attachmentLink"), //$NON-NLS-1$
    COLLAPSE_LINK("collapseLink"), //$NON-NLS-1$
    FULL_CONTENT_LINK("fullContentLink"), //$NON-NLS-1$
    FULL_CONTENT_LINK_TEXT("fullContentLinkText"), //$NON-NLS-1$
    LABELS("labels"), //$NON-NLS-1$
    LABELS_SEPARATOR("labelsSeparator"), //$NON-NLS-1$
    HEADLINE_SEPARATOR("headlineSeparator"), //$NON-NLS-1$
    PAGE_LATCH("pageLatch"), //$NON-NLS-1$
    PAGE_LATCH_LINK("pageLatchLink"), //$NON-NLS-1$
    PAGE_LATCH_TEXT("pageLatchText"); //$NON-NLS-1$

    private String fId;

    Dynamic(String id) {
      fId = id;
    }

    String getId() {
      return fId;
    }

    String getId(long id) {
      return fId + id;
    }

    String getId(EntityGroup group) {
      return fId + group.getId();
    }

    String getId(INews news) {
      return fId + news.getId();
    }

    String getId(NewsReference newsRef) {
      return fId + newsRef.getId();
    }
  }

  /* Colors and Fonts */
  private String fNewsFontFamily;
  private String fNormalFontCSS;
  private String fVerySmallFontCSS;
  private String fSmallFontCSS;
  private String fBiggerFontCSS;
  private String fBiggestFontCSS;
  private String fStickyBGColorCSS;
  private String fNewsListBGColorCSS;
  private String fLinkFGColorCSS;
  private boolean fIsNewsListBGColorDefined;
  private IPropertyChangeListener fPropertyChangeListener;

  /* Label Provider State */
  private final NewsBrowserViewer fViewer;
  private final boolean fIsIE;
  private boolean fStripImagesFromNews;
  private boolean fStripMediaFromNews;
  private boolean fForceShowFeedInformation;
  private boolean fManageLinks;
  private boolean fShowFooter;
  private boolean fHeadlinesOnly;
  private boolean fForceNoGrouping;
  private boolean fForceNoPaging;
  private final long fTodayInMillies;
  private final Map<String, String> fMapFeedLinkToName = new HashMap<String, String>();

  /**
   * Creates a new Browser LabelProvider for News
   *
   * @param browser
   */
  public NewsBrowserLabelProvider(CBrowser browser) {
    this(null, browser.isIE());
  }

  /**
   * Creates a new Browser LabelProvider for News
   *
   * @param viewer
   */
  public NewsBrowserLabelProvider(NewsBrowserViewer viewer) {
    this(viewer, viewer.getBrowser().isIE());
  }

  private NewsBrowserLabelProvider(NewsBrowserViewer viewer, boolean isIE) {
    fViewer = viewer;
    fIsIE = isIE;
    fManageLinks = OwlUI.useExternalBrowser() || Owl.getPreferenceService().getGlobalScope().getBoolean(DefaultPreferences.OPEN_LINKS_IN_NEW_TAB);
    fShowFooter = true;
    fTodayInMillies = DateUtils.getToday().getTimeInMillis();

    fImageAndMediaTags = new HashSet<String>();
    fImageAndMediaTags.addAll(fImageTags);
    fImageAndMediaTags.addAll(fMediaTags);

    createFonts();
    createColors();
    registerListeners();
  }

  /**
   * @param stripImagesFromNews <code>true</code> to strip images from the news
   * and <code>false</code> otherwise.
   * @param stripMediaFromNews <code>true</code> to strip media from the news
   * and <code>false</code> otherwise.
   */
  public void setStripMediaFromNews(boolean stripImagesFromNews, boolean stripMediaFromNews) {
    fStripImagesFromNews = stripImagesFromNews;
    fStripMediaFromNews = stripMediaFromNews;
  }

  /* Removes Media Tags from the provided String if the provider is configured to do so */
  String stripMediaTagsIfNecessary(String str) {
    if (fStripImagesFromNews && fStripMediaFromNews)
      return StringUtils.filterTags(str, fImageAndMediaTags, false);
    else if (fStripImagesFromNews)
      return StringUtils.filterTags(str, fImageTags, false);
    else if (fStripMediaFromNews)
      return StringUtils.filterTags(str, fMediaTags, false);

    return str;
  }

  /* Highlight search terms if any */
  String highlightSearchTermsIfNecessary(String str) {
    if (fViewer != null) {
      Collection<String> wordsToHighlight = fViewer.getHighlightedWords();
      if (!wordsToHighlight.isEmpty()) {
        StringBuilder highlightedResult = new StringBuilder(str.length());

        RGB searchRGB = OwlUI.getThemeRGB(OwlUI.SEARCH_HIGHLIGHT_BG_COLOR_ID, new RGB(255, 255, 0));
        String preHighlight = "<span style=\"background-color:rgb(" + OwlUI.toString(searchRGB) + ");\">"; //$NON-NLS-1$ //$NON-NLS-2$
        String postHighlight = "</span>"; //$NON-NLS-1$

        ExpandingReader resultHighlightReader = new ExpandingReader(new StringReader(str), wordsToHighlight, preHighlight, postHighlight, true);

        int len = 0;
        char[] buf = new char[1000];
        try {
          while ((len = resultHighlightReader.read(buf)) != -1)
            highlightedResult.append(buf, 0, len);

          return highlightedResult.toString();
        } catch (IOException e) {
          Activator.getDefault().logError(e.getMessage(), e);
        }
      }
    }

    return str;
  }

  /**
   * @param forceShowFeedInformation if <code>true</code> will show the name of
   * a feed of a news when shown, <code>false</code> otherwise.
   */
  public void setForceShowFeedInformation(boolean forceShowFeedInformation) {
    fForceShowFeedInformation = forceShowFeedInformation;
  }

  /**
   * @param showFooter if <code>true</code> will show the footer for each news
   * and <code>false</code> otheriwse. Default is <code>true</code>.
   */
  public void setShowFooter(boolean showFooter) {
    fShowFooter = showFooter;
  }

  /**
   * @param headlinesOnly if set to <code>true</code> only show headlines of
   * news and not the content, <code>false</code> otherwise. Default is
   * <code>false</code>.
   */
  public void setHeadlinesOnly(boolean headlinesOnly) {
    fHeadlinesOnly = headlinesOnly;
  }

  boolean isHeadlinesOnly() {
    return fHeadlinesOnly;
  }

  /*
   * @see org.eclipse.jface.viewers.BaseLabelProvider#dispose()
   */
  @Override
  public void dispose() {
    super.dispose();
    unregisterListeners();
    fMapFeedLinkToName.clear();
  }

  private void registerListeners() {

    /* Create Property Listener */
    fPropertyChangeListener = new IPropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent event) {
        String property = event.getProperty();
        if (OwlUI.NEWS_TEXT_FONT_ID.equals(property))
          createFonts();
        else if (OwlUI.STICKY_BG_COLOR_ID.equals(property) || OwlUI.LINK_FG_COLOR_ID.equals(property) || OwlUI.NEWS_LIST_BG_COLOR_ID.equals(property))
          createColors();
      }
    };

    /* Add it to listen to Theme Events */
    PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(fPropertyChangeListener);
  }

  private void unregisterListeners() {
    PlatformUI.getWorkbench().getThemeManager().removePropertyChangeListener(fPropertyChangeListener);
  }

  /* Init the Theme Font (from UI Thread) */
  private void createFonts() {
    int fontHeight = 10;
    Font newsFont = OwlUI.getThemeFont(OwlUI.NEWS_TEXT_FONT_ID, SWT.NORMAL);
    FontData[] fontData = newsFont.getFontData();
    if (fontData.length > 0) {
      fNewsFontFamily = fontData[0].getName();
      fontHeight = fontData[0].getHeight();
    }

    int normal = fontHeight;
    int verysmall = normal - 2;
    int small = normal - 1;
    int bigger = normal + 1;
    int biggest = bigger + 2;

    String fontUnit = "pt"; //$NON-NLS-1$
    fNormalFontCSS = "font-size: " + normal + fontUnit + ";"; //$NON-NLS-1$ //$NON-NLS-2$
    fVerySmallFontCSS = "font-size: " + verysmall + fontUnit + ";"; //$NON-NLS-1$ //$NON-NLS-2$
    fSmallFontCSS = "font-size: " + small + fontUnit + ";"; //$NON-NLS-1$ //$NON-NLS-2$
    fBiggerFontCSS = "font-size: " + bigger + fontUnit + ";"; //$NON-NLS-1$ //$NON-NLS-2$
    fBiggestFontCSS = "font-size: " + biggest + fontUnit + ";"; //$NON-NLS-1$ //$NON-NLS-2$
  }

  /* Init the Theme Color (from UI Thread) */
  private void createColors() {
    RGB stickyRgb = OwlUI.getThemeRGB(OwlUI.STICKY_BG_COLOR_ID, new RGB(255, 255, 180));
    fStickyBGColorCSS = "background-color: rgb(" + stickyRgb.red + "," + stickyRgb.green + "," + stickyRgb.blue + ");"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$

    RGB linkRgb = OwlUI.getThemeRGB(OwlUI.LINK_FG_COLOR_ID, new RGB(0, 0, 153));
    fLinkFGColorCSS = "color: rgb(" + linkRgb.red + "," + linkRgb.green + "," + linkRgb.blue + ");"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$

    RGB newsListRgb = OwlUI.getThemeRGB(OwlUI.NEWS_LIST_BG_COLOR_ID, new RGB(255, 255, 255));
    fNewsListBGColorCSS = "background-color: rgb(" + newsListRgb.red + "," + newsListRgb.green + "," + newsListRgb.blue + ");"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
    fIsNewsListBGColorDefined = !newsListRgb.equals(new RGB(255, 255, 255));
  }

  /*
   * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
   */
  @Override
  public String getText(Object element) {
    return getText(element, true, true, -1);
  }

  /**
   * @param element the element to get a HTML representation from.
   * @param withInternalLinks <code>true</code> to include links of the internal
   * protocol rssowl:// and <code>false</code> otherwise.
   * @param withManagedLinks if set to <code>false</code>, the output will not
   * contain any managed links.
   * @param index the zero-based index of the element from top.
   * @return the HTML representation for the given element.
   */
  public String getText(Object element, boolean withInternalLinks, boolean withManagedLinks, int index) {

    /* Return HTML for a Group */
    if (element instanceof EntityGroup)
      return getLabel((EntityGroup) element, withInternalLinks);

    /* Return HTML for a News */
    else if (element instanceof INews)
      return getLabel((INews) element, withInternalLinks, withManagedLinks, false, index);

    /* Return HTML for Page Latch */
    else if (element instanceof PageLatch)
      return getLabel((PageLatch) element);

    return ""; //$NON-NLS-1$
  }

  private boolean isSingleNewsDisplayed() {
    Object input = fViewer != null ? fViewer.getInput() : null;
    return input instanceof INews;
  }

  private boolean isEntityGroupDisplayed() {
    Object input = fViewer != null ? fViewer.getInput() : null;
    return input instanceof EntityGroup;
  }

  private boolean isGroupingEnabled() {
    if (isEntityGroupDisplayed())
      return false; //Entity Group as input does not count here

    if (fForceNoGrouping)
      return false; //Special case where content is rendered for outside the application

    if (fViewer != null) {
      IContentProvider cp = fViewer.getContentProvider();
      if (cp instanceof NewsContentProvider)
        return ((NewsContentProvider) cp).isGroupingEnabled();
    }

    return false;
  }

  private boolean showFeedInformation() {
    if (fForceShowFeedInformation)
      return true;

    Object input = fViewer != null ? fViewer.getInput() : null;
    return input instanceof FolderNewsMarkReference || input instanceof SearchMarkReference || input instanceof NewsBinReference;
  }

  /**
   * Writes the CSS information to the given Writer.
   *
   * @param writer the writer to add the CSS information to.
   * @throws IOException In case of an error while writing.
   */
  public void writeCSS(Writer writer) throws IOException {
    writeCSS(writer, isSingleNewsDisplayed(), true);
  }

  /**
   * Writes the CSS information to the given Writer.
   *
   * @param writer the writer to add the CSS information to.
   * @param forSingleNews if <code>true</code>, the site contains a single news,
   * or <code>false</code> if it contains a collection of news.
   * @param withInternalLinks <code>true</code> to include links of the internal
   * protocol rssowl:// and <code>false</code> otherwise.
   * @throws IOException In case of an error while writing.
   */
  public void writeCSS(Writer writer, boolean forSingleNews, boolean withInternalLinks) throws IOException {
    boolean isGroupingEnabled = isGroupingEnabled();

    /* Open CSS */
    writer.write("<style type=\"text/css\">\n"); //$NON-NLS-1$

    /* Common CSS Rules */
    writeCommonCSS(writer, forSingleNews, withInternalLinks);

    /* Common Headlines Layout Rules */
    if (fHeadlinesOnly)
      writeCommonHeadlinesCSS(writer);

    /* Common Newspaper Layout Rules */
    else
      writeCommonNewspaperCSS(writer);

    /* Single News */
    if (forSingleNews)
      writesingleNewsCSS(writer);

    /* Headlines Grouped */
    else if (isGroupingEnabled && fHeadlinesOnly)
      writeHeadlinesGroupedCSS(writer);

    /* Newspaper Grouped */
    else if (isGroupingEnabled && !fHeadlinesOnly)
      writeNewspaperGroupedCSS(writer);

    /* Headlines Ungrouped */
    else if (!isGroupingEnabled && fHeadlinesOnly)
      writeHeadlinesUngroupedCSS(writer);

    /* Newspaper Ungrouped */
    else if (!isGroupingEnabled && !fHeadlinesOnly)
      writeNewspaperUngroupedCSS(writer);

    writer.write("</style>\n"); //$NON-NLS-1$
  }

  /* Common CSS in all Layouts */
  private void writeCommonCSS(Writer writer, boolean forSingleNews, boolean withInternalLinks) throws IOException {

    /* General */
    writer.append("body { overflow: auto; margin: 0; font-family: ").append(fNewsFontFamily).append(",Verdanna,sans-serif; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("a { ").append(fLinkFGColorCSS).append(" text-decoration: none; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("a:hover { ").append(fLinkFGColorCSS).append(" text-decoration: underline; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("a:visited { ").append(fLinkFGColorCSS).append(" text-decoration: none; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("img { border: none; }\n"); //$NON-NLS-1$
    writer.append("div.hidden { display: none; }\n"); //$NON-NLS-1$

    /* Group */
    writer.append("div.group { color: #678; ").append(fBiggestFontCSS).append(" font-weight: bold; margin: 10px 0px 5px 5px; padding-bottom: 3px; border-bottom: 1px solid #678; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.group a { color: #678; ").append(fBiggestFontCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.group a:hover { color: #678; ").append(fBiggestFontCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.group a:visited { color: #678; ").append(fBiggestFontCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("span.groupNote { margin-left: 5px; ").append(fNormalFontCSS).append(" font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Content */
    writer.append("div.content { \n"); //$NON-NLS-1$
    writer.append("  padding: 15px 10px 15px 10px; border-top: dotted 1px silver; \n"); //$NON-NLS-1$
    writer.append("  background-color: #fff; clear: both; ").append(fNormalFontCSS).append(" font-style: normal; \n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("}\n"); //$NON-NLS-1$

    /* Restrict the style of embedded Paragraphs */
    writer.write("div.content p { margin-top: 0 !important; padding-top: 0 !important; margin-left: 0 !important; padding-left: 0 !important; }\n"); //$NON-NLS-1$

    /* Title */
    if (!withInternalLinks)
      writer.append("div.title { float: left; ").append(fBiggerFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.title span.unread { font-weight: bold; }\n"); //$NON-NLS-1$

    /* Author */
    writer.append("div.author { text-align: right; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("a.author { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.append("a.author:hover { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.append("a.author:active { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.append("a.author:visited { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$

    /* Comments */
    writer.write("a.comments { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("a.comments:hover { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("a.comments:active { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("a.comments:visited { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$

    /* Subline */
    writer.append("table.subline { margin: 0; padding: 0; }\n"); //$NON-NLS-1$
    writer.append("tr.subline { margin: 0; padding: 0; }\n"); //$NON-NLS-1$
    writer.append("td.firstactionsubline { text-align: left; width: 20px; margin: 0; padding: 0; color: rgb(80, 80, 80); ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("td.firstactionsubline a { width: 20px; }\n"); //$NON-NLS-1$
    writer.append("td.otheractionsubline { text-align: center; width: 22px; margin: 0; padding: 0; color: rgb(80, 80, 80); ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("td.otheractionsubline a { width: 22px; }\n"); //$NON-NLS-1$
    writer.append("td.subline { margin: 0; padding: 0; color: rgb(80, 80, 80); padding-right: 5px; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("td.sublineseparator { margin: 0; padding: 0; color: rgb(140, 140, 140); padding-right: 5px; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("td.actionsublineseparator { margin: 0; padding: 0; color: rgb(140, 140, 140); padding-left: 4px; padding-right: 5px; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Date */
    writer.append("div.date { float: left; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Footer Line */
    writer.append("div.footerline { clear: both; ").append(fVerySmallFontCSS).append(" padding-left: 3px; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.footerline a { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.append("div.footerline a:visited { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.append("div.footerline a:hover { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.append("table.footerline { margin: 0; padding: 0; }\n"); //$NON-NLS-1$
    writer.append("tr.footerline { margin: 0; padding: 0; }\n"); //$NON-NLS-1$
    writer.append("td.footerline { margin: 0; padding: 0; padding-right: 5px; ").append(fVerySmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("td.footerlineseparator { margin: 0; padding: 0; color: rgb(140, 140, 140); padding-right: 5px; ").append(fVerySmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Clearing Footer */
    if (!forSingleNews)
      writer.write("div.clearingFooter { clear: both; }\n"); //$NON-NLS-1$

    /* Quotes */
    writer.write("span.quote_lvl1 { color: #660066; }\n"); //$NON-NLS-1$
    writer.write("span.quote_lvl2 { color: #007777; }\n"); //$NON-NLS-1$
    writer.write("span.quote_lvl3 { color: #3377ff; }\n"); //$NON-NLS-1$
    writer.write("span.quote_lvl4 { color: #669966; }\n"); //$NON-NLS-1$

    /* Page Latch */
    writer.append("div.pageLatch { padding: 5px; text-align: center; ").append(fNormalFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.pageLatch a { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.pageLatch a:hover { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.pageLatch a:active { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.pageLatch a:visited { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /* Common CSS for Headlines Layout */
  private void writeCommonHeadlinesCSS(Writer writer) throws IOException {

    /* Title (Collapsed - Initially) */
    writer.append("div.title {  ").append(fNormalFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.title a { color: black; text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.title a.unread { font-weight: bold; text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.title a:hover { color: black; text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.title a:visited { color: black; text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$

    /* Title (Expanded) */
    writer.append("div.titleExpanded { width: 90%; float: left; ").append(fBiggerFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.titleExpanded a { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.titleExpanded a.unread { font-weight: bold; text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.titleExpanded a:hover { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.titleExpanded a:visited { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Title (Collapsed - By user) */
    writer.append("div.titleCollapsed { ").append(fNormalFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.titleCollapsed a { color: rgb(80, 80, 80); text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.titleCollapsed a.unread { color: rgb(80, 80, 80); font-weight: bold; text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.titleCollapsed a:hover { color: rgb(80, 80, 80); text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.titleCollapsed a:visited { color: rgb(80, 80, 80); text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$

    /* Subtitle */
    writer.append("a.subtitle { font-style: italic !important; text-decoration: none; padding-left: 10px; color: rgb(80, 80, 80); ").append(fVerySmallFontCSS).append(" }"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("a.subtitle:hover { font-style: italic !important; text-decoration: none; padding-left: 10px; color: rgb(80, 80, 80); ").append(fVerySmallFontCSS).append(" }"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("a.subtitle:visited { font-style: italic !important; text-decoration: none; padding-left: 10px; color: rgb(80, 80, 80); ").append(fVerySmallFontCSS).append(" }"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Delete */
    writer.append("div.delete { padding-top: 5px; text-align: right; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Subline */
    writer.append("div.subline { margin: 8px 0px 0px 0px; padding: 0; clear: left; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Footer */
    writer.append("      div.footer { padding: 3px 5px 5px 5px; line-height: 20px; border-top: dotted 1px silver; clear: both; }\n"); //$NON-NLS-1$
    writer.append("div.footerSticky { padding: 3px 5px 5px 5px; line-height: 20px; border-top: dotted 1px silver; clear: both; ").append(fStickyBGColorCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /* Common CSS for Newspaper Layout */
  private void writeCommonNewspaperCSS(Writer writer) throws IOException {

    /* Title */
    writer.append("div.title { width: 90%; float: left; padding-bottom: 6px; ").append(fBiggerFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.title a { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.title a.unread { font-weight: bold; text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$
    writer.append("div.title a:hover { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.append("div.title a:visited { ").append(fLinkFGColorCSS).append(" text-decoration: none; font-style: normal; }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Delete */
    writer.append("div.delete { text-align: right; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Subline */
    writer.append("div.subline { margin: 0; padding: 0; clear: left; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Footer */
    writer.append("      div.footer { padding: 3px 5px 3px 5px; line-height: 20px; border-top: dotted 1px silver; border-bottom: 1px solid white; clear: both; }\n"); //$NON-NLS-1$
    writer.append("div.footerSticky { padding: 3px 5px 3px 5px; line-height: 20px; border-top: dotted 1px silver; border-bottom: 1px dotted silver; clear: both; ").append(fStickyBGColorCSS).append("}\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /* Single News */
  private void writesingleNewsCSS(Writer writer) throws IOException {

    /* News Container */
    writer.write("  div.newsitemRead { margin: 0; }\n"); //$NON-NLS-1$
    writer.write("div.newsitemUnread { margin: 0; }\n"); //$NON-NLS-1$

    /* Header */
    writer.append("      div.header { padding: 10px 10px 5px 10px; background-color: rgb(242,242,242); }\n"); //$NON-NLS-1$
    writer.append("div.headerSticky { padding: 10px 10px 5px 10px; ").append(fStickyBGColorCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    writer.write("       div.footer { padding: 3px 5px 3px 5px; line-height: 20px; border-top: dotted 1px silver; border-bottom: dotted 1px silver; clear: both; background-color: rgb(248,248,248); }\n"); //$NON-NLS-1$
    writer.append("div.footerSticky { padding: 3px 5px 3px 5px; line-height: 20px; border-top: dotted 1px silver; border-bottom: dotted 1px silver; clear: both; ").append(fStickyBGColorCSS).append("}\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /* Newspaper Ungrouped */
  private void writeNewspaperUngroupedCSS(Writer writer) throws IOException {

    /* News Container */
    writer.write("div.newsitemRead   { margin: 0px 0px 10px 0px; border-bottom: 1px solid white; }\n"); //$NON-NLS-1$
    writer.write("div.newsitemUnread { margin: 0px 0px 10px 0px; border-bottom: 1px solid white; }\n"); //$NON-NLS-1$

    /* Header */
    writer.append("      div.header { padding: 10px 10px 5px 10px; border-top: 1px solid white; }\n"); //$NON-NLS-1$
    writer.append("div.headerSticky { padding: 10px 10px 5px 10px; border-top: 1px dotted silver; ").append(fStickyBGColorCSS).append("}\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /* Newspaper Grouped */
  private void writeNewspaperGroupedCSS(Writer writer) throws IOException {

    /* News Container */
    writer.write("  div.newsitemRead { margin: 0px 0px 10px 10px; border-bottom: 1px solid white; }\n"); //$NON-NLS-1$
    writer.write("div.newsitemUnread { margin: 0px 0px 10px 10px; border-bottom: 1px solid white; }\n"); //$NON-NLS-1$

    /* Header */
    writer.append("      div.header { padding: 10px 10px 5px 10px; border-top: 1px solid white; }\n"); //$NON-NLS-1$
    writer.append("div.headerSticky { padding: 10px 10px 5px 10px; border-top: 1px dotted silver; ").append(fStickyBGColorCSS).append("}\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /* Headlines Ungrouped */
  private void writeHeadlinesUngroupedCSS(Writer writer) throws IOException {

    /* News Container */
    writer.write("  div.newsitemRead { margin: 0px 0px 0px 0px; }\n"); //$NON-NLS-1$
    writer.write("div.newsitemUnread { margin: 0px 0px 0px 0px; }\n"); //$NON-NLS-1$

    /* Header */
    writer.write("       div.header { padding: 5px 10px 5px 10px; }\n"); //$NON-NLS-1$
    writer.append("div.headerSticky { padding: 5px 10px 5px 10px; ").append(fStickyBGColorCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Headlines Separator */
    writer.append("div.headlinesSeparator { margin: 0; padding: 0; border-bottom: 1px dotted silver; }\n"); //$NON-NLS-1$
  }

  /* Headlines Grouped */
  private void writeHeadlinesGroupedCSS(Writer writer) throws IOException {

    /* News Container */
    writer.write("  div.newsitemRead { margin: 0px 0px 0px 10px; }\n"); //$NON-NLS-1$
    writer.write("div.newsitemUnread { margin: 0px 0px 0px 10px; }\n"); //$NON-NLS-1$

    /* Header */
    writer.write("       div.header { padding: 5px 10px 5px 10px; }\n"); //$NON-NLS-1$
    writer.append("div.headerSticky { padding: 5px 10px 5px 10px; ").append(fStickyBGColorCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Headlines Separator */
    writer.append("div.headlinesSeparator { margin: 0px 0px 0px 10px; padding: 0; border-bottom: 1px dotted silver; }\n"); //$NON-NLS-1$
  }

  private String getLabel(EntityGroup group, boolean withInternalLinks) {
    boolean isVisible = isVisible(group);
    StringBuilder builder = new StringBuilder();

    String groupName = StringUtils.htmlEscape(group.getName());
    String groupColor = null;
    if (group.getColorHint() != null && !group.getColorHint().equals(new RGB(255, 255, 255)))
      groupColor = OwlUI.toString(group.getColorHint());

    /* DIV: Group */
    StringBuilder extraCSS = new StringBuilder();
    if (!isVisible)
      extraCSS.append("display: none; "); //$NON-NLS-1$
    if (groupColor != null)
      extraCSS.append("border-bottom-color: rgb(" + groupColor + ");"); //$NON-NLS-1$ //$NON-NLS-2$

    if (extraCSS.length() == 0)
      div(builder, "group", Dynamic.GROUP.getId(group)); //$NON-NLS-1$
    else
      div(builder, "group", extraCSS.toString(), Dynamic.GROUP.getId(group)); //$NON-NLS-1$

    /* Let the group name be a link to invoke actions on all news inside and provide a toggle */
    if (withInternalLinks) {

      /* Toggle to Expand / Collapse */
      String link = HANDLER_PROTOCOL + COLLAPSE_GROUP_HANDLER_ID + "?" + group.getId(); //$NON-NLS-1$
      imageLink(builder, link, null, null, "/icons/elcl16/expanded.gif", "expanded.gif", Dynamic.TOGGLE_GROUP_LINK.getId(group), Dynamic.TOGGLE_GROUP_IMG.getId(group)); //$NON-NLS-1$ //$NON-NLS-2$

      /* Group Name as Link */
      link = HANDLER_PROTOCOL + GROUP_MENU_HANDLER_ID + "?" + group.getId(); //$NON-NLS-1$
      link(builder, link, groupName, null, Dynamic.GROUP_MENU_LINK.getId(group), groupColor);
    }

    /* Not using internal links */
    else {
      span(builder, groupName, null, groupColor);
    }

    /* Group Note (number of articles and filtered elements if any) */
    int actualSize = group.getSizeHint();
    int totalSize = group.getEntities().size();
    String groupNote = getGroupNote(actualSize, totalSize);
    span(builder, groupNote, "groupNote", Dynamic.GROUP_NOTE.getId(group), groupColor); //$NON-NLS-1$

    /* Close: Group */
    close(builder, "div"); //$NON-NLS-1$

    return builder.toString();
  }

  String getGroupNote(int actualSize, int totalSize) {
    int sizeDiff = totalSize - actualSize;

    String groupNote;
    if (actualSize == 1) {
      if (sizeDiff == 0)
        groupNote = Messages.NewsBrowserLabelProvider_ONE_ARTICLE;
      else
        groupNote = NLS.bind(Messages.NewsBrowserLabelProvider_ONE_ARTICLE_N_FILTERED, sizeDiff);
    } else {
      if (sizeDiff == 0)
        groupNote = NLS.bind(Messages.NewsBrowserLabelProvider_N_ARTICLES, actualSize);
      else
        groupNote = NLS.bind(Messages.NewsBrowserLabelProvider_N_ARTICLES_M_FILTERED, actualSize, sizeDiff);
    }

    return groupNote;
  }

  private StringBuilder getBuilder(INews news, String description) {
    int capacity = 0;

    if (news.getTitle() != null)
      capacity += news.getTitle().length();

    if (description != null)
      capacity += description.length();

    return new StringBuilder(capacity);
  }

  String getLabel(INews news, boolean withInternalLinks, boolean withManagedLinks, boolean onlyInnerContent, int index) {
    boolean isVisible = isVisible(news) || fForceNoPaging;

    String description = null; //Fetch description lazily if only headlines shown or news hidden
    if (!fHeadlinesOnly && isVisible)
      description = stripMediaTagsIfNecessary(news.getDescription());

    StringBuilder builder = getBuilder(news, description);

    State state = news.getState();
    boolean isUnread = (state == State.NEW || state == State.UPDATED || state == State.UNREAD);

    /* DIV: NewsItem (as needed) */
    if (!onlyInnerContent) {
      StringBuilder extraCSS = new StringBuilder();
      if (!isVisible)
        extraCSS.append("display: none; "); //$NON-NLS-1$
      if (index != 0 && fIsNewsListBGColorDefined && index % 2 != 0)
        extraCSS.append(fNewsListBGColorCSS);

      if (extraCSS.length() != 0)
        div(builder, isUnread ? "newsitemUnread" : "newsitemRead", extraCSS.toString(), Dynamic.NEWS.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$
      else
        div(builder, isUnread ? "newsitemUnread" : "newsitemRead", Dynamic.NEWS.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$
    }

    /* Full content of News only added if the news is actually visible */
    if (isVisible) {
      String newsTitle = CoreUtils.getHeadline(news, false);
      String newsLink = CoreUtils.getLink(news);
      boolean hasLink = newsLink != null;

      Set<ILabel> labels = CoreUtils.getSortedLabels(news);
      String color = !labels.isEmpty() ? labels.iterator().next().getColor() : null;
      if ("0,0,0".equals(color) || "255,255,255".equals(color)) //Don't let black or white override link color //$NON-NLS-1$ //$NON-NLS-2$
        color = null;

      /* DIV: NewsItem/Header */
      if (index == 0)
        div(builder, news.isFlagged() ? "headerSticky" : "header", "border-top: none;", Dynamic.HEADER.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      else
        div(builder, news.isFlagged() ? "headerSticky" : "header", Dynamic.HEADER.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$

      /* News Title */
      {

        /* DIV: NewsItem/Header/Title */
        div(builder, "title", Dynamic.TITLE.getId(news)); //$NON-NLS-1$

        String cssClass = isUnread ? "unread" : "read"; //$NON-NLS-1$ //$NON-NLS-2$

        /* Collapsed Title Bar */
        if (fHeadlinesOnly) {

          /* Make Sticky */
          String link = HANDLER_PROTOCOL + TOGGLE_STICKY_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
          imageLink(builder, link, Messages.NewsBrowserLabelProvider_STICKY, Messages.NewsBrowserLabelProvider_STICKY, "/icons/obj16/news_pin_light_tiny.gif", "news_pin_light_tiny.gif", Dynamic.TINY_TOGGLE_STICKY_LINK.getId(news), null); //$NON-NLS-1$//$NON-NLS-2$

          /* Expand News when clicking on Title */
          link = HANDLER_PROTOCOL + EXPAND_NEWS_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
          link(builder, link, newsTitle, cssClass, Dynamic.TITLE_LINK.getId(news), color);

          /* Subtitle */
          StringBuilder subtitleContent = new StringBuilder();
          fillSubtitle(subtitleContent, news, labels, false);
          link(builder, link, subtitleContent.toString(), "subtitle", Dynamic.SUBTITLE_LINK.getId(news), "80, 80, 80"); //$NON-NLS-1$ //$NON-NLS-2$
        }

        /* Otherwise treat normally */
        else {
          if (hasLink)
            link(builder, (fManageLinks && withManagedLinks) ? URIUtils.toManaged(newsLink) : newsLink, newsTitle, cssClass, Dynamic.TITLE_LINK.getId(news), color);
          else
            span(builder, newsTitle, cssClass, Dynamic.TITLE_LINK.getId(news), color);
        }

        /* Close: NewsItem/Header/Title */
        close(builder, "div"); //$NON-NLS-1$
      }

      /* Delete */
      if (withInternalLinks) {

        /* DIV: NewsItem/Header/Delete */
        if (fHeadlinesOnly)
          div(builder, "delete", "display: none;", Dynamic.DELETE.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$
        else
          div(builder, "delete", Dynamic.DELETE.getId(news)); //$NON-NLS-1$

        String link = HANDLER_PROTOCOL + DELETE_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_DELETE, Messages.NewsBrowserLabelProvider_DELETE, "/icons/elcl16/remove_small.gif", "remove_small.gif", null, null); //$NON-NLS-1$ //$NON-NLS-2$

        /* DIV: NewsItem/Header/Delete */
        close(builder, "div"); //$NON-NLS-1$
      }

      /* DIV: NewsItem/Header/Subline */
      if (fHeadlinesOnly) //Hidden initially in headlines mode
        div(builder, "subline", "display: none;", Dynamic.SUBLINE.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$
      else
        div(builder, "subline", Dynamic.SUBLINE.getId(news)); //$NON-NLS-1$
      builder.append("<table class=\"subline\">"); //$NON-NLS-1$
      builder.append("<tr class=\"subline\">"); //$NON-NLS-1$

      /* Actions */
      if (withInternalLinks) {

        /* Toggle Read */
        builder.append("<td class=\"firstactionsubline\">"); //$NON-NLS-1$
        String link = HANDLER_PROTOCOL + TOGGLE_READ_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        String text = (news.getState() == INews.State.READ) ? Messages.NewsBrowserLabelProvider_MARK_UNREAD : Messages.NewsBrowserLabelProvider_MARK_READ;
        imageLink(builder, link, text, text, "/icons/elcl16/mark_read_light.gif", "mark_read_light.gif", Dynamic.TOGGLE_READ_LINK.getId(news), Dynamic.TOGGLE_READ_IMG.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        /* Toggle Sticky */
        builder.append("<td class=\"otheractionsubline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + TOGGLE_STICKY_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_STICKY, Messages.NewsBrowserLabelProvider_STICKY, news.isFlagged() ? "/icons/obj16/news_pinned_light.gif" : "/icons/obj16/news_pin_light.gif", news.isFlagged() ? "news_pinned_light.gif" : "news_pin_light.gif", Dynamic.TOGGLE_STICKY_LINK.getId(news), Dynamic.TOGGLE_STICKY_IMG.getId(news)); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$
        builder.append("</td>"); //$NON-NLS-1$

        /* Assign Labels */
        builder.append("<td class=\"otheractionsubline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + LABELS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_ASSIGN_LABELS, Messages.NewsBrowserLabelProvider_LABEL, "/icons/elcl16/labels_light.gif", "labels_light.gif", Dynamic.LABELS_MENU_LINK.getId(news), null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        /* Archive News */
        builder.append("<td class=\"otheractionsubline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + ARCHIVE_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_ARCHIVE_NEWS, Messages.NewsBrowserLabelProvider_ARCHIVE, "/icons/etool16/archive_light.gif", "archive_light.gif", Dynamic.ARCHIVE_LINK.getId(news), null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        /* Share News Context Menu */
        builder.append("<td class=\"otheractionsubline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + SHARE_NEWS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_SHARE_NEWS, Messages.NewsBrowserLabelProvider_SHARE, "/icons/elcl16/share_light.gif", "share_light.gif", Dynamic.SHARE_MENU_LINK.getId(news), null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        /* News Context Menu */
        builder.append("<td class=\"otheractionsubline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + NEWS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_MENU, Messages.NewsBrowserLabelProvider_MENU, "/icons/obj16/menu_light.gif", "menu_light.gif", Dynamic.NEWS_MENU_LINK.getId(news), null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"actionsublineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$
      }

      /* Date */
      builder.append("<td class=\"subline\">"); //$NON-NLS-1$
      fillDate(news, builder);
      builder.append("</td>"); //$NON-NLS-1$

      /* Author */
      IPerson author = news.getAuthor();
      if (author != null) {
        builder.append("<td class=\"sublineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"subline\">"); //$NON-NLS-1$
        fillAuthor(builder, author, true);
        builder.append("</td>"); //$NON-NLS-1$
      }

      /* Feed Information */
      if (showFeedInformation()) {
        String feedName = getFeedName(news);
        if (StringUtils.isSet(feedName)) {
          builder.append("<td class=\"sublineseparator\">"); //$NON-NLS-1$
          builder.append("|"); //$NON-NLS-1$
          builder.append("</td>"); //$NON-NLS-1$

          builder.append("<td class=\"subline\">"); //$NON-NLS-1$
          builder.append(feedName);
          builder.append("</td>"); //$NON-NLS-1$
        }
      }

      /* Comments */
      if (StringUtils.isSet(news.getComments()) && news.getComments().trim().length() > 0 && URIUtils.looksLikeLink(news.getComments())) {
        builder.append("<td class=\"sublineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"subline\">"); //$NON-NLS-1$

        String comments = news.getComments();
        imageLink(builder, comments, Messages.NewsBrowserLabelProvider_READ_COMMENTS, Messages.NewsBrowserLabelProvider_COMMENTS, "/icons/obj16/comments_light.gif", "comments_light.gif", null, null); //$NON-NLS-1$ //$NON-NLS-2$

        builder.append("</td>"); //$NON-NLS-1$
      }

      boolean hasAttachments = false;
      List<IAttachment> attachments = news.getAttachments();
      for (IAttachment attachment : attachments) {
        if (attachment.getLink() != null) {
          hasAttachments = true;
          break;
        }
      }

      /* Attachments Menu */
      if (hasAttachments) {
        builder.append("<td class=\"sublineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"subline\">"); //$NON-NLS-1$
        String link = HANDLER_PROTOCOL + ATTACHMENTS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_ATTACHMENTS, Messages.NewsBrowserLabelProvider_ATTACHMENTS, "/icons/obj16/attachment_light.gif", "attachment_light.gif", Dynamic.ATTACHMENTS_MENU_LINK.getId(news), null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$
      }

      /* Labels Separator  */
      if (labels.isEmpty())
        builder.append("<td id=\"").append(Dynamic.LABELS_SEPARATOR.getId(news)).append("\" class=\"sublineseparator\" style=\"display: none;\">"); //$NON-NLS-1$ //$NON-NLS-2$
      else
        builder.append("<td id=\"").append(Dynamic.LABELS_SEPARATOR.getId(news)).append("\" class=\"sublineseparator\">"); //$NON-NLS-1$ //$NON-NLS-2$
      builder.append("|"); //$NON-NLS-1$
      builder.append("</td>"); //$NON-NLS-1$

      /* Labels */
      builder.append("<td id=\"").append(Dynamic.LABELS.getId(news)).append("\" class=\"subline\">"); //$NON-NLS-1$ //$NON-NLS-2$
      fillLabels(builder, labels);
      builder.append("</td>"); //$NON-NLS-1$

      /* Close: NewsItem/Header/Actions */
      builder.append("</tr>"); //$NON-NLS-1$
      builder.append("</table>"); //$NON-NLS-1$
      close(builder, "div"); //$NON-NLS-1$

      /* Close: NewsItem/Header */
      close(builder, "div"); //$NON-NLS-1$

      /* News Content */
      {

        /* DIV: NewsItem/Content */
        StringBuilder extraCSS = new StringBuilder();
        if (fHeadlinesOnly) //Hidden initially in headlines mode
          extraCSS.append("display: none; "); //$NON-NLS-1$
        if (index != 0 && fIsNewsListBGColorDefined && index % 2 != 0)
          extraCSS.append(fNewsListBGColorCSS);

        if (extraCSS.length() > 0)
          div(builder, "content", extraCSS.toString(), Dynamic.CONTENT.getId(news)); //$NON-NLS-1$
        else
          div(builder, "content", Dynamic.CONTENT.getId(news)); //$NON-NLS-1$

        /* Content is provided and should be displayed */
        if (!fHeadlinesOnly) {
          if (StringUtils.isSet(description) && description != null && !description.equals(news.getTitle()))
            builder.append(description);

          /* Content is not provided */
          else {

            /* Inform the user */
            builder.append(Messages.NewsBrowserLabelProvider_NO_CONTENT);

            /* Provide a link to attempt to download the news content and show it */
            if (withInternalLinks && hasLink) {
              builder.append(" "); //$NON-NLS-1$
              String link = HANDLER_PROTOCOL + TRANSFORM_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
              link(builder, link, Messages.NewsBrowserLabelProvider_ATTEMPT_DOWNLOAD_AND_DISPLAY, null);
            }
          }
        }

        /* Close: NewsItem/Content */
        close(builder, "div"); //$NON-NLS-1$
      }

      /* News Footer */
      if (withInternalLinks && fShowFooter) {

        /* DIV: NewsItem/Footer */
        if (fHeadlinesOnly) //Hidden initially in headlines mode
          div(builder, news.isFlagged() ? "footerSticky" : "footer", "display: none;", Dynamic.FOOTER.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        else
          div(builder, news.isFlagged() ? "footerSticky" : "footer", Dynamic.FOOTER.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$

        /* DIV: NewsItem/Footer/Footerline */
        div(builder, "footerline"); //$NON-NLS-1$
        builder.append("<table class=\"footerline\">"); //$NON-NLS-1$
        builder.append("<tr class=\"footerline\">"); //$NON-NLS-1$

        /* Collapse News */
        if (fHeadlinesOnly) {
          builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
          String link = HANDLER_PROTOCOL + COLLAPSE_NEWS_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
          imageLink(builder, link, Messages.NewsBrowserLabelProvider_COLLAPSE, Messages.NewsBrowserLabelProvider_COLLAPSE_NEWS, null, "/icons/obj16/mono_collapse.gif", "mono_collapse.gif", Dynamic.COLLAPSE_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
          builder.append("</td>"); //$NON-NLS-1$

          builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
          builder.append("|"); //$NON-NLS-1$
          builder.append("</td>"); //$NON-NLS-1$
        }

        /* Mark Sticky */
        builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
        String link = HANDLER_PROTOCOL + TOGGLE_STICKY_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_STICKY, Messages.NewsBrowserLabelProvider_STICKY, null, "/icons/obj16/mono_sticky.gif", "mono_sticky.gif", Dynamic.FOOTER_STICKY_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        /* Apply Label */
        builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + LABELS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_LABEL, Messages.NewsBrowserLabelProvider_LABEL, null, "/icons/obj16/mono_label.gif", "mono_label.gif", Dynamic.FOOTER_LABEL_MENU_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        /* Archive */
        builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + ARCHIVE_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_ARCHIVE, Messages.NewsBrowserLabelProvider_ARCHIVE_NEWS, null, "/icons/obj16/mono_archive.gif", "mono_archive.gif", Dynamic.FOOTER_ARCHIVE_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        /* Share Menu */
        builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + SHARE_NEWS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_SHARE, Messages.NewsBrowserLabelProvider_SHARE_NEWS, null, "/icons/obj16/mono_share.gif", "mono_share.gif", Dynamic.FOOTER_SHARE_MENU_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        /* News Menu */
        builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + NEWS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_MENU, Messages.NewsBrowserLabelProvider_NEWS_MENU, null, "/icons/obj16/mono_menu.gif", "mono_menu.gif", Dynamic.FOOTER_NEWS_MENU_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
        builder.append("|"); //$NON-NLS-1$
        builder.append("</td>"); //$NON-NLS-1$

        /* Related News Menu */
        builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
        link = HANDLER_PROTOCOL + RELATED_NEWS_MENU_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
        imageLink(builder, link, Messages.NewsBrowserLabelProvider_RELATED_NEWS, Messages.NewsBrowserLabelProvider_SEARCH_FOR_RELATED_NEWS, null, "/icons/obj16/mono_search.gif", "mono_search.gif", Dynamic.FIND_RELATED_MENU_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
        builder.append("</td>"); //$NON-NLS-1$

        /* Transform News */
        if (hasLink) {
          builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
          builder.append("|"); //$NON-NLS-1$
          builder.append("</td>"); //$NON-NLS-1$

          builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
          link = HANDLER_PROTOCOL + TRANSFORM_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
          imageLink(builder, link, Messages.NewsBrowserLabelProvider_FULL_CONTENT, Messages.NewsBrowserLabelProvider_DISPLAY_FULL_CONTENT, null, "/icons/obj16/mono_transform.gif", "mono_transform.gif", Dynamic.FULL_CONTENT_LINK.getId(news), null, Dynamic.FULL_CONTENT_LINK_TEXT.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$
          builder.append("</td>"); //$NON-NLS-1$
        }

        /* Attachments */
        if (attachments.size() != 0) {
          builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
          builder.append("|"); //$NON-NLS-1$
          builder.append("</td>"); //$NON-NLS-1$

          for (IAttachment attachment : attachments) {
            if (attachment.getLink() != null) {
              URI attachmentLink = attachment.getLink();
              String name = URIUtils.getFile(attachmentLink, OwlUI.getExtensionForMime(attachment.getType()));
              if (!StringUtils.isSet(name))
                name = attachmentLink.toASCIIString();

              String size = OwlUI.getSize(attachment.getLength());
              if (size != null)
                name = NLS.bind(Messages.NewsBrowserLabelProvider_NAME_SIZE, StringUtils.htmlEscape(name), size);
              else
                name = StringUtils.htmlEscape(name);

              builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
              link = HANDLER_PROTOCOL + ATTACHMENT_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
              imageLink(builder, link, name, null, null, "/icons/obj16/mono_attachment.gif", "mono_attachment.gif", Dynamic.ATTACHMENT_LINK.getId(news), null, null); //$NON-NLS-1$ //$NON-NLS-2$
              builder.append("</td>"); //$NON-NLS-1$
            }
          }
        }

        /* Source */
        ISource source = news.getSource();
        if (source != null && source.getLink() != null) {
          link = source.getLink().toASCIIString();
          String name = source.getName();
          if (StringUtils.isSet(link)) {
            builder.append("<td class=\"footerlineseparator\">"); //$NON-NLS-1$
            builder.append("|"); //$NON-NLS-1$
            builder.append("</td>"); //$NON-NLS-1$

            if (StringUtils.isSet(name))
              name = StringUtils.htmlEscape(name);
            else
              name = StringUtils.htmlEscape(link);

            builder.append("<td class=\"footerline\">"); //$NON-NLS-1$
            imageLink(builder, link, name, null, null, "/icons/obj16/mono_source.gif", "mono_source.gif", null, null, null); //$NON-NLS-1$ //$NON-NLS-2$
            builder.append("</td>"); //$NON-NLS-1$
          }
        }

        /* Close: NewsItem/Footer/Footerline */
        builder.append("</tr>"); //$NON-NLS-1$
        builder.append("</table>"); //$NON-NLS-1$
        close(builder, "div"); //$NON-NLS-1$

        /* Close: NewsItem/Footer */
        close(builder, "div"); //$NON-NLS-1$
      }

      /* Even though no footer wanted, need to have a div with clear:both to avoid hanging images from content */
      else {
        div(builder, "clearingFooter"); //$NON-NLS-1$
        close(builder, "div"); //$NON-NLS-1$
      }
    }

    /* Close: NewsItem (as needed) */
    if (!onlyInnerContent) {
      close(builder, "div"); //$NON-NLS-1$

      /* Headlines Separator (as needed) */
      if (fHeadlinesOnly) {
        if (isVisible)
          div(builder, "headlinesSeparator", Dynamic.HEADLINE_SEPARATOR.getId(news)); //$NON-NLS-1$
        else
          div(builder, "headlinesSeparator", "display: none;", Dynamic.HEADLINE_SEPARATOR.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$

        close(builder, "div"); //$NON-NLS-1$
      }
    }

    /* Highlight Support (if search is active) */
    return highlightSearchTermsIfNecessary(builder.toString());
  }

  private boolean isVisible(EntityGroup group) {
    if (fViewer != null)
      return fViewer.getViewModel().isGroupVisible(group.getId());

    return true;
  }

  private boolean isVisible(INews news) {
    if (fViewer != null)
      return fViewer.getViewModel().isNewsVisible(news);

    return true;
  }

  private int getTotalNewsCount() {
    if (fViewer != null)
      return fViewer.getViewModel().getNewsCount();

    return 0;
  }

  private int getVisibleNewsCount() {
    if (fViewer != null)
      return fViewer.getViewModel().getVisibleNewsCount();

    return 0;
  }

  /**
   * @param latch the page latch in case more entries are shown than allowed per
   * page.
   */
  private String getLabel(PageLatch latch) {
    StringBuilder builder = new StringBuilder();

    /* Open: Page Latch */
    div(builder, "pageLatch", Dynamic.PAGE_LATCH.getId()); //$NON-NLS-1$

    /* Page Latch Link to navigate to next page */
    String link = HANDLER_PROTOCOL + NEXT_PAGE_HANDLER_ID;
    imageLink(builder, link, getLatchName(), null, null, "/icons/obj16/mono_transform.gif", "mono_transform.gif", Dynamic.PAGE_LATCH_LINK.getId(), null, Dynamic.PAGE_LATCH_TEXT.getId()); //$NON-NLS-1$ //$NON-NLS-2$

    /* Close: Page Latch */
    close(builder, "div"); //$NON-NLS-1$

    return builder.toString();
  }

  String getLatchName() {
    return NLS.bind(Messages.NewsBrowserLabelProvider_DISPLAY_MORE_ARTICLES, getVisibleNewsCount(), getTotalNewsCount());
  }

  void fillSubtitle(StringBuilder subtitleContent, INews news, Set<ILabel> labels, boolean withLinks) {

    /* Subtitle: Date */
    fillDate(news, subtitleContent);

    /* Subtitle: Author */
    IPerson author = news.getAuthor();
    if (author != null) {
      subtitleContent.append(" | "); //$NON-NLS-1$
      fillAuthor(subtitleContent, author, withLinks);
    }

    /* Subtitle: Feed */
    if (showFeedInformation()) {
      String feedName = getFeedName(news);
      if (StringUtils.isSet(feedName)) {
        subtitleContent.append(" | "); //$NON-NLS-1$
        subtitleContent.append(feedName);
      }
    }

    /* Subtitle: Labels */
    if (!labels.isEmpty()) {
      subtitleContent.append(" | "); //$NON-NLS-1$
      fillLabels(subtitleContent, labels);
    }
  }

  private void fillDate(INews news, StringBuilder builder) {
    Date newsDate = DateUtils.getRecentDate(news);
    if (DateUtils.isAfterIncludingToday(newsDate, fTodayInMillies))
      builder.append(fTimeFormat.format(newsDate));
    else
      builder.append(fDateFormat.format(newsDate));
  }

  private void fillAuthor(StringBuilder builder, IPerson author, boolean withLinks) {
    String name = author.getName();
    String email = (author.getEmail() != null) ? author.getEmail().toASCIIString() : null;
    if (email != null && !email.contains("mail:")) //$NON-NLS-1$
      email = "mailto:" + email; //$NON-NLS-1$

    /* Use name as email if valid */
    if (email == null && name != null && name.contains("@") && !name.contains(" ")) //$NON-NLS-1$ //$NON-NLS-2$
      email = name;

    if (withLinks && StringUtils.isSet(name) && email != null)
      link(builder, email, StringUtils.stripTags(name, false), "author"); //$NON-NLS-1$
    else if (StringUtils.isSet(name))
      builder.append(StringUtils.stripTags(name, false));
    else if (withLinks && email != null)
      link(builder, email, StringUtils.stripTags(email, false), "author"); //$NON-NLS-1$
    else if (email != null)
      builder.append(StringUtils.stripTags(email, false));
    else
      builder.append(Messages.NewsBrowserLabelProvider_UNKNOWN);
  }

  private String getFeedName(INews news) {
    String feedLinkAsText = news.getFeedLinkAsText();
    String feedName = fMapFeedLinkToName.get(feedLinkAsText);
    if (feedName == null) {
      IBookMark bm = CoreUtils.getBookMark(news.getFeedReference());
      if (bm != null) {
        feedName = StringUtils.htmlEscape(bm.getName());
        fMapFeedLinkToName.put(feedLinkAsText, feedName);
      }
    }

    return feedName;
  }

  private void fillLabels(StringBuilder builder, Set<ILabel> labels) {
    if (!labels.isEmpty())
      builder.append(Messages.NewsBrowserLabelProvider_LABELS).append(" "); //$NON-NLS-1$

    int c = 0;
    for (ILabel label : labels) {
      c++;
      if (c < labels.size())
        span(builder, StringUtils.htmlEscape(label.getName()) + ", ", null, label.getColor()); //$NON-NLS-1$
      else
        span(builder, StringUtils.htmlEscape(label.getName()), null, label.getColor());
    }
  }

  private void div(StringBuilder builder, String cssClass) {
    builder.append("<div class=\"").append(cssClass).append("\">\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  private void div(StringBuilder builder, String cssClass, String id) {
    builder.append("<div id=\"").append(id).append("\" class=\"").append(cssClass).append("\">\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  }

  private void div(StringBuilder builder, String cssClass, String extraCSS, String id) {
    builder.append("<div id=\"").append(id).append("\" class=\"").append(cssClass).append("\" style=\"").append(extraCSS).append("\">\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  }

  private void close(StringBuilder builder, String tag) {
    builder.append("</").append(tag).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  private void link(StringBuilder builder, String link, String content, String cssClass) {
    link(builder, link, content, cssClass, null);
  }

  private void link(StringBuilder builder, String link, String content, String cssClass, String color) {
    link(builder, link, content, cssClass, null, color);
  }

  private void link(StringBuilder builder, String link, String content, String cssClass, String id, String color) {
    builder.append("<a href=\"").append(link).append("\""); //$NON-NLS-1$ //$NON-NLS-2$

    if (cssClass != null)
      builder.append(" class=\"").append(cssClass).append("\""); //$NON-NLS-1$ //$NON-NLS-2$

    if (color != null)
      builder.append(" style=\"color: rgb(").append(color).append(");\""); //$NON-NLS-1$ //$NON-NLS-2$

    if (id != null)
      builder.append(" id=\"").append(id).append("\""); //$NON-NLS-1$ //$NON-NLS-2$

    builder.append(">").append(content).append("</a>"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  private void imageLink(StringBuilder builder, String link, String tooltip, String alt, String imgPath, String imgName, String linkId, String imageId) {
    imageLink(builder, link, null, tooltip, alt, imgPath, imgName, linkId, imageId, null);
  }

  private void imageLink(StringBuilder builder, String link, String text, String tooltip, String alt, String imgPath, String imgName, String linkId, String imageId, String textId) {
    builder.append("<a"); //$NON-NLS-1$

    if (linkId != null)
      builder.append(" id=\"").append(linkId).append("\""); //$NON-NLS-1$ //$NON-NLS-2$

    if (tooltip != null)
      builder.append(" title=\"").append(tooltip).append("\" href=\"").append(link).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    else
      builder.append(" href=\"").append(link).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$

    builder.append("<img"); //$NON-NLS-1$

    if (imageId != null)
      builder.append(" id=\"").append(imageId).append("\""); //$NON-NLS-1$ //$NON-NLS-2$

    String imageUri;
    if (fIsIE)
      imageUri = OwlUI.getImageUri(imgPath, imgName);
    else
      imageUri = ApplicationServer.getDefault().toResourceUrl(imgPath);

    if (alt == null)
      alt = text;

    if (alt != null)
      builder.append(" alt=\"").append(alt).append("\" border=\"0\" src=\"").append(imageUri).append("\" />"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    else
      builder.append(" border=\"0\" src=\"").append(imageUri).append("\" />"); //$NON-NLS-1$ //$NON-NLS-2$

    if (text != null) {
      if (textId != null)
        builder.append("<span id=\"").append(textId).append("\" style=\"padding-left:3px;\">").append(text).append("</span>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      else
        builder.append("<span style=\"padding-left:3px;\">").append(text).append("</span>"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    builder.append("</a>"); //$NON-NLS-1$
  }

  private void span(StringBuilder builder, String content, String cssClass, String color) {
    span(builder, content, cssClass, null, color);
  }

  private void span(StringBuilder builder, String content, String cssClass, String id, String color) {
    if (cssClass != null)
      builder.append("<span class=\"").append(cssClass).append("\""); //$NON-NLS-1$ //$NON-NLS-2$
    else
      builder.append("<span"); //$NON-NLS-1$

    if (color != null)
      builder.append(" style=\"color: rgb(").append(color).append(");\""); //$NON-NLS-1$ //$NON-NLS-2$

    if (id != null)
      builder.append(" id=\"").append(id).append("\""); //$NON-NLS-1$ //$NON-NLS-2$

    builder.append(">").append(content).append("</span>\n"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  /**
   * Renders the provided list of elements in HTML for reading.
   *
   * @param elements the elements to render as HTML.
   * @param base a URI that should be used as base URI for the HTML document.
   * @param withManagedLinks if set to <code>false</code>, the output will not
   * contain any managed links.
   * @return a HTML document with the elements provided rendered properly for
   * reading.
   */
  public String render(Object[] elements, URI base, boolean withManagedLinks) {

    /* Store existing settings to restore later */
    boolean stripImagesFromNews = fStripImagesFromNews;
    boolean stripMediaFromNews = fStripMediaFromNews;
    boolean showFooter = fShowFooter;
    boolean headlinesOnly = fHeadlinesOnly;
    boolean showFeedInformation = fForceShowFeedInformation;

    fStripImagesFromNews = false;
    fStripMediaFromNews = false;
    fShowFooter = false;
    fHeadlinesOnly = false;
    fForceShowFeedInformation = true;
    fForceNoGrouping = true;
    fForceNoPaging = true;
    try {
      return internalRender(elements, base, withManagedLinks);
    } finally {
      fStripImagesFromNews = stripImagesFromNews;
      fStripMediaFromNews = stripMediaFromNews;
      fShowFooter = showFooter;
      fHeadlinesOnly = headlinesOnly;
      fForceShowFeedInformation = showFeedInformation;
      fForceNoGrouping = false;
      fForceNoPaging = false;
    }
  }

  private String internalRender(Object[] elements, URI base, boolean withManagedLinks) {

    /* Start HTML */
    StringBuilder html = new StringBuilder();
    html.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"); //$NON-NLS-1$

    /* Windows only: Mark of the Web */
    if (Application.IS_WINDOWS) {
      html.append(IE_MOTW);
      html.append("\n"); //$NON-NLS-1$
    }

    /* Head */
    html.append("<html>\n  <head>\n"); //$NON-NLS-1$

    /* Append Base URI if available */
    if (base != null) {
      html.append("  <base href=\""); //$NON-NLS-1$
      html.append(base);
      html.append("\">"); //$NON-NLS-1$
    }

    /* Meta */
    html.append("\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"); //$NON-NLS-1$

    /* CSS */
    try {
      StringWriter writer = new StringWriter();
      writeCSS(writer, elements.length == 1, false);
      html.append(writer.toString());
    } catch (IOException e) {
      /* Will Never Happen */
    }

    /* Open Body */
    html.append("  </head>\n  <body id=\"owlbody\">\n"); //$NON-NLS-1$

    /* Write News */
    for (int i = 0; i < elements.length; i++) {
      if (elements[i] instanceof INews)
        html.append(getText(elements[i], false, withManagedLinks, i));
    }

    /* End HTML */
    html.append("\n  </body>\n</html>"); //$NON-NLS-1$

    return html.toString();
  }
}
TOP

Related Classes of org.rssowl.ui.internal.editors.feed.NewsBrowserLabelProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.