/* ********************************************************************** ** ** 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.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.IAction; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IReusableEditor; import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.part.EditorPart; import org.rssowl.core.Owl; import org.rssowl.core.connection.ConnectionException; import org.rssowl.core.connection.IAbortable; import org.rssowl.core.connection.IProtocolHandler; import org.rssowl.core.internal.persist.pref.DefaultPreferences; import org.rssowl.core.persist.IBookMark; import org.rssowl.core.persist.IEntity; import org.rssowl.core.persist.IFeed; import org.rssowl.core.persist.IFolder; import org.rssowl.core.persist.IMark; import org.rssowl.core.persist.INews; import org.rssowl.core.persist.INewsBin; import org.rssowl.core.persist.INewsMark; import org.rssowl.core.persist.ISearchCondition; import org.rssowl.core.persist.ISearchMark; import org.rssowl.core.persist.dao.DynamicDAO; import org.rssowl.core.persist.dao.IBookMarkDAO; import org.rssowl.core.persist.dao.INewsBinDAO; import org.rssowl.core.persist.dao.INewsDAO; import org.rssowl.core.persist.dao.ISearchMarkDAO; import org.rssowl.core.persist.event.BookMarkAdapter; import org.rssowl.core.persist.event.BookMarkEvent; import org.rssowl.core.persist.event.BookMarkListener; import org.rssowl.core.persist.event.FeedAdapter; import org.rssowl.core.persist.event.FeedEvent; import org.rssowl.core.persist.event.FolderAdapter; import org.rssowl.core.persist.event.FolderEvent; import org.rssowl.core.persist.event.MarkEvent; import org.rssowl.core.persist.event.NewsBinAdapter; import org.rssowl.core.persist.event.NewsBinEvent; import org.rssowl.core.persist.event.NewsBinListener; import org.rssowl.core.persist.event.SearchConditionEvent; import org.rssowl.core.persist.event.SearchConditionListener; import org.rssowl.core.persist.event.SearchMarkAdapter; import org.rssowl.core.persist.event.SearchMarkEvent; import org.rssowl.core.persist.pref.IPreferenceScope; import org.rssowl.core.persist.reference.FeedLinkReference; import org.rssowl.core.persist.reference.NewsReference; import org.rssowl.core.util.CoreUtils; import org.rssowl.core.util.ITreeNode; import org.rssowl.core.util.LoggingSafeRunnable; import org.rssowl.core.util.RetentionStrategy; import org.rssowl.core.util.StringUtils; import org.rssowl.core.util.TreeTraversal; import org.rssowl.core.util.URIUtils; import org.rssowl.ui.internal.Activator; import org.rssowl.ui.internal.Application; import org.rssowl.ui.internal.ApplicationServer; import org.rssowl.ui.internal.Controller; import org.rssowl.ui.internal.Controller.BookMarkLoadListener; import org.rssowl.ui.internal.FolderNewsMark; import org.rssowl.ui.internal.OwlUI; import org.rssowl.ui.internal.OwlUI.Layout; import org.rssowl.ui.internal.actions.DeleteTypesAction; import org.rssowl.ui.internal.actions.FindAction; import org.rssowl.ui.internal.actions.ReloadTypesAction; import org.rssowl.ui.internal.actions.RetargetActions; import org.rssowl.ui.internal.undo.NewsStateOperation; import org.rssowl.ui.internal.undo.UndoStack; import org.rssowl.ui.internal.util.CBrowser; import org.rssowl.ui.internal.util.EditorUtils; import org.rssowl.ui.internal.util.JobRunner; import org.rssowl.ui.internal.util.LayoutUtils; import org.rssowl.ui.internal.util.UIBackgroundJob; import org.rssowl.ui.internal.util.WidgetTreeNode; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; /** * The FeedView is an instance of <code>EditorPart</code> capable of displaying * News in a Table-Viewer and Browser-Viewer. It offers controls to Filter and * Group them. * * @author bpasero */ public class FeedView extends EditorPart implements IReusableEditor { /* Delay in millies to Mark *new* News to *unread* on Part-Deactivation */ private static final int HANDLE_NEWS_SEEN_DELAY = 100; /* Millies before news seen are handled */ private static final int HANDLE_NEWS_SEEN_BLOCK_DELAY = 800; /* Delay in millies to safely operate on the browser content */ private static final int BROWSER_OPERATIONS_DELAY = 100; /* Millies before the next clean up is allowed to run again */ private static final int CLEAN_UP_BLOCK_DELAY = 1000; /* System Property indicating the separator to use for CSV files */ private static final String CSV_SEPARATOR_PROPERTY = "csvSeparator"; //$NON-NLS-1$ /* The last visible Feedview */ private static FeedView fgLastVisibleFeedView = null; /* Flag to indicate if feed change events should be blocked or not */ private static boolean fgBlockFeedChangeEvent; /** ID of this EditorPart */ public static final String ID = "org.rssowl.ui.FeedView"; //$NON-NLS-1$ /** List of UI-Events interesting for the FeedView */ public enum UIEvent { /** Other Feed Displayed */ FEED_CHANGE, /** Application Minimized */ MINIMIZE, /** Application Closing */ CLOSE, /** Tab Closed */ TAB_CLOSE } /* Editor Data */ private FeedViewInput fInput; private IEditorSite fEditorSite; private IFeedViewSite fFeedViewSite; /* Part to display News in Table */ private NewsTableControl fNewsTableControl; /* Part to display News in Browser */ private NewsBrowserControl fNewsBrowserControl; /* Bars */ private FilterBar fFilterBar; private BrowserBar fBrowserBar; /* Shared Viewer classes */ private NewsFilter fNewsFilter; private NewsGrouping fNewsGrouping; private NewsContentProvider fContentProvider; /* Container for the News Table Viewer */ private Composite fNewsTableControlContainer; /* Container for the Browser Viewer */ private Composite fBrowserViewerControlContainer; /* Listeners */ private IPartListener2 fPartListener; private BookMarkListener fBookMarkListener; private SearchMarkAdapter fSearchMarkListener; private FeedAdapter fFeedListener; private SearchConditionListener fSearchConditionListener; private NewsBinListener fNewsBinListener; private FolderAdapter fFolderListener; private BookMarkLoadListener fBookMarkLoadListener; /* Settings */ NewsFilter.Type fInitialFilterType; NewsGrouping.Type fInitialGroupType; NewsFilter.SearchTarget fInitialSearchTarget; Layout fLayout; private int fInitialWeights[]; private int fCacheWeights[]; /* Global Actions */ private IAction fReloadAction; private IAction fSelectAllAction; private IAction fDeleteAction; private IAction fCutAction; private IAction fCopyAction; private IAction fPasteAction; private IAction fPrintAction; private IAction fUndoAction; private IAction fRedoAction; private IAction fFindAction; /* Misc. */ private Composite fParent; private Composite fRootComposite; private SashForm fSashForm; private Label fHorizontalTableBrowserSep; private Label fVerticalTableBrowserSep; private LocalResourceManager fResourceManager; private IPreferenceScope fPreferences; private long fOpenTime; private boolean fCreated; private final Object fCacheJobIdentifier = new Object(); private ImageDescriptor fTitleImageDescriptor; private Label fHorizontalFilterTableSep; private Label fHorizontalBrowserSep; private Label fVerticalBrowserSep; private final INewsDAO fNewsDao = Owl.getPersistenceService().getDAOService().getNewsDAO(); private boolean fIsDisposed; private AtomicLong fLastCleanUpRun = new AtomicLong(); /* * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor) */ @Override public void doSave(IProgressMonitor monitor) { /* Not Supported */ } /* * @see org.eclipse.ui.part.EditorPart#doSaveAs() */ @Override public void doSaveAs() { if (fIsDisposed || Controller.getDefault().isShuttingDown()) return; /* Ask user for File */ FileDialog dialog = new FileDialog(getSite().getShell(), SWT.SAVE); dialog.setOverwrite(true); List<String> extensions = new ArrayList<String>(); extensions.add("*.html"); //$NON-NLS-1$ if (fInput.getMark() instanceof IBookMark) extensions.add("*.xml"); //$NON-NLS-1$ if (isTableViewerVisible()) extensions.add("*.csv"); //$NON-NLS-1$ dialog.setFilterExtensions(extensions.toArray(new String[extensions.size()])); String proposedName = Application.IS_WINDOWS ? CoreUtils.getSafeFileNameForWindows(fInput.getName()) : fInput.getName(); proposedName += ".html"; //$NON-NLS-1$ dialog.setFileName(proposedName); String fileName = dialog.open(); if (fileName == null) return; if (fileName.endsWith(".xml")) //$NON-NLS-1$ saveAsXml(fileName); else if(fileName.endsWith(".csv")) //$NON-NLS-1$ saveAsCsv(fileName); else saveAsHtml(fileName); } @SuppressWarnings("restriction") private void saveAsXml(final String fileName) { final IBookMark bm = (IBookMark) fInput.getMark(); final URI feedLink = bm.getFeedLinkReference().getLink(); try { final IProtocolHandler handler = Owl.getConnectionService().getHandler(feedLink); if (handler instanceof org.rssowl.core.internal.connection.DefaultProtocolHandler) { Job downloadJob = new Job(Messages.FeedView_DOWNLOADING_FEED) { @Override protected IStatus run(IProgressMonitor monitor) { monitor.beginTask(bm.getName(), IProgressMonitor.UNKNOWN); InputStream in = null; FileOutputStream out = null; boolean canceled = false; Exception error = null; try { byte[] buffer = new byte[8192]; in = handler.openStream(feedLink, monitor, null); out = new FileOutputStream(fileName); while (true) { /* Check for Cancellation and Shutdown */ if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) { canceled = true; return Status.CANCEL_STATUS; } /* Read from Stream */ int read = in.read(buffer); if (read == -1) break; out.write(buffer, 0, read); } } catch (FileNotFoundException e) { error = e; Activator.safeLogError(e.getMessage(), e); } catch (IOException e) { error = e; Activator.safeLogError(e.getMessage(), e); } catch (ConnectionException e) { error = e; Activator.safeLogError(e.getMessage(), e); } finally { monitor.done(); if (out != null) { try { out.close(); } catch (IOException e) { Activator.safeLogError(e.getMessage(), e); } } if (in != null) { try { if ((canceled || error != null) && in instanceof IAbortable) ((IAbortable) in).abort(); else in.close(); } catch (IOException e) { Activator.safeLogError(e.getMessage(), e); } } } return Status.OK_STATUS; } }; downloadJob.schedule(); } } catch (ConnectionException e) { Activator.safeLogError(e.getMessage(), e); } } /* Build Content as String from Feed */ private void saveAsHtml(String fileName) { StringBuilder content = new StringBuilder(); NewsBrowserLabelProvider labelProvider = (NewsBrowserLabelProvider) fNewsBrowserControl.getViewer().getLabelProvider(); URI base = null; if (fInput.getMark() instanceof IBookMark) { try { base = URIUtils.toHTTP(new URI(((IBookMark) fInput.getMark()).getFeedLinkReference().getLinkAsText())); } catch (URISyntaxException e) { /* Ignore and fallback to not using a Base at all */ } } /* Save from Table */ if (isTableViewerVisible()) { Tree tree = fNewsTableControl.getViewer().getTree(); TreeItem[] items = tree.getItems(); if (items.length > 0) { List<INews> newsToSave = new ArrayList<INews>(); /* Ungrouped */ if (items[0].getItemCount() == 0) { for (TreeItem item : items) { if (item.getData() instanceof INews) newsToSave.add((INews) item.getData()); } } /* Grouped */ else { for (TreeItem parentItem : items) { TreeItem[] childItems = parentItem.getItems(); for (TreeItem item : childItems) { if (item.getData() instanceof INews) newsToSave.add((INews) item.getData()); } } } /* Render Elements */ String text = labelProvider.render(newsToSave.toArray(), base, false); content.append(text); } } /* Save from Browser */ else { NewsBrowserViewer viewer = fNewsBrowserControl.getViewer(); Object[] elements = fContentProvider.getElements(fInput.getMark().toReference()); elements = viewer.getFlattendChildren(elements, false); /* Render Elements */ String text = labelProvider.render(elements, base, false); content.append(text); } /* Write into File */ if (content.length() > 0) CoreUtils.write(fileName, content); } private void saveAsCsv(final String fileName) { StringBuilder content = new StringBuilder(); String separator = System.getProperty(CSV_SEPARATOR_PROPERTY); if (separator == null || separator.length() == 0) separator = ";"; //$NON-NLS-1$ else if (separator.equals("\\t")) //$NON-NLS-1$ separator = "\t"; //$NON-NLS-1$ Tree tree = fNewsTableControl.getViewer().getTree(); TreeItem[] items = tree.getItems(); if (items.length > 0) { List<TreeItem> itemsToSave = new ArrayList<TreeItem>(); /* Ungrouped */ if (items[0].getItemCount() == 0) { for (TreeItem item : items) { if (item.getData() instanceof INews) itemsToSave.add(item); } } /* Grouped */ else { for (TreeItem parentItem : items) { TreeItem[] childItems = parentItem.getItems(); for (TreeItem item : childItems) { if (item.getData() instanceof INews) itemsToSave.add(item); } } } /* Get header */ for (int order : tree.getColumnOrder()) { TreeColumn column = tree.getColumn(order); String text = column.getText(); if (text.length() > 0) content.append(toCSVEntry(text, separator)).append(separator); } if (content.length() > 0) { content.delete(content.length() - separator.length(), content.length()); content.append('\n'); } /* Get contents */ for (TreeItem item : itemsToSave) { boolean lineAdded = false; for (int order : tree.getColumnOrder()) { if (tree.getColumn(order).getText().length() > 0) { String text = item.getText(order); content.append(toCSVEntry(text, separator)).append(separator); lineAdded = true; } } if (lineAdded) { content.delete(content.length() - separator.length(), content.length()); content.append('\n'); } } } /* Write into File */ if (content.length() > 0) CoreUtils.write(fileName, content); } private String toCSVEntry(String value, String separator) { if (value.contains(separator)) { /* Values that contain the separator and quotes need to escape quotes */ if (value.contains("\"")) { //$NON-NLS-1$ value = StringUtils.replaceAll(value, "\"", "\"\""); //$NON-NLS-1$ //$NON-NLS-2$ } /* Values that contain the separator needs to be surrounded by quotes */ return "\"" + value + "\""; //$NON-NLS-1$ //$NON-NLS-2$ } return value; } /* * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, * org.eclipse.ui.IEditorInput) */ @Override public void init(IEditorSite site, IEditorInput input) { Assert.isTrue(input instanceof FeedViewInput); fEditorSite = site; fFeedViewSite = new FeedViewSite(this, site); setSite(site); fResourceManager = new LocalResourceManager(JFaceResources.getResources()); /* Load Settings */ fPreferences = Owl.getPreferenceService().getGlobalScope(); loadSettings((FeedViewInput) input); /* Apply Input */ setInput(input); /* Hook into Global Actions */ createGlobalActions(); setGlobalActions(); /* Register Listeners */ registerListeners(); } private boolean justOpened() { return System.currentTimeMillis() - fOpenTime < HANDLE_NEWS_SEEN_BLOCK_DELAY; } private void registerListeners() { fPartListener = new IPartListener2() { /* Mark *new* News as *unread* or *read* */ public void partHidden(IWorkbenchPartReference partRef) { /* Return early if event is too close after opening the feed */ if (justOpened()) return; /* Remember this feedview as being the last visible one */ if (FeedView.this.equals(partRef.getPart(false))) fgLastVisibleFeedView = FeedView.this; } /* Hook into Global Actions for this Editor */ public void partBroughtToTop(IWorkbenchPartReference partRef) { if (FeedView.this.equals(partRef.getPart(false))) { setGlobalActions(); OwlUI.updateWindowTitle(fInput != null ? fInput.getMark() : null); /* Notify last visible feedview about change */ if (fgLastVisibleFeedView != null && fgLastVisibleFeedView != FeedView.this && !fgLastVisibleFeedView.fIsDisposed) { fgLastVisibleFeedView.notifyUIEvent(UIEvent.FEED_CHANGE); fgLastVisibleFeedView = null; } } /* Any other editor was brought to top, reset last visible feedview */ else if (!ID.equals(partRef.getId())) fgLastVisibleFeedView = null; } public void partClosed(IWorkbenchPartReference partRef) { IEditorReference[] editors = partRef.getPage().getEditorReferences(); boolean equalsThis = FeedView.this.equals(partRef.getPart(false)); if (editors.length == 0 && equalsThis) OwlUI.updateWindowTitle((String) null); if (equalsThis) { if (fgLastVisibleFeedView == FeedView.this) //Avoids duplicate UI Event handling fgLastVisibleFeedView = null; notifyUIEvent(UIEvent.TAB_CLOSE); } } public void partDeactivated(IWorkbenchPartReference partRef) {} public void partActivated(IWorkbenchPartReference partRef) { if (FeedView.this.equals(partRef.getPart(false))) OwlUI.updateWindowTitle(fInput != null ? fInput.getMark() : null); } public void partInputChanged(IWorkbenchPartReference partRef) { if (FeedView.this.equals(partRef.getPart(false))) OwlUI.updateWindowTitle(fInput != null ? fInput.getMark() : null); } public void partOpened(IWorkbenchPartReference partRef) { if (FeedView.this.equals(partRef.getPart(false)) && isVisible()) { fOpenTime = System.currentTimeMillis(); OwlUI.updateWindowTitle(fInput != null ? fInput.getMark() : null); } } public void partVisible(IWorkbenchPartReference partRef) { if (FeedView.this.equals(partRef.getPart(false))) OwlUI.updateWindowTitle(fInput != null ? fInput.getMark() : null); } }; fEditorSite.getPage().addPartListener(fPartListener); /* React on Bookmark Events */ fBookMarkListener = new BookMarkAdapter() { @Override public void entitiesDeleted(Set<BookMarkEvent> events) { onNewsMarksDeleted(events); } @Override public void entitiesUpdated(Set<BookMarkEvent> events) { onNewsMarksUpdated(events); } }; DynamicDAO.addEntityListener(IBookMark.class, fBookMarkListener); /* React on Folder Events */ fFolderListener = new FolderAdapter() { @Override public void entitiesDeleted(Set<FolderEvent> events) { onFoldersDeleted(events); } @Override public void entitiesUpdated(Set<FolderEvent> events) { onNewsFoldersUpdated(events); } }; DynamicDAO.addEntityListener(IFolder.class, fFolderListener); /* React on Searchmark Events */ fSearchMarkListener = new SearchMarkAdapter() { @Override public void entitiesDeleted(Set<SearchMarkEvent> events) { onNewsMarksDeleted(events); } @Override public void entitiesUpdated(Set<SearchMarkEvent> events) { onNewsMarksUpdated(events); } }; DynamicDAO.addEntityListener(ISearchMark.class, fSearchMarkListener); /* Refresh on Condition Changes if SearchMark showing */ fSearchConditionListener = new SearchConditionListener() { public void entitiesAdded(Set<SearchConditionEvent> events) { refreshIfRequired(events); } public void entitiesDeleted(Set<SearchConditionEvent> events) { /* Ignore Due to Bug 1140 (http://dev.rssowl.org/show_bug.cgi?id=1140) */ } public void entitiesUpdated(Set<SearchConditionEvent> events) { /* Ignore Due to Bug 1140 (http://dev.rssowl.org/show_bug.cgi?id=1140) */ } /* We rely on the implementation detail that updating a SM means deleting/adding conditions */ private void refreshIfRequired(Set<SearchConditionEvent> events) { if (fInput.getMark() instanceof ISearchMark) { ISearchMarkDAO dao = DynamicDAO.getDAO(ISearchMarkDAO.class); for (SearchConditionEvent event : events) { ISearchCondition condition = event.getEntity(); ISearchMark searchMark = dao.load(condition); if (searchMark != null && searchMark.equals(fInput.getMark())) { JobRunner.runUIUpdater(new UIBackgroundJob(fParent) { @Override protected void runInBackground(IProgressMonitor monitor) { if (!Controller.getDefault().isShuttingDown()) fContentProvider.refreshCache(monitor, fInput.getMark()); } @Override protected void runInUI(IProgressMonitor monitor) { if (!Controller.getDefault().isShuttingDown()) refresh(true, true); } @Override public boolean belongsTo(Object family) { return fCacheJobIdentifier.equals(family); } }); break; } } } } }; DynamicDAO.addEntityListener(ISearchCondition.class, fSearchConditionListener); /* React on Newsbin Events */ fNewsBinListener = new NewsBinAdapter() { @Override public void entitiesDeleted(Set<NewsBinEvent> events) { onNewsMarksDeleted(events); } @Override public void entitiesUpdated(Set<NewsBinEvent> events) { onNewsMarksUpdated(events); } }; DynamicDAO.addEntityListener(INewsBin.class, fNewsBinListener); /* Listen if Title Image is changing */ fFeedListener = new FeedAdapter() { @Override public void entitiesUpdated(Set<FeedEvent> events) { /* Only supported for BookMarks */ if (!(fInput.getMark() instanceof IBookMark) || events.size() == 0) return; /* Check if Feed-Event affecting us */ for (FeedEvent event : events) { FeedLinkReference feedRef = ((IBookMark) fInput.getMark()).getFeedLinkReference(); if (feedRef.references(event.getEntity())) { ImageDescriptor imageDesc = fInput.getImageDescriptor(); /* Title Image Change - Update! */ if (!fTitleImageDescriptor.equals(imageDesc)) { fTitleImageDescriptor = imageDesc; JobRunner.runInUIThread(fParent, new Runnable() { public void run() { setTitleImage(OwlUI.getImage(fResourceManager, fTitleImageDescriptor)); } }); } break; } } } }; DynamicDAO.addEntityListener(IFeed.class, fFeedListener); /* Show Busy when Input is loaded */ fBookMarkLoadListener = new Controller.BookMarkLoadListener() { public void bookMarkAboutToLoad(IBookMark bookmark) { if (!fIsDisposed && bookmark.equals(fInput.getMark())) showBusyLoading(true); } public void bookMarkDoneLoading(IBookMark bookmark) { if (!fIsDisposed && bookmark.equals(fInput.getMark())) showBusyLoading(false); } }; Controller.getDefault().addBookMarkLoadListener(fBookMarkLoadListener); } private void showBusyLoading(final boolean busy) { JobRunner.runInUIThread(fParent, new Runnable() { @SuppressWarnings("restriction") public void run() { if (!fIsDisposed && getSite() instanceof org.eclipse.ui.internal.PartSite) ((org.eclipse.ui.internal.PartSite) getSite()).getPane().setBusy(busy); } }); } private void onNewsFoldersUpdated(final Set<FolderEvent> events) { JobRunner.runInUIThread(fParent, new Runnable() { public void run() { if (!(fInput.getMark() instanceof FolderNewsMark)) return; final IEditorPart activeFeedView = fEditorSite.getPage().getActiveEditor(); FolderNewsMark folderNewsMark = (FolderNewsMark) (fInput.getMark()); for (FolderEvent event : events) { final IFolder folder = event.getEntity(); if (folder.equals(folderNewsMark.getFolder())) { setPartName(folder.getName()); if (activeFeedView == FeedView.this) OwlUI.updateWindowTitle(fInput.getMark()); break; } } } }); } private void onFoldersDeleted(Set<FolderEvent> events) { if (!(fInput.getMark() instanceof FolderNewsMark)) return; FolderNewsMark folderNewsMark = (FolderNewsMark) (fInput.getMark()); for (FolderEvent event : events) { final IFolder folder = event.getEntity(); if (folder.equals(folderNewsMark.getFolder())) { fInput.setDeleted(); JobRunner.runInUIThread(fParent, new Runnable() { public void run() { fEditorSite.getPage().closeEditor(FeedView.this, false); } }); break; } } } private void onNewsMarksUpdated(final Set<? extends MarkEvent> events) { JobRunner.runInUIThread(fParent, new Runnable() { public void run() { final IEditorPart activeFeedView = fEditorSite.getPage().getActiveEditor(); for (MarkEvent event : events) { final IMark mark = event.getEntity(); if (mark.getId().equals(fInput.getMark().getId())) { setPartName(mark.getName()); if (activeFeedView == FeedView.this) OwlUI.updateWindowTitle(fInput.getMark()); break; } } } }); } private void onNewsMarksDeleted(Set<? extends MarkEvent> events) { for (MarkEvent event : events) { IMark mark = event.getEntity(); if (fInput.getMark().getId().equals(mark.getId())) { fInput.setDeleted(); JobRunner.runInUIThread(fParent, new Runnable() { public void run() { fEditorSite.getPage().closeEditor(FeedView.this, false); } }); break; } } } private void loadSettings(FeedViewInput input) { /* Filter Settings */ IPreferenceScope preferences = Owl.getPreferenceService().getEntityScope(input.getMark()); int iVal = preferences.getInteger(DefaultPreferences.BM_NEWS_FILTERING); if (iVal >= 0) fInitialFilterType = NewsFilter.Type.values()[iVal]; else fInitialFilterType = NewsFilter.Type.values()[fPreferences.getInteger(DefaultPreferences.FV_FILTER_TYPE)]; /* Group Settings */ iVal = preferences.getInteger(DefaultPreferences.BM_NEWS_GROUPING); if (iVal >= 0) fInitialGroupType = NewsGrouping.Type.values()[iVal]; else fInitialGroupType = NewsGrouping.Type.values()[fPreferences.getInteger(DefaultPreferences.FV_GROUP_TYPE)]; /* Other Settings */ fLayout = OwlUI.getLayout(preferences); fInitialWeights = fPreferences.getIntegers(DefaultPreferences.FV_SASHFORM_WEIGHTS); fInitialSearchTarget = NewsFilter.SearchTarget.values()[fPreferences.getInteger(DefaultPreferences.FV_SEARCH_TARGET)]; } private void saveSettings() { /* Update Settings in DB */ if (fCacheWeights != null && fCacheWeights[0] != fCacheWeights[1]) { int weightDiff = (fInitialWeights[0] - fCacheWeights[0]); if (Math.abs(weightDiff) > 5) { int strWeights[] = new int[] { fCacheWeights[0], fCacheWeights[1] }; fPreferences.putIntegers(DefaultPreferences.FV_SASHFORM_WEIGHTS, strWeights); } } } private void createGlobalActions() { /* Hook into Reload */ fReloadAction = new Action() { @Override public void run() { new ReloadTypesAction(new StructuredSelection(fInput.getMark()), getEditorSite().getShell()).run(); } }; /* Select All */ fSelectAllAction = new Action() { @Override public void run() { Control focusControl = fEditorSite.getShell().getDisplay().getFocusControl(); /* Select All in Text Widget */ if (focusControl instanceof Text) { ((Text) focusControl).selectAll(); } /* Select All in Viewer Tree */ else { ((Tree) fNewsTableControl.getViewer().getControl()).selectAll(); fNewsTableControl.getViewer().setSelection(fNewsTableControl.getViewer().getSelection()); } } }; /* Delete */ fDeleteAction = new Action() { @Override public void run() { new DeleteTypesAction(fParent.getShell(), (IStructuredSelection) fNewsTableControl.getViewer().getSelection()).run(); } }; /* Cut */ fCutAction = new Action() { @Override public void run() { Control focusControl = fEditorSite.getShell().getDisplay().getFocusControl(); /* Cut in Text Widget */ if (focusControl instanceof Text) ((Text) focusControl).cut(); } }; /* Copy */ fCopyAction = new Action() { @Override public void run() { Control focusControl = fEditorSite.getShell().getDisplay().getFocusControl(); /* Copy in Text Widget */ if (focusControl instanceof Text) ((Text) focusControl).copy(); } }; /* Paste */ fPasteAction = new Action() { @Override public void run() { Control focusControl = fEditorSite.getShell().getDisplay().getFocusControl(); /* Paste in Text Widget */ if (focusControl instanceof Text) ((Text) focusControl).paste(); } }; /* Print */ fPrintAction = new Action() { @Override public void run() { print(); } }; /* Undo (Eclipse Integration) */ fUndoAction = new Action() { @Override public void run() { UndoStack.getInstance().undo(); } }; /* Redo (Eclipse Integration) */ fRedoAction = new Action() { @Override public void run() { UndoStack.getInstance().redo(); } }; /* Find (Eclipse Integration) */ fFindAction = new FindAction(); } /** * Print the contents of the Browser if any. */ public void print() { /* Return early if the browser is not visible at all */ if (!isBrowserViewerVisible()) { MessageDialog.openInformation(fRootComposite.getShell(), Messages.FeedView_PRINT_NEWS, Messages.FeedView_PRINT_NEWS_HEADLINES_LAYOUT); return; } /* Pass request to browser */ if (fNewsBrowserControl != null) fNewsBrowserControl.getViewer().getBrowser().print(); } /** * The user performed the "Find" action. */ public void find() { if (fFilterBar != null) { /* Make Feed Toolbar Visible if not visible yet */ if (!fFilterBar.isVisible()) { fPreferences.putBoolean(DefaultPreferences.FV_FEED_TOOLBAR_HIDDEN, false); EditorUtils.updateToolbarVisibility(); } fFilterBar.focusQuickSearch(); } } private void setGlobalActions() { /* Define Retargetable Global Actions */ fEditorSite.getActionBars().setGlobalActionHandler(RetargetActions.RELOAD, fReloadAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), fSelectAllAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.DELETE.getId(), fDeleteAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.CUT.getId(), fCutAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.PASTE.getId(), fPasteAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.PRINT.getId(), fPrintAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.UNDO.getId(), fUndoAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.REDO.getId(), fRedoAction); fEditorSite.getActionBars().setGlobalActionHandler(ActionFactory.FIND.getId(), fFindAction); /* Disable some Edit-Actions at first */ fEditorSite.getActionBars().getGlobalActionHandler(ActionFactory.CUT.getId()).setEnabled(false); fEditorSite.getActionBars().getGlobalActionHandler(ActionFactory.COPY.getId()).setEnabled(false); fEditorSite.getActionBars().getGlobalActionHandler(ActionFactory.PASTE.getId()).setEnabled(false); } /** * Sets the given <code>IStructuredSelection</code> to the News-Table showing * in the FeedView. Will ignore the selection, if the Table is minimized. * * @param selection The Selection to show in the News-Table. */ public void setSelection(final IStructuredSelection selection) { /* Remove Filter if selection is hidden */ final AtomicBoolean unfilter = new AtomicBoolean(false); if (fNewsFilter.getType() != NewsFilter.Type.SHOW_ALL) { List<?> elements = selection.toList(); for (Object element : elements) { /* Resolve the actual News */ if (element instanceof NewsReference) { INews news = fContentProvider.obtainFromCache(((NewsReference) element).getId()); if (news != null) element = news; else element = ((NewsReference) element).resolve(); } /* This Element is filtered */ if (!fNewsFilter.select(fNewsTableControl.getViewer(), null, element)) { unfilter.set(true); break; } } } /* Remove Filter if selection is hidden */ if (unfilter.get()) { /* Provide code to be executed after unfiltering is done */ Runnable joinUIRunnable = new Runnable() { public void run() { internalShowSelection(selection, unfilter); } }; /* Remove Filter */ fFilterBar.doFilter(NewsFilter.Type.SHOW_ALL, true, false, joinUIRunnable); } /* Directly show selection as filtering was not undone */ else internalShowSelection(selection, unfilter); } private void internalShowSelection(final IStructuredSelection selection, final AtomicBoolean unfilter) { /* Scroll News into View from Browser if maximized */ if (!isTableViewerVisible()) { JobRunner.runInUIThread(BROWSER_OPERATIONS_DELAY, fNewsBrowserControl.getViewer().getControl(), new Runnable() { public void run() { //Run delayed as the browser might still be busy loading the input Runnable runnable = new Runnable() { public void run() { fNewsBrowserControl.getViewer().showSelection(selection); } }; /* If Elements got revealed, make sure they show in the Browser viewer and then select the news item delayed */ if (unfilter.get()) { fNewsBrowserControl.getViewer().refresh(); JobRunner.runInUIThread(BROWSER_OPERATIONS_DELAY, fNewsBrowserControl.getViewer().getControl(), runnable); } /* Otherwise directly select the news item */ else { runnable.run(); } } }); } /* Apply selection to Table */ else fNewsTableControl.getViewer().setSelection(selection, true); } /* * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput) */ @Override public void setInput(IEditorInput input) { Assert.isTrue(input instanceof FeedViewInput); /* Quickly cancel any caching Job and dispose content provider since input changed */ if (fCreated) { /* Keep the news for passing into notifyUIEvent() */ Collection<INews> cachedNewsCopy = fContentProvider.getCachedNewsCopy(); /* Cancel and Dispose */ Job.getJobManager().cancel(fCacheJobIdentifier); fContentProvider.dispose(); /* Handle Old being hidden now */ if (fInput != null) { notifyUIEvent(UIEvent.FEED_CHANGE, cachedNewsCopy); rememberSelection(fInput.getMark(), fNewsTableControl.getLastSelection()); } } /* Set New */ super.setInput(input); fInput = (FeedViewInput) input; /* Update UI of Feed-View if new Editor */ if (!fCreated) updateTab(fInput); /* Clear Filter Bar */ if (fFilterBar != null) fFilterBar.clearQuickSearch(false); /* Editor is being reused */ if (fCreated) { firePropertyChange(PROP_INPUT); /* Load Filter Settings for this Mark if present */ updateFilterAndGrouping(false); /* Re-Create the ContentProvider to avoid being blocked on the old content provider still resolving something */ fContentProvider = new NewsContentProvider(fNewsTableControl.getViewer(), fNewsBrowserControl.getViewer(), this); fNewsTableControl.getViewer().setContentProvider(fContentProvider); fNewsTableControl.onInputChanged(fInput); fNewsBrowserControl.getViewer().setContentProvider(fContentProvider); fNewsBrowserControl.onInputChanged(fInput); /* Reset the Quicksearch if active */ if (fNewsFilter.isPatternSet()) fNewsFilter.setPattern(""); //$NON-NLS-1$ /* Update news mark in filter */ fNewsFilter.setNewsMark(fInput.getMark()); /* Apply Input */ setInput(fInput.getMark(), true); } } /* Update Title and Image of the FeedView's Tab */ private void updateTab(FeedViewInput input) { setPartName(input.getName()); fTitleImageDescriptor = input.getImageDescriptor(); setTitleImage(OwlUI.getImage(fResourceManager, fTitleImageDescriptor)); } /** * Load Filter Settings for the Mark that is set as input if present * <p> * TODO Find a better solution once its possible to add listeners to * {@link IPreferenceScope} and then listen to changes of display-properties. * </p> * * @param refresh If TRUE, refresh the Viewer, FALSE otherwise. */ public void updateFilterAndGrouping(boolean refresh) { IPreferenceScope preferences = Owl.getPreferenceService().getEntityScope(fInput.getMark()); int iVal = preferences.getInteger(DefaultPreferences.BM_NEWS_FILTERING); if (iVal >= 0) fFilterBar.doFilter(NewsFilter.Type.values()[iVal], refresh, false); else fFilterBar.doFilter(NewsFilter.Type.values()[fPreferences.getInteger(DefaultPreferences.FV_FILTER_TYPE)], refresh, false); /* Load Group Settings for this Mark if present */ iVal = preferences.getInteger(DefaultPreferences.BM_NEWS_GROUPING); if (iVal >= 0) fFilterBar.doGrouping(NewsGrouping.Type.values()[iVal], refresh, false); else fFilterBar.doGrouping(NewsGrouping.Type.values()[fPreferences.getInteger(DefaultPreferences.FV_GROUP_TYPE)], refresh, false); } /** * Refresh the visible columns of the opened news table control. */ public void updateColumns() { if (fInput == null) return; /* Folder News Mark might require cache refresh if sorting has changed and limit reached */ if (fInput.getMark() instanceof FolderNewsMark && fInput.getMark().getNewsCount(INews.State.getVisible()) > NewsContentProvider.MAX_FOLDER_ELEMENTS) { FolderNewsMark folderMark = (FolderNewsMark) fInput.getMark(); IPreferenceScope preferences = Owl.getPreferenceService().getEntityScope(folderMark.getFolder()); NewsComparator comparator = getComparator(); NewsColumn oldSortBy = comparator.getSortBy(); boolean oldIsAscending = comparator.isAscending(); NewsColumn newSortBy = NewsColumn.values()[preferences.getInteger(DefaultPreferences.BM_NEWS_SORT_COLUMN)]; boolean newIsAscending = preferences.getBoolean(DefaultPreferences.BM_NEWS_SORT_ASCENDING); /* Sorting changed and cache is at limit, so refresh cache */ if (oldSortBy != newSortBy || oldIsAscending != newIsAscending) { NewsComparator comparer = new NewsComparator(); comparer.setSortBy(newSortBy); comparer.setAscending(newIsAscending); fContentProvider.refreshCache(null, folderMark, comparer); } } /* Update Columns and Sorting in Table Viewer */ if (isTableViewerVisible()) fNewsTableControl.updateColumns(fInput.getMark()); /* Update Sorting in Browser Viewer */ if (isBrowserViewerVisible()) fNewsBrowserControl.updateSorting(fInput.getMark(), true); } /** * Notifies this editor about a UI-Event just occured. In dependance of the * event, the Editor might want to update the state on the displayed News. * * @param event The UI-Event that just occured as described in the * <code>UIEvent</code> enumeration. */ public void notifyUIEvent(final UIEvent event) { notifyUIEvent(event, null); } private void notifyUIEvent(final UIEvent event, Collection<INews> visibleNews) { final IMark inputMark = fInput.getMark(); final IStructuredSelection lastSelection = fNewsTableControl.getLastSelection(); /* Avoid any work in case RSSOwl is shutting down in an emergency */ if (Controller.getDefault().isEmergencyShutdown()) return; /* Specially Treat Restart Situation */ if (Controller.getDefault().isRestarting()) { if (event == UIEvent.TAB_CLOSE && fInput.exists()) rememberSelection(inputMark, lastSelection); return; // Ignore other events during restart } /* Specially Treat Closing Situation */ else if (Controller.getDefault().isShuttingDown()) { if (event == UIEvent.TAB_CLOSE && fInput.exists()) rememberSelection(inputMark, lastSelection); if (event != UIEvent.CLOSE) return; // Ignore other events than CLOSE that might get issued } /* Operate on a Copy of the Content Providers News (either passed in or obtain) */ final Collection<INews> news = (visibleNews != null) ? filterHidden(visibleNews) : filterHidden(fContentProvider.getCachedNewsCopy()); IPreferenceScope inputPreferences = Owl.getPreferenceService().getEntityScope(inputMark); /* * News can be NULL at this moment, if the Job that is to refresh the cache * in the Content Provider was never scheduled. This can happen when quickly * navigating between feeds. Also, the input could have been deleted and the * editor closed. Thereby do not react. */ if (news.isEmpty() || !fInput.exists()) return; final boolean markReadOnFeedChange = inputPreferences.getBoolean(DefaultPreferences.MARK_READ_ON_CHANGE); final boolean markReadOnTabClose = inputPreferences.getBoolean(DefaultPreferences.MARK_READ_ON_TAB_CLOSE); final boolean markReadOnMinimize = inputPreferences.getBoolean(DefaultPreferences.MARK_READ_ON_MINIMIZE); /* Mark *new* News as *unread* when closing the entire application */ if (event == UIEvent.CLOSE) { /* Perform the State Change */ List<INews> newsToUpdate = new ArrayList<INews>(); for (INews newsItem : news) { if (newsItem.getState() == INews.State.NEW) newsToUpdate.add(newsItem); } /* Perform Operation */ fNewsDao.setState(newsToUpdate, INews.State.UNREAD, OwlUI.markReadDuplicates(), false); } /* Handle seen News: Feed Change (also closing the feed view), Closing or Minimize Event */ else if (event == UIEvent.FEED_CHANGE || event == UIEvent.MINIMIZE || event == UIEvent.TAB_CLOSE) { /* Return early if this is a feed change which should be ignored */ if (event == UIEvent.FEED_CHANGE && fgBlockFeedChangeEvent) return; /* * TODO This is a workaround to avoid potential race-conditions when closing a Tab. The problem * is that both FEED_CHANGE (due to hiding the tab) and TAB_CLOSE (due to actually closing * the tab) get sent when the user closes a tab. The workaround is to delay the processing of * TAB_CLOSE a bit to minimize the chance of a race condition. */ int delay = HANDLE_NEWS_SEEN_DELAY; if (event == UIEvent.TAB_CLOSE) delay += 100; JobRunner.runInBackgroundThread(delay, new Runnable() { public void run() { /* Application might be in process of closing */ if (Controller.getDefault().isShuttingDown()) return; /* Check settings if mark as read should be performed */ boolean markRead = false; switch (event) { case FEED_CHANGE: markRead = markReadOnFeedChange; break; case TAB_CLOSE: markRead = markReadOnTabClose; break; case MINIMIZE: markRead = markReadOnMinimize; break; } /* Perform the State Change */ List<INews> newsToUpdate = new ArrayList<INews>(); for (INews newsItem : news) { if (newsItem.getState() == INews.State.NEW) newsToUpdate.add(newsItem); else if (markRead && (newsItem.getState() == INews.State.UPDATED || newsItem.getState() == INews.State.UNREAD)) newsToUpdate.add(newsItem); } if (!newsToUpdate.isEmpty()) { /* Force quick update on Feed-Change or Tab Close */ if ((event == UIEvent.FEED_CHANGE || event == UIEvent.TAB_CLOSE)) Controller.getDefault().getSavedSearchService().forceQuickUpdate(); /* Support Undo */ UndoStack.getInstance().addOperation(new NewsStateOperation(newsToUpdate, markRead ? INews.State.READ : INews.State.UNREAD, OwlUI.markReadDuplicates())); /* Perform Operation */ fNewsDao.setState(newsToUpdate, markRead ? INews.State.READ : INews.State.UNREAD, OwlUI.markReadDuplicates(), false); } /* Retention Strategy */ if (inputMark instanceof IBookMark) { /* Ignore currently selected news from retention if changing feeds or minimizing */ if (event == UIEvent.FEED_CHANGE || event == UIEvent.MINIMIZE) { if (lastSelection != null && !lastSelection.isEmpty()) { Object obj = lastSelection.getFirstElement(); if (obj instanceof INews) news.remove(obj); } } /* Perform Clean Up */ performCleanUp((IBookMark) inputMark, news); } /* Also remember the last selected News */ if (event == UIEvent.TAB_CLOSE) rememberSelection(inputMark, lastSelection); } }); } } /* * In newspaper and headlines layout there is a chance that the user was not accepting incoming news * (by refreshing). In this case, we are not applying any state changes to those news hidden by asking * the browser view model for the visible news */ private Collection<INews> filterHidden(Collection<INews> news) { if (fLayout == Layout.NEWSPAPER || fLayout == Layout.HEADLINES) { NewsBrowserViewModel model = fNewsBrowserControl.getViewer().getViewModel(); if (model != null) { Iterator<INews> iterator = news.iterator(); while (iterator.hasNext()) { Long id = iterator.next().getId(); if (id != null && !model.hasNews(id)) iterator.remove(); } } } return news; } /** * @param news the {@link INews} to check for being part of the browser * @return <code>true</code> if the feedview is configured to show headlines * or newspaper layout and the news is part of the displayed items and * <code>false</code> otherwise. */ public boolean isHidden(INews news) { Long id = news.getId(); return id != null && isHidden(id); } /** * @param reference the {@link NewsReference} to check for being part of the * browser * @return <code>true</code> if the feedview is configured to show headlines * or newspaper layout and the news is part of the displayed items and * <code>false</code> otherwise. */ public boolean isHidden(NewsReference reference) { return isHidden(reference.getId()); } private boolean isHidden(long newsId) { if (fLayout == Layout.NEWSPAPER || fLayout == Layout.HEADLINES) { NewsBrowserViewModel model = fNewsBrowserControl.getViewer().getViewModel(); if (model != null) return !model.hasNews(newsId); } return false; } /** * @param news the {@link INews} to check for being contained in this * {@link FeedView}. * @return <code>true</code> if this {@link FeedView} contains the given news * and <code>false</code> otherwise. */ public boolean contains(INews news) { return fContentProvider != null && fContentProvider.hasCachedNews(news); } /** * @return a {@link Collection} of {@link INews} that contains the currently * cached news items displayed in the feed view. */ public Collection<INews> getCachedNewsCopy() { return fContentProvider != null ? fContentProvider.getCachedNewsCopy() : Collections.<INews> emptyList(); } private void performCleanUp(IBookMark bookmark, Collection<INews> news) { if (System.currentTimeMillis() - fLastCleanUpRun.get() > CLEAN_UP_BLOCK_DELAY) { RetentionStrategy.process(bookmark, news); fLastCleanUpRun.set(System.currentTimeMillis()); } } /* React on the Input being set */ private void onInputSet() { /* Check if an action is to be performed */ PerformAfterInputSet perform = fInput.getPerformOnInputSet(); perform(perform); /* DB Roundtrips done in the background */ JobRunner.runInBackgroundThread(new Runnable() { public void run() { if (fInput == null) return; IMark mark = fInput.getMark(); /* Trigger a reload if this is the first time open or previously erroneous open */ if (mark instanceof IBookMark) { IBookMark bookmark = (IBookMark) mark; if ((bookmark.getLastVisitDate() == null || bookmark.isErrorLoading()) && !fContentProvider.hasCachedNews()) new ReloadTypesAction(new StructuredSelection(mark), getEditorSite().getShell()).run(); } /* Trigger reload of not loaded included Bookmarks */ else if (mark instanceof FolderNewsMark) { IFolder folder = ((FolderNewsMark) mark).getFolder(); List<IBookMark> bookMarksToReload = new ArrayList<IBookMark>(); fillBookMarksToReload(bookMarksToReload, folder); if (!bookMarksToReload.isEmpty()) new ReloadTypesAction(new StructuredSelection(bookMarksToReload.toArray()), getEditorSite().getShell()).run(); } /* Mark the Bookmark as visited */ if (mark instanceof IBookMark) DynamicDAO.getDAO(IBookMarkDAO.class).visited((IBookMark) mark); /* Mark the Searchmark as visited */ else if (mark instanceof ISearchMark) DynamicDAO.getDAO(ISearchMarkDAO.class).visited((ISearchMark) mark); /* Mark the newsbin as visited */ else if (mark instanceof INewsBin) DynamicDAO.getDAO(INewsBinDAO.class).visited((INewsBin) mark); } }); } /** * @param perform the action to perform on this editor. */ public void perform(PerformAfterInputSet perform) { if (perform != null) { /* Select first News */ if (perform.getType() == PerformAfterInputSet.Kind.SELECT_FIRST_NEWS) { if (fLayout != Layout.NEWSPAPER) //Newspaper will always show a full news on top, so ignore here navigate(false, true, true, false); } /* Select first unread News */ else if (perform.getType() == PerformAfterInputSet.Kind.SELECT_UNREAD_NEWS) navigate(false, true, true, true); /* Select specific News */ else if (perform.getType() == PerformAfterInputSet.Kind.SELECT_SPECIFIC_NEWS) setSelection(new StructuredSelection(perform.getNewsToSelect())); /* Make sure to activate this FeedView in case of an action */ if (perform.shouldActivate()) fEditorSite.getPage().activate(fEditorSite.getPart()); } } private void fillBookMarksToReload(List<IBookMark> bookMarksToReload, IFolder folder) { List<IMark> marks = folder.getMarks(); for (IMark mark : marks) { if (mark instanceof IBookMark) { if ((((IBookMark) mark).getMostRecentNewsDate() == null)) bookMarksToReload.add((IBookMark) mark); } } List<IFolder> childs = folder.getFolders(); for (IFolder child : childs) { fillBookMarksToReload(bookMarksToReload, child); } } /* Set Input to Viewers */ private void setInput(final INewsMark mark, final boolean reused) { /* Update Cache in Background and then apply to UI */ JobRunner.runUIUpdater(new UIBackgroundJob(fParent) { private IProgressMonitor fBgMonitor; @Override public boolean belongsTo(Object family) { return fCacheJobIdentifier.equals(family); } @Override protected void runInBackground(IProgressMonitor monitor) { fBgMonitor = monitor; if (!monitor.isCanceled()) fContentProvider.refreshCache(monitor, mark); } @Override protected void runInUI(IProgressMonitor monitor) { IStructuredSelection oldSelection = null; IPreferenceScope entityPreferences = Owl.getPreferenceService().getEntityScope(mark); long value = entityPreferences.getLong(DefaultPreferences.NM_SELECTED_NEWS); if (value > 0) { boolean isListLayout = (OwlUI.getLayout(entityPreferences) == Layout.LIST); boolean openEmptyNews = entityPreferences.getBoolean(DefaultPreferences.BM_OPEN_SITE_FOR_EMPTY_NEWS); boolean openAllNews = entityPreferences.getBoolean(DefaultPreferences.BM_OPEN_SITE_FOR_NEWS); boolean useTransformer = entityPreferences.getBoolean(DefaultPreferences.BM_USE_TRANSFORMER); boolean useExternalBrowser = OwlUI.useExternalBrowser(); /* Only re-select if this has not the potential of opening in external Browser */ if (!useExternalBrowser || isListLayout || useTransformer || (!openAllNews && !openEmptyNews)) oldSelection = new StructuredSelection(new NewsReference(value)); } /* Update Layout */ if (reused) updateLayout(false); /* Hide the Info Bar if it is visible */ if (reused) fNewsBrowserControl.setInfoBarVisible(false); /* Set input to News-Table if Visible */ if (!fBgMonitor.isCanceled() && isTableViewerVisible()) stableSetInputToNewsTable(mark, oldSelection); /* Clear old Input from Table */ else if (!fBgMonitor.isCanceled() && reused) fNewsTableControl.setPartInput(null); /* Set input to News-Browser if visible */ if (!fBgMonitor.isCanceled() && !isTableViewerVisible()) fNewsBrowserControl.setPartInput(mark); /* Reset old Input to Browser if available */ else if (!fBgMonitor.isCanceled() && oldSelection != null && isBrowserViewerVisible()) { ISelection selection = fNewsTableControl.getViewer().getSelection(); if (!selection.isEmpty()) //Could be filtered fNewsBrowserControl.setPartInput(oldSelection.getFirstElement()); } /* Clear old Input from Browser */ else if (!fBgMonitor.isCanceled() && reused) fNewsBrowserControl.setPartInput(null); /* Update Tab now */ if (reused) updateTab(fInput); /* Handle Input being set now */ onInputSet(); } }); } /* * @see org.eclipse.ui.part.EditorPart#isDirty() */ @Override public boolean isDirty() { return false; } /* * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed() */ @Override public boolean isSaveAsAllowed() { return true; } /* * @see org.eclipse.ui.part.WorkbenchPart#setFocus() */ @Override public void setFocus() { /* Focus Headlines */ if (isTableViewerVisible()) fNewsTableControl.setFocus(); /* Focus Browser */ else { Runnable runnable = new Runnable() { public void run() { fNewsBrowserControl.setFocus(); } }; /* Run setFocus() delayed if input not yet set */ Browser browser = fNewsBrowserControl.getViewer().getBrowser().getControl(); if (!StringUtils.isSet(browser.getUrl())) JobRunner.runDelayedInUIThread(browser, runnable); else runnable.run(); } } @Override public void dispose() { saveSettings(); unregisterListeners(); super.dispose(); fContentProvider.dispose(); fNewsTableControl.dispose(); fNewsBrowserControl.dispose(); fResourceManager.dispose(); fIsDisposed = true; } private void unregisterListeners() { fEditorSite.getPage().removePartListener(fPartListener); DynamicDAO.removeEntityListener(IBookMark.class, fBookMarkListener); DynamicDAO.removeEntityListener(IFolder.class, fFolderListener); DynamicDAO.removeEntityListener(ISearchMark.class, fSearchMarkListener); DynamicDAO.removeEntityListener(IFeed.class, fFeedListener); DynamicDAO.removeEntityListener(ISearchCondition.class, fSearchConditionListener); DynamicDAO.removeEntityListener(INewsBin.class, fNewsBinListener); Controller.getDefault().removeBookMarkLoadListener(fBookMarkLoadListener); } /** * Update the Layout in the Feed View. */ public void updateLayout() { fRootComposite.setRedraw(false); try { updateLayout(true); } finally { fRootComposite.setRedraw(true); } } private void updateLayout(boolean updateInput) { IPreferenceScope preferences = Owl.getPreferenceService().getEntityScope(fInput.getMark()); Layout layout = OwlUI.getLayout(preferences); /* Return early if layout already up to date */ if (fLayout == layout) return; /* Notify Toolbar */ fFilterBar.doLayout(layout, false); /* Notify Controls */ fNewsTableControl.onLayoutChanged(layout); fNewsBrowserControl.onLayoutChanged(layout); /* Classic Layout (default) */ if (layout == Layout.CLASSIC) { restoreTable(updateInput); fSashForm.setOrientation(SWT.VERTICAL); } /* Vertical Layout */ else if (layout == Layout.VERTICAL) { restoreTable(updateInput); fSashForm.setOrientation(SWT.HORIZONTAL); } /* List Layout */ else if (layout == Layout.LIST) { maximizeTable(updateInput); } /* Newspaper / Headlines Layout */ else if (layout == Layout.NEWSPAPER || layout == Layout.HEADLINES) { maximizeBrowser(updateInput); if (updateInput && (fLayout == Layout.NEWSPAPER || fLayout == Layout.HEADLINES)) refreshBrowserViewer(); //A change between Newspaper and Headlines needs a refresh due to the different CSS } /* Update Separators */ updateSeparators(layout); /* Hide the Info Bar if it is visible */ fNewsBrowserControl.setInfoBarVisible(false); /* Layout */ fNewsTableControlContainer.layout(); fBrowserViewerControlContainer.layout(); /* Remember Layout */ fLayout = layout; } private void maximizeTable(boolean updateInput) { Control maximizedControl = fSashForm.getMaximizedControl(); if (fNewsTableControlContainer.equals(maximizedControl)) return; fSashForm.setMaximizedControl(fNewsTableControlContainer); if (updateInput) { if (fBrowserViewerControlContainer.equals(maximizedControl)) { fNewsTableControl.setPartInput(fInput.getMark()); fNewsTableControl.adjustScrollPosition(); if (fNewsGrouping.getType() != NewsGrouping.Type.NO_GROUPING) expandNewsTableViewerGroups(true, StructuredSelection.EMPTY); } fNewsBrowserControl.setPartInput(null); } fNewsTableControl.setFocus(); } private void maximizeBrowser(boolean updateInput) { Control maximizedControl = fSashForm.getMaximizedControl(); if (fBrowserViewerControlContainer.equals(maximizedControl)) return; fSashForm.setMaximizedControl(fBrowserViewerControlContainer); if (updateInput) { fNewsTableControl.getViewer().setSelection(StructuredSelection.EMPTY); fNewsBrowserControl.setPartInput(fInput.getMark()); fNewsTableControl.setPartInput(null); } fNewsBrowserControl.setFocus(); } private void restoreTable(boolean updateInput) { Control maximizedControl = fSashForm.getMaximizedControl(); if (maximizedControl == null) return; fSashForm.setMaximizedControl(null); if (updateInput) { fNewsTableControl.setPartInput(fInput.getMark()); fNewsTableControl.adjustScrollPosition(); if (fNewsGrouping.getType() != NewsGrouping.Type.NO_GROUPING) expandNewsTableViewerGroups(true, StructuredSelection.EMPTY); fNewsBrowserControl.setPartInput(null); } fNewsTableControl.setFocus(); } private void updateSeparators(Layout layout) { /* Table Separators */ boolean showFilterTableSeparator = false; if (!Application.IS_MAC || layout != Layout.CLASSIC) showFilterTableSeparator = fFilterBar.isVisible(); ((GridData) fHorizontalFilterTableSep.getLayoutData()).exclude = !showFilterTableSeparator; ((GridData) fVerticalTableBrowserSep.getLayoutData()).exclude = (layout != Layout.VERTICAL); ((GridData) fHorizontalTableBrowserSep.getLayoutData()).exclude = (layout != Layout.CLASSIC); /* Browser Separators */ ((GridData) fVerticalBrowserSep.getLayoutData()).exclude = (layout != Layout.VERTICAL); /* Horizontal Layout */ if (layout == Layout.CLASSIC) { fHorizontalBrowserSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1)); ((GridData) fHorizontalBrowserSep.getLayoutData()).exclude = false; } /* Verical Layout */ else if (layout == Layout.VERTICAL) { fHorizontalBrowserSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1)); ((GridData) fHorizontalBrowserSep.getLayoutData()).exclude = !fBrowserBar.isVisible(); } /* Newspaper / Headlines Layout */ else if (layout == Layout.NEWSPAPER || layout == Layout.HEADLINES) { fHorizontalBrowserSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1)); ((GridData) fHorizontalBrowserSep.getLayoutData()).exclude = !fBrowserBar.isVisible(); } /* Update Visibility based on Layout Data */ fHorizontalFilterTableSep.setVisible(!((GridData) fHorizontalFilterTableSep.getLayoutData()).exclude); fVerticalTableBrowserSep.setVisible(!((GridData) fVerticalTableBrowserSep.getLayoutData()).exclude); fHorizontalTableBrowserSep.setVisible(!((GridData) fHorizontalTableBrowserSep.getLayoutData()).exclude); fVerticalBrowserSep.setVisible(!((GridData) fVerticalBrowserSep.getLayoutData()).exclude); fHorizontalBrowserSep.setVisible(!((GridData) fHorizontalBrowserSep.getLayoutData()).exclude); } /** * Update the visibility of the filter bar and browser bar. */ public void updateToolbarVisibility() { fFilterBar.updateVisibility(); fBrowserBar.updateVisibility(); updateSeparators(fLayout); fRootComposite.layout(true, true); } /** * Toggle between newspaper and classic/vertical layout. */ public void toggleNewspaperLayout() { /* Lookup current layout from actual UI to support toggling */ boolean isNewspaperLayout = !isTableViewerVisible(); boolean isClassicLayout = (fSashForm.getOrientation() & SWT.VERTICAL) != 0; /* Determine new layout based on existing */ Layout newLayout = isNewspaperLayout ? (isClassicLayout ? Layout.CLASSIC : Layout.VERTICAL) : Layout.NEWSPAPER; /* Save only into Entity if the Entity was configured with the given Settings before */ FeedViewInput input = ((FeedViewInput) getEditorInput()); IPreferenceScope entityPreferences; if (input.getMark() instanceof FolderNewsMark) entityPreferences = Owl.getPreferenceService().getEntityScope(((FolderNewsMark) input.getMark()).getFolder()); else entityPreferences = Owl.getPreferenceService().getEntityScope(input.getMark()); if (entityPreferences.hasKey(DefaultPreferences.FV_LAYOUT)) { entityPreferences.putInteger(DefaultPreferences.FV_LAYOUT, newLayout.ordinal()); entityPreferences.flush(); /* Update Layout (on current feed view) */ updateLayout(); } /* Save Globally */ else { fPreferences.putInteger(DefaultPreferences.FV_LAYOUT, newLayout.ordinal()); /* Update Layout (on all opened feed views) */ EditorUtils.updateLayout(); } } /** * Refreshes all parts of this editor. * * @param delayRedraw If <code>TRUE</code> delay redraw until operation is * done. * @param updateLabels If <code>TRUE</code> update all Labels. */ void refresh(boolean delayRedraw, boolean updateLabels) { refreshTableViewer(delayRedraw, updateLabels); refreshBrowserViewer(); } /** * A special key was pressed from the Quicksearch Input-Field. Handle it. * * @param traversal The Traversal that occured from the quicksearch. * @param clear If <code>true</code> indicates that the quicksearch was * cleared. */ void handleQuicksearchTraversalEvent(int traversal, boolean clear) { /* Enter was hit */ if ((traversal & SWT.TRAVERSE_RETURN) != 0) { /* Select and Focus TreeViewer */ if (isTableViewerVisible()) { Tree tree = (Tree) fNewsTableControl.getViewer().getControl(); if (tree.getItemCount() > 0) { IStructuredSelection lastSelection = fNewsTableControl.getLastNonEmptySelection(); if (lastSelection.isEmpty() || !clear) //When not clearing, select the first result from the list lastSelection = new StructuredSelection(tree.getItem(0).getData()); fNewsTableControl.getViewer().setSelection(lastSelection); fNewsTableControl.setFocus(); } } /* Move Focus into BrowserViewer */ else { fNewsBrowserControl.setFocus(); } } /* Page Up / Down was hit */ else if ((traversal & SWT.TRAVERSE_PAGE_NEXT) != 0 || (traversal & SWT.TRAVERSE_PAGE_PREVIOUS) != 0) { setFocus(); } } /* Refresh Table-Viewer if visible */ void refreshTableViewer(boolean delayRedraw, boolean updateLabels) { /* Return on Shutdown */ if (Controller.getDefault().isShuttingDown()) return; /* Only if Table Viewer is visible */ if (isTableViewerVisible()) { boolean groupingEnabled = fNewsGrouping.getType() != NewsGrouping.Type.NO_GROUPING; /* Remember Selection if grouping enabled */ ISelection selection = StructuredSelection.EMPTY; if (groupingEnabled) selection = fNewsTableControl.getViewer().getSelection(); /* Delay redraw operations if requested */ if (delayRedraw) fNewsTableControl.getViewer().getControl().getParent().setRedraw(false); try { /* Refresh */ fNewsTableControl.getViewer().refresh(updateLabels); /* Expand all Groups if grouping is enabled */ if (groupingEnabled) expandNewsTableViewerGroups(false, selection); } /* Redraw now if delayed before */ finally { if (delayRedraw) fNewsTableControl.getViewer().getControl().getParent().setRedraw(true); } } } private void expandNewsTableViewerGroups(boolean delayRedraw, ISelection oldSelection) { TreeViewer viewer = fNewsTableControl.getViewer(); Tree tree = (Tree) viewer.getControl(); /* Remember TopItem if required */ TreeItem topItem = oldSelection.isEmpty() ? tree.getTopItem() : null; /* Expand All & Restore Selection with redraw false */ if (delayRedraw) tree.getParent().setRedraw(false); try { viewer.expandAll(); /* Restore selection if required */ if (!oldSelection.isEmpty() && viewer.getSelection().isEmpty()) viewer.setSelection(oldSelection, true); else if (topItem != null) tree.setTopItem(topItem); } finally { if (delayRedraw) tree.getParent().setRedraw(true); } } /* TODO This is a Workaround until Eclipse Bug #159586 is fixed */ private void stableSetInputToNewsTable(Object input, ISelection oldSelection) { TreeViewer viewer = fNewsTableControl.getViewer(); Tree tree = (Tree) viewer.getControl(); /* Set Input & Restore Selection with redraw false */ tree.getParent().setRedraw(false); try { fNewsTableControl.setPartInput(input); /* Restore selection if required */ if (oldSelection != null) { fNewsTableControl.setBlockNewsStateTracker(true); try { viewer.setSelection(oldSelection); } finally { fNewsTableControl.setBlockNewsStateTracker(false); } } /* Adjust Scroll Position */ fNewsTableControl.adjustScrollPosition(); } finally { tree.getParent().setRedraw(true); } } private void rememberSelection(final IMark inputMark, final IStructuredSelection selection) { SafeRunnable.run(new LoggingSafeRunnable() { public void run() throws Exception { IPreferenceScope inputPrefs = Owl.getPreferenceService().getEntityScope(inputMark); long oldSelectionValue = inputPrefs.getLong(DefaultPreferences.NM_SELECTED_NEWS); /* Find Selected News ID */ long newSelectionValue = 0; if (!selection.isEmpty()) { Object obj = selection.getFirstElement(); if (obj instanceof INews) newSelectionValue = ((INews) obj).getId(); } boolean needToSave = false; /* Selection Provided */ if (newSelectionValue > 0) { if (oldSelectionValue != newSelectionValue) { needToSave = true; inputPrefs.putLong(DefaultPreferences.NM_SELECTED_NEWS, newSelectionValue); } } /* No Selection Provided */ else { if (oldSelectionValue > 0) { needToSave = true; inputPrefs.delete(DefaultPreferences.NM_SELECTED_NEWS); } } IEntity entityToSave; if (fInput.getMark() instanceof FolderNewsMark) entityToSave = ((FolderNewsMark) fInput.getMark()).getFolder(); else entityToSave = fInput.getMark(); if (needToSave) DynamicDAO.save(entityToSave); } }); } /* Refresh Browser-Viewer */ void refreshBrowserViewer() { /* Return on Shutdown */ if (Controller.getDefault().isShuttingDown()) return; /* Refresh if browser is visible */ if (isBrowserViewerVisible()) fNewsBrowserControl.getViewer().refresh(); } /** * Check wether the News-Table-Part of this Editor is visible or not * (minmized). * * @return TRUE if the News-Table-Part is visible, FALSE otherwise. */ boolean isTableViewerVisible() { return fSashForm.getMaximizedControl() == null || fSashForm.getMaximizedControl() == fNewsTableControlContainer; } /** * Check wether the Browser-Part of this Editor is visible or not (minmized). * * @return TRUE if the Browser-Table-Part is visible, FALSE otherwise. */ boolean isBrowserViewerVisible() { return fSashForm.getMaximizedControl() == null || fSashForm.getMaximizedControl() == fBrowserViewerControlContainer; } /** * Get the shared ViewerFilter used to filter News. * * @return the shared ViewerFilter used to filter News. */ NewsFilter getFilter() { return fNewsFilter; } /** * Get the ViewerComparator that is used for sorting news. * * @return the {@link ViewerComparator} for sorting news. */ NewsComparator getComparator() { if (isTableViewerVisible()) return (NewsComparator) fNewsTableControl.getViewer().getComparator(); return (NewsComparator) fNewsBrowserControl.getViewer().getComparator(); } /** * Get the shared Viewer-Grouper used to group News. * * @return the shared Viewer-Grouper used to group News. */ NewsGrouping getGrouper() { return fNewsGrouping; } NewsBrowserControl getNewsBrowserControl() { return fNewsBrowserControl; } NewsTableControl getNewsTableControl() { return fNewsTableControl; } /* * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ @Override public void createPartControl(Composite parent) { fCreated = true; fParent = parent; /* Shared Viewer Helper */ fNewsFilter = new NewsFilter(); fNewsFilter.setType(fInitialFilterType); fNewsFilter.setSearchTarget(fInitialSearchTarget); fNewsFilter.setNewsMark(fInput.getMark()); fNewsGrouping = new NewsGrouping(); fNewsGrouping.setType(fInitialGroupType); /* Top-Most root Composite in Editor */ fRootComposite = new Composite(fParent, SWT.NONE); fRootComposite.setLayout(LayoutUtils.createGridLayout(1, 0, 0)); ((GridLayout) fRootComposite.getLayout()).verticalSpacing = 0; /* FilterBar */ fFilterBar = new FilterBar(this, fRootComposite); /* Separate Filter from Table */ boolean showSeparator = false; if (!Application.IS_MAC || fLayout != Layout.CLASSIC) showSeparator = fFilterBar.isVisible(); fHorizontalFilterTableSep = new Label(fRootComposite, SWT.SEPARATOR | SWT.HORIZONTAL); fHorizontalFilterTableSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); ((GridData) fHorizontalFilterTableSep.getLayoutData()).exclude = !showSeparator; fHorizontalFilterTableSep.setVisible(showSeparator); /* SashForm dividing Feed and News View */ boolean useClassicLayout = (fLayout != Layout.VERTICAL); fSashForm = new SashForm(fRootComposite, (useClassicLayout ? SWT.VERTICAL : SWT.HORIZONTAL) | SWT.SMOOTH); fSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); /* Table-Viewer to display headlines */ NewsTableViewer tableViewer; { fNewsTableControlContainer = new Composite(fSashForm, SWT.None); fNewsTableControlContainer.setLayout(LayoutUtils.createGridLayout(2, 0, 0, 0, 0, false)); fNewsTableControlContainer.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { fCacheWeights = fSashForm.getWeights(); } }); fNewsTableControl = new NewsTableControl(); fNewsTableControl.init(fFeedViewSite); fNewsTableControl.onInputChanged(fInput); /* Create Viewer */ fNewsTableControl.createPart(fNewsTableControlContainer); tableViewer = fNewsTableControl.getViewer(); /* Clear any quicksearch when ESC is hit from the Tree */ tableViewer.getControl().addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.ESC) fFilterBar.clearQuickSearch(true); } }); /* Separate from Browser-Viewer (Vertically) */ fVerticalTableBrowserSep = new Label(fNewsTableControlContainer, SWT.SEPARATOR | SWT.VERTICAL); fVerticalTableBrowserSep.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false)); ((GridData) fVerticalTableBrowserSep.getLayoutData()).exclude = (fLayout != Layout.VERTICAL); /* Separate from Browser-Viewer (Horizontally) */ fHorizontalTableBrowserSep = new Label(fNewsTableControlContainer, SWT.SEPARATOR | SWT.HORIZONTAL); fHorizontalTableBrowserSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1)); ((GridData) fHorizontalTableBrowserSep.getLayoutData()).exclude = (fLayout != Layout.CLASSIC); } /* Browser-Viewer to display news */ NewsBrowserViewer browserViewer; { fBrowserViewerControlContainer = new Composite(fSashForm, SWT.None); fBrowserViewerControlContainer.setLayout(LayoutUtils.createGridLayout(2, 0, 0, 0, 0, false)); fBrowserViewerControlContainer.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); /* Separate to Browser (Vertically) */ fVerticalBrowserSep = new Label(fBrowserViewerControlContainer, SWT.SEPARATOR | SWT.VERTICAL); fVerticalBrowserSep.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false, 1, 3)); ((GridData) fVerticalBrowserSep.getLayoutData()).exclude = (fLayout != Layout.VERTICAL); /* Browser Bar for Navigation */ fBrowserBar = new BrowserBar(this, fBrowserViewerControlContainer); /* Separate to Browser (Horizontally) */ fHorizontalBrowserSep = new Label(fBrowserViewerControlContainer, SWT.SEPARATOR | SWT.HORIZONTAL); /* Horizontal Layout */ if (fLayout == Layout.CLASSIC) { fHorizontalBrowserSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1)); ((GridData) fHorizontalBrowserSep.getLayoutData()).exclude = false; } /* Verical Layout */ else if (fLayout == Layout.VERTICAL) { fHorizontalBrowserSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1)); ((GridData) fHorizontalBrowserSep.getLayoutData()).exclude = !fBrowserBar.isVisible(); } /* Browser Maximized */ else if (fLayout == Layout.NEWSPAPER || fLayout == Layout.HEADLINES) { fHorizontalBrowserSep.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1)); ((GridData) fHorizontalBrowserSep.getLayoutData()).exclude = !fBrowserBar.isVisible(); } fNewsBrowserControl = new NewsBrowserControl(); fNewsBrowserControl.init(fFeedViewSite); fNewsBrowserControl.onInputChanged(fInput); /* Create Viewer */ fNewsBrowserControl.createPart(fBrowserViewerControlContainer); browserViewer = fNewsBrowserControl.getViewer(); /* Clear any quicksearch when ESC is hit from the Tree */ browserViewer.getControl().addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.ESC) fFilterBar.clearQuickSearch(true); } }); /* Init the Browser Bar with the CBrowser */ fBrowserBar.init(browserViewer.getBrowser()); } /* SashForm weights */ fSashForm.setWeights(fInitialWeights); if (fLayout == Layout.NEWSPAPER || fLayout == Layout.HEADLINES) fSashForm.setMaximizedControl(fBrowserViewerControlContainer); else if (fLayout == Layout.LIST) fSashForm.setMaximizedControl(fNewsTableControlContainer); /* Create the shared Content-Provider */ fContentProvider = new NewsContentProvider(tableViewer, browserViewer, this); /* Init all Viewers */ fNewsTableControl.initViewer(fContentProvider, fNewsFilter); fNewsBrowserControl.initViewer(fContentProvider, fNewsFilter); /* Set Input to Viewers */ setInput(fInput.getMark(), false); } /* * This method is currently only being used when the news filter was changed and * the input is of type news bin or search mark. In this case, the cache is clever * enough to not resolve all news, but only the ones necessary from the used filter. * * However, if the filter changes, the feed view needs to ensure the cache is up to * date in case more elements are now visible. Thus, the cache is refreshed, but * only for added news that have not been there previously. */ void revalidateCaches() { fContentProvider.refreshCache(null, fInput.getMark()); } /** * Navigate to the next/previous read or unread News respecting the News-Items * that are displayed in the NewsTableControl. * * @param respectSelection If <code>TRUE</code>, respect the current selected * Item from the Tree as starting-node for the navigation, or * <code>FALSE</code> otherwise. * @param onInputSet if <code>true</code> this method is called directly after * an input was set, <code>false</code> otherwise. * @param next If <code>TRUE</code>, move to the next item, or previous if * <code>FALSE</code>. * @param unread If <code>TRUE</code>, only move to unread items, or ignore if * <code>FALSE</code>. * @return Returns <code>TRUE</code> in case navigation found a valid item, or * <code>FALSE</code> otherwise. */ public boolean navigate(boolean respectSelection, final boolean onInputSet, final boolean next, final boolean unread) { /* Check for unread counter */ if (unread && fInput.getMark().getNewsCount(EnumSet.of(INews.State.NEW, INews.State.UNREAD, INews.State.UPDATED)) == 0) return false; /* Navigate in maximized Browser */ if (!isTableViewerVisible()) { /* Delay navigation because input was just set and browser needs a little to render */ if (onInputSet) { JobRunner.runInUIThread(BROWSER_OPERATIONS_DELAY, fNewsBrowserControl.getViewer().getControl(), new Runnable() { public void run() { fNewsBrowserControl.getViewer().navigate(next, unread, onInputSet); } }); } /* Directly Navigate */ else fNewsBrowserControl.getViewer().navigate(next, unread, onInputSet); return true; } Tree newsTree = fNewsTableControl.getViewer().getTree(); /* Nothing to Navigate to */ if (newsTree.getItemCount() == 0 || newsTree.isDisposed()) return false; /* Navigate */ return navigate(newsTree, respectSelection, next, unread); } private boolean navigate(Tree tree, boolean respectSelection, boolean next, boolean unread) { /* Selection is Present */ if (respectSelection && tree.getSelectionCount() > 0) { /* Try navigating from Selection */ ITreeNode startingNode = new WidgetTreeNode(tree.getSelection()[0], fNewsTableControl.getViewer()); if (navigate(startingNode, next, unread)) return true; } /* No Selection is Present */ else { ITreeNode startingNode = new WidgetTreeNode(tree, fNewsTableControl.getViewer()); return navigate(startingNode, true, unread); } return false; } private boolean navigate(ITreeNode startingNode, boolean next, final boolean unread) { /* Create Traverse-Helper */ TreeTraversal traverse = new TreeTraversal(startingNode) { @Override public boolean select(ITreeNode node) { return isValidNavigation(node, unread); } }; /* Retrieve and select new Target Node */ ITreeNode targetNode = (next ? traverse.nextNode() : traverse.previousNode()); if (targetNode != null) { ISelection selection = new StructuredSelection(targetNode.getData()); fNewsTableControl.getViewer().setSelection(selection, true); return true; } return false; } private boolean isValidNavigation(ITreeNode node, boolean unread) { Object data = node.getData(); /* Require a News */ if (!(data instanceof INews)) return false; /* Check if News is unread if set as flag */ INews news = (INews) data; if (unread && !CoreUtils.isUnread(news.getState())) return false; return true; } /** * @return <code>true</code> if this feedview is currently visible or * <code>false</code> otherwise. */ public boolean isVisible() { return fEditorSite.getPage().isPartVisible(fEditorSite.getPart()); } /** * Returns the <code>Composite</code> that is the Parent Control of this * Editor Part. * * @return The <code>Composite</code> that is the Parent Control of this * Editor Part. */ Composite getEditorControl() { return fParent; } /** * @param blockFeedChangeEvent <code>true</code> to block the processing of * feed change events and <code>false</code> otherwise. */ public static void setBlockFeedChangeEvent(boolean blockFeedChangeEvent) { fgBlockFeedChangeEvent = blockFeedChangeEvent; } /** * @return <code>true</code> if the news browser viewer of this feed view is * showing the contents of a website and <code>false</code> otherwise. */ public boolean isBrowserShowingNews() { if (fNewsBrowserControl != null && fNewsBrowserControl.getViewer() != null) { if (isBrowserViewerVisible()) { CBrowser browser = fNewsBrowserControl.getViewer().getBrowser(); if (browser != null && browser.getControl() != null && !browser.getControl().isDisposed()) { String url = browser.getControl().getUrl(); return StringUtils.isSet(url) && ApplicationServer.getDefault().isNewsServerUrl(url); } } } return false; } }