/* ********************************************************************** **
** 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.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.SameShellProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.rssowl.core.Owl;
import org.rssowl.core.connection.ConnectionException;
import org.rssowl.core.connection.IProtocolHandler;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
import org.rssowl.core.persist.ICategory;
import org.rssowl.core.persist.IEntity;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.INewsMark;
import org.rssowl.core.persist.IPerson;
import org.rssowl.core.persist.ISearch;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.ISearchField;
import org.rssowl.core.persist.SearchSpecifier;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.INewsDAO;
import org.rssowl.core.persist.event.NewsEvent;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.ITask;
import org.rssowl.core.util.ITask.Priority;
import org.rssowl.core.util.Pair;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.URIUtils;
import org.rssowl.ui.internal.Activator;
import org.rssowl.ui.internal.ApplicationActionBarAdvisor;
import org.rssowl.ui.internal.ApplicationServer;
import org.rssowl.ui.internal.Controller;
import org.rssowl.ui.internal.EntityGroup;
import org.rssowl.ui.internal.EntityGroupItem;
import org.rssowl.ui.internal.ILinkHandler;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.OwlUI.Layout;
import org.rssowl.ui.internal.OwlUI.PageSize;
import org.rssowl.ui.internal.actions.ArchiveNewsAction;
import org.rssowl.ui.internal.actions.AutomateFilterAction;
import org.rssowl.ui.internal.actions.CreateFilterAction.PresetAction;
import org.rssowl.ui.internal.actions.MakeNewsStickyAction;
import org.rssowl.ui.internal.actions.MarkAllNewsReadAction;
import org.rssowl.ui.internal.actions.MoveCopyNewsToBinAction;
import org.rssowl.ui.internal.actions.NavigationActionFactory;
import org.rssowl.ui.internal.actions.OpenInExternalBrowserAction;
import org.rssowl.ui.internal.actions.OpenNewsAction;
import org.rssowl.ui.internal.actions.ToggleReadStateAction;
import org.rssowl.ui.internal.dialogs.SearchNewsDialog;
import org.rssowl.ui.internal.editors.feed.NewsBrowserLabelProvider.Dynamic;
import org.rssowl.ui.internal.undo.NewsStateOperation;
import org.rssowl.ui.internal.undo.StickyOperation;
import org.rssowl.ui.internal.undo.UndoStack;
import org.rssowl.ui.internal.util.CBrowser;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.util.JobTracker;
import org.rssowl.ui.internal.util.ModelUtils;
import org.rssowl.ui.internal.util.UIBackgroundJob;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
/**
* @author bpasero
*/
public class NewsBrowserViewer extends ContentViewer implements ILinkHandler {
/* ID for Link Handlers */
static final String MARK_READ_HANDLER_ID = "org.rssowl.ui.MarkRead"; //$NON-NLS-1$
static final String TOGGLE_READ_HANDLER_ID = "org.rssowl.ui.ToggleRead"; //$NON-NLS-1$
static final String TOGGLE_STICKY_HANDLER_ID = "org.rssowl.ui.ToggleSticky"; //$NON-NLS-1$
static final String ARCHIVE_HANDLER_ID = "org.rssowl.ui.Archive"; //$NON-NLS-1$
static final String DELETE_HANDLER_ID = "org.rssowl.ui.Delete"; //$NON-NLS-1$
static final String ATTACHMENT_HANDLER_ID = "org.rssowl.ui.DownloadAttachment"; //$NON-NLS-1$
static final String ATTACHMENTS_MENU_HANDLER_ID = "org.rssowl.ui.AttachmentsMenu"; //$NON-NLS-1$
static final String LABELS_MENU_HANDLER_ID = "org.rssowl.ui.LabelsMenu"; //$NON-NLS-1$
static final String EXPAND_NEWS_HANDLER_ID = "org.rssowl.ui.ExpandNews"; //$NON-NLS-1$
static final String COLLAPSE_NEWS_HANDLER_ID = "org.rssowl.ui.CollapseNews"; //$NON-NLS-1$
static final String EXPAND_GROUP_HANDLER_ID = "org.rssowl.ui.ExpandGroup"; //$NON-NLS-1$
static final String COLLAPSE_GROUP_HANDLER_ID = "org.rssowl.ui.CollapseGroup"; //$NON-NLS-1$
static final String GROUP_MENU_HANDLER_ID = "org.rssowl.ui.GroupMenu"; //$NON-NLS-1$
static final String NEWS_MENU_HANDLER_ID = "org.rssowl.ui.NewsMenu"; //$NON-NLS-1$
static final String SHARE_NEWS_MENU_HANDLER_ID = "org.rssowl.ui.ShareNewsMenu"; //$NON-NLS-1$
static final String NEXT_NEWS_HANDLER_ID = "org.rssowl.ui.NextNews"; //$NON-NLS-1$
static final String NEXT_UNREAD_NEWS_HANDLER_ID = "org.rssowl.ui.NextUnreadNews"; //$NON-NLS-1$
static final String PREVIOUS_NEWS_HANDLER_ID = "org.rssowl.ui.PreviousNews"; //$NON-NLS-1$
static final String PREVIOUS_UNREAD_NEWS_HANDLER_ID = "org.rssowl.ui.PreviousUnreadNews"; //$NON-NLS-1$
static final String TRANSFORM_HANDLER_ID = "org.rssowl.ui.TransformNews"; //$NON-NLS-1$
static final String RELATED_NEWS_MENU_HANDLER_ID = "org.rssowl.ui.RelatedNewsMenu"; //$NON-NLS-1$
static final String NEXT_PAGE_HANDLER_ID = "org.rssowl.ui.NextPage"; //$NON-NLS-1$
static final String SCROLL_NEXT_PAGE_HANDLER_ID = "org.rssowl.ui.ScrollNextPage"; //$NON-NLS-1$
/* Delay in millies before reacting on user interaction */
private static final int USER_INTERACTION_DELAY = 500;
/* Unique identifier of the <body> element */
private static final String BODY_ELEMENT_ID = "owlbody"; //$NON-NLS-1$
private Object fInput;
private CBrowser fBrowser;
private IFeedViewSite fSite;
private boolean fIsEmbedded;
private Menu fNewsContextMenu;
private Menu fAttachmentsContextMenu;
private Menu fLabelsContextMenu;
private Menu fShareNewsContextMenu;
private Menu fFindRelatedContextMenu;
private IStructuredSelection fCurrentSelection = StructuredSelection.EMPTY;
private final ApplicationServer fServer;
private final String fId;
private boolean fBlockRefresh;
private boolean fMarkReadOnExpand = true;
private boolean fMarkReadOnScrolling = true;
private int fPageSize;
private final IModelFactory fFactory;
private final IPreferenceScope fPreferences = Owl.getPreferenceService().getGlobalScope();
private final INewsDAO fNewsDao = DynamicDAO.getDAO(INewsDAO.class);
private final JobTracker fUserInteractionTracker = new JobTracker(USER_INTERACTION_DELAY, false, true, Priority.INTERACTIVE);
private final Set<Long> fMarkedUnreadByUserCache = Collections.synchronizedSet(new HashSet<Long>());
/* This viewer's sorter. <code>null</code> means there is no sorter. */
private ViewerComparator fSorter;
/* This viewer's filters (element type: <code>ViewerFilter</code>). */
private List<ViewerFilter> fFilters;
private NewsFilter fNewsFilter;
/* A model of what is displayed in the browser */
private final NewsBrowserViewModel fViewModel;
/* Special Element that denotes a Paging Latch */
static final class PageLatch {}
/* Task to perform some news related actions based on user interaction */
final class UserInteractionTask implements ITask {
private final NewsBrowserViewModel fViewModel;
private final CBrowser fCBrowser;
public UserInteractionTask(NewsBrowserViewModel model, CBrowser browser) {
fViewModel = model;
fCBrowser = browser;
}
public IStatus run(IProgressMonitor monitor) {
/* Return early if canceled or disposed */
if (monitor.isCanceled() || fCBrowser.getControl().isDisposed())
return Status.OK_STATUS;
/* Reveal next page as necessary */
if (fPageSize != 0 && fViewModel.hasHiddenNews()) {
long lastVisibleNewsId = fViewModel.getLastVisibleNews();
if (lastVisibleNewsId != -1) {
StringBuilder js = new StringBuilder();
if (fCBrowser.isIE()) {
js.append("var scrollPosY = document.body.scrollTop; "); //$NON-NLS-1$
js.append("var windowHeight = document.body.clientHeight; "); //$NON-NLS-1$
} else {
js.append("var scrollPosY = window.pageYOffset; "); //$NON-NLS-1$
js.append("var windowHeight = window.innerHeight; "); //$NON-NLS-1$
}
js.append("if (scrollPosY > 0) {"); //$NON-NLS-1$
js.append(" var node = document.getElementById('").append(Dynamic.NEWS.getId(lastVisibleNewsId)).append("'); "); //$NON-NLS-1$//$NON-NLS-2$
js.append(" if (node) {"); //$NON-NLS-1$
js.append(" if ((scrollPosY + windowHeight) >= node.offsetTop) {"); //$NON-NLS-1$
js.append(" window.location.href = '").append(ILinkHandler.HANDLER_PROTOCOL + SCROLL_NEXT_PAGE_HANDLER_ID).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append(" }"); //$NON-NLS-1$
js.append(" }"); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
if (!monitor.isCanceled() && !fCBrowser.getControl().isDisposed())
fCBrowser.execute(js.toString(), "UserInteractionTask#0"); //$NON-NLS-1$
}
}
/* Return early if canceled or disposed */
if (monitor.isCanceled() || fCBrowser.getControl().isDisposed())
return Status.OK_STATUS;
/* Mark Seen News as Read if necessary */
if (fMarkReadOnScrolling && !isGroupingByState()) {//Ignore if grouping by state to avoid refresh
StringBuilder js = new StringBuilder();
if (fCBrowser.isIE()) {
js.append("var scrollPosY = document.body.scrollTop; "); //$NON-NLS-1$
js.append("var windowHeight = document.body.clientHeight; "); //$NON-NLS-1$
} else {
js.append("var scrollPosY = window.pageYOffset; "); //$NON-NLS-1$
js.append("var windowHeight = window.innerHeight; "); //$NON-NLS-1$
}
boolean varDefined = false;
List<Long> visibleUnreadNews = fViewModel.getVisibleUnreadNews();
long lastNews = fViewModel.getLastNews();
js.append("var lastNews = document.getElementById('").append(Dynamic.NEWS.getId(lastNews)).append("'); "); //$NON-NLS-1$//$NON-NLS-2$
js.append("if (lastNews) {"); //$NON-NLS-1$
js.append(" var newsIds = ''; "); //$NON-NLS-1$
js.append(" var lastNewsPosY = lastNews.offsetTop; "); //$NON-NLS-1$
js.append(" var lastNewsHeight = lastNews.offsetHeight; "); //$NON-NLS-1$
for (Long id : visibleUnreadNews) {
if (fMarkedUnreadByUserCache.contains(id))
continue; //Skip those news explicitly marked as unread by the user
if (!varDefined) {
js.append("var "); //$NON-NLS-1$
varDefined = true;
}
/*
* Conditions under which a news gets marked as read:
*
* "divPosY < scrollPosY" : Top Border of News is above top scroll position
* "lastNewsPosY < scrollPosY + windowHeight" : Last news is visible
*/
js.append("node = document.getElementById('").append(Dynamic.NEWS.getId(id)).append("'); "); //$NON-NLS-1$//$NON-NLS-2$
js.append(" if (node) {"); //$NON-NLS-1$
js.append(" var divPosY = node.offsetTop; "); //$NON-NLS-1$
js.append(" var divHeight = node.offsetHeight; "); //$NON-NLS-1$
js.append(" if (divPosY < scrollPosY || (lastNewsPosY > 0 && lastNewsPosY < scrollPosY + windowHeight)) {"); //$NON-NLS-1$
js.append(" newsIds = newsIds + '").append(id).append(",'; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append(" }"); //$NON-NLS-1$
js.append(" }"); //$NON-NLS-1$
}
js.append(" if (newsIds.length != 0) { "); //$NON-NLS-1$
js.append(" window.location.href = '").append(ILinkHandler.HANDLER_PROTOCOL + MARK_READ_HANDLER_ID + "?").append("' + newsIds; "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
js.append(" } "); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
if (!monitor.isCanceled() && !fCBrowser.getControl().isDisposed())
fCBrowser.execute(js.toString(), "UserInteractionTask#1"); //$NON-NLS-1$
}
return Status.OK_STATUS;
}
public String getName() {
return null;
}
public Priority getPriority() {
return Priority.INTERACTIVE;
}
}
/**
* @param parent the parent {@link Composite} to host this viewer.
* @param style the style of the {@link Browser} in this viewer.
*/
public NewsBrowserViewer(Composite parent, int style) {
this(parent, style, null);
}
/**
* @param parent the parent {@link Composite} to host this viewer.
* @param style the style of the {@link Browser} in this viewer.
* @param site the {@link IFeedViewSite} if this viewer is being used from a
* {@link FeedView} or <code>null</code> otherwise.
*/
public NewsBrowserViewer(Composite parent, int style, IFeedViewSite site) {
fBrowser = new CBrowser(parent, style);
fBrowser.setCanOpenLinksInTabs(true);
fViewModel = new NewsBrowserViewModel(this);
fSite = site;
fIsEmbedded = (fSite != null);
hookControl(fBrowser.getControl());
hookNewsContextMenu();
hookAttachmentsContextMenu();
hookLabelContextMenu();
hookShareNewsContextMenu();
hookFindRelatedContextMenu();
fId = String.valueOf(hashCode());
fServer = ApplicationServer.getDefault();
fServer.register(fId, this);
fFactory = Owl.getModelFactory();
/* Register Link Handler */
fBrowser.addLinkHandler(MARK_READ_HANDLER_ID, this);
fBrowser.addLinkHandler(TOGGLE_READ_HANDLER_ID, this);
fBrowser.addLinkHandler(TOGGLE_STICKY_HANDLER_ID, this);
fBrowser.addLinkHandler(ARCHIVE_HANDLER_ID, this);
fBrowser.addLinkHandler(DELETE_HANDLER_ID, this);
fBrowser.addLinkHandler(ATTACHMENT_HANDLER_ID, this);
fBrowser.addLinkHandler(ATTACHMENTS_MENU_HANDLER_ID, this);
fBrowser.addLinkHandler(LABELS_MENU_HANDLER_ID, this);
fBrowser.addLinkHandler(EXPAND_NEWS_HANDLER_ID, this);
fBrowser.addLinkHandler(COLLAPSE_NEWS_HANDLER_ID, this);
fBrowser.addLinkHandler(EXPAND_GROUP_HANDLER_ID, this);
fBrowser.addLinkHandler(COLLAPSE_GROUP_HANDLER_ID, this);
fBrowser.addLinkHandler(GROUP_MENU_HANDLER_ID, this);
fBrowser.addLinkHandler(NEWS_MENU_HANDLER_ID, this);
fBrowser.addLinkHandler(SHARE_NEWS_MENU_HANDLER_ID, this);
fBrowser.addLinkHandler(NEXT_NEWS_HANDLER_ID, this);
fBrowser.addLinkHandler(NEXT_UNREAD_NEWS_HANDLER_ID, this);
fBrowser.addLinkHandler(PREVIOUS_NEWS_HANDLER_ID, this);
fBrowser.addLinkHandler(PREVIOUS_UNREAD_NEWS_HANDLER_ID, this);
fBrowser.addLinkHandler(TRANSFORM_HANDLER_ID, this);
fBrowser.addLinkHandler(RELATED_NEWS_MENU_HANDLER_ID, this);
fBrowser.addLinkHandler(NEXT_PAGE_HANDLER_ID, this);
fBrowser.addLinkHandler(SCROLL_NEXT_PAGE_HANDLER_ID, this);
/* React on User Interaction (Mouse Scrolling, Mouse Down, Key Pressed) */
Listener listener = new Listener() {
public void handleEvent(Event event) {
onUserInteraction();
}
};
fBrowser.getControl().addListener(SWT.MouseWheel, listener);
fBrowser.getControl().addListener(SWT.MouseDown, listener);
fBrowser.getControl().addListener(SWT.KeyDown, listener);
}
private void onUserInteraction() {
/* Return if feature not necessary at all */
if (!fIsEmbedded || (!fMarkReadOnScrolling && fPageSize == 0))
return;
/* Return if disposed or already running */
if (fBrowser.getControl().isDisposed() || fUserInteractionTracker.isRunning())
return;
/* Tell the tracker about user interaction*/
fUserInteractionTracker.run(new UserInteractionTask(fViewModel, fBrowser));
}
private void hookNewsContextMenu() {
MenuManager manager = new MenuManager();
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(new IMenuListener() {
@SuppressWarnings("restriction")
public void menuAboutToShow(IMenuManager manager) {
/* Open */
{
boolean useSeparator = true;
/* Open in FeedView */
if (!fIsEmbedded) {
manager.add(new Separator("internalopen")); //$NON-NLS-1$
if (!fCurrentSelection.isEmpty()) {
manager.appendToGroup("internalopen", new OpenNewsAction(fCurrentSelection, fBrowser.getControl().getShell())); //$NON-NLS-1$
useSeparator = false;
}
}
manager.add(useSeparator ? new Separator("open") : new GroupMarker("open")); //$NON-NLS-1$ //$NON-NLS-2$
/* Show only when internal browser is used */
if (!fCurrentSelection.isEmpty() && !OwlUI.useExternalBrowser())
manager.add(new OpenInExternalBrowserAction(fCurrentSelection));
}
/* Attachments */
{
ApplicationActionBarAdvisor.fillAttachmentsMenu(manager, fCurrentSelection, new SameShellProvider(fBrowser.getControl().getShell()), false);
}
/* Mark / Label */
{
manager.add(new Separator("mark")); //$NON-NLS-1$
/* Mark */
MenuManager markMenu = new MenuManager(Messages.NewsBrowserViewer_MARK, "mark"); //$NON-NLS-1$
manager.add(markMenu);
/* Mark as Read */
IAction action = new ToggleReadStateAction(fCurrentSelection);
action.setEnabled(!fCurrentSelection.isEmpty());
markMenu.add(action);
/* Mark All Read */
action = new MarkAllNewsReadAction();
markMenu.add(action);
/* Sticky */
markMenu.add(new Separator());
action = new MakeNewsStickyAction(fCurrentSelection);
action.setEnabled(!fCurrentSelection.isEmpty());
markMenu.add(action);
/* Label */
ApplicationActionBarAdvisor.fillLabelMenu(manager, fCurrentSelection, new SameShellProvider(fBrowser.getControl().getShell()), false);
}
/* Move To / Copy To */
if (!fCurrentSelection.isEmpty()) {
manager.add(new Separator("movecopy")); //$NON-NLS-1$
/* Load all news bins and sort by name */
List<INewsBin> newsbins = new ArrayList<INewsBin>(DynamicDAO.loadAll(INewsBin.class));
Comparator<INewsBin> comparator = new Comparator<INewsBin>() {
public int compare(INewsBin o1, INewsBin o2) {
return o1.getName().compareTo(o2.getName());
};
};
Collections.sort(newsbins, comparator);
/* Move To */
MenuManager moveMenu = new MenuManager(Messages.NewsBrowserViewer_MOVE_TO, "moveto"); //$NON-NLS-1$
manager.add(moveMenu);
for (INewsBin bin : newsbins) {
if (contained(bin, fCurrentSelection))
continue;
moveMenu.add(new MoveCopyNewsToBinAction(fCurrentSelection, bin, true));
}
moveMenu.add(new MoveCopyNewsToBinAction(fCurrentSelection, null, true));
moveMenu.add(new Separator());
moveMenu.add(new AutomateFilterAction(PresetAction.MOVE, fCurrentSelection));
/* Copy To */
MenuManager copyMenu = new MenuManager(Messages.NewsBrowserViewer_COPY_TO, "copyto"); //$NON-NLS-1$
manager.add(copyMenu);
for (INewsBin bin : newsbins) {
if (contained(bin, fCurrentSelection))
continue;
copyMenu.add(new MoveCopyNewsToBinAction(fCurrentSelection, bin, false));
}
copyMenu.add(new MoveCopyNewsToBinAction(fCurrentSelection, null, false));
copyMenu.add(new Separator());
copyMenu.add(new AutomateFilterAction(PresetAction.COPY, fCurrentSelection));
/* Archive */
manager.add(new ArchiveNewsAction(fCurrentSelection));
}
/* Share */
boolean entityGroupSelected = ModelUtils.isEntityGroupSelected(fCurrentSelection);
if (!entityGroupSelected)
ApplicationActionBarAdvisor.fillShareMenu(manager, fCurrentSelection, new SameShellProvider(fBrowser.getControl().getShell()), false);
manager.add(new Separator("filter")); //$NON-NLS-1$
manager.add(new Separator("copy")); //$NON-NLS-1$
manager.add(new GroupMarker("edit")); //$NON-NLS-1$
/* Collapse All */
if (entityGroupSelected) {
manager.add(new Separator());
ImageDescriptor icon = OwlUI.getImageDescriptor("icons/etool16/collapseall.gif"); //$NON-NLS-1$
manager.add(new Action(Messages.NewsBrowserViewer_COLLAPSE_GROUPS, icon) {
@Override
public void run() {
Set<Entry<Long, List<Long>>> groups = fViewModel.getGroups().entrySet();
for (Entry<Long, List<Long>> group : groups) {
Long groupId = group.getKey();
if (fViewModel.isGroupVisible(groupId)) {
List<Long> newsIds = group.getValue();
if (newsIds != null && !newsIds.isEmpty())
setGroupExpanded(groupId, newsIds, false);
}
}
};
});
}
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
/* Fill Contributions if Context Menu not registered */
if (fSite == null)
org.eclipse.ui.internal.ObjectActionContributorManager.getManager().contributeObjectActions(null, manager, NewsBrowserViewer.this);
}
});
/* Create and Register with Workbench */
fNewsContextMenu = manager.createContextMenu(fBrowser.getControl().getShell());
/* Register with Part Site if possible */
if (fSite != null)
fSite.getEditorSite().registerContextMenu(manager, this);
}
private void hookAttachmentsContextMenu() {
MenuManager manager = new MenuManager();
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
ApplicationActionBarAdvisor.fillAttachmentsMenu(manager, fCurrentSelection, new SameShellProvider(fBrowser.getControl().getShell()), true);
}
});
/* Create */
fAttachmentsContextMenu = manager.createContextMenu(fBrowser.getControl().getShell());
}
private void hookLabelContextMenu() {
MenuManager manager = new MenuManager();
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
ApplicationActionBarAdvisor.fillLabelMenu(manager, fCurrentSelection, new SameShellProvider(fBrowser.getControl().getShell()), true);
}
});
/* Create */
fLabelsContextMenu = manager.createContextMenu(fBrowser.getControl().getShell());
}
private void hookShareNewsContextMenu() {
MenuManager manager = new MenuManager();
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
ApplicationActionBarAdvisor.fillShareMenu(manager, fCurrentSelection, new SameShellProvider(fBrowser.getControl().getShell()), true);
}
});
/* Create */
fShareNewsContextMenu = manager.createContextMenu(fBrowser.getControl().getShell());
}
private void hookFindRelatedContextMenu() {
MenuManager manager = new MenuManager();
manager.setRemoveAllWhenShown(true);
manager.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
if (fCurrentSelection.size() == 1) {
Object element = fCurrentSelection.getFirstElement();
if (element instanceof INews) {
final INews news = (INews) element;
final String entity = INews.class.getName();
/* Find Related by Title */
manager.add(new Action(Messages.NewsBrowserViewer_SIMILAR_CONTENT) {
@Override
public void run() {
List<ISearchCondition> conditions = new ArrayList<ISearchCondition>(1);
String headline = CoreUtils.getHeadline(news, false);
ISearchField field = fFactory.createSearchField(IEntity.ALL_FIELDS, entity);
ISearchCondition condition = fFactory.createSearchCondition(field, SearchSpecifier.CONTAINS, headline);
conditions.add(condition);
doSearch(conditions, true);
};
});
/* Find Related by Author */
if (news.getAuthor() != null) {
IPerson person = news.getAuthor();
String name = person.getName();
String email = (person.getEmail() != null) ? person.getEmail().toASCIIString() : null;
final String author = StringUtils.isSet(name) ? name : email;
if (StringUtils.isSet(author)) {
manager.add(new Separator());
manager.add(new Action(NLS.bind(Messages.NewsBrowserViewer_AUTHORED_BY, escapeForMenu(author))) {
@Override
public void run() {
List<ISearchCondition> conditions = new ArrayList<ISearchCondition>(1);
ISearchField field = fFactory.createSearchField(INews.AUTHOR, entity);
ISearchCondition condition = fFactory.createSearchCondition(field, SearchSpecifier.CONTAINS_ALL, author);
conditions.add(condition);
doSearch(conditions, false);
};
});
}
}
/* Find Related by Category */
if (!news.getCategories().isEmpty()) {
/* Directly show for one category */
if (news.getCategories().size() == 1) {
final String name = news.getCategories().get(0).getName();
if (StringUtils.isSet(name)) {
manager.add(new Action(NLS.bind(Messages.NewsBrowserViewer_CATEGORIZED_N, escapeForMenu(name))) {
@Override
public void run() {
List<ISearchCondition> conditions = new ArrayList<ISearchCondition>(1);
ISearchField field = fFactory.createSearchField(INews.CATEGORIES, entity);
ISearchCondition condition = fFactory.createSearchCondition(field, SearchSpecifier.IS, name);
conditions.add(condition);
doSearch(conditions, false);
};
});
}
}
/* Use a Sub Menu for many categories */
else {
MenuManager categoriesMenu = new MenuManager(Messages.NewsBrowserViewer_BY_CATEGORY);
for (ICategory category : news.getCategories()) {
final String name = category.getName();
if (StringUtils.isSet(name)) {
categoriesMenu.add(new Action(escapeForMenu(name)) {
@Override
public void run() {
List<ISearchCondition> conditions = new ArrayList<ISearchCondition>(1);
ISearchField field = fFactory.createSearchField(INews.CATEGORIES, entity);
ISearchCondition condition = fFactory.createSearchCondition(field, SearchSpecifier.IS, name);
conditions.add(condition);
doSearch(conditions, false);
};
});
}
}
manager.add(categoriesMenu);
}
}
/* Find Related by Labels */
if (!news.getLabels().isEmpty()) {
manager.add(new Separator());
for (final ILabel label : news.getLabels()) {
manager.add(new Action(NLS.bind(Messages.NewsBrowserViewer_LABELED_N, escapeForMenu(label.getName()))) {
@Override
public void run() {
List<ISearchCondition> conditions = new ArrayList<ISearchCondition>(1);
ISearchField field = fFactory.createSearchField(INews.LABEL, entity);
ISearchCondition condition = fFactory.createSearchCondition(field, SearchSpecifier.IS, label.getName());
conditions.add(condition);
doSearch(conditions, false);
};
});
}
}
}
}
}
});
/* Create */
fFindRelatedContextMenu = manager.createContextMenu(fBrowser.getControl().getShell());
}
private String escapeForMenu(String str) {
return StringUtils.replaceAll(str, "&", "&&"); //$NON-NLS-1$ //$NON-NLS-2$
}
private void doSearch(final List<ISearchCondition> conditions, final boolean useLowScoreFilter) {
if (conditions.size() >= 1 && !fBrowser.getControl().isDisposed()) {
/* See Bug 747 - run asynced */
delayInUI(new Runnable() {
public void run() {
SearchNewsDialog dialog = new SearchNewsDialog(fBrowser.getControl().getShell(), conditions, true, true);
dialog.setUseLowScoreFilter(useLowScoreFilter);
dialog.open();
}
});
}
}
private boolean contained(INewsBin bin, IStructuredSelection selection) {
if (selection == null || selection.isEmpty())
return false;
Object element = selection.getFirstElement();
if (element instanceof INews) {
INews news = (INews) element;
return news.getParentId() == bin.getId();
}
return false;
}
void setBlockRefresh(boolean block) {
fBlockRefresh = block;
}
/*
* @see org.rssowl.ui.internal.ILinkHandler#handle(java.lang.String, java.net.URI)
*/
public void handle(final String id, URI link) {
/* Extract Query Part and Decode */
String query = link.getQuery();
boolean queryProvided = StringUtils.isSet(query);
if (queryProvided) {
query = URIUtils.urlDecode(query).trim();
queryProvided = StringUtils.isSet(query);
}
/* Mark Read */
if (queryProvided && MARK_READ_HANDLER_ID.equals(id)) {
final List<INews> news = getNewsList(query);
Runnable runnable = new Runnable() {
public void run() {
INews.State newState = INews.State.READ;
boolean affectEquivalentNews = OwlUI.markReadDuplicates();
UndoStack.getInstance().addOperation(new NewsStateOperation(news, newState, affectEquivalentNews));
fNewsDao.setState(news, newState, affectEquivalentNews, false);
}
};
if (CBrowser.isMozillaRunningOnWindows()) //Bug in XULRunner, otherwise won't work
delayInUI(runnable);
else
runnable.run();
}
/* Toggle Read */
else if (queryProvided && TOGGLE_READ_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
/* Remove Focus from Link */
blur(Dynamic.TOGGLE_READ_LINK.getId(news));
/* Update State */
INews.State newState = (news.getState() == INews.State.READ) ? INews.State.UNREAD : INews.State.READ;
Set<INews> singleNewsSet = Collections.singleton(news);
boolean affectEquivalentNews = (newState != INews.State.UNREAD && OwlUI.markReadDuplicates());
UndoStack.getInstance().addOperation(new NewsStateOperation(singleNewsSet, newState, affectEquivalentNews));
fNewsDao.setState(singleNewsSet, newState, affectEquivalentNews, false);
if (newState == INews.State.UNREAD)
fMarkedUnreadByUserCache.add(news.getId());
}
}
/* Toggle Sticky */
else if (queryProvided && TOGGLE_STICKY_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
/* Remove Focus from Link */
blur(Dynamic.TOGGLE_STICKY_LINK.getId(news));
if (isHeadlinesLayout())
blur(Dynamic.TINY_TOGGLE_STICKY_LINK.getId(news), true);
/* Toggle Sticky State */
Set<INews> singleNewsSet = Collections.singleton(news);
UndoStack.getInstance().addOperation(new StickyOperation(singleNewsSet, !news.isFlagged()));
news.setFlagged(!news.isFlagged());
Controller.getDefault().getSavedSearchService().forceQuickUpdate();
DynamicDAO.saveAll(singleNewsSet);
}
}
/* Archive */
else if (queryProvided && ARCHIVE_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
ArchiveNewsAction action = new ArchiveNewsAction(new StructuredSelection(news));
action.run();
}
}
/* Delete */
else if (queryProvided && DELETE_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
Set<INews> singleNewsSet = Collections.singleton(news);
UndoStack.getInstance().addOperation(new NewsStateOperation(singleNewsSet, INews.State.HIDDEN, false));
fNewsDao.setState(singleNewsSet, INews.State.HIDDEN, false, false);
}
}
/* Labels Menu */
else if (queryProvided && LABELS_MENU_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
/* Remove Focus from Link */
blur(Dynamic.LABELS_MENU_LINK.getId(news));
/* Show Menu */
setSelection(new StructuredSelection(news));
Point cursorLocation = fBrowser.getControl().getDisplay().getCursorLocation();
cursorLocation.y = cursorLocation.y + 16;
fLabelsContextMenu.setLocation(cursorLocation);
fLabelsContextMenu.setVisible(true);
}
}
/* Attachments Menu */
else if (queryProvided && (ATTACHMENTS_MENU_HANDLER_ID.equals(id) || ATTACHMENT_HANDLER_ID.equals(id))) {
INews news = getNews(query);
if (news != null) {
/* Remove Focus from Link */
if (ATTACHMENT_HANDLER_ID.equals(id))
blur(Dynamic.ATTACHMENT_LINK.getId(news));
else if (ATTACHMENTS_MENU_HANDLER_ID.equals(id))
blur(Dynamic.ATTACHMENTS_MENU_LINK.getId(news));
/* Show Menu */
setSelection(new StructuredSelection(news));
Point cursorLocation = fBrowser.getControl().getDisplay().getCursorLocation();
cursorLocation.y = cursorLocation.y + 16;
fAttachmentsContextMenu.setLocation(cursorLocation);
fAttachmentsContextMenu.setVisible(true);
}
}
/* Toggle News Item Visibility */
else if (queryProvided && (EXPAND_NEWS_HANDLER_ID.equals(id) || COLLAPSE_NEWS_HANDLER_ID.equals(id))) {
INews news = getNews(query);
if (news != null) {
setNewsExpanded(news, EXPAND_NEWS_HANDLER_ID.equals(id));
onUserInteraction();
}
}
/* Toggle Group Items Visibility */
else if (queryProvided && (EXPAND_GROUP_HANDLER_ID.equals(id) || COLLAPSE_GROUP_HANDLER_ID.equals(id))) {
long groupId = getId(query);
List<Long> newsIds = fViewModel.getNewsIds(groupId);
if (!newsIds.isEmpty())
setGroupExpanded(groupId, newsIds, EXPAND_GROUP_HANDLER_ID.equals(id));
}
/* Group Context Menu */
else if (queryProvided && GROUP_MENU_HANDLER_ID.equals(id)) {
EntityGroup group = getEntityGroup(query);
if (group != null) {
/* Remove Focus from Link */
blur(Dynamic.GROUP_MENU_LINK.getId(group));
/* Show Menu */
setSelection(new StructuredSelection(group));
Point cursorLocation = fBrowser.getControl().getDisplay().getCursorLocation();
cursorLocation.y = cursorLocation.y + 16;
fNewsContextMenu.setLocation(cursorLocation);
fNewsContextMenu.setVisible(true);
}
}
/* News Context Menu */
else if (queryProvided && NEWS_MENU_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
/* Remove Focus from Link */
blur(Dynamic.NEWS_MENU_LINK.getId(news), true);
blur(Dynamic.FOOTER_NEWS_MENU_LINK.getId(news), true);
/* Show Menu */
setSelection(new StructuredSelection(news));
Point cursorLocation = fBrowser.getControl().getDisplay().getCursorLocation();
cursorLocation.y = cursorLocation.y + 16;
fNewsContextMenu.setLocation(cursorLocation);
fNewsContextMenu.setVisible(true);
}
}
/* Share News Context Menu */
else if (queryProvided && SHARE_NEWS_MENU_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
/* Remove Focus from Link */
blur(Dynamic.SHARE_MENU_LINK.getId(news));
/* Show Menu */
setSelection(new StructuredSelection(news));
Point cursorLocation = fBrowser.getControl().getDisplay().getCursorLocation();
cursorLocation.y = cursorLocation.y + 16;
fShareNewsContextMenu.setLocation(cursorLocation);
fShareNewsContextMenu.setVisible(true);
}
}
/* Find Related Context Menu */
else if (queryProvided && RELATED_NEWS_MENU_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null) {
/* Remove Focus from Link */
blur(Dynamic.FIND_RELATED_MENU_LINK.getId(news));
/* Show Menu */
setSelection(new StructuredSelection(news));
Point cursorLocation = fBrowser.getControl().getDisplay().getCursorLocation();
cursorLocation.y = cursorLocation.y + 16;
fFindRelatedContextMenu.setLocation(cursorLocation);
fFindRelatedContextMenu.setVisible(true);
}
}
/* Go to Next News / Go to Next Unread News / Go to Previous News / Go to Previous Unread News */
else if (NEXT_NEWS_HANDLER_ID.equals(id) || NEXT_UNREAD_NEWS_HANDLER_ID.equals(id) || PREVIOUS_NEWS_HANDLER_ID.equals(id) || PREVIOUS_UNREAD_NEWS_HANDLER_ID.equals(id)) {
Runnable runnable = new Runnable() {
public void run() {
handleNavigateAction(id);
}
};
if (CBrowser.isMozillaRunningOnWindows()) //Bug in XULRunner, otherwise won't work
delayInUI(runnable);
else
runnable.run();
}
/* Transform News */
else if (TRANSFORM_HANDLER_ID.equals(id)) {
INews news = getNews(query);
if (news != null)
transformNews(news);
}
/* Reveal Next Page */
else if (NEXT_PAGE_HANDLER_ID.equals(id)) {
revealNextPage(true);
}
/* Scroll Reveal Next Page */
else if (SCROLL_NEXT_PAGE_HANDLER_ID.equals(id)) {
Runnable runnable = new Runnable() {
public void run() {
revealNextPage(false);
}
};
if (CBrowser.isMozillaRunningOnWindows()) //Bug in XULRunner, otherwise won't work
delayInUI(runnable);
else
runnable.run();
}
}
private void handleNavigateAction(final String id) {
/* Special Case Navigation in Newspaper mode when some news are hidden from the page */
if (!isHeadlinesLayout() && fPageSize != 0 && (NEXT_NEWS_HANDLER_ID.equals(id) || NEXT_UNREAD_NEWS_HANDLER_ID.equals(id))) {
boolean onlyUnread = NEXT_UNREAD_NEWS_HANDLER_ID.equals(id);
int totalNewsCount = fViewModel.getNewsCount();
int visibleNewsCount = fViewModel.getVisibleNewsCount();
/* There are hidden News beyond the Page Latch */
if (totalNewsCount != 0 && totalNewsCount > visibleNewsCount) {
long firstHiddenNewsId = fViewModel.getFirstHiddenNews(onlyUnread);
if (firstHiddenNewsId != -1) {
showSelection(new StructuredSelection(new NewsReference(firstHiddenNewsId)));
return;
}
}
}
/* Forward the navigation action to the outer scope */
delayInUI(new Runnable() {
public void run() {
NavigationActionFactory factory = new NavigationActionFactory();
try {
NavigationActionFactory.NavigationActionType type = null;
if (NEXT_NEWS_HANDLER_ID.equals(id))
type = NavigationActionFactory.NavigationActionType.NEXT_FEED_NEXT_NEWS;
else if (NEXT_UNREAD_NEWS_HANDLER_ID.equals(id))
type = NavigationActionFactory.NavigationActionType.NEXT_UNREAD_FEED_NEXT_UNREAD_NEWS;
else if (PREVIOUS_NEWS_HANDLER_ID.equals(id))
type = NavigationActionFactory.NavigationActionType.PREVIOUS_FEED_PREVIOUS_NEWS;
else if (PREVIOUS_UNREAD_NEWS_HANDLER_ID.equals(id))
type = NavigationActionFactory.NavigationActionType.PREVIOUS_UNREAD_FEED_PREVIOUS_UNREAD_NEWS;
if (type != null) {
factory.setInitializationData(null, null, type.getId());
IWorkbenchWindowActionDelegate action = (IWorkbenchWindowActionDelegate) factory.create();
action.run(null);
}
} catch (CoreException e) {
/* Ignore */
}
}
});
}
private void setNewsExpanded(INews news, boolean expanded) {
setNewsExpanded(news, expanded, true);
}
private void setNewsExpanded(INews news, boolean expanded, boolean scrollIntoView) {
/* Return early if visibility already matches state */
if (expanded == fViewModel.isNewsExpanded(news))
return;
/* Link and Image */
final StringBuilder js = new StringBuilder();
String newsLink = CoreUtils.getLink(news);
/* Blur Links */
js.append(getElementById(Dynamic.TITLE_LINK.getId(news)).append(".blur(); ")); //$NON-NLS-1$
/* Update Links */
String link = HANDLER_PROTOCOL + (expanded ? COLLAPSE_NEWS_HANDLER_ID : EXPAND_NEWS_HANDLER_ID) + "?" + news.getId(); //$NON-NLS-1$
if (expanded && StringUtils.isSet(newsLink))
js.append(getElementById(Dynamic.TITLE_LINK.getId(news)).append(".href = '" + URIUtils.toManaged(newsLink) + "'; ")); //$NON-NLS-1$ //$NON-NLS-2$
else
js.append(getElementById(Dynamic.TITLE_LINK.getId(news)).append(".href='").append(link).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
/* Update Toggle Sticky Link Visibility */
if (expanded)
js.append(getElementById(Dynamic.TINY_TOGGLE_STICKY_LINK.getId(news))).append(".style.display='none'; "); //$NON-NLS-1$
else
js.append(getElementById(Dynamic.TINY_TOGGLE_STICKY_LINK.getId(news))).append(".style.display='inline'; "); //$NON-NLS-1$
/* Update Subtitle if present */
if (expanded)
js.append(getElementById(Dynamic.SUBTITLE_LINK.getId(news))).append(".style.display='none'; "); //$NON-NLS-1$
else {
StringBuilder subtitleContent = new StringBuilder();
IBaseLabelProvider lp = getLabelProvider();
if (lp instanceof NewsBrowserLabelProvider)
((NewsBrowserLabelProvider) lp).fillSubtitle(subtitleContent, news, CoreUtils.getSortedLabels(news), false);
if (subtitleContent.length() > 0)
js.append(getElementById(Dynamic.SUBTITLE_LINK.getId(news))).append(".innerHTML='").append(escapeForInnerHtml(subtitleContent.toString())).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append(getElementById(Dynamic.SUBTITLE_LINK.getId(news))).append(".style.display='inline'; "); //$NON-NLS-1$
}
/* Update News Div Visibility */
Set<Dynamic> elements = EnumSet.of(Dynamic.SUBLINE, Dynamic.DELETE, Dynamic.CONTENT, Dynamic.FOOTER);
for (Dynamic element : elements) {
if (expanded)
js.append(getElementById(element.getId(news))).append(".style.display='block'; "); //$NON-NLS-1$
else
js.append(getElementById(element.getId(news))).append(".style.display='none'; "); //$NON-NLS-1$
}
/* Update Headlines Separator if present and parent group (if any) is not collapsed */
boolean showHeadlinesSeparator = !expanded;
if (isGroupingEnabled()) {
long groupId = fViewModel.findGroup(news.getId());
if (!fViewModel.isGroupExpanded(groupId))
showHeadlinesSeparator = false;
}
js.append("if (").append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(news))).append(") {"); //$NON-NLS-1$ //$NON-NLS-2$
if (showHeadlinesSeparator)
js.append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(news))).append(".style.display='block'; "); //$NON-NLS-1$
else
js.append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(news))).append(".style.display='none'; "); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
/* Update Title CSS */
if (expanded)
js.append(getElementById(Dynamic.TITLE.getId(news))).append(".className='titleExpanded'; "); //$NON-NLS-1$
else
js.append(getElementById(Dynamic.TITLE.getId(news))).append(".className='titleCollapsed'; "); //$NON-NLS-1$
/* Update News Content as needed */
if (expanded)
fillNewsContent(news, js, newsLink);
else
js.append(getElementById(Dynamic.CONTENT.getId(news)).append(".innerHTML = ''; ")); //$NON-NLS-1$
/* Scroll expanded news into view as necessary */
if (scrollIntoView && expanded)
scrollIfNecessary(news, js);
/* Collapse other visible news if present */
if (expanded) {
long expandedNewsId = fViewModel.getExpandedNews();
if (expandedNewsId != -1) {
INews item = resolve(expandedNewsId);
if (item != null)
setNewsExpanded(item, false);
}
}
/* Block external navigation while setting innerHTML */
fBrowser.blockExternalNavigationWhile(new Runnable() {
public void run() {
fBrowser.execute(js.toString(), "setNewsExpanded"); //$NON-NLS-1$
}
});
/* Update State if not already marked as read */
if (fMarkReadOnExpand && expanded && news.getState() != INews.State.READ && !isGroupingByState()) { //Ignore if grouping by state to avoid refresh
Set<INews> singleNewsSet = Collections.singleton(news);
boolean affectEquivalentNews = OwlUI.markReadDuplicates();
UndoStack.getInstance().addOperation(new NewsStateOperation(singleNewsSet, INews.State.READ, affectEquivalentNews));
fNewsDao.setState(singleNewsSet, INews.State.READ, affectEquivalentNews, false);
}
/* Update Cache of Expanded News */
fViewModel.setNewsExpanded(news, expanded);
}
private void fillNewsContent(INews news, final StringBuilder js, String newsLink) {
String description = news.getDescription();
/* Content is provided */
if (StringUtils.isSet(description) && !description.equals(news.getTitle())) {
IBaseLabelProvider labelProvider = getLabelProvider();
if (labelProvider instanceof NewsBrowserLabelProvider) {
description = ((NewsBrowserLabelProvider) labelProvider).stripMediaTagsIfNecessary(description);
description = ((NewsBrowserLabelProvider) labelProvider).highlightSearchTermsIfNecessary(description);
}
}
/* Content is not provided */
else {
StringBuilder emptyDescription = new StringBuilder();
emptyDescription.append(Messages.NewsBrowserViewer_NO_CONTENT);
if (StringUtils.isSet(newsLink)) {
String link = HANDLER_PROTOCOL + TRANSFORM_HANDLER_ID + "?" + news.getId(); //$NON-NLS-1$
emptyDescription.append(" <a href=\"").append(link).append("\">").append(Messages.NewsBrowserViewer_DOWNLOAD_CONTENT).append("</a>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
description = emptyDescription.toString();
}
js.append(getElementById(Dynamic.CONTENT.getId(news)).append(".innerHTML = '" + escapeForInnerHtml(description) + "'; ")); //$NON-NLS-1$ //$NON-NLS-2$
}
private void scrollIfNecessary(INews news, final StringBuilder js) {
if (fBrowser.isIE()) {
js.append("var scrollPosY = document.body.scrollTop; "); //$NON-NLS-1$
js.append("var windowHeight = document.body.clientHeight; "); //$NON-NLS-1$
} else {
js.append("var scrollPosY = window.pageYOffset; "); //$NON-NLS-1$
js.append("var windowHeight = window.innerHeight; "); //$NON-NLS-1$
}
js.append("var divPosY = ").append(getElementById(Dynamic.NEWS.getId(news))).append(".offsetTop; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("var divHeight = ").append(getElementById(Dynamic.NEWS.getId(news))).append(".offsetHeight; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (scrollPosY > divPosY || divHeight > windowHeight) {"); //$NON-NLS-1$ //Scroll up to reveal the top of the news (also scroll up if the news is larger the client height
js.append(getElementById(Dynamic.NEWS.getId(news))).append(".scrollIntoView(true); "); //$NON-NLS-1$
js.append("} else if (scrollPosY + windowHeight < divPosY + divHeight) {"); //$NON-NLS-1$ //Scroll down to reveal the bottom of the news
js.append(getElementById(Dynamic.NEWS.getId(news))).append(".scrollIntoView(false); "); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
}
private void setGroupExpanded(long groupId, List<Long> newsIds, boolean expanded) {
setGroupExpanded(groupId, newsIds, expanded, true);
}
private void setGroupExpanded(long groupId, List<Long> newsIds, boolean expanded, boolean scrollIntoView) {
/* Image */
StringBuilder js = new StringBuilder();
String newToggleImgUri;
if (fBrowser.isIE())
newToggleImgUri = expanded ? OwlUI.getImageUri("/icons/elcl16/expanded.gif", "expanded.gif") : OwlUI.getImageUri("/icons/elcl16/collapsed.gif", "collapsed.gif"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
else
newToggleImgUri = expanded ? ApplicationServer.getDefault().toResourceUrl("/icons/elcl16/expanded.gif") : ApplicationServer.getDefault().toResourceUrl("/icons/elcl16/collapsed.gif"); //$NON-NLS-1$ //$NON-NLS-2$
/* Blur Links */
js.append(getElementById(Dynamic.TOGGLE_GROUP_LINK.getId(groupId)).append(".blur(); ")); //$NON-NLS-1$
js.append(getElementById(Dynamic.GROUP_MENU_LINK.getId(groupId)).append(".blur(); ")); //$NON-NLS-1$
/* Update Links */
String toggleVisibilityLink = HANDLER_PROTOCOL + (expanded ? COLLAPSE_GROUP_HANDLER_ID : EXPAND_GROUP_HANDLER_ID) + "?" + groupId; //$NON-NLS-1$
js.append(getElementById(Dynamic.TOGGLE_GROUP_LINK.getId(groupId)).append(".href='").append(toggleVisibilityLink).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
if (expanded)
js.append(getElementById(Dynamic.GROUP_MENU_LINK.getId(groupId)).append(".href='").append(HANDLER_PROTOCOL + GROUP_MENU_HANDLER_ID + "?" + groupId).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
else
js.append(getElementById(Dynamic.GROUP_MENU_LINK.getId(groupId)).append(".href='").append(toggleVisibilityLink).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
/* Update Triangle Image */
js.append(getElementById(Dynamic.TOGGLE_GROUP_IMG.getId(groupId)).append(".src = '" + newToggleImgUri + "'; ")); //$NON-NLS-1$ //$NON-NLS-2$
/* Update Visibility */
for (Long id : newsIds) {
if (!fViewModel.isNewsVisible(id))
continue; //Skip news if it is actually hidden
/* News Item */
if (expanded)
js.append(getElementById(Dynamic.NEWS.getId(id))).append(".style.display='block'; "); //$NON-NLS-1$
else
js.append(getElementById(Dynamic.NEWS.getId(id))).append(".style.display='none'; "); //$NON-NLS-1$
/* Separator if using headlines layout */
if (isHeadlinesLayout()) {
js.append("if (").append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(id))).append(") {"); //$NON-NLS-1$ //$NON-NLS-2$
if (expanded)
js.append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(id))).append(".style.display='block'; "); //$NON-NLS-1$
else
js.append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(id))).append(".style.display='none'; "); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
}
}
/* Scroll expanded group into view as necessary */
if (scrollIntoView && expanded && !newsIds.isEmpty()) {
if (fBrowser.isIE()) {
js.append("var scrollPosY = document.body.scrollTop; "); //$NON-NLS-1$
js.append("var windowHeight = document.body.clientHeight; "); //$NON-NLS-1$
} else {
js.append("var scrollPosY = window.pageYOffset; "); //$NON-NLS-1$
js.append("var windowHeight = window.innerHeight; "); //$NON-NLS-1$
}
/* Find the last visible news of the group */
long lastVisibleNewsId = -1;
for (int i = newsIds.size() - 1; i >= 0; i--) {
if (fViewModel.isNewsVisible(newsIds.get(i))) {
lastVisibleNewsId = newsIds.get(i);
break;
}
}
if (lastVisibleNewsId != -1) {
js.append("var divPosY = ").append(getElementById(Dynamic.NEWS.getId(lastVisibleNewsId))).append(".offsetTop; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("var divHeight = ").append(getElementById(Dynamic.NEWS.getId(lastVisibleNewsId))).append(".offsetHeight; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (scrollPosY + windowHeight < divPosY + divHeight) {"); //$NON-NLS-1$ //Scroll up to reveal as much news as possible from the group
js.append(getElementById(Dynamic.TOGGLE_GROUP_IMG.getId(groupId))).append(".scrollIntoView(true); "); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
}
}
/* Execute */
fBrowser.execute(js.toString(), "setGroupExpanded"); //$NON-NLS-1$
/* Update View Model */
fViewModel.setGroupExpanded(groupId, expanded);
}
private void blur(String elementId) {
blur(elementId, false);
}
private void blur(String elementId, boolean guardNull) {
StringBuilder js = new StringBuilder();
if (guardNull) {
js.append("if (").append(getElementById(elementId)).append(") {"); //$NON-NLS-1$ //$NON-NLS-2$
js.append(getElementById(elementId).append(".blur();")); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
} else
js.append(getElementById(elementId).append(".blur();")); //$NON-NLS-1$
fBrowser.execute(js.toString(), "blur"); //$NON-NLS-1$
}
private void transformNews(final INews news) {
String link = CoreUtils.getLink(news);
if (!StringUtils.isSet(link))
return;
/* Indicate Progress */
StringBuilder js = new StringBuilder();
js.append(getElementById(Dynamic.FULL_CONTENT_LINK_TEXT.getId(news)).append(".innerHTML='").append(escapeForInnerHtml(Messages.NewsBrowserViewer_LOADING)).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
js.append(getElementById(Dynamic.FULL_CONTENT_LINK.getId(news)).append(".blur(); ")); //$NON-NLS-1$
js.append(getElementById(Dynamic.FULL_CONTENT_LINK.getId(news)).append(".style.fontStyle = 'italic'; ")); //$NON-NLS-1$
fBrowser.execute(js.toString(), "transformNews"); //$NON-NLS-1$
/* First cancel all running jobs for this news if any */
NewsReference reference = news.toReference();
Job.getJobManager().cancel(reference);
/* Load news content in background and update HTML afterwards */
final String transformedUrl = Controller.getDefault().getEmbeddedTransformedUrl(link);
UIBackgroundJob transformationJob = new UIBackgroundJob(fBrowser.getControl(), Messages.NewsBrowserViewer_RETRIEVING_ARTICLE_CONTENT, reference) {
StringBuilder result = new StringBuilder();
@Override
protected void runInBackground(IProgressMonitor monitor) {
try {
URI uri = new URI(transformedUrl);
IProtocolHandler handler = Owl.getConnectionService().getHandler(uri);
if (handler != null) {
BufferedReader reader = null;
try {
InputStream inS = handler.openStream(uri, monitor, null);
reader = new BufferedReader(new InputStreamReader(inS, "UTF-8")); //$NON-NLS-1$
String line;
while (!monitor.isCanceled() && (line = reader.readLine()) != null) {
result.append(line);
}
} catch (IOException e) {
Activator.getDefault().logError(e.getMessage(), e);
monitor.setCanceled(true);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
monitor.setCanceled(true);
}
}
}
}
} catch (URISyntaxException e) {
Activator.getDefault().logError(e.getMessage(), e);
monitor.setCanceled(true);
} catch (ConnectionException e) {
Activator.getDefault().logError(e.getMessage(), e);
monitor.setCanceled(true);
}
}
@Override
protected void runInUI(IProgressMonitor monitor) {
if (result.length() > 0 && !monitor.isCanceled() && !fBrowser.getControl().isDisposed())
showTransformation(news, result.toString());
}
};
JobRunner.runUIUpdater(transformationJob, true);
}
private void showTransformation(INews news, String result) {
/* Transformer script surrounds content with a DIV, display it inline to avoid extra whitespace */
int index = result.indexOf("<div"); //$NON-NLS-1$
if (index != -1 && index < 10) { //Ensure the DIV is really at the beginning
index += "<div".length(); //$NON-NLS-1$
StringBuilder inlineResult = new StringBuilder();
inlineResult.append(result.substring(0, index));
inlineResult.append(" style=\"display: inline;\" "); //$NON-NLS-1$
inlineResult.append(result.substring(index));
result = inlineResult.toString();
}
/* Support stripping media tags if set and highlight search terms if any */
IBaseLabelProvider labelProvider = getLabelProvider();
if (labelProvider instanceof NewsBrowserLabelProvider) {
result = ((NewsBrowserLabelProvider) labelProvider).stripMediaTagsIfNecessary(result);
result = ((NewsBrowserLabelProvider) labelProvider).highlightSearchTermsIfNecessary(result);
}
final StringBuilder js = new StringBuilder();
js.append(getElementById(Dynamic.CONTENT.getId(news)).append(".innerHTML='").append(escapeForInnerHtml(result)).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
js.append(getElementById(Dynamic.FULL_CONTENT_LINK.getId(news)).append(".style.fontStyle = 'normal'; ")); //$NON-NLS-1$
js.append(getElementById(Dynamic.FULL_CONTENT_LINK_TEXT.getId(news)).append(".innerHTML='").append(escapeForInnerHtml(Messages.NewsBrowserViewer_FULL_CONTENT)).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
scrollIfNecessary(news, js);
/* Block external navigation while setting innerHTML */
fBrowser.blockExternalNavigationWhile(new Runnable() {
public void run() {
fBrowser.execute(js.toString(), "showTransformation"); //$NON-NLS-1$
}
});
}
private String escapeForInnerHtml(String str) {
StringBuilder result = new StringBuilder(str.length());
BufferedReader reader = new BufferedReader(new StringReader(str));
String line;
try {
while ((line = reader.readLine()) != null) {
/* Escape single and double quotes */
line = StringUtils.replaceAll(line, "\"", "\\\""); //$NON-NLS-1$ //$NON-NLS-2$
line = StringUtils.replaceAll(line, "'", "\\'"); //$NON-NLS-1$ //$NON-NLS-2$
/* XULRunner: Need to escape % with its ASCII HEX counterpart as XUL decodes it */
if (CBrowser.isMozillaRunningOnWindows())
line = StringUtils.replaceAll(line, "%", "%25"); //$NON-NLS-1$ //$NON-NLS-2$
/* Properly escape multilines */
result.append(line).append("\\n"); //$NON-NLS-1$
}
} catch (IOException e) {
}
return result.toString().trim();
}
@SuppressWarnings("unchecked")
private void revealNextPage(boolean scrollIntoView) {
/* Get next page from View Model */
Pair<List<Long>, List<Long>> nextPage = fViewModel.getNextPage(fPageSize);
List<Long> revealedGroups = nextPage.getFirst();
List<Long> revealedNews = nextPage.getSecond();
revealItems(revealedGroups, revealedNews, scrollIntoView);
}
@SuppressWarnings("unchecked")
private void revealItems(List<Long> revealedGroups, List<Long> revealedNews, boolean scrollIntoView) {
/* Return early if no more news to reveal */
if (revealedNews.isEmpty())
return;
final StringBuilder js = new StringBuilder();
/* Blur Links */
if (scrollIntoView) //Action was invoked from latch
js.append(getElementById(Dynamic.PAGE_LATCH_LINK.getId()).append(".blur(); ")); //$NON-NLS-1$
/* Check if the first revealed news belongs to a group that is already showing but collapsed */
long group = fViewModel.findGroup(revealedNews.get(0));
if (group != -1 && fViewModel.isGroupVisible(group) && !fViewModel.isGroupExpanded(group))
setGroupExpanded(group, fViewModel.getNewsIds(group), true, false);
/* Reveal Groups */
for (Long groupId : revealedGroups) {
if (fViewModel.isGroupVisible(groupId))
continue; //Skip if already visible
/* Update View Model */
fViewModel.setGroupVisible(groupId, true);
/* Show Group */
js.append(getElementById(Dynamic.GROUP.getId(groupId))).append(".style.display='block'; "); //$NON-NLS-1$
}
/* Reveal News */
Long firstNewsId = null;
for (Long newsId : revealedNews) {
if (fViewModel.isNewsVisible(newsId))
continue; //Skip if already visible
/* Remember the first news made visible to scroll to */
if (firstNewsId == null)
firstNewsId = newsId;
/* Update View Model */
fViewModel.setNewsVisible(newsId, true);
/* Get News from Contentprovider */
INews news = resolve(newsId);
if (news == null)
continue;
final StringBuilder newsJs = new StringBuilder(); //Use own builder since News JS can be large depending on content
String html = ((NewsBrowserLabelProvider) getLabelProvider()).getLabel(news, true, true, true, fViewModel.indexOfNewsItem(newsId));
newsJs.append(getElementById(Dynamic.NEWS.getId(newsId))).append(".style.display='block'; "); //$NON-NLS-1$
newsJs.append(getElementById(Dynamic.NEWS.getId(newsId))).append(".innerHTML ='").append(escapeForInnerHtml(html)).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$;
if (isHeadlinesLayout())
newsJs.append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(newsId))).append(".style.display='block'; "); //$NON-NLS-1$
/* Block external navigation while setting innerHTML */
fBrowser.blockExternalNavigationWhile(new Runnable() {
public void run() {
fBrowser.execute(newsJs.toString(), "revealItems#0"); //$NON-NLS-1$
}
});
}
/* Update Scroll Position */
if (firstNewsId != null && scrollIntoView)
js.append(getElementById(Dynamic.NEWS.getId(firstNewsId))).append(".scrollIntoView(true); "); //$NON-NLS-1$
/* Update Latch if necessary */
updateLatchIfNecessary(js);
/* Block external navigation while setting innerHTML */
fBrowser.blockExternalNavigationWhile(new Runnable() {
public void run() {
fBrowser.execute(js.toString(), "revealItems#1"); //$NON-NLS-1$
}
});
}
private void updateLatchIfNecessary(StringBuilder js) {
if (fPageSize == 0)
return; //Return if pagination turned off
/* Latch no longer required - hide */
if (fViewModel.getVisibleNewsCount() == fViewModel.getNewsCount()) {
js.append("var latch = ").append(getElementById(Dynamic.PAGE_LATCH.getId())).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (latch) {"); //$NON-NLS-1$
js.append(" latch.style.display='none'; "); //$NON-NLS-1$
js.append("}"); //$NON-NLS-1$
}
/* Otherwise update Latch Label */
else if (getLabelProvider() instanceof NewsBrowserLabelProvider) {
String updatedLatchName = ((NewsBrowserLabelProvider) getLabelProvider()).getLatchName();
js.append("var latch = ").append(getElementById(Dynamic.PAGE_LATCH_TEXT.getId())).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (latch) {"); //$NON-NLS-1$
js.append(" latch.innerHTML='").append(escapeForInnerHtml(updatedLatchName)).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("}"); //$NON-NLS-1$
}
}
private void delayInUI(Runnable runnable) {
JobRunner.runInUIThread(0, true, getControl(), runnable);
}
private long getId(String query) {
return Long.parseLong(query);
}
private INews getNews(String query) {
try {
long id = getId(query);
return resolve(id);
} catch (NullPointerException e) {
return null;
} catch (NumberFormatException e) {
return null;
}
}
private List<INews> getNewsList(String query) {
List<INews> news = new ArrayList<INews>();
StringTokenizer tokenizer = new StringTokenizer(query, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
String nextElement = tokenizer.nextToken();
if (StringUtils.isSet(nextElement)) {
INews item = getNews(nextElement);
if (item != null)
news.add(item);
}
}
return news;
}
private EntityGroup getEntityGroup(String query) {
long id = getId(query);
/* Try to resolve the news from the mapping table */
List<Long> newsIds = fViewModel.getNewsIds(id);
if (!newsIds.isEmpty()) {
List<INews> news = new ArrayList<INews>(newsIds.size());
for (Long newsId : newsIds) {
try {
INews item = resolve(newsId);
if (item != null)
news.add(item);
} catch (NullPointerException e) {
continue;
}
}
/* Create a temporary new EntityGroup to be used from the context menu */
EntityGroup group = new EntityGroup(id, NewsGrouping.GROUP_CATEGORY_ID);
for (INews item : news) {
new EntityGroupItem(group, item);
}
return group;
}
return null;
}
/*
* @see org.eclipse.jface.viewers.ContentViewer#setLabelProvider(org.eclipse.jface.viewers.IBaseLabelProvider)
*/
@Override
public void setLabelProvider(IBaseLabelProvider labelProvider) {
fBlockRefresh = true;
try {
super.setLabelProvider(labelProvider);
} finally {
fBlockRefresh = false;
}
}
/*
* @see org.eclipse.jface.viewers.ContentViewer#setContentProvider(org.eclipse.jface.viewers.IContentProvider)
*/
@Override
public void setContentProvider(IContentProvider contentProvider) {
fBlockRefresh = true;
try {
super.setContentProvider(contentProvider);
} finally {
fBlockRefresh = false;
}
}
/*
* @see org.eclipse.jface.viewers.Viewer#refresh()
*/
@Override
public void refresh() {
if (!fBlockRefresh) {
fBrowser.refresh();
onRefresh();
}
}
/**
* A special way of refreshing this viewer with additional options to control
* the behavior.
*
* @param restoreInput if set to <code>true</code> will restore the initial
* input that was set to the browser in case the user navigated to a different
* URL.
* @param moveToTop if <code>true</code> will scroll the browser to the top
* position to reveal additional content.
*/
public void refresh(boolean restoreInput, boolean moveToTop) {
/* Browser not showing initial input anymore, so restore if asked for */
if (restoreInput && !ApplicationServer.getDefault().isDisplayOperation(fBrowser.getControl().getUrl()))
internalSetInput(fInput, true, false);
/* Otherwise perform the normal refresh */
else {
/* Move scroll position to top if set */
if (moveToTop)
fBrowser.execute("scroll(0,0);", "refresh"); //$NON-NLS-1$ //$NON-NLS-2$
/* Refresh */
refresh();
}
}
/**
* Method is called whenever the viewer is refreshed. Subclasses my override
* to do something.
*/
protected void onRefresh() {
//Do nothing here.
}
/*
* @see org.eclipse.jface.viewers.ContentViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
*/
@Override
protected void handleDispose(DisposeEvent event) {
fServer.unregister(fId);
fCurrentSelection = null;
OwlUI.safeDispose(fNewsContextMenu);
OwlUI.safeDispose(fAttachmentsContextMenu);
OwlUI.safeDispose(fLabelsContextMenu);
OwlUI.safeDispose(fShareNewsContextMenu);
OwlUI.safeDispose(fFindRelatedContextMenu);
super.handleDispose(event);
}
/*
* @see org.eclipse.jface.viewers.ContentViewer#setInput(java.lang.Object)
*/
@Override
public void setInput(Object input) {
setInput(input, false);
}
/**
* @param input the input to show in this news browser viewer.
* @param blockExternalNavigation <code>true</code> to block any potential
* external navigation when setting the input and <code>false</code> otherwise
* (default).
*/
public void setInput(Object input, boolean blockExternalNavigation) {
internalSetInput(input, false, blockExternalNavigation);
}
/**
* @param newLayout the new {@link Layout} to use in this viewer.
*/
void onLayoutChanged(Layout newLayout) {
if (fSite == null)
return;
/* Update Page Size */
if (newLayout == Layout.HEADLINES || newLayout == Layout.NEWSPAPER)
fPageSize = OwlUI.getPageSize(fSite.getInputPreferences()).getPageSize();
else
fPageSize = PageSize.NO_PAGING.getPageSize();
/* Update "Mark Read on Scrolling" */
fMarkReadOnScrolling = (newLayout == Layout.NEWSPAPER) && fSite.getInputPreferences().getBoolean(DefaultPreferences.MARK_READ_ON_SCROLLING);
}
private void internalSetInput(Object input, boolean force, boolean blockExternalNavigation) {
/* Ignore this Input if its already set */
if (!force && sameInput(input))
return;
/* Remember Input */
fInput = input;
/* Clear Cache of news marked as unread by user */
fMarkedUnreadByUserCache.clear();
/* Update Settings based on Input */
if (fSite != null) {
IPreferenceScope inputPreferences = fSite.getInputPreferences();
fMarkReadOnExpand = inputPreferences.getBoolean(DefaultPreferences.MARK_READ_STATE);
/* Update settings based on Layout */
Layout layout = OwlUI.getLayout(inputPreferences);
onLayoutChanged(layout);
}
/* Stop any other Website if required */
String url = fBrowser.getControl().getUrl();
if (!"".equals(url)) //$NON-NLS-1$
fBrowser.getControl().stop();
/* Input is a URL - display it */
if (input instanceof String) {
fBrowser.setUrl((String) input, !blockExternalNavigation);
return;
}
/* Set URL if its not already showing and contains a display-operation */
String inputUrl = fServer.toUrl(fId, input);
if (fServer.isDisplayOperation(inputUrl) && !inputUrl.equals(url))
fBrowser.setUrl(inputUrl);
/* Hide the Browser as soon as the input is set to Null */
if (input == null && fBrowser.getControl().getVisible())
fBrowser.getControl().setVisible(false);
}
/* Checks wether the given Input is same to the existing one */
private boolean sameInput(Object input) {
if (fInput instanceof Object[])
return input instanceof Object[] && Arrays.equals((Object[]) fInput, (Object[]) input);
if (fInput != null)
return fInput.equals(input);
return false;
}
/*
* @see org.eclipse.jface.viewers.ContentViewer#getInput()
*/
@Override
public Object getInput() {
return fInput;
}
/**
* Adds the given filter to this viewer.
*
* @param filter a viewer filter
*/
public void addFilter(ViewerFilter filter) {
if (fFilters == null)
fFilters = new ArrayList<ViewerFilter>();
fFilters.add(filter);
if (filter instanceof NewsFilter)
fNewsFilter = (NewsFilter) filter;
}
/**
* Removes the given filter from this viewer, and triggers refiltering and
* resorting of the elements if required. Has no effect if the identical
* filter is not registered.
*
* @param filter a viewer filter
*/
public void removeFilter(ViewerFilter filter) {
Assert.isNotNull(filter);
if (fFilters != null) {
for (Iterator<ViewerFilter> i = fFilters.iterator(); i.hasNext();) {
Object o = i.next();
if (o == filter) {
i.remove();
refresh();
if (fFilters.size() == 0)
fFilters = null;
return;
}
}
}
if (filter == fNewsFilter)
fNewsFilter = null;
}
/**
* @param comparator
*/
public void setComparator(ViewerComparator comparator) {
if (fSorter != comparator)
fSorter = comparator;
}
/*
* @see org.eclipse.jface.viewers.Viewer#getControl()
*/
@Override
public Control getControl() {
return fBrowser.getControl();
}
/**
* @return The wrapped Browser (CBrowser).
*/
public CBrowser getBrowser() {
return fBrowser;
}
/*
* @see org.eclipse.jface.viewers.Viewer#getSelection()
*/
@Override
public ISelection getSelection() {
return fCurrentSelection;
}
/*
* @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection,
* boolean)
*/
@Override
public void setSelection(ISelection selection, boolean reveal) {
fCurrentSelection = (IStructuredSelection) selection;
fireSelectionChanged(new SelectionChangedEvent(this, selection));
}
/* Find a news from the selection and scroll it into view using JavaScript */
void showSelection(ISelection selection) {
if (!(selection instanceof StructuredSelection) || selection.isEmpty())
return;
/* Find the News to Show */
NewsReference newsToShow = null;
Object firstElement = ((StructuredSelection) selection).getFirstElement();
if (firstElement instanceof INews)
newsToShow = ((INews) firstElement).toReference();
else if (firstElement instanceof NewsReference)
newsToShow = (NewsReference) firstElement;
/* Scroll the News into View if present and expand as necessary */
if (newsToShow != null) {
/* First determine if there are hidden elements that need to be expanded */
Pair<List<Long>, List<Long>> itemsToReveal = fViewModel.revealPage(newsToShow.getId(), fPageSize);
revealItems(itemsToReveal.getFirst(), itemsToReveal.getSecond(), false);
/* Headlines Layout */
if (isHeadlinesLayout()) {
INews news = resolve(newsToShow.getId());
if (news != null)
setNewsExpanded(news, true);
}
/* Newspaper Layout */
else {
StringBuilder js = new StringBuilder();
js.append(getElementById(Dynamic.NEWS.getId(newsToShow))).append(".scrollIntoView(true);"); //$NON-NLS-1$
fBrowser.execute(js.toString(), "showSelection"); //$NON-NLS-1$
}
}
}
void navigate(boolean next, boolean unread, boolean onInputSet) {
/* Navigate in Headlines Layout based on expanded element */
if (isHeadlinesLayout())
navigateInHeadlines(next, unread);
/* Navigate in Newspaper Layout based on scroll position */
else
navigateInNewspaper(next, unread, onInputSet);
}
private void navigateInNewspaper(boolean next, boolean unread, boolean onInputSet) {
/* Check if the first news is already unread and in this case avoid navigation */
if (unread && onInputSet && fViewModel.isFirstItemUnread())
return;
/* Otherwise need to navigate to a specific unread news */
StringBuilder js = new StringBuilder();
if (fBrowser.isIE())
js.append("var scrollPosY = document.body.scrollTop; "); //$NON-NLS-1$
else
js.append("var scrollPosY = window.pageYOffset; "); //$NON-NLS-1$
js.append("var body = ").append(getElementById(BODY_ELEMENT_ID)).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("var divs = body.childNodes; "); //$NON-NLS-1$
/* Next News (need to fake Y position by some pixels to avoid the same news being selected over and over) */
if (next) {
js.append(" for (var i = 1; i < divs.length; i++) { "); //$NON-NLS-1$
js.append(" if (divs[i].nodeType != 1) { "); //$NON-NLS-1$
js.append(" continue; "); //$NON-NLS-1$
js.append(" } "); //$NON-NLS-1$
js.append(" var divPosY = divs[i].offsetTop; "); //$NON-NLS-1$
if (unread) {
js.append(" if (divPosY > scrollPosY + 15 && divs[i].className == \"newsitemUnread\") { "); //$NON-NLS-1$
} else
js.append(" if (divPosY > scrollPosY + 15 && (divs[i].className == \"newsitemUnread\" || divs[i].className == \"newsitemRead\")) { "); //$NON-NLS-1$
js.append(" divs[i].scrollIntoView(true); "); //$NON-NLS-1$
js.append(" break; "); //$NON-NLS-1$
js.append(" } "); //$NON-NLS-1$
js.append(" } "); //$NON-NLS-1$
}
/* Previous News (need to fake Y position by some pixels to avoid the same news being selected over and over) */
else {
js.append(" for (var i = divs.length - 1; i >= 0; i--) { "); //$NON-NLS-1$
js.append(" if (divs[i].nodeType != 1) { "); //$NON-NLS-1$
js.append(" continue; "); //$NON-NLS-1$
js.append(" } "); //$NON-NLS-1$
js.append(" var divPosY = divs[i].offsetTop; "); //$NON-NLS-1$
if (unread) {
js.append(" if (divPosY < scrollPosY - 15 && divs[i].className == \"newsitemUnread\") { "); //$NON-NLS-1$
} else
js.append(" if (divPosY < scrollPosY - 15 && (divs[i].className == \"newsitemUnread\" || divs[i].className == \"newsitemRead\")) { "); //$NON-NLS-1$
js.append(" divs[i].scrollIntoView(true); "); //$NON-NLS-1$
js.append(" break; "); //$NON-NLS-1$
js.append(" } "); //$NON-NLS-1$
js.append(" } "); //$NON-NLS-1$
}
/* See if the Scroll Position Changed at all and handle */
if (fBrowser.isIE())
js.append("var newScrollPosY = document.body.scrollTop; "); //$NON-NLS-1$
else
js.append("var newScrollPosY = window.pageYOffset; "); //$NON-NLS-1$
if (unread || fViewModel.hasItems()) { //Workaround for a Bug that would cause endless navigation if viewer contains no news otherwise
js.append("if (scrollPosY == newScrollPosY) { "); //$NON-NLS-1$
js.append(" window.location.href = '").append(ILinkHandler.HANDLER_PROTOCOL + getNavigationActionId(next, unread)).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("} "); //$NON-NLS-1$
}
/* Execute in Browser */
fBrowser.execute(js.toString(), "navigateInNewspaper"); //$NON-NLS-1$
/* If we are at the end of the page, reveal more items if possible */
onUserInteraction();
}
private void navigateInHeadlines(boolean next, boolean unread) {
long targetNews = -1L;
long offset = fViewModel.getExpandedNews();
/* Navigate to Next News */
if (next)
targetNews = fViewModel.nextNews(unread, offset);
/* Navigate to Previous News */
else
targetNews = fViewModel.previousNews(unread, offset);
/* Expand target and the parent group if necessary */
if (targetNews != -1) {
/* First determine if there are hidden elements that need to be expanded */
Pair<List<Long>, List<Long>> itemsToReveal = fViewModel.revealPage(targetNews, fPageSize);
revealItems(itemsToReveal.getFirst(), itemsToReveal.getSecond(), false);
/* Group */
long groupId = fViewModel.findGroup(targetNews);
if (groupId != -1 && !fViewModel.isGroupExpanded(groupId)) {
List<Long> newsIds = fViewModel.getNewsIds(groupId);
if (!newsIds.isEmpty())
setGroupExpanded(groupId, newsIds, true, false);
}
/* News */
setNewsExpanded(resolve(targetNews), true, false);
StringBuilder js = new StringBuilder();
js.append(getElementById(Dynamic.NEWS.getId(targetNews))).append(".scrollIntoView(true); "); //$NON-NLS-1$
fBrowser.execute(js.toString(), "navigateInHeadlines#0"); //$NON-NLS-1$
/* If we are at the end of the page, reveal more items if possible */
onUserInteraction();
}
/* If navigation did not find a suitable target, call the outer navigation function */
else if (unread || fViewModel.hasItems()) { //Workaround for a Bug that would cause endless navigation if viewer contains no news otherwise
StringBuilder js = new StringBuilder();
js.append("window.location.href = '").append(ILinkHandler.HANDLER_PROTOCOL + getNavigationActionId(next, unread)).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$
fBrowser.execute(js.toString(), "navigateInHeadlines#1"); //$NON-NLS-1$
}
}
private String getNavigationActionId(boolean next, boolean unread) {
if (next) {
if (unread)
return NewsBrowserViewer.NEXT_UNREAD_NEWS_HANDLER_ID;
return NewsBrowserViewer.NEXT_NEWS_HANDLER_ID;
}
if (unread)
return NewsBrowserViewer.PREVIOUS_UNREAD_NEWS_HANDLER_ID;
return NewsBrowserViewer.PREVIOUS_NEWS_HANDLER_ID;
}
/**
* Shows the intial Input in the Browser.
*/
public void home() {
internalSetInput(fInput, true, false);
}
/**
* @return the {@link ViewerComparator} used for sorting news.
*/
ViewerComparator getComparator() {
return fSorter;
}
private Object[] getSortedChildren(Object parent) {
Object[] result = getFilteredChildren(parent);
if (fSorter != null) {
/* Avoid modifying the original array from the model */
result = result.clone();
fSorter.sort(this, result);
}
return result;
}
private Object[] getFilteredChildren(Object parent) {
Object[] result = getRawChildren(parent);
/* Never filter a selected News, thereby return here */
if (fInput instanceof INews)
return result;
/* Run Filters over result */
if (fFilters != null) {
for (Object filter : fFilters) {
ViewerFilter f = (ViewerFilter) filter;
result = f.filter(this, parent, result);
}
}
return result;
}
private Object[] getRawChildren(Object parent) {
Object[] result = null;
if (parent != null) {
IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider();
if (cp != null)
result = cp.getElements(parent);
}
return (result != null) ? result : new Object[0];
}
/**
* Asks the NewsViewModel to update based on the given input.
*
* @param input the list of elements that becomes visible in the browser
* viewer.
*/
public void updateViewModel(Object[] input) {
fViewModel.setInput(input, fPageSize);
}
/**
* @param input Can either be an Array of Feeds or News
* @return An flattend array of Objects.
*/
public Object[] getFlattendChildren(Object input) {
return getFlattendChildren(input, true);
}
/**
* @param input Can either be an Array of Feeds or News
* @param withGroups if <code>true</code> also return groups if present.
* @return An flattend array of Objects.
*/
public Object[] getFlattendChildren(Object input, boolean withGroups) {
/* Using NewsContentProvider */
if (input != null && getContentProvider() instanceof NewsContentProvider) {
NewsContentProvider cp = (NewsContentProvider) getContentProvider();
/*
* Flatten Children since Grouping is Enabled and the Parent is not
* containing just News (so either Feed or ViewerGroups).
*/
if (cp.isGroupingEnabled() && !isNews(input)) {
List<Object> flatList = new ArrayList<Object>();
/* Wrap into Object-Array */
if (!(input instanceof Object[]))
input = new Object[] { input };
/* For each Group retrieve Children (sorted and filtered) */
int newsCount = 0;
Object groups[] = (Object[]) input;
for (Object group : groups) {
/* Make sure this child has children */
if (cp.hasChildren(group)) {
Object sortedChilds[] = getSortedChildren(group);
/* Only add if there are Childs */
if (sortedChilds.length > 0) {
/* Add the Group itself */
if (withGroups)
flatList.add(group);
/* Store the actual number of news of the group too */
if (group instanceof EntityGroup)
((EntityGroup) group).setSizeHint(sortedChilds.length);
/* Add childs of group to the list */
flatList.addAll(Arrays.asList(sortedChilds));
newsCount += sortedChilds.length;
}
}
/* Otherwise just add */
else {
if (withGroups)
flatList.add(group);
}
}
return fillPagingIfNecessary(flatList.toArray(), newsCount);
}
/* Grouping is not enabled, just return sorted Children */
return fillPagingIfNecessary(getSortedChildren(input));
}
/* Structured ContentProvider */
else if (input != null && getContentProvider() instanceof IStructuredContentProvider)
return getSortedChildren(input);
/* No Element to show */
return new Object[0];
}
private Object[] fillPagingIfNecessary(Object[] elements) {
return fillPagingIfNecessary(elements, elements.length);
}
private Object[] fillPagingIfNecessary(Object[] elements, int newsCount) {
if (fPageSize == 0 || newsCount <= fPageSize)
return elements;
Object[] elementsWithPaging = new Object[elements.length + 1];
System.arraycopy(elements, 0, elementsWithPaging, 0, elements.length);
elementsWithPaging[elements.length] = new PageLatch();
return elementsWithPaging;
}
/* Returns TRUE if the Input consists only of INews */
private boolean isNews(Object input) {
if (input instanceof Object[]) {
Object elements[] = (Object[]) input;
for (Object element : elements) {
if (!(element instanceof INews))
return false;
}
} else if (!(input instanceof INews))
return false;
return true;
}
/**
* @param parentElement
* @param childElements
*/
public void add(Object parentElement, Object[] childElements) {
Assert.isNotNull(parentElement);
assertElementsNotNull(childElements);
if (childElements.length > 0)
refresh(); // TODO Optimize
}
/**
* @param news
*/
public void update(Collection<NewsEvent> news) {
/*
* The update-event could have been sent out a lot faster than the Browser
* having a chance to react. In this case, rather then refreshing a possible
* blank page (or wrong page), re-set the input.
*/
String inputUrl = fServer.toUrl(fId, fInput);
String browserUrl = fBrowser.getControl().getUrl();
boolean resetInput = browserUrl.length() == 0 || URIUtils.ABOUT_BLANK.equals(browserUrl);
if (inputUrl.equals(browserUrl)) {
if (!internalUpdate(news))
refresh(); // Refresh if dynamic update failed
} else if (fServer.isDisplayOperation(inputUrl) && resetInput)
fBrowser.setUrl(inputUrl);
}
/**
* @param objects
*/
public void remove(Object[] objects) {
assertElementsNotNull(objects);
/* Refresh if dynamic removal failed */
if (!internalRemove(objects))
refresh();
}
/**
* @param element
*/
public void remove(Object element) {
Assert.isNotNull(element);
/* Refresh if dynamic removal failed */
if (!internalRemove(new Object[] { element }))
refresh();
}
private boolean internalUpdate(Collection<NewsEvent> newsEvents) {
boolean toggleJS = fBrowser.shouldDisableScript();
try {
if (toggleJS)
fBrowser.setScriptDisabled(false);
/* Update for each Event */
for (NewsEvent newsEvent : newsEvents) {
INews news = newsEvent.getEntity();
if (!fViewModel.isNewsVisible(news) || news.getId() == null || !fViewModel.hasNews(news.getId()))
continue; //Do not update if news not visible at all or not part of the browsers content
StringBuilder js = new StringBuilder();
/* State (Bold/Plain Title, Mark Read Tooltip) */
if (CoreUtils.isStateChange(newsEvent)) {
String markRead = Messages.NewsBrowserViewer_MARK_READ;
String markUnread = Messages.NewsBrowserViewer_MARK_UNREAD;
boolean isRead = (INews.State.READ == news.getState());
js.append(getElementById(Dynamic.NEWS.getId(news)).append(isRead ? ".className='newsitemRead'; " : ".className='newsitemUnread'; ")); //$NON-NLS-1$ //$NON-NLS-2$
js.append(getElementById(Dynamic.TITLE_LINK.getId(news)).append(isRead ? ".className='read'; " : ".className='unread'; ")); //$NON-NLS-1$ //$NON-NLS-2$
js.append(getElementById(Dynamic.TOGGLE_READ_LINK.getId(news)).append(isRead ? ".title='" + markUnread + "'; " : ".title='" + markRead + "'; ")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
js.append(getElementById(Dynamic.TOGGLE_READ_IMG.getId(news)).append(isRead ? ".alt='" + markUnread + "'; " : ".alt='" + markRead + "'; ")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
/* Sticky (Title Background, Footer Background, Mark Sticky Image) */
if (CoreUtils.isStickyStateChange(newsEvent)) {
boolean isSticky = news.isFlagged();
js.append(getElementById(Dynamic.HEADER.getId(news)).append(isSticky ? ".className='headerSticky'; " : ".className='header'; ")); //$NON-NLS-1$ //$NON-NLS-2$
js.append("var footer = ").append(getElementById(Dynamic.FOOTER.getId(news))).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (footer) {"); //$NON-NLS-1$
js.append(" footer").append(isSticky ? ".className='footerSticky'; " : ".className='footer'; "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
js.append("}"); //$NON-NLS-1$
String stickyImgUri;
if (fBrowser.isIE())
stickyImgUri = isSticky ? OwlUI.getImageUri("/icons/obj16/news_pinned_light.gif", "news_pinned_light.gif") : OwlUI.getImageUri("/icons/obj16/news_pin_light.gif", "news_pin_light.gif"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
else
stickyImgUri = isSticky ? ApplicationServer.getDefault().toResourceUrl("/icons/obj16/news_pinned_light.gif") : ApplicationServer.getDefault().toResourceUrl("/icons/obj16/news_pin_light.gif"); //$NON-NLS-1$ //$NON-NLS-2$
js.append(getElementById(Dynamic.TOGGLE_STICKY_IMG.getId(news)).append(".src='").append(stickyImgUri).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
}
/* Label (Title Foreground, Label List) */
if (CoreUtils.isLabelChange(newsEvent)) {
Set<ILabel> labels = CoreUtils.getSortedLabels(news);
String defaultColor = (CoreUtils.getLink(news) != null && !isHeadlinesLayout()) ? "#009" : "rgb(0,0,0)"; //$NON-NLS-1$ //$NON-NLS-2$
String color = (labels.isEmpty()) ? defaultColor : "rgb(" + OwlUI.toString(OwlUI.getRGB(labels.iterator().next())) + ")"; //$NON-NLS-1$ //$NON-NLS-2$
if ("rgb(0,0,0)".equals(color)) //Don't let black override link color //$NON-NLS-1$
color = defaultColor;
js.append(getElementById(Dynamic.TITLE_LINK.getId(news)).append(".style.color='").append(color).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
/* Remove Labels */
if (labels.isEmpty()) {
js.append(getElementById(Dynamic.LABELS_SEPARATOR.getId(news)).append(".style.display='none'; ")); //$NON-NLS-1$
js.append(getElementById(Dynamic.LABELS.getId(news)).append(".innerHTML=''; ")); //$NON-NLS-1$
}
/* Show Labels */
else {
js.append(getElementById(Dynamic.LABELS_SEPARATOR.getId(news)).append(".style.display='inline'; ")); //$NON-NLS-1$
StringBuilder labelsHtml = new StringBuilder(Messages.NewsBrowserViewer_LABELS);
labelsHtml.append(" "); //$NON-NLS-1$
int c = 0;
for (ILabel label : labels) {
c++;
if (c < labels.size())
span(labelsHtml, StringUtils.htmlEscape(label.getName()) + ", ", label.getColor()); //$NON-NLS-1$
else
span(labelsHtml, StringUtils.htmlEscape(label.getName()), label.getColor());
}
js.append(getElementById(Dynamic.LABELS.getId(news)).append(".innerHTML='").append(escapeForInnerHtml(labelsHtml.toString())).append("'; ")); //$NON-NLS-1$ //$NON-NLS-2$
}
/* Make sure to also update collapsed subtitles if present */
if (isHeadlinesLayout() && !fViewModel.isNewsExpanded(news)) {
StringBuilder subtitleContent = new StringBuilder();
IBaseLabelProvider lp = getLabelProvider();
if (lp instanceof NewsBrowserLabelProvider)
((NewsBrowserLabelProvider) lp).fillSubtitle(subtitleContent, news, labels, false);
if (subtitleContent.length() > 0)
js.append(getElementById(Dynamic.SUBTITLE_LINK.getId(news))).append(".innerHTML='").append(escapeForInnerHtml(subtitleContent.toString())).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/* Execute */
if (js.length() > 0) {
boolean res = fBrowser.execute(js.toString(), false, "internalUpdate"); //$NON-NLS-1$
if (!res)
return false;
}
}
} finally {
if (toggleJS)
fBrowser.setScriptDisabled(true);
}
return true;
}
private void span(StringBuilder builder, String content, String color) {
builder.append("<span style=\"color: rgb(").append(color).append(");\""); //$NON-NLS-1$ //$NON-NLS-2$
builder.append(">").append(content).append("</span>"); //$NON-NLS-1$ //$NON-NLS-2$
}
private StringBuilder getElementById(String id) {
return new StringBuilder("document.getElementById('" + id + "')"); //$NON-NLS-1$ //$NON-NLS-2$
}
private boolean internalRemove(Object[] elements) {
StringBuilder js = new StringBuilder();
js.append("var body = ").append(getElementById(BODY_ELEMENT_ID)).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
boolean varDefined = false;
Set<Long> groupsToUpdate = new HashSet<Long>();
for (Object element : elements) {
if (element instanceof INews) {
INews news = (INews) element;
if (news.getId() == null || !fViewModel.hasNews(news.getId()))
continue; //Do not update if news not part of the browsers content at all
/* Remove from View Model */
long groupToUpdate = fViewModel.removeNews(news);
if (groupToUpdate != -1)
groupsToUpdate.add(groupToUpdate);
/* Remove News from DOM */
if (!varDefined) {
js.append("var "); //$NON-NLS-1$
varDefined = true;
}
js.append("node = ").append(getElementById(Dynamic.NEWS.getId(news))).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (node && node.parentNode == body) { "); //$NON-NLS-1$
js.append(" body.removeChild(node); "); //$NON-NLS-1$
js.append("} else if (node) { "); //$NON-NLS-1$
js.append(" node.className='hidden';"); //$NON-NLS-1$
js.append("} "); //$NON-NLS-1$
/* Hide Separator if using headlines layout */
if (isHeadlinesLayout()) {
js.append("node = ").append(getElementById(Dynamic.HEADLINE_SEPARATOR.getId(news))).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (node && node.parentNode == body) { "); //$NON-NLS-1$
js.append(" body.removeChild(node); "); //$NON-NLS-1$
js.append("} else if (node) {"); //$NON-NLS-1$
js.append(" node.className='hidden';"); //$NON-NLS-1$
js.append("} "); //$NON-NLS-1$
}
}
}
/* Update Groups */
IBaseLabelProvider labelProvider = getLabelProvider();
for (Long groupId : groupsToUpdate) {
/* Remove Groups from DOM or update it */
if (!varDefined) {
js.append("var "); //$NON-NLS-1$
varDefined = true;
}
/* Group is empty now: Remove it from DOM */
if (!fViewModel.hasGroup(groupId)) {
js.append("node = ").append(getElementById(Dynamic.GROUP.getId(groupId))).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (node && node.parentNode == body) { "); //$NON-NLS-1$
js.append(" body.removeChild(node); "); //$NON-NLS-1$
js.append("} else if (node) { "); //$NON-NLS-1$
js.append(" node.className='hidden';"); //$NON-NLS-1$
js.append("} "); //$NON-NLS-1$
}
/* Group has a new Element count: Update it */
else if (labelProvider instanceof NewsBrowserLabelProvider) {
int count = fViewModel.getGroupSize(groupId);
NewsBrowserLabelProvider browserLabelProvider = (NewsBrowserLabelProvider) labelProvider;
String groupNote = browserLabelProvider.getGroupNote(count, count);
js.append("node = ").append(getElementById(Dynamic.GROUP_NOTE.getId(groupId))).append("; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("if (node) { "); //$NON-NLS-1$
js.append(" node.innerHTML = '").append(escapeForInnerHtml(groupNote)).append("'; "); //$NON-NLS-1$ //$NON-NLS-2$
js.append("} "); //$NON-NLS-1$
}
}
/* Update Latch as necessary */
updateLatchIfNecessary(js);
return fBrowser.execute(js.toString(), "internalRemove"); //$NON-NLS-1$
}
private void assertElementsNotNull(Object[] elements) {
Assert.isNotNull(elements);
for (Object element : elements) {
Assert.isNotNull(element);
}
}
/**
* @return Returns a List of Strings that should get highlighted per News that
* is displayed.
*/
protected Collection<String> getHighlightedWords() {
if (getContentProvider() instanceof NewsContentProvider && fPreferences.getBoolean(DefaultPreferences.FV_HIGHLIGHT_SEARCH_RESULTS)) {
INewsMark mark = ((NewsContentProvider) getContentProvider()).getInput();
Set<String> extractedWords;
/* Extract from Conditions if any */
if (mark instanceof ISearch) {
List<ISearchCondition> conditions = ((ISearch) mark).getSearchConditions();
extractedWords = CoreUtils.extractWords(conditions);
} else
extractedWords = new HashSet<String>(1);
/* Fill Pattern if set */
if (fNewsFilter != null && StringUtils.isSet(fNewsFilter.getPatternString())) {
String pattern = fNewsFilter.getPatternString();
/* News Filter sometimes converts to wildcard query */
if (StringUtils.supportsTrailingWildcards(pattern))
pattern = pattern + "*"; //$NON-NLS-1$
/* Extract Words */
extractedWords.addAll(CoreUtils.extractWords(pattern));
}
return extractedWords;
}
return Collections.emptyList();
}
NewsBrowserViewModel getViewModel() {
return fViewModel;
}
private boolean isGroupingEnabled() {
IContentProvider cp = getContentProvider();
if (cp instanceof NewsContentProvider)
return ((NewsContentProvider) cp).isGroupingEnabled();
return false;
}
private boolean isGroupingByState() {
IContentProvider cp = getContentProvider();
if (cp instanceof NewsContentProvider)
return ((NewsContentProvider) cp).isGroupingByState();
return false;
}
private boolean isHeadlinesLayout() {
IBaseLabelProvider lp = getLabelProvider();
if (lp instanceof NewsBrowserLabelProvider)
return ((NewsBrowserLabelProvider) lp).isHeadlinesOnly();
return false;
}
INews resolve(long newsId) {
INews news = null;
/* First Ask ContentProvider Cache */
if (getContentProvider() instanceof NewsContentProvider)
news = ((NewsContentProvider) getContentProvider()).obtainFromCache(newsId);
/* Then fallback to resolve from DB */
if (news == null)
return fNewsDao.load(newsId);
return news;
}
}