org.rssowl.ui.internal.notifier.NotificationPopup.java Source code

Java tutorial

Introduction

Here is the source code for org.rssowl.ui.internal.notifier.NotificationPopup.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.notifier;

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.jface.dialogs.PopupDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
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.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
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.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.UIJob;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.util.StringUtils;
import org.rssowl.ui.internal.Controller;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.notifier.NotificationService.Mode;
import org.rssowl.ui.internal.util.CCLabel;
import org.rssowl.ui.internal.util.LayoutUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Todo:
 * <ul>
 * <li>SearchNotificationItems are not aggregated if the max. number of items is
 * already showing</li>
 * </ul>
 *
 * @author bpasero
 */
public class NotificationPopup extends PopupDialog {

    /* Height of a CLabel with 16px Icon inside */
    private static final int CLABEL_HEIGHT = 22;

    /* Max. Number of Items being displayed in the Popup */
    private static final int MAX_ITEMS = 30;

    /* Default Width of the Popup */
    private static final int DEFAULT_WIDTH = 420;

    /* Milliseconds before incrementing the alpha for fade in / fade out */
    private static final int FADE_DELAY = 40;

    /* Alpha increment for fade in */
    private static final int FADE_IN_INCREMENT = 20;

    /* A field that is set to true in case fading is not supported on the OS */
    private static boolean fgFadeSupported = true;

    /* System Property to disable Transparency for the Notifier */
    private static final String DISABLE_TRANSPARENCY = "disableNotifierTransparency"; //$NON-NLS-1$
    private static final boolean FADE_ENABLED = System.getProperty(DISABLE_TRANSPARENCY) == null;

    private Shell fShell;
    private final List<NotificationItem> fDisplayedItems = new ArrayList<NotificationItem>();
    private final ResourceManager fResources;
    private Image fCloseImageNormal;
    private Image fCloseImageHot;
    private Image fCloseImagePressed;
    private CLabel fTitleCircleLabel;
    private CLabel fFooterCircleLabel;
    private Composite fInnerContentCircle;
    private Composite fOuterContentCircle;
    private final Font fNormalTextFont;
    private final Font fBoldTextFont;
    private UIJob fAutoCloser;
    private MouseTrackListener fMouseTrackListner;
    private final IPreferenceScope fGlobalScope;
    private final int fVisibleItemsCount;
    private int fItemLimit;
    private NotifierColors fNotifierColors;
    private Region fLastUsedRegion;
    private Image fMarkReadIcon;
    private Image fMarkReadDisabledIcon;
    private Image fItemStickyIcon;
    private Image fItemNonStickyIcon;
    private Image fItemNonStickyDisabledIcon;
    private Color fStickyBgColor;
    private Image fPrevImageNormal;
    private Image fPrevImagePressed;
    private Image fPrevImageDisabled;
    private Image fNextImageNormal;
    private Image fNextImagePressed;
    private Image fNextImageDisabled;
    private Image fTitleBgImage;
    private Image fFooterBgImage;
    private CLabel fNextButton;
    private CLabel fPrevButton;
    private int fDisplayOffset;
    private boolean fMouseOverNotifier;
    private FadeJob fFadeJob;
    private Collection<NotificationItem> fInitialItems;
    private final Mode fMode;

    private class FadeJob extends UIJob {
        FadeJob() {
            super(""); //$NON-NLS-1$
            setSystem(true);
        }

        private boolean proceed(IProgressMonitor monitor) {
            Shell shell = getShell();
            if (monitor.isCanceled() || shell == null || shell.isDisposed() || shell.getDisplay().isDisposed()
                    || Controller.getDefault().isShuttingDown())
                return false;

            return true;
        }

        @Override
        public IStatus runInUIThread(final IProgressMonitor monitor) {
            if (proceed(monitor)) {
                final int alpha = getShell().getAlpha();

                if (shouldFadeIn() && alpha < 255) {
                    if (proceed(monitor)) {
                        getShell().getDisplay().syncExec(new Runnable() {
                            public void run() {
                                if (proceed(monitor)) {
                                    int newAlpha = Math.min(alpha + FADE_IN_INCREMENT, 255);
                                    getShell().setAlpha(newAlpha);

                                    if (newAlpha != getShell().getAlpha())
                                        fgFadeSupported = false;
                                }
                            }
                        });
                        schedule(FADE_DELAY);
                    }
                }
            }

            return Status.OK_STATUS;
        }
    };

    NotificationPopup(int visibleItemCount, Mode mode) {
        super(new Shell(PlatformUI.getWorkbench().getDisplay()), SWT.NO_TRIM | SWT.ON_TOP,
                mode != Mode.INCOMING_AUTOMATIC, false, false, false, false, null, null);
        fMode = mode;
        fResources = new LocalResourceManager(JFaceResources.getResources());
        fBoldTextFont = OwlUI.getThemeFont(OwlUI.NOTIFICATION_POPUP_FONT_ID, SWT.BOLD);
        fNormalTextFont = OwlUI.getThemeFont(OwlUI.NOTIFICATION_POPUP_FONT_ID, SWT.NORMAL);
        fGlobalScope = Owl.getPreferenceService().getGlobalScope();

        fItemLimit = fGlobalScope.getInteger(DefaultPreferences.LIMIT_NOTIFICATION_SIZE);
        if (fItemLimit <= 0)
            fItemLimit = MAX_ITEMS;

        fVisibleItemsCount = (visibleItemCount > fItemLimit) ? fItemLimit : visibleItemCount;
        createAutoCloser();
        createMouseTrackListener();

        initResources();
    }

    /*
     * @see org.eclipse.jface.window.Window#getShellStyle()
     */
    @Override
    protected int getShellStyle() {
        return SWT.NO_TRIM | SWT.ON_TOP;
    }

    /*
     * @see org.eclipse.jface.window.Window#create()
     */
    @Override
    public void create() {
        super.create();

        /* Show initial set of items */
        makeVisible(fInitialItems);

        /* Make shell invisible initially if fading in */
        if (shouldFadeIn() && fGlobalScope.getBoolean(DefaultPreferences.FADE_NOTIFIER))
            getShell().setAlpha(0);
    }

    /**
     * @param items the initial items to show inside the window.
     * @return the status code from opening the window.
     */
    public int open(Collection<NotificationItem> items) {
        Assert.isLegal(items != null && !items.isEmpty());
        fInitialItems = items;
        int res = super.open();

        /* Make shell fade in */
        if (shouldFadeIn() && fGlobalScope.getBoolean(DefaultPreferences.FADE_NOTIFIER))
            fadeIn();

        return res;
    }

    private void fadeIn() {
        if (fFadeJob != null)
            fFadeJob.cancel();

        fFadeJob = new FadeJob();
        fFadeJob.schedule();
    }

    private void addRegion(Shell shell) {
        Region region = new Region();
        Point s = shell.getSize();

        /* Add entire Shell */
        region.add(0, 0, s.x, s.y);

        /* Subtract Top-Left Corner */
        region.subtract(0, 0, 5, 1);
        region.subtract(0, 1, 3, 1);
        region.subtract(0, 2, 2, 1);
        region.subtract(0, 3, 1, 1);
        region.subtract(0, 4, 1, 1);

        /* Subtract Top-Right Corner */
        region.subtract(s.x - 5, 0, 5, 1);
        region.subtract(s.x - 3, 1, 3, 1);
        region.subtract(s.x - 2, 2, 2, 1);
        region.subtract(s.x - 1, 3, 1, 1);
        region.subtract(s.x - 1, 4, 1, 1);

        /* Subtract Bottom-Left Corner */
        region.subtract(0, s.y, 5, 1);
        region.subtract(0, s.y - 1, 3, 1);
        region.subtract(0, s.y - 2, 2, 1);
        region.subtract(0, s.y - 3, 1, 1);
        region.subtract(0, s.y - 4, 1, 1);

        /* Subtract Bottom-Right Corner */
        region.subtract(s.x - 5, 0, 5, 1);
        region.subtract(s.x - 3, 1, 3, 1);
        region.subtract(s.x - 2, 2, 2, 1);
        region.subtract(s.x - 1, 3, 1, 1);
        region.subtract(s.x - 1, 4, 1, 1);

        /* Dispose old first */
        if (shell.getRegion() != null)
            shell.getRegion().dispose();

        /* Apply Region */
        shell.setRegion(region);

        /* Remember to dispose later */
        fLastUsedRegion = region;
    }

    private void createAutoCloser() {
        fAutoCloser = new UIJob(PlatformUI.getWorkbench().getDisplay(), "") { //$NON-NLS-1$
            @Override
            public IStatus runInUIThread(IProgressMonitor monitor) {
                if (!fMouseOverNotifier && getShell() != null && !getShell().isDisposed() && !monitor.isCanceled())
                    doClose();

                return Status.OK_STATUS;
            }
        };

        fAutoCloser.setSystem(true);
    }

    /* Listener to control Auto-Close Job */
    private void createMouseTrackListener() {
        fMouseTrackListner = new MouseTrackAdapter() {
            @Override
            public void mouseEnter(MouseEvent e) {
                fMouseOverNotifier = true;

                if (!fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP))
                    fAutoCloser.cancel();

                if (shouldFadeIn() && fGlobalScope.getBoolean(DefaultPreferences.FADE_NOTIFIER)
                        && getShell().getAlpha() < 255) {
                    if (fFadeJob != null)
                        fFadeJob.cancel();
                    getShell().setAlpha(255);
                }
            }

            @Override
            public void mouseExit(MouseEvent e) {
                fMouseOverNotifier = false;

                if (!fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP))
                    fAutoCloser.schedule(
                            fGlobalScope.getInteger(DefaultPreferences.AUTOCLOSE_NOTIFICATION_VALUE) * 1000);
            }
        };
    }

    void makeVisible(Collection<NotificationItem> items) {

        /* Cancel Auto Closer and reschedule */
        if (!fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP)) {
            fAutoCloser.cancel();
            fAutoCloser.schedule(fGlobalScope.getInteger(DefaultPreferences.AUTOCLOSE_NOTIFICATION_VALUE) * 1000);
        }

        /* Add / Update Notification Items to display */
        for (NotificationItem item : items) {
            int indexOfItem = fDisplayedItems.indexOf(item);

            /* Replace existing Item */
            if (indexOfItem >= 0) {
                NotificationItem existingItem = fDisplayedItems.get(indexOfItem);
                if (existingItem.getColor() != null && item.getColor() == null)
                    item.setColor(existingItem.getColor());

                fDisplayedItems.set(indexOfItem, item);
            }

            /* Add new Item to the end */
            else
                fDisplayedItems.add(item);
        }

        /* Update Title Label */
        updateTitleLabel();

        /* Update Contents */
        updateContents(fDisplayOffset);
    }

    private void updateTitleLabel() {
        int totalPages = (fDisplayedItems.size() / fItemLimit)
                + ((fDisplayedItems.size() % fItemLimit != 0) ? 1 : 0);
        int currentPage = (fDisplayOffset / fItemLimit) + 1;

        /* Count News Notification Items and Search Notification Items */
        int newsCount = 0;
        int searchCount = 0;
        for (NotificationItem item : fDisplayedItems) {
            if (item instanceof NewsNotificationItem)
                newsCount++;
            else
                searchCount++;
        }

        String titlePart;
        if (fMode == Mode.RECENT)
            titlePart = Messages.NotificationPopup_RECENT_NEWS;
        else if (fMode == Mode.INCOMING_MANUAL)
            titlePart = Messages.NotificationPopup_INCOMING_NEWS;
        else if (newsCount == 0 && searchCount > 0)
            titlePart = (searchCount == 1) ? NLS.bind(Messages.NotificationPopup_N_SEARCH_MATCH, searchCount)
                    : NLS.bind(Messages.NotificationPopup_N_SEARCH_MATCHES, searchCount);
        else
            titlePart = NLS.bind(Messages.NotificationPopup_N_INCOMING, newsCount);

        String footerPart = ""; //$NON-NLS-1$

        /* More than one page */
        if (totalPages > 1)
            footerPart = NLS.bind(Messages.NotificationPopup_PAGE_N_OF_M, currentPage, totalPages);

        /* Apply Text */
        fTitleCircleLabel.setText(titlePart);
        fFooterCircleLabel.setText(footerPart);
    }

    private void updateContents(int offset) {

        /* Dispose old Items first */
        Control[] children = fInnerContentCircle.getChildren();
        for (Control child : children) {
            child.dispose();
        }

        /* Show Items */
        for (int i = offset; i < offset + fItemLimit && i < fDisplayedItems.size(); i++)
            renderItem(fDisplayedItems.get(i));

        /* Layout */
        fOuterContentCircle.layout(true, true);

        /* Update Shell Bounds */
        Point oldSize = fShell.getSize();
        int newHeight = fShell.computeSize(DEFAULT_WIDTH, SWT.DEFAULT).y;
        newHeight = Math.max(oldSize.y, newHeight);

        Point newSize = new Point(oldSize.x, newHeight);
        Point newLocation = getInitialLocation(newSize);
        fShell.setBounds(newLocation.x, newLocation.y, newSize.x, newSize.y);

        /* Add Region to Shell */
        addRegion(fShell);

        /* Update Nav Buttons */
        updateNavButtons();
    }

    private void updateItemColor(NotificationItem item, CCLabel itemLabel) {
        if (!OwlUI.isHighContrast()) {
            if (item.getColor() == null)
                itemLabel.setForeground(itemLabel.getDisplay().getSystemColor(SWT.COLOR_BLACK));
            else
                itemLabel.setForeground(OwlUI.getColor(fResources, item.getColor()));
        }
    }

    private void renderItem(final NotificationItem item) {

        /* Image Label with Tooltip */
        final CLabel imageLabel = new CLabel(fInnerContentCircle, SWT.NONE);
        imageLabel.setBackground(fInnerContentCircle.getBackground());
        imageLabel.setCursor(imageLabel.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        imageLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
        imageLabel.addMouseTrackListener(fMouseTrackListner);
        imageLabel.setImage(OwlUI.getImage(fResources, item.getImage()));
        imageLabel.setToolTipText(item.getOrigin());

        /* Use a CCLabel per Item */
        final CCLabel itemLabel = new CCLabel(fInnerContentCircle, SWT.NONE);
        itemLabel.setBackground(fInnerContentCircle.getBackground());
        itemLabel.setCursor(itemLabel.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        itemLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        itemLabel.setText(item.getText());
        itemLabel.setFont(item.isRead() ? fNormalTextFont : fBoldTextFont);
        updateItemColor(item, itemLabel);
        itemLabel.addMouseTrackListener(fMouseTrackListner);

        /* Paint text blue on mouse-enter */
        MouseTrackAdapter mouseTrackListener = new MouseTrackAdapter() {

            @Override
            public void mouseEnter(MouseEvent e) {
                if (!OwlUI.isHighContrast())
                    itemLabel.setForeground(itemLabel.getDisplay().getSystemColor(SWT.COLOR_BLUE));
            }

            @Override
            public void mouseExit(MouseEvent e) {
                updateItemColor(item, itemLabel);
            }
        };

        itemLabel.addMouseTrackListener(mouseTrackListener);
        imageLabel.addMouseTrackListener(mouseTrackListener);

        /* Clicked on item to open it */
        MouseAdapter mouseListener = new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {

                /* Close Popup if required after opening */
                if (fGlobalScope.getBoolean(DefaultPreferences.CLOSE_NOTIFIER_ON_OPEN)) {
                    item.open(e);
                    doClose();
                }

                /* Indicate the item is marked as read now and open */
                else {
                    item.setRead(true);
                    itemLabel.setFont(fNormalTextFont);
                    item.open(e);
                }
            }
        };

        itemLabel.addMouseListener(mouseListener);
        imageLabel.addMouseListener(mouseListener);

        /* Offer Label to mark item read */
        CLabel markReadLabel = new CLabel(fInnerContentCircle, SWT.NONE);
        markReadLabel.setImage(item.supportsMarkRead() ? fMarkReadIcon : fMarkReadDisabledIcon);
        markReadLabel.setBackground(fInnerContentCircle.getBackground());
        markReadLabel.addMouseTrackListener(fMouseTrackListner);
        markReadLabel.setEnabled(item.supportsMarkRead());
        markReadLabel.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        markReadLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                boolean newStateRead = !item.isRead();

                /* Update Font */
                itemLabel.setFont(newStateRead ? fNormalTextFont : fBoldTextFont);

                /* Apply state */
                item.setRead(newStateRead);
            }
        });

        /* Offer Label to mark item sticky */
        final CLabel markStickyLabel = new CLabel(fInnerContentCircle, SWT.NONE);
        markStickyLabel.setImage(item.supportsSticky() ? fItemNonStickyIcon : fItemNonStickyDisabledIcon);
        markStickyLabel.setBackground(fInnerContentCircle.getBackground());
        markStickyLabel.setEnabled(item.supportsSticky());
        markStickyLabel.addMouseTrackListener(fMouseTrackListner);
        markStickyLabel.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        markStickyLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                boolean newStateSticky = !item.isSticky();

                /* Update Background Color */
                if (!OwlUI.isHighContrast())
                    itemLabel.setRoundRectangleBackground(newStateSticky ? fStickyBgColor : null);

                /* Update Image */
                markStickyLabel.setImage(newStateSticky ? fItemStickyIcon : fItemNonStickyIcon);

                /* Apply state */
                item.setSticky(newStateSticky);
            }
        });

        /* Show Sticky if required */
        if (item.supportsSticky() && item.isSticky()) {
            if (!OwlUI.isHighContrast())
                itemLabel.setRoundRectangleBackground(fStickyBgColor);
            markStickyLabel.setImage(fItemStickyIcon);
        }

        /* Show excerpt of content if set */
        if (fGlobalScope.getBoolean(DefaultPreferences.SHOW_EXCERPT_IN_NOTIFIER)) {
            String description = item.getDescription();
            if (!StringUtils.isSet(description))
                description = Messages.NotificationPopup_NO_CONTENT;

            final Composite descriptionContainer = new Composite(fInnerContentCircle, SWT.NONE);
            descriptionContainer.setLayout(LayoutUtils.createGridLayout(1));
            ((GridLayout) descriptionContainer.getLayout()).marginBottom = 5;
            descriptionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 4, 1));
            descriptionContainer.addMouseTrackListener(fMouseTrackListner);
            descriptionContainer.setBackground(fInnerContentCircle.getBackground());
            descriptionContainer.addPaintListener(new PaintListener() {
                public void paintControl(PaintEvent e) {
                    GC gc = e.gc;
                    Rectangle clArea = descriptionContainer.getClientArea();

                    gc.setForeground(fNotifierColors.getBorder());
                    gc.setBackground(fNotifierColors.getGradientEnd());
                    gc.fillGradientRectangle(4, 1, clArea.width, 1, false);
                }
            });

            Label descriptionText = new Label(descriptionContainer, SWT.WRAP);
            descriptionText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
            descriptionText.setFont(fNormalTextFont);
            descriptionText.setBackground(fInnerContentCircle.getBackground());
            descriptionText.setText(description);
            descriptionText.addMouseTrackListener(fMouseTrackListner);
        }
    }

    private void initResources() {

        /* Colors */
        fNotifierColors = new NotifierColors(getParentShell().getDisplay(), fResources);
        fStickyBgColor = OwlUI.getThemeColor(OwlUI.STICKY_BG_COLOR_ID, fResources, new RGB(255, 255, 180));

        /* Icons */
        fCloseImageNormal = OwlUI.getImage(fResources, "icons/etool16/close_normal.png"); //$NON-NLS-1$
        fCloseImageHot = OwlUI.getImage(fResources, "icons/etool16/close_hot.png"); //$NON-NLS-1$
        fCloseImagePressed = OwlUI.getImage(fResources, "icons/etool16/close_pressed.png"); //$NON-NLS-1$
        fMarkReadIcon = OwlUI.getImage(fResources, "icons/elcl16/mark_read_light16.gif"); //$NON-NLS-1$
        fMarkReadDisabledIcon = OwlUI.getImage(fResources, "icons/dlcl16/mark_read.gif"); //$NON-NLS-1$
        fItemStickyIcon = OwlUI.getImage(fResources, "icons/obj16/news_pinned_light16.gif"); //$NON-NLS-1$
        fItemNonStickyIcon = OwlUI.getImage(fResources, "icons/obj16/news_pin_light16.gif"); //$NON-NLS-1$
        fItemNonStickyDisabledIcon = OwlUI.getImage(fResources, "icons/obj16/news_pin_disabled.gif"); //$NON-NLS-1$
        fPrevImageNormal = OwlUI.getImage(fResources, "icons/etool16/prev_normal.png"); //$NON-NLS-1$
        fPrevImagePressed = OwlUI.getImage(fResources, "icons/etool16/prev_pressed.png"); //$NON-NLS-1$
        fPrevImageDisabled = OwlUI.getImage(fResources, "icons/etool16/prev_disabled.png"); //$NON-NLS-1$
        fNextImageNormal = OwlUI.getImage(fResources, "icons/etool16/next_normal.png"); //$NON-NLS-1$
        fNextImagePressed = OwlUI.getImage(fResources, "icons/etool16/next_pressed.png"); //$NON-NLS-1$
        fNextImageDisabled = OwlUI.getImage(fResources, "icons/etool16/next_disabled.png"); //$NON-NLS-1$
    }

    /*
     * @see org.eclipse.jface.dialogs.PopupDialog#createContents(org.eclipse.swt.widgets.Composite)
     */
    @Override
    protected Control createContents(Composite parent) {
        fShell = parent.getShell();
        parent.setBackground(fNotifierColors.getBorder());

        return createDialogArea(parent);
    }

    /*
     * @see org.eclipse.jface.dialogs.PopupDialog#createDialogArea(org.eclipse.swt.widgets.Composite)
     */
    @Override
    protected Control createDialogArea(Composite parent) {
        ((GridLayout) parent.getLayout()).marginWidth = 1;
        ((GridLayout) parent.getLayout()).marginHeight = 1;

        /* Outer Compositing holding the controlls */
        final Composite outerCircle = new Composite(parent, SWT.NO_FOCUS);
        outerCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        outerCircle.setLayout(LayoutUtils.createGridLayout(1, 0, 0, 0));

        /* Title area containing label and close button */
        final Composite titleCircle = new Composite(outerCircle, SWT.NO_FOCUS);
        titleCircle.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        titleCircle.setBackgroundMode(SWT.INHERIT_FORCE);
        titleCircle.setLayout(LayoutUtils.createGridLayout(2, 3, 0, 5, 3, false));
        titleCircle.addMouseTrackListener(fMouseTrackListner);
        titleCircle.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Rectangle clArea = titleCircle.getClientArea();
                Image oldBgImage = fTitleBgImage;
                fTitleBgImage = new Image(titleCircle.getDisplay(), clArea.width, clArea.height);
                GC gc = new GC(fTitleBgImage);

                /* Gradient */
                drawGradient(gc, clArea);

                /* Fix Region Shape */
                fixRegion(gc, clArea);

                gc.dispose();

                titleCircle.setBackgroundImage(fTitleBgImage);

                if (oldBgImage != null)
                    oldBgImage.dispose();
            }

            private void drawGradient(GC gc, Rectangle clArea) {
                gc.setForeground(fNotifierColors.getGradientBegin());
                gc.setBackground(fNotifierColors.getGradientEnd());
                gc.fillGradientRectangle(clArea.x, clArea.y, clArea.width, clArea.height, true);
            }

            private void fixRegion(GC gc, Rectangle clArea) {
                gc.setForeground(fNotifierColors.getBorder());

                /* Fill Top Left */
                gc.drawPoint(2, 0);
                gc.drawPoint(3, 0);
                gc.drawPoint(1, 1);
                gc.drawPoint(0, 2);
                gc.drawPoint(0, 3);

                /* Fill Top Right */
                gc.drawPoint(clArea.width - 4, 0);
                gc.drawPoint(clArea.width - 3, 0);
                gc.drawPoint(clArea.width - 2, 1);
                gc.drawPoint(clArea.width - 1, 2);
                gc.drawPoint(clArea.width - 1, 3);
            }
        });

        /* Title Label displaying RSSOwl */
        fTitleCircleLabel = new CLabel(titleCircle, SWT.NO_FOCUS);
        fTitleCircleLabel.setImage(OwlUI.getImage(fResources, "icons/product/24x24.png")); //$NON-NLS-1$
        fTitleCircleLabel.setText("RSSOwl"); //$NON-NLS-1$
        fTitleCircleLabel.setFont(fBoldTextFont);
        fTitleCircleLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
        fTitleCircleLabel.addMouseTrackListener(fMouseTrackListner);
        fTitleCircleLabel.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        fTitleCircleLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                doRestore();
            }
        });

        /* CLabel to display a cross to close the popup */
        final CLabel closeButton = new CLabel(titleCircle, SWT.NO_FOCUS);
        closeButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
        closeButton.setImage(fCloseImageNormal);
        closeButton.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        closeButton.addMouseTrackListener(fMouseTrackListner);
        closeButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                doClose();
            }

            @Override
            public void mouseDown(MouseEvent e) {
                closeButton.setImage(fCloseImagePressed);
            }
        });

        closeButton.addMouseTrackListener(new MouseTrackAdapter() {
            @Override
            public void mouseEnter(MouseEvent e) {
                closeButton.setImage(fCloseImageHot);
            }

            @Override
            public void mouseExit(MouseEvent e) {
                closeButton.setImage(fCloseImageNormal);
            }
        });

        /* Outer composite to hold content controlls */
        fOuterContentCircle = new Composite(outerCircle, SWT.NONE);
        fOuterContentCircle.setLayout(LayoutUtils.createGridLayout(1, 0, 0));
        fOuterContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        fOuterContentCircle.setBackground(outerCircle.getBackground());

        /* Middle composite to show a 1px black line around the content controlls */
        Composite middleContentCircle = new Composite(fOuterContentCircle, SWT.NO_FOCUS);
        middleContentCircle.setLayout(LayoutUtils.createGridLayout(1, 0, 0));
        ((GridLayout) middleContentCircle.getLayout()).marginTop = 1;
        ((GridLayout) middleContentCircle.getLayout()).marginBottom = 1;
        middleContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        middleContentCircle.setBackground(fNotifierColors.getBorder());

        /* Inner composite containing the content controlls */
        fInnerContentCircle = new Composite(middleContentCircle, SWT.NO_FOCUS);
        fInnerContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        fInnerContentCircle.setLayout(LayoutUtils.createGridLayout(4, 0, 5, 0, 0, false));
        ((GridLayout) fInnerContentCircle.getLayout()).marginLeft = 5;
        ((GridLayout) fInnerContentCircle.getLayout()).marginRight = 2;
        fInnerContentCircle.addMouseTrackListener(fMouseTrackListner);
        fInnerContentCircle.setBackground(fShell.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));

        /* Footer Area containing navigational controls */
        final Composite footerCircle = new Composite(outerCircle, SWT.NO_FOCUS);
        footerCircle.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        footerCircle.setBackgroundMode(SWT.INHERIT_FORCE);
        footerCircle.setLayout(LayoutUtils.createGridLayout(3, 3, 0, 5, 3, false));
        footerCircle.addMouseTrackListener(fMouseTrackListner);
        footerCircle.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Rectangle clArea = footerCircle.getClientArea();
                Image oldBgImage = fFooterBgImage;
                fFooterBgImage = new Image(footerCircle.getDisplay(), clArea.width, clArea.height);
                GC gc = new GC(fFooterBgImage);

                /* Gradient */
                drawGradient(gc, clArea);

                /* Fix Region Shape */
                fixRegion(gc, clArea);

                gc.dispose();

                footerCircle.setBackgroundImage(fFooterBgImage);

                if (oldBgImage != null)
                    oldBgImage.dispose();
            }

            private void drawGradient(GC gc, Rectangle clArea) {
                gc.setBackground(fNotifierColors.getGradientBegin());
                gc.setForeground(fNotifierColors.getGradientEnd());
                gc.fillGradientRectangle(clArea.x, clArea.y, clArea.width, clArea.height, true);
            }

            private void fixRegion(GC gc, Rectangle clArea) {
                gc.setForeground(fNotifierColors.getBorder());

                /* Fill Bottom Left */
                gc.drawPoint(2, clArea.height - 0);
                gc.drawPoint(3, clArea.height - 0);
                gc.drawPoint(1, clArea.height - 1);
                gc.drawPoint(0, clArea.height - 2);
                gc.drawPoint(0, clArea.height - 3);

                /* Fill Bottom Right */
                gc.drawPoint(clArea.width - 4, clArea.height - 0);
                gc.drawPoint(clArea.width - 3, clArea.height - 0);
                gc.drawPoint(clArea.width - 2, clArea.height - 1);
                gc.drawPoint(clArea.width - 1, clArea.height - 2);
                gc.drawPoint(clArea.width - 1, clArea.height - 3);
            }
        });

        /* Title Label displaying RSSOwl */
        fFooterCircleLabel = new CLabel(footerCircle, SWT.NO_FOCUS);
        fFooterCircleLabel.setFont(fBoldTextFont);
        fFooterCircleLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));

        /* Nav to previous News */
        fPrevButton = new CLabel(footerCircle, SWT.NO_FOCUS);
        fPrevButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, true));
        fPrevButton.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        fPrevButton.addMouseTrackListener(fMouseTrackListner);
        fPrevButton.setImage(fPrevImageDisabled);
        fPrevButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                onNavPrevious();
            }

            @Override
            public void mouseDown(MouseEvent e) {
                if (canNavPrevious())
                    fPrevButton.setImage(fPrevImagePressed);
            }
        });

        /* Nav to next News */
        fNextButton = new CLabel(footerCircle, fMode == Mode.INCOMING_AUTOMATIC ? SWT.NO_FOCUS : SWT.NONE);
        fNextButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, true));
        fNextButton.addMouseTrackListener(fMouseTrackListner);
        fNextButton.setCursor(fShell.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
        fNextButton.setImage(fNextImageDisabled);
        fNextButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                onNavNext();
            }

            @Override
            public void mouseDown(MouseEvent e) {
                if (canNavNext())
                    fNextButton.setImage(fNextImagePressed);
            }
        });

        /* Simple Keybinding Support */
        KeyAdapter keyListener = new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                handleUserAction();

                if (e.keyCode == SWT.ARROW_LEFT || e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.PAGE_UP)
                    onNavPrevious();
                else if (e.keyCode == SWT.ARROW_RIGHT || e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.PAGE_DOWN)
                    onNavNext();
                else if (e.keyCode == SWT.HOME)
                    onNavFirstPage();
                else if (e.keyCode == SWT.END)
                    onNavLastPage();
                else if (e.keyCode == ' ')
                    onNavNextAndMarkRead();
                else if (e.keyCode == SWT.CR)
                    doRestore();
                else if (e.keyCode == SWT.ESC)
                    doClose();
            }
        };

        if (fMode == Mode.INCOMING_AUTOMATIC)
            fShell.addKeyListener(keyListener);
        else
            fNextButton.addKeyListener(keyListener);

        /* Allow to navigate with Mouse Wheel Too */
        fShell.addMouseWheelListener(new MouseWheelListener() {
            public void mouseScrolled(MouseEvent e) {
                handleUserAction();

                if (e.count < 0)
                    onNavNext();
                else if (e.count > 0)
                    onNavPrevious();
            }
        });

        /* Close Shell once deactivated and not incoming automatic */
        fShell.addListener(SWT.Deactivate, new Listener() {
            public void handleEvent(Event event) {
                if (fMode != Mode.INCOMING_AUTOMATIC
                        && fGlobalScope.getBoolean(DefaultPreferences.CLOSE_NOTIFIER_ON_OPEN))
                    doClose();
            }
        });

        return outerCircle;
    }

    private void handleUserAction() {
        if (fMouseOverNotifier || fGlobalScope.getBoolean(DefaultPreferences.STICKY_NOTIFICATION_POPUP))
            return;

        fAutoCloser.cancel();
        fAutoCloser.schedule(fGlobalScope.getInteger(DefaultPreferences.AUTOCLOSE_NOTIFICATION_VALUE) * 1000);
    }

    /*
     * @see org.eclipse.jface.dialogs.PopupDialog#getFocusControl()
     */
    @Override
    protected Control getFocusControl() {
        return fNextButton;
    }

    private void onNavFirstPage() {
        int newOffset = 0;
        fDisplayOffset = newOffset;
        updateTitleLabel();
        updateContents(newOffset);
    }

    private void onNavLastPage() {
        int newOffset;
        if (fDisplayedItems.size() % fItemLimit == 0)
            newOffset = fDisplayedItems.size() - fItemLimit;
        else
            newOffset = fDisplayedItems.size() - (fDisplayedItems.size() % fItemLimit);

        if (newOffset >= 0) {
            fDisplayOffset = newOffset;
            updateTitleLabel();
            updateContents(newOffset);
        }
    }

    private boolean onNavPrevious() {
        int newOffset = fDisplayOffset - fItemLimit;
        if (newOffset >= 0) {
            fDisplayOffset = newOffset;
            updateTitleLabel();
            updateContents(newOffset);

            return true;
        }

        return false;
    }

    private boolean onNavNext() {
        int newOffset = fDisplayOffset + fItemLimit;
        if (newOffset < fDisplayedItems.size()) {
            fDisplayOffset = newOffset;
            updateTitleLabel();
            updateContents(newOffset);

            return true;
        }

        return false;
    }

    private void onNavNextAndMarkRead() {
        for (int i = fDisplayOffset; i < fDisplayOffset + fItemLimit && i < fDisplayedItems.size(); i++) {
            NotificationItem item = fDisplayedItems.get(i);
            item.setRead(true);
        }

        boolean navigated = onNavNext();
        if (!navigated)
            doClose();
    }

    private void updateNavButtons() {
        boolean isPrevEnabled = canNavPrevious();
        boolean isNextEnabled = canNavNext();

        fPrevButton.setImage(isPrevEnabled ? fPrevImageNormal : fPrevImageDisabled);
        fNextButton.setImage(isNextEnabled ? fNextImageNormal : fNextImageDisabled);
    }

    private boolean canNavNext() {
        return fDisplayOffset + fItemLimit < fDisplayedItems.size();
    }

    private boolean canNavPrevious() {
        return fDisplayOffset - fItemLimit >= 0;
    }

    /*
     * @see org.eclipse.jface.dialogs.PopupDialog#getInitialLocation(org.eclipse.swt.graphics.Point)
     */
    @Override
    protected Point getInitialLocation(Point initialSize) {
        Rectangle clArea = getPrimaryClientArea();

        return new Point(clArea.width + clArea.x - initialSize.x, clArea.height + clArea.y - initialSize.y);
    }

    /*
     * @see org.eclipse.jface.dialogs.PopupDialog#getInitialSize()
     */
    @Override
    protected Point getInitialSize() {
        int initialHeight = fShell.computeSize(DEFAULT_WIDTH, SWT.DEFAULT).y;

        return new Point(DEFAULT_WIDTH, initialHeight + fVisibleItemsCount * CLABEL_HEIGHT);
    }

    /**
     * Get the Client Area of the primary Monitor.
     *
     * @return Returns the Client Area of the primary Monitor.
     */
    private Rectangle getPrimaryClientArea() {
        Monitor primaryMonitor = fShell.getDisplay().getPrimaryMonitor();
        return (primaryMonitor != null) ? primaryMonitor.getClientArea() : fShell.getDisplay().getClientArea();
    }

    /*
     * @see org.eclipse.jface.dialogs.PopupDialog#close()
     */
    @Override
    public boolean close() {

        /*
         * This method is overriden because the parent PopupDialog class automatically
         * closes the popup when the Shell is deactivated.
         * We want to leave the popup open in this case though for stability reasons.
         */
        return false;
    }

    /**
     * The actual implementation of close().
     *
     * @return <code>true</code> if the window is (or was already) closed, and
     * <code>false</code> if it is still open
     */
    protected boolean doClose() {
        fResources.dispose();
        if (fLastUsedRegion != null)
            fLastUsedRegion.dispose();
        if (fTitleBgImage != null)
            fTitleBgImage.dispose();
        if (fFooterBgImage != null)
            fFooterBgImage.dispose();
        if (fInitialItems != null)
            fInitialItems.clear();

        return super.close();
    }

    /* Restore RSSOwl and Close the Notifier */
    private void doRestore() {
        IWorkbenchPage page = OwlUI.getPage();
        if (page != null) {

            /* Restore Window */
            OwlUI.restoreWindow(page);

            /* Close Notifier */
            doClose();
        }
    }

    private boolean shouldFadeIn() {
        return fMode == Mode.INCOMING_AUTOMATIC && FADE_ENABLED && fgFadeSupported;
    }
}