org.rssowl.ui.internal.ApplicationWorkbenchWindowAdvisor.java Source code

Java tutorial

Introduction

Here is the source code for org.rssowl.ui.internal.ApplicationWorkbenchWindowAdvisor.java

Source

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

import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tray;
import org.eclipse.swt.widgets.TrayItem;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.application.ActionBarAdvisor;
import org.eclipse.ui.application.IActionBarConfigurer;
import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
import org.eclipse.ui.application.WorkbenchWindowAdvisor;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.ISearch;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.ISearchField;
import org.rssowl.core.persist.SearchSpecifier;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.IPreferenceDAO;
import org.rssowl.core.persist.event.NewsAdapter;
import org.rssowl.core.persist.event.NewsEvent;
import org.rssowl.core.persist.event.PreferenceEvent;
import org.rssowl.core.persist.event.PreferenceListener;
import org.rssowl.core.persist.event.runnable.EventType;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.LoggingSafeRunnable;
import org.rssowl.core.util.SearchHit;
import org.rssowl.ui.internal.dialogs.ActivityDialog;
import org.rssowl.ui.internal.editors.feed.FeedView;
import org.rssowl.ui.internal.notifier.NotificationService;
import org.rssowl.ui.internal.notifier.NotificationService.Mode;
import org.rssowl.ui.internal.util.JobRunner;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author bpasero
 */
public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {

    /** Key for Data-Slot in Controls that support this Hook */
    public static final String FOCUSLESS_SCROLL_HOOK = "org.rssowl.ui.internal.FocuslessScrollHook"; //$NON-NLS-1$

    /* WebSite class being used for the Browser on Windows only */
    private static final String SWT_BROWSER_WIN = "org.eclipse.swt.browser.WebSite"; //$NON-NLS-1$

    /* Maximum Number of News for Teasing */
    private static final int TEASE_LIMIT = 200;

    private TrayItem fTrayItem;
    private boolean fTrayTeasing;
    private boolean fTrayEnabled;
    private boolean fMinimizedToTray;
    private ApplicationActionBarAdvisor fActionBarAdvisor;
    private LocalResourceManager fResources;
    private IPreferenceScope fPreferences;
    private boolean fBlockIconifyEvent;
    private boolean fMinimizeFromClose;
    private Menu fTrayMenu;
    private List<Long> fTeasingNewsCache;
    private ISearch fTodaysNewsSearch;

    /* Listeners */
    private NewsAdapter fNewsListener;
    private ShellListener fTrayShellListener;
    private PreferenceListener fPrefListener;

    /**
     * @param configurer
     */
    public ApplicationWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer) {
        super(configurer);
        fResources = new LocalResourceManager(JFaceResources.getResources());
        fTeasingNewsCache = new ArrayList<Long>();
    }

    /*
     * @see org.eclipse.ui.application.WorkbenchWindowAdvisor#createActionBarAdvisor(org.eclipse.ui.application.IActionBarConfigurer)
     */
    @Override
    public ActionBarAdvisor createActionBarAdvisor(IActionBarConfigurer configurer) {
        fActionBarAdvisor = new ApplicationActionBarAdvisor(configurer);
        return fActionBarAdvisor;
    }

    /*
     * @see org.eclipse.ui.application.WorkbenchWindowAdvisor#preWindowOpen()
     */
    @Override
    public void preWindowOpen() {
        IWorkbenchWindowConfigurer configurer = getWindowConfigurer();

        /* Set Window State and define visibility of UI elements */
        configurer.setShowCoolBar(true);
        configurer.setShowPerspectiveBar(false);
        configurer.setShowStatusLine(true);
        configurer.setShowMenuBar(true);
        configurer.setShowFastViewBars(false);
        configurer.setShowProgressIndicator(true);
        configurer.setTitle("RSSOwl"); //$NON-NLS-1$

        /* Set Window Size to match monitor size (only on single monitor) */
        Point size = OwlUI.getFirstMonitorSize();
        if (size != null)
            configurer.setInitialSize(size);

        /* Apply DND Support for Editor Area */
        configurer.addEditorAreaTransfer(LocalSelectionTransfer.getTransfer());
        configurer.configureEditorAreaDropListener(new EditorDNDImpl());

        /* Retrieve Preferences */
        fPreferences = Owl.getPreferenceService().getGlobalScope();
    }

    void setToolBarVisible(boolean visible, boolean layout) {
        getWindowConfigurer().setShowCoolBar(visible);

        if (layout)
            getWindowConfigurer().getWindow().getShell().layout();
    }

    /**
     * @param visible
     * @param layout
     */
    public void setStatusVisible(boolean visible, boolean layout) {
        getWindowConfigurer().setShowStatusLine(visible);
        getWindowConfigurer().setShowProgressIndicator(visible);

        /* Hack: To avoid cheese, update ToolBar Too */
        boolean showsToolBar = getWindowConfigurer().getShowCoolBar();
        getWindowConfigurer().setShowCoolBar(!showsToolBar);
        getWindowConfigurer().setShowCoolBar(showsToolBar);

        if (layout)
            getWindowConfigurer().getWindow().getShell().layout();
    }

    /*
     * @see org.eclipse.ui.application.WorkbenchWindowAdvisor#postWindowCreate()
     */
    @Override
    public void postWindowCreate() {

        /* If set to move to tray on startup, block shell opening once */
        IWorkbenchWindow window = getWindowConfigurer().getWindow();
        if (window != null && fPreferences.getBoolean(DefaultPreferences.TRAY_ON_START))
            blockShellOpen(window);

        /* Toolbar & Status Visibility */
        IPreferenceScope preferences = Owl.getPreferenceService().getGlobalScope();
        if (!preferences.getBoolean(DefaultPreferences.SHOW_TOOLBAR))
            setToolBarVisible(false, false);
        if (!preferences.getBoolean(DefaultPreferences.SHOW_STATUS))
            setStatusVisible(false, false);
    }

    /* Calls safely into a patched version of JFaces Window class to block shell opening once */
    private void blockShellOpen(IWorkbenchWindow window) {
        try {
            Method method = window.getClass().getMethod("blockShellOpenOnce"); //$NON-NLS-1$
            if (method != null)
                method.invoke(window);
        } catch (Exception e) {
            /* Ignore Silently */
        }
    }

    /*
     * @see org.eclipse.ui.application.WorkbenchWindowAdvisor#postWindowOpen()
     */
    @Override
    public void postWindowOpen() {
        final Shell shell = getWindowConfigurer().getWindow().getShell();

        /* System Tray */
        SafeRunner.run(new LoggingSafeRunnable() {
            public void run() throws Exception {
                boolean trayEnabled = false;

                /* Hook TrayItem if supported on OS and 1st Window */
                if (fPreferences.getBoolean(DefaultPreferences.TRAY_ON_MINIMIZE)
                        || fPreferences.getBoolean(DefaultPreferences.TRAY_ON_CLOSE)
                        || fPreferences.getBoolean(DefaultPreferences.TRAY_ON_START))
                    trayEnabled = enableTray();

                /* Win only: Allow Scroll over Cursor-Control */
                if (Application.IS_WINDOWS)
                    hookFocuslessScrolling(shell.getDisplay());

                /* Register Listeners */
                registerListeners();

                /* Move to Tray if set */
                if (trayEnabled && fPreferences.getBoolean(DefaultPreferences.TRAY_ON_START))
                    moveToTray(shell);
            }
        });

        /* Hook into Selection Listeners to open Activiy Dialog if needed (this is a hack :-) ) */
        shell.getDisplay().addFilter(SWT.Selection, new Listener() {
            @SuppressWarnings("restriction")
            public void handleEvent(Event event) {
                if (event.item == null && event.widget instanceof ToolItem) {
                    ToolItem item = (ToolItem) event.widget;
                    ToolBar toolbar = item.getParent();
                    Composite parent = toolbar.getParent();
                    if (!(parent instanceof Shell)) {
                        parent = parent.getParent();
                        if (parent != null && !(parent instanceof Shell) && !parent.isDisposed() && parent
                                .getLayoutData() instanceof org.eclipse.ui.internal.progress.ProgressRegion) {
                            event.doit = false;
                            event.type = SWT.None;
                            asyncOpenActivityDialog(toolbar.getShell());
                        }
                    }
                }
            }
        });

        /* Notify Controller */
        Controller.getDefault().postWindowOpen();
    }

    private void asyncOpenActivityDialog(final Shell shell) {
        shell.getDisplay().asyncExec(new Runnable() {
            public void run() {
                ActivityDialog instance = ActivityDialog.getVisibleInstance();
                if (instance == null) {
                    ActivityDialog dialog = new ActivityDialog(shell);
                    dialog.setBlockOnOpen(false);
                    dialog.open();
                } else {
                    instance.getShell().forceActive();
                }
            }
        });
    }

    /*
     * @see org.eclipse.ui.application.WorkbenchWindowAdvisor#preWindowShellClose()
     */
    @Override
    public boolean preWindowShellClose() {
        final boolean[] res = new boolean[] { true };
        SafeRunner.run(new LoggingSafeRunnable() {
            public void run() throws Exception {

                /* Check if Prefs tell to move to tray */
                if (ApplicationWorkbenchWindowAdvisor.this
                        .equals(ApplicationWorkbenchAdvisor.fgPrimaryApplicationWorkbenchWindowAdvisor)
                        && fPreferences.getBoolean(DefaultPreferences.TRAY_ON_CLOSE)) {
                    fMinimizeFromClose = true;
                    getWindowConfigurer().getWindow().getShell().notifyListeners(SWT.Iconify, new Event());
                    res[0] = false;
                    fMinimizeFromClose = false;
                }

                /* Notify any open feedview about closing application */
                else {
                    onClose();
                }
            }
        });

        return res[0];
    }

    /**
     * @return TRUE if the Window is minimized to the tray.
     */
    public boolean isMinimizedToTray() {
        return fMinimizedToTray;
    }

    /**
     * @return TRUE if the Window is minimized.
     */
    public boolean isMinimized() {
        return getWindowConfigurer().getWindow().getShell().getMinimized();
    }

    private void registerListeners() {

        /* Add Shell sListener */
        getWindowConfigurer().getWindow().getShell().addShellListener(new ShellAdapter() {
            @Override
            public void shellIconified(ShellEvent e) {
                if (!fBlockIconifyEvent)
                    onMinimize();
            }
        });

        /* Listen on Preferences Changes */
        fPrefListener = new PreferenceListener() {
            public void entitiesAdded(Set<PreferenceEvent> events) {
                onPreferencesChange(events, EventType.PERSIST);
            }

            public void entitiesDeleted(Set<PreferenceEvent> events) {
                onPreferencesChange(events, EventType.REMOVE);
            }

            public void entitiesUpdated(Set<PreferenceEvent> events) {
                onPreferencesChange(events, EventType.UPDATE);
            }
        };
        DynamicDAO.getDAO(IPreferenceDAO.class).addEntityListener(fPrefListener);
    }

    private void unregisterListeners() {
        DynamicDAO.getDAO(IPreferenceDAO.class).removeEntityListener(fPrefListener);
    }

    private void onPreferencesChange(Set<PreferenceEvent> events, EventType type) {
        IPreferenceScope defaultScope = Owl.getPreferenceService().getDefaultScope();
        boolean useTray = false;
        boolean affectsTray = false;

        for (PreferenceEvent event : events) {
            String key = event.getEntity().getKey();

            /* Tray Preference Change: Tray on Minimize */
            if (DefaultPreferences.TRAY_ON_MINIMIZE.equals(key)) {
                affectsTray = true;
                if (type == EventType.REMOVE)
                    useTray = defaultScope.getBoolean(DefaultPreferences.TRAY_ON_MINIMIZE)
                            || fPreferences.getBoolean(DefaultPreferences.TRAY_ON_CLOSE);
                else
                    useTray = event.getEntity().getBoolean()
                            || fPreferences.getBoolean(DefaultPreferences.TRAY_ON_CLOSE);
            }

            /* Tray Preference Change: Tray on Close */
            else if (DefaultPreferences.TRAY_ON_CLOSE.equals(key)) {
                affectsTray = true;
                if (type == EventType.REMOVE)
                    useTray = defaultScope.getBoolean(DefaultPreferences.TRAY_ON_CLOSE)
                            || fPreferences.getBoolean(DefaultPreferences.TRAY_ON_MINIMIZE);
                else
                    useTray = event.getEntity().getBoolean()
                            || fPreferences.getBoolean(DefaultPreferences.TRAY_ON_MINIMIZE);
            }

            /* Enable Tray */
            if (affectsTray && useTray && !fTrayEnabled) {
                JobRunner.runInUIThread(null, new Runnable() {
                    public void run() {
                        enableTray();
                    }
                });
            }

            /* Disable Tray */
            else if (affectsTray && !useTray && fTrayEnabled) {
                JobRunner.runInUIThread(null, new Runnable() {
                    public void run() {
                        disableTray();
                    }
                });
            }
        }
    }

    /*
     * @see org.eclipse.ui.application.WorkbenchWindowAdvisor#dispose()
     */
    @Override
    public void dispose() {
        unregisterListeners();

        if (fTrayItem != null)
            fTrayItem.dispose();

        if (fNewsListener != null)
            DynamicDAO.removeEntityListener(INews.class, fNewsListener);

        fResources.dispose();
    }

    private void onMinimize() {

        /* Mark displayed News as Read on Minimize if set in Preferences */
        IEditorPart activeEditor = OwlUI.getActiveEditor();
        if (activeEditor != null && activeEditor instanceof FeedView) {
            FeedView feedView = (FeedView) activeEditor;
            feedView.notifyUIEvent(FeedView.UIEvent.MINIMIZE);
        }

        /* Trigger synchronization as the user is leaving the RSSOwl window */
        Controller.getDefault().getSyncService().synchronize();
    }

    private void onClose() {

        /* Mark new News as Unread on Close */
        IEditorPart activeEditor = OwlUI.getActiveEditor();
        if (activeEditor != null && activeEditor instanceof FeedView) {
            FeedView feedView = (FeedView) activeEditor;
            feedView.notifyUIEvent(FeedView.UIEvent.CLOSE);
        }
    }

    /* Enable System-Tray Support */
    private boolean enableTray() {

        /* Avoid that this is being called redundantly */
        if (fTrayEnabled)
            return true;

        /* Only enable for Primary Window */
        IWorkbenchWindow primaryWindow = OwlUI.getPrimaryWindow();
        if (primaryWindow == null || !primaryWindow.equals(getWindowConfigurer().getWindow()))
            return false;

        final Shell shell = primaryWindow.getShell();
        final int doubleClickTime = shell.getDisplay().getDoubleClickTime();
        final Tray tray = shell.getDisplay().getSystemTray();

        /* Tray not support on the OS */
        if (tray == null)
            return false;

        /* Create Item in Tray */
        fTrayItem = new TrayItem(tray, SWT.NONE);
        fTrayItem.setToolTipText("RSSOwl"); //$NON-NLS-1$
        fTrayEnabled = true;

        if (Application.IS_WINDOWS)
            fTrayItem.setVisible(false);

        /* Apply Image */
        fTrayItem.setImage(OwlUI.getImage(fResources, OwlUI.TRAY_OWL));

        /* Minimize to Tray on Shell Iconify if set */
        fTrayShellListener = new ShellAdapter() {

            @Override
            public void shellIconified(ShellEvent e) {
                if (!fBlockIconifyEvent
                        && (fMinimizeFromClose || fPreferences.getBoolean(DefaultPreferences.TRAY_ON_MINIMIZE)))
                    moveToTray(shell);
            }
        };
        shell.addShellListener(fTrayShellListener);

        /* Show Menu on Selection */
        fTrayItem.addListener(SWT.MenuDetect, new Listener() {
            public void handleEvent(Event event) {
                showTrayMenu(shell);
            }
        });

        /* Handle Selection (Default and Normal) */
        Listener selectionListener = new Listener() {
            private long lastDoubleClickTime;

            public void handleEvent(Event event) {
                boolean restoreOnDoubleclick = fPreferences.getBoolean(DefaultPreferences.RESTORE_TRAY_DOUBLECLICK);
                boolean isDoubleClick = (event.type == SWT.DefaultSelection);

                /* Remember when Doubleclick was invoked */
                if (isDoubleClick)
                    lastDoubleClickTime = System.currentTimeMillis();

                /* Invoke Single Click Action if this is not a double click and we are on Windows */
                if (!isDoubleClick && restoreOnDoubleclick && Application.IS_WINDOWS) {
                    NotificationService service = Controller.getDefault().getNotificationService();

                    /* Notifier showing - close it instantly */
                    if (service.isPopupVisible()) {
                        service.closePopup();
                    }

                    /* Notifier not showing - invoke single click action (only if not recently closed) */
                    else if (!service.wasPopupRecentlyClosed()) {
                        JobRunner.runInBackgroundThread(doubleClickTime, new Runnable() {
                            public void run() {
                                if (lastDoubleClickTime < System.currentTimeMillis() - doubleClickTime) {
                                    JobRunner.runInUIThread(tray, new Runnable() {
                                        public void run() {
                                            if (!shell.isDisposed())
                                                onSingleClick(shell);
                                        }
                                    });
                                }
                            }
                        });
                    }
                }

                /* Do not restore if settings are different */
                if (restoreOnDoubleclick != isDoubleClick)
                    return;

                /* Restore from Tray */
                if (!shell.isVisible())
                    restoreFromTray(shell);

                /* Move to Tray */
                else if (!Application.IS_WINDOWS)
                    moveToTray(shell);
            }
        };

        fTrayItem.addListener(SWT.DefaultSelection, selectionListener);
        fTrayItem.addListener(SWT.Selection, selectionListener);

        /* Indicate new News in Tray */
        fNewsListener = new NewsAdapter() {

            @Override
            public void entitiesAdded(final Set<NewsEvent> events) {
                if (!CoreUtils.containsState(events, INews.State.NEW))
                    return;

                JobRunner.runInUIThread(fTrayItem, new Runnable() {
                    public void run() {

                        /* Update Icon only when Tray is visible */
                        if (!fTrayItem.getVisible() || shell.getVisible())
                            return;

                        /* Return on Shutdown */
                        if (Controller.getDefault().isShuttingDown())
                            return;

                        /* Remember Added News (Windows Only and Only if restoring with Doubleclick) */
                        if (Application.IS_WINDOWS
                                && fPreferences.getBoolean(DefaultPreferences.RESTORE_TRAY_DOUBLECLICK)) {
                            synchronized (fTeasingNewsCache) {
                                for (NewsEvent event : events) {
                                    if (event.getEntity().getState() == INews.State.NEW)
                                        fTeasingNewsCache.add(event.getEntity().getId());
                                }

                                if (!fTeasingNewsCache.isEmpty())
                                    fTrayItem.setToolTipText(
                                            NLS.bind(Messages.ApplicationWorkbenchWindowAdvisor_N_INCOMING_NEWS,
                                                    fTeasingNewsCache.size()));
                            }
                        }

                        /* Show Teaser */
                        if (!fTrayTeasing) {
                            fTrayTeasing = true;
                            fTrayItem.setImage(OwlUI.getImage(fResources, OwlUI.TRAY_OWL_TEASING));
                        }
                    }
                });
            }
        };
        DynamicDAO.addEntityListener(INews.class, fNewsListener);

        return true;
    }

    /* Move to System Tray */
    private void moveToTray(Shell shell) {
        if (Application.IS_WINDOWS)
            fTrayItem.setVisible(true);

        /*
         * Bug in SWT: For some reason, calling setVisible(false) here will result
         * in a second Iconify Event. The fix is to disable processing of this event
         * meanwhile.
         */
        fBlockIconifyEvent = true;
        try {
            shell.setVisible(false);
        } finally {
            fBlockIconifyEvent = false;
        }

        fMinimizedToTray = true;
    }

    /**
     * @param shell
     */
    public void restoreFromTray(Shell shell) {

        /* Mac: Un-Minimize if minimized */
        if (Application.IS_MAC && shell.getMinimized())
            shell.setMinimized(false);

        /* Restore Shell */
        shell.setVisible(true);
        if (Application.IS_MAC)
            shell.forceActive();
        else
            shell.setActive();

        /* Non Mac: Un-Minimize if minimized */
        if (!Application.IS_MAC && shell.getMinimized())
            shell.setMinimized(false);

        if (Application.IS_WINDOWS)
            fTrayItem.setVisible(false);

        fMinimizedToTray = false;

        clearTease(false);
    }

    private void clearTease(boolean clearTray) {
        if (fTrayTeasing)
            fTrayItem.setImage(OwlUI.getImage(fResources, OwlUI.TRAY_OWL));

        fTrayTeasing = false;

        if (Application.IS_WINDOWS) {
            synchronized (fTeasingNewsCache) {
                fTeasingNewsCache.clear();
            }

            fTrayItem.setToolTipText(clearTray ? "" : "RSSOwl"); //$NON-NLS-1$ //$NON-NLS-2$
        }
    }

    /* Disable System-Tray Support */
    private void disableTray() {

        /* Avoid that this is being called redundantly */
        if (!fTrayEnabled)
            return;

        /* First make sure to have the Window restored */
        restoreFromTray(getWindowConfigurer().getWindow().getShell());

        fTrayEnabled = false;
        fMinimizedToTray = false;

        if (fTrayItem != null)
            fTrayItem.dispose();

        if (fNewsListener != null)
            DynamicDAO.removeEntityListener(INews.class, fNewsListener);

        if (fTrayShellListener != null)
            getWindowConfigurer().getWindow().getShell().removeShellListener(fTrayShellListener);
    }

    /* Support for focusless scrolling */
    private void hookFocuslessScrolling(final Display display) {
        display.addFilter(SWT.MouseWheel, new Listener() {
            public void handleEvent(Event event) {
                Control control = display.getCursorControl();

                /* Control must be non-focus undisposed */
                if (control == null || control.isDisposed() || control.isFocusControl())
                    return;

                /* Pass focus to control and disable event if allowed */
                boolean isBrowser = SWT_BROWSER_WIN.equals(control.getClass().getName());
                if (isBrowser || control.getData(FOCUSLESS_SCROLL_HOOK) != null) {

                    /* Break Condition */
                    control.setFocus();

                    /* Re-Post Event to Cursor Control */
                    event.doit = false;
                    event.widget = control;
                    display.post(event);
                }
            }
        });
    }

    /*
     * @see org.eclipse.ui.application.WorkbenchWindowAdvisor#getWindowConfigurer()
     */
    @Override
    public IWorkbenchWindowConfigurer getWindowConfigurer() {
        return super.getWindowConfigurer();
    }

    private void showTrayMenu(final Shell shell) {
        MenuManager trayMenuManager = new MenuManager();

        /* Restore */
        trayMenuManager.add(new ContributionItem() {
            @Override
            public void fill(Menu menu, int index) {
                MenuItem restoreItem = new MenuItem(menu, SWT.PUSH);
                restoreItem.setText(Messages.ApplicationWorkbenchWindowAdvisor_RESTORE);
                restoreItem.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        restoreFromTray(shell);
                    }
                });
                menu.setDefaultItem(restoreItem);
            }
        });

        /* Separator */
        trayMenuManager.add(new Separator());

        /* Other Items */
        fActionBarAdvisor.fillTrayItem(trayMenuManager, shell, ApplicationWorkbenchWindowAdvisor.this);

        if (fTrayMenu != null)
            OwlUI.safeDispose(fTrayMenu);

        fTrayMenu = trayMenuManager.createContextMenu(shell);
        fTrayMenu.setVisible(true);
    }

    private void onSingleClick(Shell shell) {
        NotificationService service = Controller.getDefault().getNotificationService();
        List<INews> newsToShow = new ArrayList<INews>();
        Mode mode = Mode.RECENT;

        /* Return early if Notifier already showing */
        if (service.isPopupVisible())
            return;

        /* Show News Downloaded while Minimized */
        if (!fTeasingNewsCache.isEmpty()) {
            synchronized (fTeasingNewsCache) {
                int counter = 0;
                for (int i = fTeasingNewsCache.size() - 1; i >= 0; i--) {
                    NewsReference reference = new NewsReference(fTeasingNewsCache.get(i));
                    INews newsitem = reference.resolve();
                    if (newsitem != null && newsitem.getState() == INews.State.NEW
                            && service.shouldShow(newsitem)) {
                        newsToShow.add(newsitem);

                        if (++counter >= TEASE_LIMIT)
                            break;
                    } else
                        CoreUtils.reportIndexIssue();

                    /* Return early if Notifier already showing */
                    if (service.isPopupVisible())
                        return;
                }
            }
            mode = Mode.INCOMING_MANUAL;
        }

        /* Show Recent News if no teasing news present */
        if (newsToShow.isEmpty()) {

            /* Build the Search if not yet done */
            if (fTodaysNewsSearch == null) {
                IModelFactory factory = Owl.getModelFactory();

                String newsClassName = INews.class.getName();
                ISearchField ageInDaysField = factory.createSearchField(INews.AGE_IN_DAYS, newsClassName);
                ISearchField ageInMinutesField = factory.createSearchField(INews.AGE_IN_MINUTES, newsClassName);

                ISearchCondition dayCondition = factory.createSearchCondition(ageInDaysField,
                        SearchSpecifier.IS_LESS_THAN, 1); // From Today after Midnight
                ISearchCondition recentCondition = factory.createSearchCondition(ageInMinutesField,
                        SearchSpecifier.IS_LESS_THAN, -60 * 6); // Up to 6 Hours Ago

                fTodaysNewsSearch = factory.createSearch(null);
                fTodaysNewsSearch.setMatchAllConditions(false);
                fTodaysNewsSearch.addSearchCondition(dayCondition);
                fTodaysNewsSearch.addSearchCondition(recentCondition);
            }

            /* Sort by Id (simulate sorting by date) */
            List<SearchHit<NewsReference>> result = Owl.getPersistenceService().getModelSearch()
                    .searchNews(fTodaysNewsSearch);
            Set<NewsReference> recentNews = new TreeSet<NewsReference>(new Comparator<NewsReference>() {
                public int compare(NewsReference ref1, NewsReference ref2) {
                    if (ref1.equals(ref2))
                        return 0;

                    return ref1.getId() > ref2.getId() ? -1 : 1;
                }
            });

            for (SearchHit<NewsReference> hit : result) {
                recentNews.add(hit.getResult());
            }

            /* Resolve and Add News from Result */
            int counter = 0;
            for (NewsReference reference : recentNews) {
                INews newsitem = reference.resolve();
                if (newsitem != null && newsitem.isVisible() && service.shouldShow(newsitem)) {
                    newsToShow.add(newsitem);

                    if (++counter >= TEASE_LIMIT)
                        break;
                } else if (newsitem == null)
                    CoreUtils.reportIndexIssue();

                /* Return early if Notifier already showing */
                if (service.isPopupVisible())
                    return;
            }

            mode = Mode.RECENT;
        }

        /* Return early if Notifier already showing */
        if (service.isPopupVisible())
            return;

        /* Tease with News */
        if (!newsToShow.isEmpty()) {
            service.show(newsToShow, null, mode);
            clearTease(true);
        }

        /* Otherwise show Context Menu */
        else
            showTrayMenu(shell);
    }
}