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

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

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

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

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