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.ATTACHMENTS_MENU_HANDLER_ID;
import static org.rssowl.ui.internal.editors.feed.NewsBrowserViewer.DELETE_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.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 org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
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.ICategory;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.IPerson;
import org.rssowl.core.persist.ISource;
import org.rssowl.core.persist.INews.State;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.reference.NewsBinReference;
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.ApplicationServer;
import org.rssowl.ui.internal.EntityGroup;
import org.rssowl.ui.internal.ILinkHandler;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.FolderNewsMark.FolderNewsMarkReference;
import org.rssowl.ui.internal.util.CBrowser;

import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;
import java.net.URI;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Calendar;
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 Media Tags */
  private final Set<String> fMediaTags = new HashSet<String>(Arrays.asList(new String[] { "img", "applet", "embed", "area", "frame", "frameset", "iframe", "map", "object" })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$

  /* Dynamic HTML in Content */
  enum Dynamic {
    NEWS("newsitem"), TITLE("title"), TOGGLE_READ_LINK("toggleRead"), TOGGLE_READ_IMG("toggleReadImg"), HEADER("header"), FOOTER("footer"), TOGGLE_STICKY("toggleSticky"), LABELS("labels"), LABELS_SEPARATOR("labelsSeparator"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$

    private String fId;

    Dynamic(String id) {
      fId = id;
    }

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

  private String fNewsFontFamily;
  private String fNormalFontCSS;
  private String fSmallFontCSS;
  private String fBiggerFontCSS;
  private String fBiggestFontCSS;
  private String fStickyBGColorCSS;
  private IPropertyChangeListener fPropertyChangeListener;
  private final boolean fIsIE;
  private final NewsBrowserViewer fViewer;
  private boolean fStripMediaFromNews;
  private boolean fForceShowFeedInformation;
  private boolean fManageLinks;
  private final Calendar fSharedCalendar = Calendar.getInstance();
  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;

    IPreferenceScope preferences = Owl.getPreferenceService().getGlobalScope();
    fManageLinks = (preferences.getBoolean(DefaultPreferences.USE_DEFAULT_EXTERNAL_BROWSER) || preferences.getBoolean(DefaultPreferences.USE_CUSTOM_EXTERNAL_BROWSER));

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

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

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

  /*
   * @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))
          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 small = normal - 1;
    int bigger = normal + 1;
    int biggest = bigger + 6;

    String fontUnit = "pt"; //$NON-NLS-1$
    fNormalFontCSS = "font-size: " + normal + 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$
  }

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

  /**
   * @param element the element to get a HTML representation from.
   * @param index the zero-based index of the element from top.
   * @return the HTML representation for the given element.
   */
  public String getText(Object element, int index) {
    return getText(element, true, index);
  }

  /**
   * @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.
   * @return the HTML representation for the given element.
   */
  public String getText(Object element, boolean withInternalLinks) {
    return getText(element, withInternalLinks, -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 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, int index) {
    return getText(element, withInternalLinks, true, index);
  }

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

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

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

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

  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 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 withInternalLinks) throws IOException {
    writeCSS(writer, isSingleNewsDisplayed(), withInternalLinks);
  }

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

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

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

    /* Group */
    writer.append("div.group { color: #678; ").append(fBiggestFontCSS).append(" font-weight: bold; padding: 10px 0px 10px 5px; }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    /* Main DIV per Item */
    if (forSingleNews) {
      writer.write("div.newsitemUnread { margin: 0; border-bottom: dotted 1px silver; }\n"); //$NON-NLS-1$
      writer.write("div.newsitemRead { margin: 0; border-bottom: dotted 1px silver; }\n"); //$NON-NLS-1$
    } else {
      writer.write("div.newsitemUnread { margin: 0px 0px 25px 0px; border-top: dotted 1px silver; border-bottom: dotted 1px silver; }\n"); //$NON-NLS-1$
      writer.write("div.newsitemRead { margin: 0px 0px 25px 0px; border-top: dotted 1px silver; border-bottom: dotted 1px silver; }\n"); //$NON-NLS-1$
    }

    /* Main DIV Item Areas */
    writer.write("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.content { \n"); //$NON-NLS-1$
    writer.write("   padding: 15px 10px 15px 10px; border-top: dotted 1px silver; \n"); //$NON-NLS-1$
    writer.append("  background-color: #fff; clear: both; ").append(fNormalFontCSS).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.write("}\n"); //$NON-NLS-1$
    writer.write("div.footer { background-color: rgb(248,248,248); padding: 5px 10px 5px 10px; line-height: 20px; border-top: dotted 1px silver; clear: both; }\n"); //$NON-NLS-1$
    writer.append("div.footerSticky { ").append(fStickyBGColorCSS).append(" padding: 5px 10px 5px 10px; line-height: 20px; border-top: dotted 1px silver; clear: both; }\n"); //$NON-NLS-1$ //$NON-NLS-2$

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

    /* Title */
    if (withInternalLinks) //Need to set width to avoid float drop bug of delete button on all OS (see Bug 1393)
      writer.append("div.title { width: 90%; float: left; padding-bottom: 6px; ").append(fBiggerFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    else
      writer.append("div.title { float: left; padding-bottom: 6px; ").append(fBiggerFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$

    writer.write("div.title a { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.title a.unread { font-weight: bold; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.title a:hover { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.title a:visited { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$

    /* Author */
    writer.write("a.author { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("a.author:hover { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("a.author:active { color: rgb(80,80,80); text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("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$

    writer.write("div.title span.unread { font-weight: bold; }\n"); //$NON-NLS-1$

    /* 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$
    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.subline { margin: 0; padding: 0; color: rgb(80, 80, 80); padding-right: 8px; ").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$

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

    /* Attachments */
    writer.append("div.attachments { clear: both; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.write("div.attachments span.label { float: left; padding-right: 5px; }\n"); //$NON-NLS-1$
    writer.write("div.attachments a { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.attachments a:visited { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.attachments a:hover { text-decoration: underline; }\n"); //$NON-NLS-1$

    /* Categories */
    writer.append("div.categories { clear: both; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.write("div.categories span.label { float: left; padding-right: 5px; }\n"); //$NON-NLS-1$
    writer.write("div.categories a { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.categories a:visited { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.categories a:hover { text-decoration: underline; }\n"); //$NON-NLS-1$

    /* Source */
    writer.append("div.source { clear: both; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.write("div.source span.label {float: left; padding-right: 5px; }\n"); //$NON-NLS-1$
    writer.write("div.source a { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.source a:visited { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.source a:hover { text-decoration: underline; }\n"); //$NON-NLS-1$

    /* Comments */
    writer.append("div.comments { clear: both; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.write("div.comments span.label {float: left; padding-right: 5px; }\n"); //$NON-NLS-1$
    writer.write("div.comments a { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.comments a:visited { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.comments a:hover { text-decoration: underline; }\n"); //$NON-NLS-1$

    /* Search Related */
    writer.append("div.searchrelated { clear: both; ").append(fSmallFontCSS).append(" }\n"); //$NON-NLS-1$ //$NON-NLS-2$
    writer.write("div.searchrelated span.label {float: left; padding-right: 5px; }\n"); //$NON-NLS-1$
    writer.write("div.searchrelated a { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.searchrelated a:visited { color: #009; text-decoration: none; }\n"); //$NON-NLS-1$
    writer.write("div.searchrelated a:hover { text-decoration: underline; }\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$

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

  private String getLabel(EntityGroup group) {
    StringBuilder builder = new StringBuilder();

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

    if (group.getColorHint() != null && !group.getColorHint().equals(new RGB(255, 255, 255)))
      span(builder, StringUtils.htmlEscape(group.getName()), null, OwlUI.toString(group.getColorHint()));
    else
      builder.append(StringUtils.htmlEscape(group.getName()));

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

    return builder.toString();
  }

  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);
  }

  private String getLabel(INews news, boolean withInternalLinks, boolean withManagedLinks, int index) {
    String description = news.getDescription();
    if (fStripMediaFromNews)
      description = StringUtils.filterTags(description, fMediaTags, false);
    StringBuilder builder = getBuilder(news, description);
    StringBuilder search = new StringBuilder();

    String newsTitle = CoreUtils.getHeadline(news, false);
    String newsLink = CoreUtils.getLink(news);
    boolean hasLink = newsLink != null;
    State state = news.getState();
    boolean isUnread = (state == State.NEW || state == State.UPDATED || state == State.UNREAD);
    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;

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

    /* Offer Search to Find Related News from Title */
    String relatedSearchLink = ILinkHandler.HANDLER_PROTOCOL + NewsBrowserViewer.TITLE_HANDLER_ID + "?" + URIUtils.urlEncode(newsTitle); //$NON-NLS-1$
    link(search, relatedSearchLink, Messages.NewsBrowserLabelProvider_SIMILAR, "searchrelated"); //$NON-NLS-1$
    search.append(", "); //$NON-NLS-1$

    /* Add Labels to Search */
    for (ILabel label : labels) {
      String link = ILinkHandler.HANDLER_PROTOCOL + NewsBrowserViewer.LABEL_HANDLER_ID + "?" + URIUtils.urlEncode(label.getName()); //$NON-NLS-1$
      String labelColor = label.getColor();

      if (!"0,0,0".equals(labelColor) && !"255,255,255".equals(labelColor)) //$NON-NLS-1$ //$NON-NLS-2$
        link(search, link, StringUtils.htmlEscape(label.getName()), "searchrelated", labelColor); //$NON-NLS-1$
      else
        link(search, link, StringUtils.htmlEscape(label.getName()), "searchrelated"); //$NON-NLS-1$

      search.append(", "); //$NON-NLS-1$
    }

    /* DIV: NewsItem */
    if (index == 0)
      div(builder, isUnread ? "newsitemUnread" : "newsitemRead", "border-top: none;", Dynamic.NEWS.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    else
      div(builder, isUnread ? "newsitemUnread" : "newsitemRead", Dynamic.NEWS.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$

    /* DIV: NewsItem/Header */
    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"); //$NON-NLS-1$

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

      /* Link */
      if (hasLink)
        link(builder, (fManageLinks && withManagedLinks) ? URIUtils.toManaged(newsLink) : newsLink, newsTitle, cssClass, Dynamic.TITLE.getId(news), color);

      /* Normal */
      else
        span(builder, newsTitle, cssClass, Dynamic.TITLE.getId(news), color);

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

    /* Delete */
    if (withInternalLinks) {

      /* DIV: NewsItem/Header/Delete */
      div(builder, "delete"); //$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_light.gif", "remove_light.gif", null, null); //$NON-NLS-1$ //$NON-NLS-2$

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

    /* DIV: NewsItem/Header/Subline */
    div(builder, "subline"); //$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=\"subline\">"); //$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=\"subline\">"); //$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", null, Dynamic.TOGGLE_STICKY.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=\"subline\">"); //$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", null, null); //$NON-NLS-1$ //$NON-NLS-2$
      builder.append("</td>"); //$NON-NLS-1$

      /* Share News Context Menu */
      builder.append("<td class=\"subline\">"); //$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", null, null); //$NON-NLS-1$ //$NON-NLS-2$
      builder.append("</td>"); //$NON-NLS-1$

      /* News Context Menu */
      builder.append("<td class=\"subline\">"); //$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", null, null); //$NON-NLS-1$ //$NON-NLS-2$
      builder.append("</td>"); //$NON-NLS-1$

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

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

    Date newsDate = DateUtils.getRecentDate(news);
    if (isToday(newsDate))
      builder.append(fTimeFormat.format(newsDate));
    else
      builder.append(fDateFormat.format(newsDate));

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

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

      builder.append("<td class=\"subline\">"); //$NON-NLS-1$
      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.contains("@") && !name.contains(" ")) //$NON-NLS-1$ //$NON-NLS-2$
        email = name;

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

      /* Add to Search */
      String value = StringUtils.isSet(name) ? name : email;
      if (StringUtils.isSet(value)) {
        String link = ILinkHandler.HANDLER_PROTOCOL + NewsBrowserViewer.AUTHOR_HANDLER_ID + "?" + URIUtils.urlEncode(value); //$NON-NLS-1$
        link(search, link, NLS.bind(Messages.NewsBrowserLabelProvider_BY_AUTHOR, StringUtils.htmlEscape(value)), "searchrelated"); //$NON-NLS-1$
        search.append(", "); //$NON-NLS-1$
      }
      builder.append("</td>"); //$NON-NLS-1$
    }

    /* Feed Information */
    if (showFeedInformation()) {

      /* Retrieve Name */
      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);
        }
      }

      /* Show Name if Provided */
      if (StringUtils.isSet(feedName)) {
        builder.append("<td class=\"subline\">"); //$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=\"subline\">"); //$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$
    }

    /* Go to Attachments */
    if (hasAttachments) {
      builder.append("<td class=\"subline\">"); //$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", null, 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=\"subline\" style=\"display: none;\">"); //$NON-NLS-1$ //$NON-NLS-2$
    else
      builder.append("<td id=\"").append(Dynamic.LABELS_SEPARATOR.getId(news)).append("\" class=\"subline\">"); //$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$

    if (!labels.isEmpty())
      builder.append(Messages.NewsBrowserLabelProvider_LABELS).append(" "); //$NON-NLS-1$

    /* Append Labels to Footer */
    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());
    }

    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 */
      div(builder, "content"); //$NON-NLS-1$

      if (StringUtils.isSet(description) && !description.equals(news.getTitle()))
        builder.append(description);
      else {
        builder.append(Messages.NewsBrowserLabelProvider_NO_CONTENT);

        if (hasLink) {
          builder.append(" "); //$NON-NLS-1$
          link(builder, (fManageLinks && withManagedLinks) ? URIUtils.toManaged(newsLink) : newsLink, Messages.NewsBrowserLabelProvider_OPEN_IN_BROWSER, null);
        }
      }

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

    /* News Footer */
    {
      StringBuilder footer = new StringBuilder();

      /* DIV: NewsItem/Footer */
      div(footer, news.isFlagged() ? "footerSticky" : "footer", Dynamic.FOOTER.getId(news)); //$NON-NLS-1$ //$NON-NLS-2$

      /* Attachments */
      if (attachments.size() != 0) {

        /* DIV: NewsItem/Footer/Attachments */
        div(footer, "attachments"); //$NON-NLS-1$

        /* Label */
        span(footer, attachments.size() == 1 ? Messages.NewsBrowserLabelProvider_ATTACHMENT : Messages.NewsBrowserLabelProvider_ATTACHMENTSS, "label"); //$NON-NLS-1$

        /* For each Attachment */
        boolean strip = false;
        for (IAttachment attachment : attachments) {
          if (attachment.getLink() != null) {
            strip = true;
            URI link = attachment.getLink();
            String name = URIUtils.getFile(link, OwlUI.getExtensionForMime(attachment.getType()));
            if (!StringUtils.isSet(name))
              name = link.toASCIIString();

            String size = OwlUI.getSize(attachment.getLength());
            if (size != null)
              link(footer, link.toASCIIString(), NLS.bind(Messages.NewsBrowserLabelProvider_NAME_SIZE, StringUtils.htmlEscape(name), size), "attachment"); //$NON-NLS-1$
            else
              link(footer, link.toASCIIString(), StringUtils.htmlEscape(name), "attachment"); //$NON-NLS-1$

            footer.append(", "); //$NON-NLS-1$
          }
        }

        if (strip && footer.length() > 0)
          footer.delete(footer.length() - 2, footer.length());

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

      /* Source */
      ISource source = news.getSource();
      if (source != null) {
        String link = (source.getLink() != null) ? source.getLink().toASCIIString() : null;
        String name = source.getName();
        if (StringUtils.isSet(link) || StringUtils.isSet(name)) {

          /* DIV: NewsItem/Footer/Source */
          div(footer, "source"); //$NON-NLS-1$

          /* Label */
          span(footer, Messages.NewsBrowserLabelProvider_SOURCE, "label"); //$NON-NLS-1$

          if (StringUtils.isSet(name) && link != null)
            link(footer, link, StringUtils.htmlEscape(name), "source"); //$NON-NLS-1$
          else if (link != null)
            link(footer, link, StringUtils.htmlEscape(link), "source"); //$NON-NLS-1$
          else if (StringUtils.isSet(name))
            footer.append(StringUtils.htmlEscape(name));

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

      /* Add Categories to Search */
      List<ICategory> categories = news.getCategories();
      if (categories.size() > 0) {

        /* For each Category */
        for (ICategory category : categories) {
          String name = category.getName();

          /* Add to Search */
          if (StringUtils.isSet(name)) {
            String link = ILinkHandler.HANDLER_PROTOCOL + NewsBrowserViewer.CATEGORY_HANDLER_ID + "?" + URIUtils.urlEncode(name); //$NON-NLS-1$
            link(search, link, StringUtils.htmlEscape(name), "searchrelated"); //$NON-NLS-1$
            search.append(", "); //$NON-NLS-1$
          }
        }
      }

      /* Find related News */
      if (search.length() > 0) {
        search.delete(search.length() - 2, search.length());

        /* DIV: NewsItem/Footer/SearchRelated */
        div(footer, "searchrelated"); //$NON-NLS-1$

        /* Label */
        if (withInternalLinks)
          span(footer, Messages.NewsBrowserLabelProvider_FIND_RELATED, "label"); //$NON-NLS-1$

        /* Append to Footer */
        if (withInternalLinks)
          footer.append(search);

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

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

      /* Append */
      builder.append(footer);
    }

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

    String result = builder.toString();

    /* Highlight Support */
    if (fViewer != null) {
      Collection<String> wordsToHighlight = fViewer.getHighlightedWords();
      if (!wordsToHighlight.isEmpty()) {
        StringBuilder highlightedResult = new StringBuilder(result.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(result), 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 result;
  }

  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) {
    builder.append("<a"); //$NON-NLS-1$

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

    builder.append(" title=\"").append(tooltip).append("\" href=\"").append(link).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    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);

    builder.append(" alt=\"").append(alt).append("\" border=\"0\" src=\"").append(imageUri).append("\" />"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    builder.append("</a>"); //$NON-NLS-1$
  }

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

  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$
  }

  private boolean isToday(Date date) {
    fSharedCalendar.set(Calendar.HOUR_OF_DAY, 0);
    fSharedCalendar.set(Calendar.MINUTE, 0);
    fSharedCalendar.set(Calendar.SECOND, 0);
    fSharedCalendar.set(Calendar.MILLISECOND, 0);

    return date.compareTo(fSharedCalendar.getTime()) >= 0;
  }
}
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.