/* ********************************************************************** **
** 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 org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.OwnerDrawLabelProvider;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerColumn;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TreeItem;
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.INewsBin;
import org.rssowl.core.persist.IPerson;
import org.rssowl.core.persist.INews.State;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.reference.NewsBinReference;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.DateUtils;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.URIUtils;
import org.rssowl.ui.internal.Application;
import org.rssowl.ui.internal.EntityGroup;
import org.rssowl.ui.internal.OwlUI;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Ismael Juma (ismael@juma.me.uk)
* @author bpasero
*/
public class NewsTableLabelProvider extends OwnerDrawLabelProvider {
/** News Column Model to use */
protected NewsColumnViewModel fColumnModel;
/* Some Colors of a Label */
private static final String LABEL_COLOR_BLACK = "0,0,0"; //$NON-NLS-1$
private static final String LABEL_COLOR_WHITE = "255,255,255"; //$NON-NLS-1$
/** Resource Manager to use */
protected LocalResourceManager fResources;
/* Date Formatter for News */
private final DateFormat fDateFormat = OwlUI.getShortDateFormat();
/* Pre-Cache some Colors being used */
private Color fStickyBgColor;
private Color fGradientFgColor;
private Color fGradientBgColor;
private Color fGradientEndColor;
private Color fGroupFgColor;
private Color fGroupBgColor;
private Color fNewsBgGradientStartColor;
private Color fNewsBgGradientEndColor;
private RGB fListBackground;
private RGB fListSelectionBackground;
/* Pre-Cache some Images being used */
private Image fNewsUnreadIcon;
private Image fNewsNewIcon;
private Image fNewsUpdatedIcon;
private Image fNewsReadIcon;
private Image fNewsStickyIcon;
private Image fNewsNonStickyIcon;
private Image fGroupIcon;
/* Pre-Cache some Fonts being used */
private Font fBoldFont;
/* A cache for the Feed and Location Column */
private Map<Long, String> fMapBinIdToLocation = new HashMap<Long, String>();
private Map<String, String> fMapFeedLinkToLocation = new HashMap<String, String>();
private Map<String, ImageDescriptor> fMapFeedLinkToFeedIcon = new HashMap<String, ImageDescriptor>();
/**
* Creates a new instance of this LabelProvider
*
* @param model the column model.
*/
public NewsTableLabelProvider(NewsColumnViewModel model) {
fColumnModel = model;
fResources = new LocalResourceManager(JFaceResources.getResources());
createResources();
}
/*
* @see org.eclipse.jface.viewers.OwnerDrawLabelProvider#initialize(org.eclipse.jface.viewers.ColumnViewer, org.eclipse.jface.viewers.ViewerColumn)
*/
@Override
protected void initialize(ColumnViewer viewer, ViewerColumn column) {
super.initialize(viewer, column, false); //Disable Custom Ownerdrawn
}
/**
* @param model
*/
public void init(NewsColumnViewModel model) {
fColumnModel = model;
}
void updateResources() {
/* Sticky Color */
fStickyBgColor = OwlUI.getThemeColor(OwlUI.STICKY_BG_COLOR_ID, fResources, new RGB(255, 255, 180));
/* News Background Color */
createNewsListBackgroundResources();
}
private void createResources() {
/* Colors */
fStickyBgColor = OwlUI.getThemeColor(OwlUI.STICKY_BG_COLOR_ID, fResources, new RGB(255, 255, 180));
fGradientFgColor = OwlUI.getColor(fResources, OwlUI.GROUP_GRADIENT_FG_COLOR);
fGradientBgColor = OwlUI.getColor(fResources, OwlUI.GROUP_GRADIENT_BG_COLOR);
fGradientEndColor = OwlUI.getColor(fResources, OwlUI.GROUP_GRADIENT_END_COLOR);
fGroupFgColor = OwlUI.getColor(fResources, OwlUI.GROUP_FG_COLOR);
fGroupBgColor = OwlUI.getColor(fResources, OwlUI.GROUP_BG_COLOR);
fListBackground = fResources.getDevice().getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB();
fListSelectionBackground = fResources.getDevice().getSystemColor(SWT.COLOR_LIST_SELECTION).getRGB();
createNewsListBackgroundResources();
/* Icons */
fNewsUnreadIcon = OwlUI.getImage(fResources, OwlUI.NEWS_STATE_UNREAD);
fNewsNewIcon = OwlUI.getImage(fResources, OwlUI.NEWS_STATE_NEW);
fNewsUpdatedIcon = OwlUI.getImage(fResources, OwlUI.NEWS_STATE_UPDATED);
fNewsReadIcon = OwlUI.getImage(fResources, OwlUI.NEWS_STATE_READ);
fNewsStickyIcon = OwlUI.getImage(fResources, OwlUI.NEWS_PINNED);
fNewsNonStickyIcon = OwlUI.getImage(fResources, OwlUI.NEWS_PIN);
fGroupIcon = OwlUI.getImage(fResources, OwlUI.GROUP);
/* Fonts */
fBoldFont = OwlUI.getThemeFont(OwlUI.HEADLINES_FONT_ID, SWT.BOLD);
}
private void createNewsListBackgroundResources() {
fNewsBgGradientStartColor = null;
fNewsBgGradientEndColor = null;
RGB listBackgroundRGB = Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB();
RGB newsBgColorRGB = OwlUI.getThemeRGB(OwlUI.NEWS_LIST_BG_COLOR_ID, listBackgroundRGB);
if (newsBgColorRGB != null && !listBackgroundRGB.equals(newsBgColorRGB) && !isInvalidColor(newsBgColorRGB)) {
fNewsBgGradientEndColor = OwlUI.getColor(fResources, newsBgColorRGB);
RGB newsBgColorRGBLighter = new RGB(0, 0, 0);
newsBgColorRGBLighter.red = Math.min(newsBgColorRGB.red + 5, 255);
newsBgColorRGBLighter.green = Math.min(newsBgColorRGB.green + 5, 255);
newsBgColorRGBLighter.blue = Math.min(newsBgColorRGB.blue + 5, 255);
fNewsBgGradientStartColor = OwlUI.getColor(fResources, newsBgColorRGBLighter);
}
}
/*
* @see org.eclipse.jface.viewers.OwnerDrawLabelProvider#update(org.eclipse.jface.viewers.ViewerCell)
*/
@Override
public void update(ViewerCell cell) {
NewsColumn column = fColumnModel.getColumn(cell.getColumnIndex());
/* Text */
cell.setText(getColumnText(cell.getElement(), column, cell.getColumnIndex()));
/* Image */
cell.setImage(getColumnImage(cell.getElement(), column, cell.getColumnIndex()));
/* Font */
cell.setFont(getFont(cell.getElement(), cell.getColumnIndex()));
/* Foreground */
Color foreground = getForeground(cell.getElement(), cell.getColumnIndex());
/* This is required to invalidate + redraw the entire TableItem! */
if (!OwlUI.isHighContrast()) {
Item item = (Item) cell.getItem();
if (item instanceof TreeItem)
((TreeItem) cell.getItem()).setForeground(foreground);
else if (item instanceof TableItem)
((TableItem) cell.getItem()).setForeground(foreground);
}
/* Background */
if (!OwlUI.isHighContrast())
cell.setBackground(getBackground(cell.getElement(), cell.getColumnIndex()));
}
/*
* @see org.eclipse.jface.viewers.CellLabelProvider#getToolTipText(java.lang.Object)
*/
@Override
public String getToolTipText(Object element) {
/* News */
if (element instanceof INews) {
INews news = (INews) element;
String feedRef = news.getFeedLinkAsText();
IBookMark bookMark = CoreUtils.getBookMark(feedRef);
String name = null;
if (bookMark != null)
name = bookMark.getName();
else
name = feedRef;
if (news.getParentId() != 0) {
INewsBin bin = DynamicDAO.load(INewsBin.class, news.getParentId());
if (bin != null) {
name = NLS.bind(Messages.NewsTableLabelProvider_BIN_NAME, bin.getName(), name);
}
}
return StringUtils.replaceAll(name, "&", "&&"); //$NON-NLS-1$ //$NON-NLS-2$
}
/* Entity Group */
else if (element instanceof EntityGroup) {
return StringUtils.replaceAll(((EntityGroup) element).getName(), "&", "&&"); //$NON-NLS-1$ //$NON-NLS-2$
}
return super.getToolTipText(element);
}
/*
* @see org.eclipse.jface.viewers.CellLabelProvider#getToolTipShift(java.lang.Object)
*/
@Override
public Point getToolTipShift(Object object) {
if (Application.IS_WINDOWS)
return new Point(0, 21);
return super.getToolTipShift(object);
}
/**
* @param element
* @param column
* @param colIndex
* @return String
*/
protected String getColumnText(Object element, NewsColumn column, int colIndex) {
String text = null;
/* Handle News */
if (element instanceof INews) {
INews news = (INews) element;
switch (column) {
case TITLE:
text = CoreUtils.getHeadline(news, true);
break;
case DATE:
Date date = DateUtils.getRecentDate(news);
text = fDateFormat.format(date);
break;
case PUBLISHED:
Date published = news.getPublishDate();
if (published != null)
text = fDateFormat.format(published);
break;
case MODIFIED:
Date modified = news.getModifiedDate();
if (modified != null)
text = fDateFormat.format(modified);
break;
case RECEIVED:
Date received = news.getReceiveDate();
if (received != null)
text = fDateFormat.format(received);
break;
case AUTHOR:
IPerson author = news.getAuthor();
if (author != null) {
if (author.getName() != null)
text = author.getName();
else if (author.getEmail() != null)
text = author.getEmail().toString();
}
break;
case CATEGORY:
List<ICategory> categories = news.getCategories();
if (!categories.isEmpty()) {
StringBuilder str = new StringBuilder();
for (ICategory category : categories) {
if (StringUtils.isSet(category.getName()))
str.append(category.getName().trim()).append(", "); //$NON-NLS-1$
else if (StringUtils.isSet(category.getDomain()))
str.append(category.getDomain().trim()).append(", "); //$NON-NLS-1$
}
if (str.length() > 0)
str = str.delete(str.length() - 2, str.length());
text = str.toString();
}
break;
case LABELS:
Set<ILabel> labels = CoreUtils.getSortedLabels(news);
if (!labels.isEmpty()) {
StringBuilder str = new StringBuilder();
for (ILabel label : labels) {
str.append(label.getName()).append(", "); //$NON-NLS-1$
}
if (str.length() > 0)
str = str.delete(str.length() - 2, str.length());
text = str.toString();
}
break;
case STATUS:
State state = news.getState();
if (state == State.NEW)
text = Messages.NewsTableLabelProvider_NEW;
else if (state == State.UNREAD)
text = Messages.NewsTableLabelProvider_UNREAD;
else if (state == State.UPDATED)
text = Messages.NewsTableLabelProvider_UPDATED;
else if (state == State.READ)
text = Messages.NewsTableLabelProvider_READ;
break;
case LOCATION:
/* Location: Bin */
if (news.getParentId() > 0) {
String location = fMapBinIdToLocation.get(news.getParentId());
if (location == null) {
NewsBinReference ref = new NewsBinReference(news.getParentId());
INewsBin bin = ref.resolve();
location = bin.getName();
fMapBinIdToLocation.put(news.getParentId(), location);
}
text = location;
}
/* Location: Bookmark */
else {
String location = fMapFeedLinkToLocation.get(news.getFeedLinkAsText());
if (location == null) {
IBookMark bookmark = CoreUtils.getBookMark(news.getFeedLinkAsText());
if (bookmark != null) {
location = bookmark.getName();
fMapFeedLinkToLocation.put(news.getFeedLinkAsText(), location);
}
}
text = location;
}
break;
case LINK:
text = CoreUtils.getLink(news);
if (StringUtils.isSet(text)) {
text = StringUtils.replaceAll(text, URIUtils.HTTP, ""); //$NON-NLS-1$
text = StringUtils.replaceAll(text, "www.", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
/* Handle EntityGroup */
else if (element instanceof EntityGroup && column == NewsColumn.TITLE)
text = ((EntityGroup) element).getName();
/* Make sure to normalize the Text for the Table */
return text != null ? StringUtils.normalizeString(text) : null;
}
/**
* @param element
* @param newsColumn
* @param colIndex
* @return Image
*/
protected Image getColumnImage(Object element, NewsColumn newsColumn, int colIndex) {
/* News */
if (element instanceof INews) {
INews news = (INews) element;
/* News Icon */
if (newsColumn == NewsColumn.TITLE) {
if (news.getState() == INews.State.UNREAD)
return fNewsUnreadIcon;
else if (news.getState() == INews.State.NEW)
return fNewsNewIcon;
else if (news.getState() == INews.State.UPDATED)
return fNewsUpdatedIcon;
else if (news.getState() == INews.State.READ)
return fNewsReadIcon;
}
/* Feed Column */
else if (newsColumn == NewsColumn.FEED) {
String feedRef = news.getFeedLinkAsText();
ImageDescriptor feedIcon = fMapFeedLinkToFeedIcon.get(feedRef);
if (feedIcon == null) {
IBookMark bookMark = CoreUtils.getBookMark(feedRef);
if (bookMark != null)
feedIcon = OwlUI.getFavicon(bookMark);
if (feedIcon == null)
feedIcon = OwlUI.BOOKMARK;
fMapFeedLinkToFeedIcon.put(feedRef, feedIcon);
}
return OwlUI.getImage(fResources, feedIcon);
}
/* Sticky State */
else if (newsColumn == NewsColumn.STICKY) {
if (news.isFlagged())
return fNewsStickyIcon;
return fNewsNonStickyIcon;
}
/* Attachment */
else if (newsColumn == NewsColumn.ATTACHMENTS) {
List<IAttachment> attachments = news.getAttachments();
if (!attachments.isEmpty())
return OwlUI.getImage(fResources, OwlUI.ATTACHMENT);
}
}
/* EntityGroup Image */
else if (element instanceof EntityGroup && newsColumn == NewsColumn.TITLE) {
EntityGroup group = (EntityGroup) element;
if (group.getImage() != null)
return OwlUI.getImage(fResources, group.getImage());
return fGroupIcon;
}
return null;
}
/**
* @param element
* @param columnIndex
* @return Font
*/
protected Font getFont(Object element, int columnIndex) {
/* Use a Bold Font for Unread News */
if (element instanceof INews) {
INews news = (INews) element;
INews.State state = news.getState();
if (state == null)
return null;
/* Bold for New, Updated and Unread News */
if (state == INews.State.NEW || state == INews.State.UPDATED || state == INews.State.UNREAD)
return fBoldFont;
}
/* Use Bold Font for EntityGroup */
if (element instanceof EntityGroup)
return fBoldFont;
return null;
}
/**
* @param element
* @param columnIndex
* @return Color
*/
protected Color getBackground(Object element, int columnIndex) {
/* Handle INews */
if (element instanceof INews && ((INews) element).isFlagged())
return fStickyBgColor;
/* Handle EntityGroup */
else if (element instanceof EntityGroup)
return fGroupBgColor;
return null;
}
/**
* @param element
* @param columnIndex
* @return Color
*/
protected Color getForeground(Object element, int columnIndex) {
/* Handle EntityGroup */
if (element instanceof EntityGroup) {
EntityGroup group = (EntityGroup) element;
if (group.getColorHint() != null) {
if (!fListBackground.equals(group.getColorHint()) && !fListSelectionBackground.equals(group.getColorHint()))
return OwlUI.getColor(fResources, group.getColorHint());
}
return fGroupFgColor;
}
/* Handle INews */
else if (element instanceof INews) {
Set<ILabel> labels = CoreUtils.getSortedLabels((INews) element);
if (!labels.isEmpty()) {
RGB labelRGB = OwlUI.getRGB(labels.iterator().next());
if (!fListBackground.equals(labelRGB) && !fListSelectionBackground.equals(labelRGB))
return OwlUI.getColor(fResources, labelRGB);
}
}
return null;
}
/*
* @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
*/
@Override
public void dispose() {
fResources.dispose();
}
/*
* @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,
* java.lang.String)
*/
@Override
public boolean isLabelProperty(Object element, String property) {
return false;
}
/*
* @see org.eclipse.jface.viewers.OwnerDrawLabelProvider#erase(org.eclipse.swt.widgets.Event,
* java.lang.Object)
*/
@Override
public void erase(Event event, Object element) {
/* Erase News */
if (element instanceof INews)
eraseNews(event, (INews) element);
/* Erase Group */
else if (element instanceof EntityGroup)
eraseGroup(event, (EntityGroup) element);
}
private void eraseGroup(Event event, EntityGroup group) {
Scrollable scrollable = (Scrollable) event.widget;
GC gc = event.gc;
/* Draw Color if Selected */
if (group.getColorHint() != null && (event.detail & SWT.SELECTED) != 0) {
/* Some conditions under which we don't override the selection color */
if (!scrollable.isFocusControl() || isInvalidColor(group.getColorHint()))
return;
Rectangle clArea = scrollable.getClientArea();
Rectangle itemRect = event.getBounds();
/* Paint the selection beyond the end of last column */
OwlUI.codExpandRegion(event, scrollable, gc, clArea);
/* Draw Rectangle */
Color oldBackground = gc.getBackground();
gc.setBackground(OwlUI.getColor(fResources, group.getColorHint()));
gc.fillRectangle(0, itemRect.y, clArea.width, itemRect.height);
gc.setBackground(oldBackground);
gc.setForeground(scrollable.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
/* Mark as Selected being handled */
event.detail &= ~SWT.SELECTED;
}
/* Draw Gradient */
else
OwlUI.codDrawGradient(event, fGradientFgColor, fGradientBgColor, fGradientEndColor);
}
private void eraseNews(Event event, INews news) {
Scrollable scrollable = (Scrollable) event.widget;
GC gc = event.gc;
/* Handle selected News (Linux: Note Bug 444) */
if ((event.detail & SWT.SELECTED) != 0 && (Application.IS_WINDOWS || !news.isFlagged())) {
/* Do not override selection color if not focus control */
if (!scrollable.isFocusControl())
return;
/* Load Labels */
Set<ILabel> labels = CoreUtils.getSortedLabels(news);
if (labels.isEmpty())
return;
ILabel label = labels.iterator().next();
if (isInvalidColor(label))
return;
Rectangle clArea = scrollable.getClientArea();
Rectangle itemRect = event.getBounds();
/* Paint the selection beyond the end of last column */
OwlUI.codExpandRegion(event, scrollable, gc, clArea);
/* Draw Rectangle */
Color oldBackground = gc.getBackground();
gc.setBackground(OwlUI.getColor(fResources, label));
gc.fillRectangle(0, itemRect.y, clArea.width, itemRect.height);
gc.setBackground(oldBackground);
gc.setForeground(scrollable.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
/* Mark as Selected being handled */
event.detail &= ~SWT.SELECTED;
}
/* Handle Non-Selected flagged News */
else if (news.isFlagged()) {
Rectangle clArea = scrollable.getClientArea();
Rectangle itemRect = event.getBounds();
/* Paint the selection beyond the end of last column */
OwlUI.codExpandRegion(event, scrollable, gc, clArea);
/* Draw Rectangle */
Color oldBackground = gc.getBackground();
gc.setBackground(fStickyBgColor);
gc.fillRectangle(0, itemRect.y, clArea.width, itemRect.height);
gc.setBackground(oldBackground);
/* Mark as Background being handled */
event.detail &= ~SWT.BACKGROUND;
}
/* Handle News List Background Color if set */
else if (fNewsBgGradientStartColor != null && fNewsBgGradientEndColor != null) {
int index = 0;
/* Tree */
if (event.item instanceof TreeItem) {
TreeItem item = (TreeItem) event.item;
TreeItem parentItem = item.getParentItem();
if (parentItem != null)
index = parentItem.indexOf(item);
else
index = item.getParent().indexOf(item);
}
/* Table */
else if (event.item instanceof TableItem) {
TableItem item = (TableItem) event.item;
index = item.getParent().indexOf(item);
}
if (index % 2 != 0)
OwlUI.codDrawGradient(event, fNewsBgGradientStartColor, fNewsBgGradientEndColor, fNewsBgGradientEndColor);
}
}
private boolean isInvalidColor(ILabel label) {
return label.getColor().equals(LABEL_COLOR_BLACK) || label.getColor().equals(LABEL_COLOR_WHITE);
}
private boolean isInvalidColor(RGB color) {
if (color.blue == 0 && color.red == 0 && color.green == 0)
return true;
if (color.blue == 255 && color.red == 255 && color.green == 255)
return true;
return false;
}
/*
* @see org.eclipse.jface.viewers.OwnerDrawLabelProvider#measure(org.eclipse.swt.widgets.Event,
* java.lang.Object)
*/
@Override
protected void measure(Event event, Object element) {
/* Ignore */
}
/*
* @see org.eclipse.jface.viewers.OwnerDrawLabelProvider#paint(org.eclipse.swt.widgets.Event,
* java.lang.Object)
*/
@Override
protected void paint(Event event, Object element) {
/* Ignore */
}
}