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

Java tutorial

Introduction

Here is the source code for org.rssowl.ui.internal.OwlUI.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.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IControlContentAdapter;
import org.eclipse.jface.fieldassist.SimpleContentProposalProvider;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.ColorDescriptor;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.DeviceResourceException;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.DragDetectEvent;
import org.eclipse.swt.events.DragDetectListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Drawable;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
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.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
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.FileDialog;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ITheme;
import org.rssowl.core.Owl;
import org.rssowl.core.connection.MonitorCanceledException;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.IMark;
import org.rssowl.core.persist.INewsMark;
import org.rssowl.core.persist.IPreference;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.IFolderDAO;
import org.rssowl.core.persist.dao.IPreferenceDAO;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.reference.FolderReference;
import org.rssowl.core.persist.service.PersistenceException;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.Pair;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.SyncUtils;
import org.rssowl.ui.internal.dialogs.CustomWizardDialog;
import org.rssowl.ui.internal.dialogs.LoginDialog;
import org.rssowl.ui.internal.editors.browser.WebBrowserInput;
import org.rssowl.ui.internal.editors.browser.WebBrowserView;
import org.rssowl.ui.internal.editors.feed.FeedView;
import org.rssowl.ui.internal.editors.feed.FeedViewInput;
import org.rssowl.ui.internal.editors.feed.PerformAfterInputSet;
import org.rssowl.ui.internal.util.ContentAssistAdapter;
import org.rssowl.ui.internal.util.EditorUtils;
import org.rssowl.ui.internal.views.explorer.BookMarkExplorer;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Central Facade for UI-related tasks.
 *
 * @author bpasero
 */
public class OwlUI {

    /** Top-Level Menu ID for "Tools" */
    public static final String M_TOOLS = "tools"; //$NON-NLS-1$

    /** Top-Level Menu ID for "Mark" */
    public static final String M_MARK = "mark"; //$NON-NLS-1$

    /** Top-Level Menu ID for "Open" */
    public static final String M_OPEN = "open"; //$NON-NLS-1$

    /** Default */
    public static final ImageDescriptor UNKNOWN = Activator.getImageDescriptor("icons/obj16/default.gif"); //$NON-NLS-1$

    /** Folder */
    public static final ImageDescriptor FOLDER = Activator.getImageDescriptor("icons/obj16/folder.gif"); //$NON-NLS-1$

    /** Folder with new News */
    public static final ImageDescriptor FOLDER_NEW = Activator.getImageDescriptor("icons/obj16/folder_new.gif"); //$NON-NLS-1$

    /** Bookmark Set */
    public static final ImageDescriptor BOOKMARK_SET = Activator.getImageDescriptor("icons/obj16/bkmrk_set.gif"); //$NON-NLS-1$

    /** BookMark */
    public static final ImageDescriptor BOOKMARK = Activator.getImageDescriptor("icons/obj16/bookmark.gif"); //$NON-NLS-1$

    /** BookMark (Error) */
    public static final ImageDescriptor BOOKMARK_ERROR = Activator
            .getImageDescriptor("icons/obj16/bkmrk_error.gif"); //$NON-NLS-1$

    /** NewsBin */
    public static final ImageDescriptor NEWSBIN = Activator.getImageDescriptor("icons/obj16/newsbin.gif"); //$NON-NLS-1$

    /** NewsBin (New) */
    public static final ImageDescriptor NEWSBIN_NEW = Activator.getImageDescriptor("icons/obj16/newsbin_new.gif"); //$NON-NLS-1$

    /** NewsBin (Empty) */
    public static final ImageDescriptor NEWSBIN_EMPTY = Activator
            .getImageDescriptor("icons/obj16/newsbin_empty.gif"); //$NON-NLS-1$

    /** SearchMark */
    public static final ImageDescriptor SEARCHMARK = Activator.getImageDescriptor("icons/obj16/searchmark.gif"); //$NON-NLS-1$

    /** SearchMark (New) */
    public static final ImageDescriptor SEARCHMARK_NEW = Activator
            .getImageDescriptor("icons/obj16/searchmark_new.gif"); //$NON-NLS-1$

    /** SearchMark (Empty) */
    public static final ImageDescriptor SEARCHMARK_EMPTY = Activator
            .getImageDescriptor("icons/obj16/searchmark_empty.gif"); //$NON-NLS-1$

    /** Group */
    public static final ImageDescriptor GROUP = Activator.getImageDescriptor("icons/obj16/group.gif"); //$NON-NLS-1$

    /** News: Unread */
    public static final ImageDescriptor NEWS_STATE_UNREAD = Activator
            .getImageDescriptor("icons/obj16/news_unread.gif"); //$NON-NLS-1$

    /** News: Read */
    public static final ImageDescriptor NEWS_STATE_READ = Activator.getImageDescriptor("icons/obj16/news_read.gif"); //$NON-NLS-1$

    /** News: New */
    public static final ImageDescriptor NEWS_STATE_NEW = Activator.getImageDescriptor("icons/obj16/news_new.gif"); //$NON-NLS-1$

    /** News: Updated */
    public static final ImageDescriptor NEWS_STATE_UPDATED = Activator
            .getImageDescriptor("icons/obj16/news_updated.gif"); //$NON-NLS-1$

    /** News: Pin */
    public static final ImageDescriptor NEWS_PIN = Activator.getImageDescriptor("icons/obj16/news_pin.gif"); //$NON-NLS-1$

    /** News: Pinned */
    public static final ImageDescriptor NEWS_PINNED = Activator.getImageDescriptor("icons/obj16/news_pinned.gif"); //$NON-NLS-1$

    /** Tray Icon: Not Teasing */
    public static final ImageDescriptor TRAY_OWL = Activator.getImageDescriptor("icons/elcl16/trayowl.png"); //$NON-NLS-1$

    /** Tray Icon: Teasing */
    public static final ImageDescriptor TRAY_OWL_TEASING = Activator
            .getImageDescriptor("icons/elcl16/trayowl_tease.png"); //$NON-NLS-1$

    /** Info */
    public static final ImageDescriptor INFO = Activator.getImageDescriptor("icons/obj16/info.gif"); //$NON-NLS-1$

    /** Warning */
    public static final ImageDescriptor WARNING = Activator.getImageDescriptor("icons/obj16/warning.gif"); //$NON-NLS-1$

    /** Error */
    public static final ImageDescriptor ERROR = Activator.getImageDescriptor("icons/obj16/error.gif"); //$NON-NLS-1$

    /** Attachment */
    public static final ImageDescriptor ATTACHMENT = Activator.getImageDescriptor("icons/obj16/attachment.gif"); //$NON-NLS-1$

    /** Columns */
    public static final ImageDescriptor COLUMNS = Activator.getImageDescriptor("icons/etool16/columns.gif"); //$NON-NLS-1$

    /** Share */
    public static final ImageDescriptor SHARE = Activator.getImageDescriptor("icons/elcl16/share.gif"); //$NON-NLS-1$

    /** Filter */
    public static final ImageDescriptor FILTER = Activator.getImageDescriptor("icons/etool16/filter.gif"); //$NON-NLS-1$

    /** Archive */
    public static final ImageDescriptor ARCHIVE = Activator.getImageDescriptor("icons/etool16/archive.gif"); //$NON-NLS-1$

    /** Archive (New) */
    public static final ImageDescriptor ARCHIVE_NEW = Activator.getImageDescriptor("icons/obj16/archive_new.gif"); //$NON-NLS-1$

    /** Archive (Disabled) */
    public static final ImageDescriptor ARCHIVE_DISABLED = Activator
            .getImageDescriptor("icons/dtool16/archive.gif"); //$NON-NLS-1$

    /** Group Foreground Color */
    public static final RGB GROUP_FG_COLOR = new RGB(0, 0, 128);

    /** Group Background Color (non Custom Owner Drawn) */
    public static final RGB GROUP_BG_COLOR = new RGB(235, 235, 235);

    /** Group Gradient Foreground Color */
    public static final RGB GROUP_GRADIENT_FG_COLOR = new RGB(250, 250, 250);

    /** Group Gradient Background Color */
    public static final RGB GROUP_GRADIENT_BG_COLOR = new RGB(220, 220, 220);

    /** Group Gradient End Color */
    public static final RGB GROUP_GRADIENT_END_COLOR = new RGB(200, 200, 200);

    /** Minimum width of Dialogs in Dialog Units */
    public static final int MIN_DIALOG_WIDTH_DLU = 320;

    /** News-Text Font Id */
    public static final String NEWS_TEXT_FONT_ID = "org.rssowl.ui.NewsTextFont"; //$NON-NLS-1$

    /** Headlines Font Id */
    public static final String HEADLINES_FONT_ID = "org.rssowl.ui.HeadlinesFont"; //$NON-NLS-1$

    /** BookMark Explorer Font Id */
    public static final String BKMRK_EXPLORER_FONT_ID = "org.rssowl.ui.BookmarkExplorerFont"; //$NON-NLS-1$

    /** Notification Popup Font Id */
    public static final String NOTIFICATION_POPUP_FONT_ID = "org.rssowl.ui.NotificationPopupFont"; //$NON-NLS-1$

    /** Dialog Font Id */
    public static final String DIALOG_FONT_ID = "org.eclipse.jface.dialogfont"; //$NON-NLS-1$

    /** Sticky Background Color */
    public static final String STICKY_BG_COLOR_ID = "org.rssowl.ui.StickyBGColor"; //$NON-NLS-1$

    /** Search Highlight Background Color */
    public static final String SEARCH_HIGHLIGHT_BG_COLOR_ID = "org.rssowl.ui.SearchHighlightBGColor"; //$NON-NLS-1$

    /** News Background Color */
    public static final String NEWS_LIST_BG_COLOR_ID = "org.rssowl.ui.NewsListBackgroundColor"; //$NON-NLS-1$

    /** Link Color */
    public static final String LINK_FG_COLOR_ID = "org.rssowl.ui.LinkFGColor"; //$NON-NLS-1$

    /* ID of the High Contrast Theme */
    private static final String HIGH_CONTRAST_THEME = "org.eclipse.ui.ide.systemDefault"; //$NON-NLS-1$

    /* Used to cache Image-Descriptors for Favicons */
    private static final Map<Long, ImageDescriptor> FAVICO_CACHE = new HashMap<Long, ImageDescriptor>();

    /* Used to cache Image-Descriptors obtained from a file-path */
    private static final Map<String, ImageDescriptor> DESCRIPTOR_CACHE = new HashMap<String, ImageDescriptor>();

    /* Used to cache the path of Images used in the embedded Browser */
    private static final Map<String, String> fgImageUriMap = new ConcurrentHashMap<String, String>();

    /* Name of Folder for storing Icons */
    private static final String ICONS_FOLDER = "icons"; //$NON-NLS-1$

    /* Shared Clipboard instance */
    private static Clipboard fgClipboard;

    /* Cache the OSTheme once retrieved */
    private static OSTheme fgCachedOSTheme;

    /* Workaround for unknown Date Width */
    private static int DATE_WIDTH = -1;

    /* Workaround for unknown State Width */
    private static int STATE_WIDTH = -1;

    /* Default News Text Font Height */
    private static final int DEFAULT_NEWS_TEXT_FONT_HEIGHT = 10;

    /* System Properties for Date Format */
    private static final String SHORT_DATE_FORMAT_PROPERTY = "shortDateFormat"; //$NON-NLS-1$
    private static final String LONG_DATE_FORMAT_PROPERTY = "longDateFormat"; //$NON-NLS-1$
    private static final String SHORT_TIME_FORMAT_PROPERTY = "shortTimeFormat"; //$NON-NLS-1$

    /* Packed Wizard Width per OS (in DLUs) */
    private static final int WINDOWS_PACKED_WIZARD_WIDTH = 380;
    private static final int LINUX_PACKED_WIZARD_WIDTH = 370;
    private static final int MAC_PACKED_WIZARD_WIDTH = 300;

    /* Map Common Label Colors to RGB Values */
    private static final Map<String, RGB> fgMapCommonColorToRGB = new HashMap<String, RGB>();

    /* Map Common Mime Types to Extensions (used for Attachments) */
    private static final Map<String, String> fgMapMimeToExtension = new HashMap<String, String>();
    static {

        /* Audio */
        fgMapMimeToExtension.put("audio/mpeg", "mp3"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("audio/mpeg3", "mp3"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("audio/x-mpeg3", "mp3"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("audio/mpeg4", "mp4"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("audio/x-mpeg4", "mp4"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("audio/aac", "aac"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("audio/aacp", "aac"); //$NON-NLS-1$ //$NON-NLS-2$

        /* Image */
        fgMapMimeToExtension.put("image/bmp", "bmp"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("image/x-windows-bmp", "bmp"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("image/gif", "gif"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("image/jpeg", "jpg"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("image/pjpeg", "jpg"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("image/png", "png"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("image/x-quicktime", "qti"); //$NON-NLS-1$ //$NON-NLS-2$

        /* Video */
        fgMapMimeToExtension.put("video/x-ms-asf", "asd"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("application/x-troff-msvideo", "avi"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("video/avi", "avi"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("video/msvideo", "avi"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("video/x-msvideo", "avi"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("video/x-flv", "flv"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("video/quicktime", "mov"); //$NON-NLS-1$ //$NON-NLS-2$

        /* Application */
        fgMapMimeToExtension.put("application/msword", "doc"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("application/pdf", "pdf"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("application/rtf", "rtf"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("text/richtext", "rtf"); //$NON-NLS-1$ //$NON-NLS-2$
        fgMapMimeToExtension.put("application/x-rtf", "rtf"); //$NON-NLS-1$ //$NON-NLS-2$

        /* Common Colors to RGB */
        fgMapCommonColorToRGB.put("0,0,0", new RGB(0, 0, 0)); // "Black",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("124,10,2", new RGB(124, 10, 2)); // "Barn Red",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("163,21,2", new RGB(163, 21, 2)); // "Salem Red",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("214,148,99", new RGB(214, 148, 99)); // "Salmon",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("200,118,10", new RGB(200, 118, 10)); // "Pumpkin",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("240,177,12", new RGB(240, 177, 12)); // "Marigold Yellow",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("209,161,17", new RGB(209, 161, 17)); // "Mustard",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("136,128,54", new RGB(136, 128, 54)); // "Bayberry Green",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("129,150,93", new RGB(129, 150, 93)); // "Tavern Green",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("82,92,58", new RGB(82, 92, 58)); // "Lexington Green",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("126,135,130", new RGB(126, 135, 130)); // "Sea Green",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("111,121,174", new RGB(111, 121, 174)); // "Federal Blue",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("92,101,126", new RGB(92, 101, 126)); // "Soldier Blue",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("144,152,163", new RGB(144, 152, 163)); // "Slate",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("25,16,17", new RGB(25, 16, 17)); // "Pitch Black",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("82,66,41", new RGB(82, 66, 41)); // "Driftwood",//$NON-NLS-1$
        fgMapCommonColorToRGB.put("82,16,0", new RGB(82, 16, 0)); // "Chocolate Brown" //$NON-NLS-1$
        fgMapCommonColorToRGB.put("255,0,0", new RGB(255, 0, 0)); // "Red" //$NON-NLS-1$
        fgMapCommonColorToRGB.put("0,255,0", new RGB(0, 255, 0)); // "Greeen" //$NON-NLS-1$
        fgMapCommonColorToRGB.put("0,0,255", new RGB(0, 0, 255)); // "Blue" //$NON-NLS-1$
    }

    /** An enumeration of Operating System Themes */
    public enum OSTheme {

        /** Windows XP Blue */
        WINDOWS_BLUE,

        /** Windows XP Silver */
        WINDOWS_SILVER,

        /** Windows XP Olive */
        WINDOWS_OLIVE,

        /** Windows Classic */
        WINDOWS_CLASSIC,

        /** High Contrast */
        HIGH_CONTRAST,

        /** Any other Theme */
        OTHER
    }

    /** An enumeration of Open Modes when opening something in the Feed View */
    public enum FeedViewOpenMode {

        /** Force to Activate the Feed */
        FORCE_ACTIVATE,

        /** Ignore Feed if already opened */
        IGNORE_ALREADY_OPENED,

        /** Ignore Tab reuse for Feeds */
        IGNORE_REUSE;
    }

    /** Supported Feedview Layouts */
    public enum Layout {
        CLASSIC(Messages.OwlUI_CLASSIC_LAYOUT), VERTICAL(Messages.OwlUI_VERTICAL_LAYOUT), LIST(
                Messages.OwlUI_LIST_LAYOUT), NEWSPAPER(
                        Messages.OwlUI_NEWSPAPER_LAYOUT), HEADLINES(Messages.OwlUI_HEADLINES_LAYOUT);

        private final String fName;

        private Layout(String name) {
            fName = name;
        }

        /**
         * @return the name of this layout option.
         */
        public String getName() {
            return fName;
        }
    }

    /** Supported Page Sizes for Newspaper/Headlines Layout */
    public enum PageSize {
        TEN(Messages.OwlUI_T_ARTICLES, 10), TWENTY_FIVE(Messages.OwlUI_TF_ARTICLES, 25), FIFTY(
                Messages.OwlUI_F_ARTICLES,
                50), HUNDRED(Messages.OwlUI_H_ARTICLES, 100), NO_PAGING(Messages.OwlUI_ALL_ARTICLES, 0);

        private final String fName;
        private final int fPageSize;

        private PageSize(String name, int pageSize) {
            fName = name;
            fPageSize = pageSize;
        }

        /**
         * @return the name of the page size option.
         */
        public String getName() {
            return fName;
        }

        /**
         * @return the page size.
         */
        public int getPageSize() {
            return fPageSize;
        }

        /**
         * @param pageSize the configured page size.
         * @return the matching {@link PageSize} value from the enum or
         * <code>NO_PAGING</code> if none.
         */
        public static PageSize from(int pageSize) {
            switch (pageSize) {
            case 10:
                return TEN;
            case 25:
                return TWENTY_FIVE;
            case 50:
                return FIFTY;
            case 100:
                return HUNDRED;
            }

            return NO_PAGING;
        }
    }

    /* Helper to ensure favicons cause no errors if corrupt */
    private static class FavIconImageDescriptor extends ImageDescriptor {
        private final ImageDescriptor fDescriptor;
        private final File fFaviconFile;

        private FavIconImageDescriptor(File faviconFile, ImageDescriptor descriptor) {
            Assert.isNotNull(faviconFile);
            Assert.isNotNull(descriptor);
            fFaviconFile = faviconFile;
            fDescriptor = descriptor;
        }

        /*
         * @see org.eclipse.jface.resource.ImageDescriptor#getImageData()
         */
        @Override
        public ImageData getImageData() {
            return fDescriptor.getImageData();
        }

        /*
         * @see org.eclipse.jface.resource.ImageDescriptor#createImage(boolean, org.eclipse.swt.graphics.Device)
         */
        @Override
        public Image createImage(boolean returnMissingImageOnError, Device device) {
            try {
                return internalCreateImage(returnMissingImageOnError, device);
            } catch (SWTException e) {
                //Fallback to default Image
            } catch (SWTError error) {
                //Fallback to default Image
            }

            return BOOKMARK.createImage(returnMissingImageOnError, device);
        }

        private Image internalCreateImage(boolean returnMissingImageOnError, Device device) {
            try {
                if (Application.IS_LINUX) //Use native loading on Linux to support alpha in ICO
                    return new Image(device, fFaviconFile.toString());

                ImageLoader loader = new ImageLoader();
                ImageData[] datas = loader.load(fFaviconFile.toString());
                if (datas != null && datas.length > 0)
                    return new Image(device, datas[0]);
            } catch (SWTException e) {
                //Fallback to alternative method to load Image
            } catch (SWTError error) {
                //Fallback to alternative method to load Image
            }

            return fDescriptor.createImage(returnMissingImageOnError, device);
        }

        /*
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            return fDescriptor.equals(obj);
        }

        /*
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return fDescriptor.hashCode();
        }

        /*
         * @see org.eclipse.jface.resource.ImageDescriptor#destroyResource(java.lang.Object)
         */
        @Override
        public void destroyResource(Object previouslyCreatedObject) {
            fDescriptor.destroyResource(previouslyCreatedObject);
        }
    }

    /**
     * Returns the <code>OSTheme</code> that is currently being used.
     *
     * @param display An instance of the SWT <code>Display</code> used for
     * determining the used theme.
     * @return Returns the <code>OSTheme</code> that is currently being used.
     */
    public static OSTheme getOSTheme(Display display) {

        /* Check Cached version first */
        if (fgCachedOSTheme != null)
            return fgCachedOSTheme;

        ITheme currentTheme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
        if (HIGH_CONTRAST_THEME.equals(currentTheme.getId())) {
            fgCachedOSTheme = OSTheme.HIGH_CONTRAST;
            return fgCachedOSTheme;
        }

        RGB widgetBackground = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND).getRGB();
        RGB listSelection = display.getSystemColor(SWT.COLOR_LIST_SELECTION).getRGB();

        /* Theme: Windows Blue */
        if (widgetBackground.equals(new RGB(236, 233, 216)) && listSelection.equals(new RGB(49, 106, 197)))
            fgCachedOSTheme = OSTheme.WINDOWS_BLUE;

        /* Theme: Windows Classic */
        else if (widgetBackground.equals(new RGB(212, 208, 200)) && listSelection.equals(new RGB(10, 36, 106)))
            fgCachedOSTheme = OSTheme.WINDOWS_CLASSIC;

        /* Theme: Windows Silver */
        else if (widgetBackground.equals(new RGB(224, 223, 227)) && listSelection.equals(new RGB(178, 180, 191)))
            fgCachedOSTheme = OSTheme.WINDOWS_SILVER;

        /* Theme: Windows Olive */
        else if (widgetBackground.equals(new RGB(236, 233, 216)) && listSelection.equals(new RGB(147, 160, 112)))
            fgCachedOSTheme = OSTheme.WINDOWS_OLIVE;

        /* Any other Theme */
        else
            fgCachedOSTheme = OSTheme.OTHER;

        return fgCachedOSTheme;
    }

    /**
     * @return <code>true</code> if the display settings is set to high contrast
     * mode and <code>false</code> otherwise.
     */
    public static boolean isHighContrast() {
        return getOSTheme(Display.getDefault()) == OSTheme.HIGH_CONTRAST;
    }

    /**
     * Get the shared instance of <code>Clipboard</code>.
     *
     * @return the shared instance of <code>Clipboard</code>.
     */
    public static Clipboard getClipboard() {
        return getClipboard(PlatformUI.getWorkbench().getDisplay());
    }

    /**
     * Get the shared instance of <code>Clipboard</code>.
     *
     * @param display the {@link Display} the clipboard is operating on.
     * @return the shared instance of <code>Clipboard</code>.
     */
    public static Clipboard getClipboard(Display display) {
        if (fgClipboard == null)
            fgClipboard = new Clipboard(display);

        return fgClipboard;
    }

    /**
     * @param path
     * @return ImageDescriptor
     */
    public static ImageDescriptor getImageDescriptor(String path) {
        return getImageDescriptor(Activator.PLUGIN_ID, path);
    }

    /**
     * @param pluginId
     * @param path
     * @return ImageDescriptor
     */
    public static ImageDescriptor getImageDescriptor(String pluginId, String path) {
        ImageDescriptor desc = DESCRIPTOR_CACHE.get(pluginId + path);
        if (desc == null) {
            desc = Activator.getImageDescriptor(pluginId, path);
            DESCRIPTOR_CACHE.put(pluginId + path, desc);
        }

        return desc;
    }

    /**
     * @param manager
     * @param descriptor
     * @return Image
     */
    public static Image getImage(ResourceManager manager, ImageDescriptor descriptor) {
        try {
            return manager.createImage(descriptor);
        } catch (DeviceResourceException e) {
            return getDefaultImage(manager);
        } catch (SWTException e) {
            return getDefaultImage(manager);
        }
    }

    /* Returns the default Image or NULL if unable to create */
    private static Image getDefaultImage(ResourceManager manager) {
        try {
            return manager.createImage(UNKNOWN);
        } catch (DeviceResourceException e1) {
            return null; // Should not happen
        }
    }

    /**
     * @param manager
     * @param path
     * @return Image
     */
    public static Image getImage(ResourceManager manager, String path) {
        return getImage(manager, getImageDescriptor(path));
    }

    /**
     * @param owner
     * @param path
     * @return Image
     */
    public static Image getImage(Control owner, String path) {
        LocalResourceManager manager = new LocalResourceManager(JFaceResources.getResources(), owner);
        return getImage(manager, path);
    }

    /**
     * @param owner
     * @param descriptor
     * @return Image
     */
    public static Image getImage(Control owner, ImageDescriptor descriptor) {
        LocalResourceManager manager = new LocalResourceManager(JFaceResources.getResources(), owner);
        return getImage(manager, descriptor);
    }

    /**
     * @param path the path to the image as absolute path from the plugin root.
     * @param name the name of the image.
     * @return an {@link URI} to a file where the image has been saved to.
     */
    public static String getImageUri(String path, String name) {

        /* Check Cache */
        String imgUri = fgImageUriMap.get(path);
        if (imgUri != null)
            return imgUri;

        /* Check Filesystem */
        File imgFile = getImageFile(name);
        if (imgFile.exists()) {
            imgUri = getImageUri(imgFile);
            fgImageUriMap.put(path, imgUri);
            return imgUri;
        }

        /* Copy to Filesystem */
        try {
            CoreUtils.copy(OwlUI.class.getResourceAsStream(path), new FileOutputStream(imgFile));
            imgUri = getImageUri(imgFile);
            fgImageUriMap.put(path, imgUri);
            return imgUri;
        } catch (IOException e) {
            Activator.getDefault().logError(e.getMessage(), e);
        }

        return null;
    }

    private static String getImageUri(File file) {
        URI uri = file.toURI();
        String s = uri.toString();
        return s.replaceFirst("/", "///"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    /**
     * @param manager
     * @param rgb
     * @return Color
     */
    public static Color getColor(ResourceManager manager, RGB rgb) {
        try {
            return manager.createColor(rgb);
        } catch (DeviceResourceException e) {
            return manager.getDevice().getSystemColor(SWT.COLOR_BLACK);
        }
    }

    /**
     * @param manager
     * @param descriptor
     * @return Color
     */
    public static Color getColor(ResourceManager manager, ColorDescriptor descriptor) {
        try {
            return manager.createColor(descriptor);
        } catch (DeviceResourceException e) {
            return manager.getDevice().getSystemColor(SWT.COLOR_BLACK);
        }
    }

    /**
     * @param resources
     * @param label
     * @return Color
     */
    public static Color getColor(ResourceManager resources, ILabel label) {
        RGB rgb = getRGB(label);

        return getColor(resources, rgb);
    }

    /**
     * @param label
     * @return RGB
     */
    public static RGB getRGB(ILabel label) {
        return getRGB(label.getColor());
    }

    /**
     * @param rgb
     * @return RGB
     */
    public static RGB getRGB(String rgb) {
        if (!StringUtils.isSet(rgb))
            return null;

        RGB commonRGB = fgMapCommonColorToRGB.get(rgb);
        if (commonRGB != null)
            return commonRGB;

        String color[] = rgb.split(","); //$NON-NLS-1$
        return new RGB(Integer.parseInt(color[0]), Integer.parseInt(color[1]), Integer.parseInt(color[2]));
    }

    /**
     * @param rgb
     * @return String
     */
    public static String toString(RGB rgb) {
        return rgb.red + "," + rgb.green + "," + rgb.blue; //$NON-NLS-1$ //$NON-NLS-2$
    }

    /**
     * @param key
     * @return Font
     */
    public static Font getFont(String key) {
        return JFaceResources.getFontRegistry().get(key);
    }

    /**
     * @param key
     * @return Font
     */
    public static Font getBold(String key) {
        return JFaceResources.getFontRegistry().getBold(key);
    }

    /**
     * @param key
     * @return Font
     */
    public static Font getItalic(String key) {
        return JFaceResources.getFontRegistry().getItalic(key);
    }

    /**
     * @param key
     * @param style
     * @return Font
     */
    public static Font getThemeFont(String key, int style) {
        FontRegistry fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry();
        if (fontRegistry != null) {
            if (style == SWT.NORMAL)
                return fontRegistry.get(key);
            else if ((style & SWT.BOLD) != 0)
                return fontRegistry.getBold(key);
            else if ((style & SWT.ITALIC) != 0)
                return fontRegistry.getItalic(key);
        }

        return getFont(key);
    }

    /**
     * @param key
     * @param manager
     * @param defaultColor
     * @return Font
     */
    public static Color getThemeColor(String key, ResourceManager manager, RGB defaultColor) {
        ColorRegistry colorRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
                .getColorRegistry();
        if (colorRegistry != null)
            return getColor(manager, colorRegistry.getColorDescriptor(key));

        return getColor(manager, defaultColor);
    }

    /**
     * @param key
     * @param defaultRGB
     * @return Font
     */
    public static RGB getThemeRGB(String key, RGB defaultRGB) {
        ColorRegistry colorRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
                .getColorRegistry();
        if (colorRegistry != null)
            return colorRegistry.getRGB(key);

        return defaultRGB;
    }

    /**
     * @param drawable
     * @param text
     * @param font
     * @return The size of the Text as Point.
     */
    public static Point getTextSize(Drawable drawable, Font font, String text) {
        GC gc = new GC(drawable);
        gc.setFont(font);
        Point p = gc.textExtent(text);
        gc.dispose();

        return p;
    }

    /**
     * @param bookmark
     * @return ImageDescriptor
     */
    public static ImageDescriptor getFavicon(IBookMark bookmark) {
        if (bookmark.getId() == null)
            return null;

        /* 1.) Check if ImageDescriptor exists in Memory */
        ImageDescriptor descriptor = FAVICO_CACHE.get(bookmark.getId());
        if (descriptor != null)
            return descriptor;

        /* 2.) Check if ImageDescriptor exists in File System */
        File favicon = getImageFile(bookmark.getId());
        if (favicon != null && favicon.exists()) {
            try {
                descriptor = new FavIconImageDescriptor(favicon,
                        ImageDescriptor.createFromURL(favicon.toURI().toURL()));
                FAVICO_CACHE.put(bookmark.getId(), descriptor);
                return descriptor;
            } catch (MalformedURLException e) {
                Activator.getDefault().logError(e.getMessage(), e);
            }
        }

        return null;
    }

    /**
     * @param id
     */
    public static void deleteImage(long id) {

        /* Delete from Cache */
        FAVICO_CACHE.remove(id);

        /* Delete from Disk */
        File file = getImageFile(id);
        if (file != null && file.exists())
            file.delete();
    }

    /**
     * Deletes all stored icons from the org.rssowl.ui icons folder.
     */
    public static void clearFavicons() {
        Activator activator = Activator.getDefault();
        if (activator == null)
            return;

        IPath path = new Path(activator.getStateLocation().toOSString());
        path = path.append(ICONS_FOLDER);
        File iconsFolder = new File(path.toOSString());
        if (!iconsFolder.exists())
            return;

        File[] files = iconsFolder.listFiles();
        for (File file : files) {
            if (file.getName().endsWith(".ico")) //$NON-NLS-1$
                file.delete();
        }
    }

    /**
     * @param id
     * @param bytes
     * @param defaultImage
     * @param wHint
     * @param hHint
     */
    public static void storeImage(long id, byte[] bytes, ImageDescriptor defaultImage, int wHint, int hHint) {
        Assert.isNotNull(defaultImage);
        Assert.isLegal(wHint > 0);
        Assert.isLegal(hHint > 0);

        ImageData imgData = null;

        /* Bytes Provided */
        if (bytes != null && bytes.length > 0) {
            ByteArrayInputStream inS = null;
            try {
                inS = new ByteArrayInputStream(bytes);
                ImageLoader loader = new ImageLoader();
                ImageData[] imageDatas = loader.load(inS);

                /* Look for the Icon with the best quality */
                if (imageDatas != null)
                    imgData = getBestQuality(imageDatas, wHint, hHint);
            } catch (SWTException e) {
                /* Ignore any Image-Format exceptions */
            } finally {
                if (inS != null) {
                    try {
                        inS.close();
                    } catch (IOException e) {
                        if (!(e instanceof MonitorCanceledException))
                            Activator.getDefault().logError(e.getMessage(), e);
                    }
                }
            }
        }

        /* Use default Image if img-data is null */
        if (imgData == null)
            imgData = defaultImage.getImageData();

        /* Save Image into Cache-Area on File-System */
        if (imgData != null) {
            File imageFile = getImageFile(id);
            if (imageFile == null)
                return;

            /* Scale if required */
            if (imgData.width != 16 || imgData.height != 16)
                imgData = imgData.scaledTo(16, 16);

            /* Try using native Image Format */
            try {
                if (storeImage(imgData, imageFile, imgData.type))
                    return;
            } catch (SWTException e) {
                /* Ignore any Image-Format exceptions */
            }

            /* Try using various other Image-Formats */
            int formats[] = new int[] { SWT.IMAGE_PNG, SWT.IMAGE_ICO, SWT.IMAGE_GIF, SWT.IMAGE_BMP };
            for (int format : formats) {
                if (format != imgData.type) {
                    try {
                        if (storeImage(imgData, imageFile, format))
                            return;
                    } catch (SWTException e) {
                        /* Ignore any Image-Format exceptions */
                    }
                }
            }
        }
    }

    /* Returns the ImageData with best Depth or Size */
    private static ImageData getBestQuality(ImageData datas[], int wHint, int hHint) {
        ImageData bestSize = null;
        ImageData bestDepth = null;
        int maxDepth = -1;
        int maxSize = -1;

        /* Foreach Image: Check best Depth */
        for (ImageData data : datas) {
            if (data.depth > maxDepth) {
                maxDepth = data.depth;
                bestDepth = data;
            }
        }

        /* Foreach Image: Check best Size */
        for (ImageData data : datas) {

            /* Only consider best depth */
            if (data.depth == maxDepth) {

                /* Return if Size matches Hint */
                if (data.width == wHint && data.height == hHint)
                    return data;

                /* Otherwise look for bigges */
                if (data.width * data.height > maxSize) {
                    maxSize = data.width * data.height;
                    bestSize = data;
                }
            }
        }

        return (bestDepth != null) ? bestDepth : bestSize;
    }

    /* Saves the Image to the given File with the given Image-Format */
    private static boolean storeImage(ImageData imgData, File file, int format) {
        ImageLoader loader = new ImageLoader();
        loader.data = new ImageData[] { imgData };
        FileOutputStream fOs = null;
        try {
            fOs = new FileOutputStream(file);
            loader.save(fOs, format);
        } catch (FileNotFoundException e) {
            Activator.getDefault().logError(e.getMessage(), e);
        } finally {
            if (fOs != null)
                try {
                    fOs.close();
                } catch (IOException e) {
                    Activator.getDefault().logError(e.getMessage(), e);
                }
        }

        return true;
    }

    private static File getImageFile(String fileName) {
        boolean res = false;

        Activator activator = Activator.getDefault();
        if (activator == null)
            return null;

        IPath path = new Path(activator.getStateLocation().toOSString());
        path = path.append(ICONS_FOLDER);
        File root = new File(path.toOSString());
        if (!root.exists())
            res = root.mkdir();
        else
            res = true;

        path = path.append(fileName);

        if (!res)
            return null;

        return new File(path.toOSString());
    }

    private static File getImageFile(long id) {
        return getImageFile(id + ".ico"); //$NON-NLS-1$
    }

    /**
     * Attempts to find the primary <code>IWorkbenchWindow</code> from the
     * PlatformUI facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @return the primary <code>IWorkbenchWindow</code> from the PlatformUI
     * facade or <code>NULL</code> if none.
     */
    public static IWorkbenchWindow getPrimaryWindow() {

        /* Return the first Window of the Workbench */
        IWorkbenchWindow windows[] = PlatformUI.getWorkbench().getWorkbenchWindows();
        if (windows.length > 0)
            return windows[0];

        return null;
    }

    /**
     * Attempts to find the first <code>IWorkbenchWindow</code> from the
     * PlatformUI facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @return the first <code>IWorkbenchWindow</code> from the PlatformUI facade
     * or <code>NULL</code> if none.
     */
    public static IWorkbenchWindow getWindow() {

        /* First try active Window */
        IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
        if (activeWorkbenchWindow != null)
            return activeWorkbenchWindow;

        /* Finally try any Window */
        IWorkbenchWindow windows[] = PlatformUI.getWorkbench().getWorkbenchWindows();
        if (windows.length > 0)
            return windows[0];

        return null;
    }

    /**
     * Attempts to find the <code>IWorkbenchWindow</code> from the PlatformUI
     * facade that is located at where the mouse is pointing at. Otherwise,
     * returns <code>NULL</code> if none.
     *
     * @return the first <code>IWorkbenchWindow</code> from the PlatformUI facade
     * that is located at where the mouse is pointing at or <code>NULL</code> if
     * none.
     */
    public static IWorkbenchWindow getWindowAtCursor() {

        /* Get the Control at the Cursor position */
        Control cursorControl = Display.getDefault().getCursorControl();
        if (cursorControl == null)
            return null;

        /* Return Window that belongs to Cursor-Shell */
        Shell cursorShell = cursorControl.getShell();
        IWorkbenchWindow windows[] = PlatformUI.getWorkbench().getWorkbenchWindows();
        for (IWorkbenchWindow workbenchWindow : windows) {
            if (workbenchWindow.getShell().equals(cursorShell))
                return workbenchWindow;
        }

        return null;
    }

    /**
     * Attempts to find the first <code>IWorkbenchPage</code> from the PlatformUI
     * facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @return the first <code>IWorkbenchPage</code> from the PlatformUI facade or
     * <code>NULL</code> if none.
     */
    public static IWorkbenchPage getPage() {
        IWorkbenchWindow window = getWindow();
        return getPage(window);
    }

    /**
     * Attempts to find the first <code>IWorkbenchPage</code> from the PlatformUI
     * facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @param window the {@link IWorkbenchWindow} to search for a
     * {@link IWorkbenchPage}.
     * @return the first <code>IWorkbenchPage</code> from the PlatformUI facade or
     * <code>NULL</code> if none.
     */
    public static IWorkbenchPage getPage(IWorkbenchWindow window) {
        if (window != null) {

            /* First try active Page */
            if (window.getActivePage() != null)
                return window.getActivePage();

            /* Finally try any Page */
            IWorkbenchPage[] pages = window.getPages();
            if (pages.length > 0)
                return pages[0];
        }

        return null;
    }

    /**
     * Attempts to find the active <code>IWorkbenchPart</code> from the PlatformUI
     * facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @param window the {@link IWorkbenchWindow} to search in.
     * @return the active <code>IWorkbenchPart</code> from the PlatformUI facade
     * or <code>NULL</code> if none.
     */
    public static IWorkbenchPart getActivePart(IWorkbenchWindow window) {
        if (window != null) {

            /* First try active Page */
            if (window.getActivePage() != null)
                return window.getActivePage().getActivePart();

            /* Finally try any Page */
            IWorkbenchPage[] pages = window.getPages();
            for (IWorkbenchPage page : pages) {
                if (page.getActivePart() != null)
                    return page.getActivePart();
            }
        }

        return null;
    }

    /**
     * Attempts to return the index of the given workbench window or
     * <code>-1</code> if none.
     *
     * @param window the {@link IWorkbenchWindow} to get the index in the stack of
     * windows that are open.
     * @return the index of the given workbench window or <code>-1</code> if none.
     */
    public static int getWindowIndex(IWorkbenchWindow window) {
        if (window != null) {
            IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
            for (int i = 0; i < windows.length; i++)
                if (windows[i].equals(window))
                    return i;
        }

        return 0;
    }

    /**
     * Attempts to find the <code>IWorkbenchPage</code> from the Workbench-Window
     * the mouse is currently over from the PlatformUI facade. Otherwise, returns
     * <code>NULL</code> if none.
     *
     * @return the first <code>IWorkbenchPage</code> from the Workbench-Window the
     * mouse is currently over from the PlatformUI facade or <code>NULL</code> if
     * none.
     */
    public static IWorkbenchPage getPageAtCursor() {
        IWorkbenchWindow window = getWindowAtCursor();
        if (window != null) {

            /* First try active Page */
            if (window.getActivePage() != null)
                return window.getActivePage();

            /* Finally try any Page */
            IWorkbenchPage[] pages = window.getPages();
            if (pages.length > 0)
                return pages[0];
        }

        return null;
    }

    /**
     * Attempts to find the first active <code>IEditorPart</code> from the
     * PlatformUI facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @return the first active <code>IEditorPart</code> from the PlatformUI
     * facade or <code>NULL</code> if none.
     */
    public static IEditorPart getActiveEditor() {
        IWorkbenchPage page = getPage();
        if (page != null)
            return page.getActiveEditor();

        return null;
    }

    /**
     * @return a list of all editors currently open in the UI as references.
     */
    public static List<IEditorReference> getEditorReferences() {
        IWorkbenchPage page = getPage();
        if (page != null) {
            IEditorReference[] references = page.getEditorReferences();
            return Arrays.asList(references);
        }

        return Collections.emptyList();
    }

    /**
     * @return the number of opened feed views (will not trigger editor
     * restoring).
     */
    public static int getOpenFeedViewCount() {
        int count = 0;
        List<IEditorReference> editors = getEditorReferences();
        for (IEditorReference reference : editors) {
            if (FeedView.ID.equals(reference.getId()))
                count++;
        }

        return count;
    }

    /**
     * Attempts to find the first active <code>FeedView</code> from the PlatformUI
     * facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @return the first active <code>FeedView</code> from the PlatformUI facade
     * or <code>NULL</code> if none.
     */
    public static FeedView getActiveFeedView() {
        IWorkbenchPage page = getPage();
        if (page != null) {
            IEditorPart activeEditor = page.getActiveEditor();
            if (activeEditor != null && activeEditor instanceof FeedView)
                return (FeedView) activeEditor;
        }

        return null;
    }

    /**
     * @return the {@link INewsMark} currently showing in the active feed view if
     * any or <code>null</code> otherwise.
     */
    public static INewsMark getActiveFeedViewNewsMark() {
        try {
            FeedView activeFeedView = getActiveFeedView();
            if (activeFeedView != null) {
                FeedViewInput input = (FeedViewInput) activeFeedView.getEditorInput();
                return input.getMark();
            }
        } catch (Error e) {
            /* Since this method might be called from any thread, protect fully */
        }

        return null;
    }

    /**
     * Attempts to find the first active <code>FeedView</code> from the PlatformUI
     * facade and then will return the feed view input preferences. Otherwise,
     * returns <code>NULL</code> if none.
     *
     * @return the first active <code>FeedView</code> input preferences from the
     * PlatformUI facade or <code>NULL</code> if none.
     */
    public static IPreferenceScope getActiveFeedViewPreferences() {
        FeedView feedView = getActiveFeedView();
        if (feedView != null) {
            IEditorInput input = feedView.getEditorInput();
            if (input instanceof FeedViewInput) {
                FeedViewInput feedViewInput = (FeedViewInput) input;
                if (feedViewInput.getMark() != null) {
                    INewsMark mark = feedViewInput.getMark();
                    if (mark instanceof FolderNewsMark)
                        return Owl.getPreferenceService().getEntityScope(((FolderNewsMark) mark).getFolder());

                    return Owl.getPreferenceService().getEntityScope(feedViewInput.getMark());
                }
            }
        }

        return null;
    }

    /**
     * Attempts to find all open <code>FeedView</code>s from the PlatformUI
     * facade. Otherwise, returns an empty list if none.
     *
     * @return all open <code>FeedView</code>s from the PlatformUI facade or an
     * empty list if none.
     */
    public static List<FeedView> getFeedViews() {
        List<FeedView> feedViews = new ArrayList<FeedView>();

        List<IEditorReference> references = getEditorReferences();
        for (IEditorReference reference : references) {
            if (FeedView.ID.equals(reference.getId())) {
                IEditorPart editor = reference.getEditor(true);
                if (editor instanceof FeedView)
                    feedViews.add((FeedView) editor);
            }
        }

        return feedViews;
    }

    /**
     * Attempts to find the selection from the first active <code>FeedView</code>
     * from the PlatformUI facade. Otherwise, returns
     * <code>StructuredSelection.EMPTY</code> if none.
     *
     * @return the selection from the first active <code>FeedView</code> from the
     * PlatformUI facade or <code>StructuredSelection.EMPTY</code> if none.
     */
    public static IStructuredSelection getActiveFeedViewSelection() {
        FeedView feedview = getActiveFeedView();
        if (feedview == null)
            return StructuredSelection.EMPTY;

        ISelectionProvider selectionProvider = feedview.getSite().getSelectionProvider();
        if (selectionProvider == null)
            return StructuredSelection.EMPTY;

        return (IStructuredSelection) selectionProvider.getSelection();
    }

    /**
     * Attempts to find the selection from the first active <code>Part</code> from
     * the PlatformUI facade. Otherwise, returns
     * <code>StructuredSelection.EMPTY</code> if none.
     *
     * @return the selection from the first active <code>Part</code> from the
     * PlatformUI facade or <code>StructuredSelection.EMPTY</code> if none.
     */
    public static IStructuredSelection getActiveSelection() {
        IWorkbenchPage page = getPage();
        if (page != null) {
            IWorkbenchPart part = page.getActivePart();
            if (part != null && part.getSite() != null) {
                ISelectionProvider selectionProvider = part.getSite().getSelectionProvider();
                if (selectionProvider != null) {
                    ISelection selection = selectionProvider.getSelection();
                    if (!selection.isEmpty() && selection instanceof IStructuredSelection)
                        return (IStructuredSelection) selection;
                }
            }
        }

        return StructuredSelection.EMPTY;
    }

    /**
     * Attempts to find the first <code>FeedView</code> from the active Workbench
     * Window of the PlatformUI facade. Otherwise, returns <code>NULL</code> if
     * none.
     *
     * @return the first <code>FeedView</code> from the active Workbench Window of
     * the PlatformUI facade or <code>NULL</code> if none.
     */
    public static FeedView getFirstActiveFeedView() {
        IWorkbenchPage page = getPage();
        if (page != null) {

            /* First try current active editor */
            IEditorPart activeEditor = page.getActiveEditor();
            if (activeEditor instanceof FeedView)
                return (FeedView) activeEditor;

            /* Then navigate through all from first to last */
            IEditorReference[] editorReferences = page.getEditorReferences();
            for (IEditorReference editorReference : editorReferences) {
                if (FeedView.ID.equals(editorReference.getId()))
                    return (FeedView) editorReference.getEditor(true);
            }
        }

        return null;
    }

    /**
     * Attempts to find the first <code>WebBrowserView</code> from the active
     * Workbench Window of the PlatformUI facade. Otherwise, returns
     * <code>NULL</code> if none.
     *
     * @return the first <code>WebBrowserView</code> from the active Workbench
     * Window of the PlatformUI facade or <code>NULL</code> if none.
     */
    public static WebBrowserView getFirstActiveBrowser() {
        IWorkbenchPage page = getPage();
        if (page != null) {
            IEditorReference[] editorReferences = page.getEditorReferences();
            for (IEditorReference editorReference : editorReferences) {
                try {
                    if (editorReference.getEditorInput() instanceof WebBrowserInput)
                        return (WebBrowserView) editorReference.getEditor(true);
                } catch (PartInitException e) {
                    /* Ignore Silently */
                }
            }
        }

        return null;
    }

    /**
     * Attempts to find the opened <code>BookMarkExplorer</code> from the
     * PlatformUI facade. Otherwise, returns <code>NULL</code> if none.
     *
     * @return the <code>BookMarkExplorer</code> from the PlatformUI facade or
     * <code>NULL</code> if not opened.
     */
    public static BookMarkExplorer getOpenedBookMarkExplorer() {
        IWorkbenchPage page = getPage();
        if (page != null) {
            IViewReference[] viewReferences = page.getViewReferences();
            for (IViewReference viewRef : viewReferences) {
                if (viewRef.getId().equals(BookMarkExplorer.VIEW_ID)) {
                    IViewPart view = viewRef.getView(true);
                    if (view instanceof BookMarkExplorer)
                        return (BookMarkExplorer) view;
                }
            }
        }

        return null;
    }

    /**
     * Attempts to find the primary <code>Shell</code> from the PlatformUI facade.
     * Otherwise, returns <code>NULL</code> if none.
     *
     * @return the primary <code>Shell</code> from the PlatformUI facade or
     * <code>NULL</code> if none.
     */
    public static Shell getPrimaryShell() {
        IWorkbenchWindow window = getPrimaryWindow();
        if (window != null)
            return window.getShell();

        return null;
    }

    /**
     * Attempts to find the active <code>Shell</code> from the PlatformUI facade.
     * Otherwise, returns <code>NULL</code> if none.
     *
     * @return the active <code>Shell</code> from the PlatformUI facade or
     * <code>NULL</code> if none.
     */
    public static Shell getActiveShell() {
        IWorkbenchWindow window = getWindow();
        if (window != null)
            return window.getShell();

        return null;
    }

    /**
     * Update the current active window title based on the given array of
     * {@link IMark}.
     *
     * @param input the input that is currently visible in RSSOwl.
     */
    public static void updateWindowTitle(IMark input) {
        if (input != null)
            updateWindowTitle(input.getName());
    }

    /**
     * Update the current active window title based on the given title.
     *
     * @param title the name of the input that is currently visible in RSSOwl.
     */
    public static void updateWindowTitle(String title) {
        IWorkbenchWindow window = getWindow();
        if (window != null) {
            String appTitle = "RSSOwl"; //$NON-NLS-1$
            if (StringUtils.isSet(title))
                title = NLS.bind(Messages.OwlUI_TITLE, title, appTitle);
            else
                title = appTitle;

            String shellText = window.getShell().getText();
            if (shellText == null || !shellText.equals(title))
                window.getShell().setText(title);
        }
    }

    /**
     * A helper method that can be used to restore the application when its
     * minimized.
     *
     * @param page the workbench page the application is running in.
     */
    public static void restoreWindow(IWorkbenchPage page) {
        Shell applicationShell = page.getWorkbenchWindow().getShell();
        restoreWindow(applicationShell);
    }

    /**
     * A helper method that can be used to restore the application when its
     * minimized.
     *
     * @param applicationShell the main {@link Shell} of the application.
     */
    public static void restoreWindow(Shell applicationShell) {
        ApplicationWorkbenchWindowAdvisor advisor = ApplicationWorkbenchAdvisor.fgPrimaryApplicationWorkbenchWindowAdvisor;

        /* Restore From Tray */
        if (advisor != null && advisor.isMinimizedToTray())
            advisor.restoreFromTray(applicationShell);

        /* Restore from being Minimized */
        else if (applicationShell.getMinimized()) {
            applicationShell.setMinimized(false);
            applicationShell.forceActive();
        }

        /* Otherwise force Active */
        else
            applicationShell.forceActive();
    }

    /**
     * @return the current selected {@link IFolder} of the bookmark explorer or
     * the parent of the current selected {@link IMark} or <code>null</code> if
     * none.
     */
    public static IFolder getBookMarkExplorerSelection() {
        IWorkbenchPage page = getPage();
        if (page != null) {
            IViewPart viewPart = page.findView(BookMarkExplorer.VIEW_ID);
            if (viewPart != null) {
                IStructuredSelection selection = (IStructuredSelection) viewPart.getSite().getSelectionProvider()
                        .getSelection();
                if (!selection.isEmpty()) {
                    Object selectedEntity = selection.iterator().next();
                    if (selectedEntity instanceof IFolder)
                        return (IFolder) selectedEntity;
                    else if (selectedEntity instanceof IMark)
                        return ((IMark) selectedEntity).getParent();
                }
            }
        }

        return null;
    }

    /**
     * Opens a selection of {@link INewsMark} inside the feed view.
     *
     * @param page
     * @param selection
     */
    public static void openInFeedView(IWorkbenchPage page, IStructuredSelection selection) {
        openInFeedView(page, selection, false);
    }

    /**
     * Opens a selection of {@link INewsMark} inside the feed view.
     *
     * @param page
     * @param selection
     * @param forceActivate
     */
    public static void openInFeedView(IWorkbenchPage page, IStructuredSelection selection, boolean forceActivate) {
        openInFeedView(page, selection, forceActivate, false);
    }

    /**
     * Opens a selection of {@link INewsMark} inside the feed view.
     *
     * @param page
     * @param selection
     * @param forceActivate
     * @param ignoreAlreadyOpened
     */
    public static void openInFeedView(IWorkbenchPage page, IStructuredSelection selection, boolean forceActivate,
            boolean ignoreAlreadyOpened) {
        openInFeedView(page, selection, forceActivate, ignoreAlreadyOpened, null);
    }

    /**
     * Opens a selection of {@link INewsMark} inside the feed view.
     *
     * @param page
     * @param selection
     * @param perform
     * @param forceActivate
     * @param ignoreAlreadyOpened
     */
    public static void openInFeedView(IWorkbenchPage page, IStructuredSelection selection, boolean forceActivate,
            boolean ignoreAlreadyOpened, PerformAfterInputSet perform) {
        try {
            internalOpenInFeedView(page, selection, forceActivate, ignoreAlreadyOpened, false, perform);
        } finally {
            FeedView.setBlockFeedChangeEvent(false);
        }
    }

    /**
     * @param page
     * @param selection
     * @param openModes
     */
    public static void openInFeedView(IWorkbenchPage page, IStructuredSelection selection,
            EnumSet<FeedViewOpenMode> openModes) {
        boolean forceActivate = openModes.contains(FeedViewOpenMode.FORCE_ACTIVATE);
        boolean ignoreAlreadyOpened = openModes.contains(FeedViewOpenMode.IGNORE_ALREADY_OPENED);
        boolean ignoreReuse = openModes.contains(FeedViewOpenMode.IGNORE_REUSE);

        try {
            internalOpenInFeedView(page, selection, forceActivate, ignoreAlreadyOpened, ignoreReuse, null);
        } finally {
            FeedView.setBlockFeedChangeEvent(false);
        }
    }

    private static void internalOpenInFeedView(IWorkbenchPage page, IStructuredSelection selection,
            boolean forceActivate, boolean ignoreAlreadyOpened, boolean ignoreReuse, PerformAfterInputSet perform) {
        List<?> list = selection.toList();
        boolean activateEditor = forceActivate || OpenStrategy.activateOnOpen();
        int openedEditors = 0;
        int maxOpenEditors = EditorUtils.getOpenEditorLimit();
        boolean reuseFeedView = !ignoreReuse
                && Owl.getPreferenceService().getGlobalScope().getBoolean(DefaultPreferences.ALWAYS_REUSE_FEEDVIEW);

        /* Open Editors for the given Selection */
        for (int i = 0; i < list.size() && openedEditors < maxOpenEditors; i++) {
            Object object = list.get(i);

            /* Convert folder to news mark in case folder selected */
            if (object instanceof IFolder)
                object = new FolderNewsMark((IFolder) object);

            /* Only news marks supported at this point */
            if (object instanceof INewsMark) {
                INewsMark mark = ((INewsMark) object);
                FeedViewInput input = new FeedViewInput(mark, perform);

                /* Start Blocking Feed Change Events if we open more than one Feed */
                if (i == 1)
                    FeedView.setBlockFeedChangeEvent(true);

                /* Open in existing Feedview if set */
                if (reuseFeedView) {

                    /* Feed could be already open in editor (avoid duplicates) */
                    IEditorPart existingEditor = page.findEditor(input);
                    if (existingEditor != null) {
                        if (activateEditor)
                            page.activate(existingEditor);
                        else
                            page.bringToTop(existingEditor);

                        if (perform != null && existingEditor instanceof FeedView)
                            ((FeedView) existingEditor).perform(perform);

                        break;
                    }

                    /* Otherwise replace the input in the first active feed view */
                    FeedView activeFeedView = OwlUI.getFirstActiveFeedView();
                    if (activeFeedView != null) {
                        activeFeedView.setInput(input);
                        if (activateEditor)
                            page.activate(activeFeedView);
                        else
                            page.bringToTop(activeFeedView);
                        break;
                    }
                }

                /* Otherwise simply open */
                try {
                    boolean explicitPerform = false;
                    IEditorPart existingEditor = null;
                    if (perform != null) {
                        existingEditor = page.findEditor(input);
                        explicitPerform = (existingEditor != null);
                    }

                    /* Open Editor (check for already opened if set) */
                    if (!ignoreAlreadyOpened || page.findEditor(input) == null)
                        page.openEditor(input, FeedView.ID, activateEditor);

                    openedEditors++;

                    /* Pass in Perform Code */
                    if (explicitPerform && existingEditor instanceof FeedView)
                        ((FeedView) existingEditor).perform(perform);

                    /* Break loop if we reuse feed views (thus can only display a single feed) */
                    if (reuseFeedView)
                        break;
                } catch (PartInitException e) {
                    Activator.getDefault().getLog().log(e.getStatus());
                }
            }
        }
    }

    /**
     * Set's the checked state of all visible items to the suplied one.
     *
     * @param tree
     * @param state
     */
    public static void setAllChecked(Tree tree, boolean state) {
        setAllChecked(state, tree.getItems());
    }

    private static void setAllChecked(boolean state, TreeItem[] items) {
        for (int i = 0; i < items.length; i++) {
            items[i].setChecked(state);
            TreeItem[] children = items[i].getItems();
            setAllChecked(state, children);
        }
    }

    /** Identical with ActionFactory.CLOSE_OTHERS */
    public static void closeOtherEditors() {
        IWorkbenchPage page = getPage();
        if (page != null) {
            IEditorReference[] refArray = page.getEditorReferences();
            if (refArray != null && refArray.length > 1) {
                IEditorReference[] otherEditors = new IEditorReference[refArray.length - 1];
                IEditorReference activeEditor = (IEditorReference) page.getReference(page.getActiveEditor());
                for (int i = 0; i < refArray.length; i++) {
                    if (refArray[i] != activeEditor)
                        continue;
                    System.arraycopy(refArray, 0, otherEditors, 0, i);
                    System.arraycopy(refArray, i + 1, otherEditors, i, refArray.length - 1 - i);
                    break;
                }
                page.closeEditors(otherEditors, true);
            }
        }
    }

    /**
     * @param text the {@link Text} to hook auto complete into.
     * @param values the values that show up as proposal.
     * @param decorate if <code>true</code>, decorate the control to indicate
     * content assist is available.
     * @param autoActivate
     * @return Pair
     */
    public static Pair<SimpleContentProposalProvider, ContentProposalAdapter> hookAutoComplete(final Text text,
            Collection<String> values, boolean decorate, boolean autoActivate) {
        return hookAutoComplete(text, new ContentAssistAdapter(text, ' ', false), values, decorate, autoActivate);
    }

    /**
     * @param combo the {@link Combo} to hook auto complete into.
     * @param values the values that show up as proposal.
     * @param decorate if <code>true</code>, decorate the control to indicate
     * content assist is available.
     * @return Pair
     */
    public static Pair<SimpleContentProposalProvider, ContentProposalAdapter> hookAutoComplete(final Combo combo,
            Collection<String> values, boolean decorate) {
        return hookAutoComplete(combo, new ContentAssistAdapter(combo, ' ', false), values, decorate, true);
    }

    /**
     * @param control the {@link Control} to hook auto complete into.
     * @param contentAdapter a {@link IControlContentAdapter} for the content
     * @param values the values that show up as proposal.
     * @param decorate if <code>true</code>, decorate the control to indicate
     * content assist is available.
     * @param autoActivate
     * @return Pair
     */
    public static Pair<SimpleContentProposalProvider, ContentProposalAdapter> hookAutoComplete(
            final Control control, IControlContentAdapter contentAdapter, Collection<String> values,
            boolean decorate, final boolean autoActivate) {

        /* Show UI Hint that Content Assist is available */
        if (decorate) {
            ControlDecoration controlDeco = new ControlDecoration(control, SWT.LEFT | SWT.TOP);
            controlDeco.setImage(FieldDecorationRegistry.getDefault()
                    .getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage());
            controlDeco.setDescriptionText(Messages.OwlUI_CONTENT_ASSIST);
            controlDeco.setShowOnlyOnFocus(true);
        }

        /* Auto-Activate on Key-Down */
        KeyStroke activationKey = KeyStroke.getInstance(SWT.ARROW_DOWN);

        /* Create Content Proposal Adapter */
        SimpleContentProposalProvider proposalProvider = new SimpleContentProposalProvider(new String[0]) {
            @Override
            public IContentProposal[] getProposals(String contents, int position) {
                if (Display.getCurrent() != null && !control.isVisible())
                    return new IContentProposal[0];

                return super.getProposals(contents, position);
            }
        };
        proposalProvider.setFiltering(true);
        final ContentProposalAdapter adapter = new ContentProposalAdapter(control, contentAdapter, proposalProvider,
                activationKey, null);
        adapter.setPropagateKeys(true);
        adapter.setAutoActivationDelay(1500);
        adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_INSERT);

        /* Apply Proposals */
        if (values != null)
            applyAutoCompleteProposals(values, proposalProvider, adapter, autoActivate);

        /*
         * TODO: This is a hack but there doesnt seem to be any API to set the size
         * of the popup to match the actual size of the Text widget being used.
         */
        control.getDisplay().timerExec(100, new Runnable() {
            public void run() {
                if (!control.isDisposed()) {
                    adapter.setPopupSize(new Point(control.getSize().x, 120));
                }
            }
        });

        return Pair.create(proposalProvider, adapter);
    }

    /**
     * @param values
     * @param provider
     * @param adapter
     * @param autoActivate
     */
    public static void applyAutoCompleteProposals(Collection<String> values, SimpleContentProposalProvider provider,
            ContentProposalAdapter adapter, boolean autoActivate) {

        /* Extract Proposals */
        final String[] proposals = new String[values.size()];
        Set<Character> charSet = new HashSet<Character>();
        int i = 0;
        for (String value : values) {
            proposals[i] = value;

            char c = value.charAt(0);
            charSet.add(Character.toLowerCase(c));
            charSet.add(Character.toUpperCase(c));
            i++;
        }

        /* Auto-Activate on first Key typed */
        char[] activationChars = new char[charSet.size()];
        i = 0;
        for (char c : charSet) {
            activationChars[i] = c;
            i++;
        }

        /* Apply proposals and auto-activation chars */
        provider.setProposals(proposals);
        if (autoActivate)
            adapter.setAutoActivationCharacters(activationChars);
    }

    /**
     * @param display
     * @param rgb the color value to use in the image
     * @return an {@link Image} for the color that must be disposed when no longer
     * used.
     */
    public static Image createColorImage(Display display, RGB rgb) {
        Color color = new Color(display, rgb);

        Image image = new Image(display, 12, 12);

        GC gc = new GC(image);

        gc.setBackground(color);
        gc.fillRectangle(0, 0, 12, 12);

        gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
        gc.drawRectangle(0, 0, 11, 11);

        gc.dispose();
        color.dispose();

        return image;
    }

    /**
     * @return the width for displaying a date.
     */
    public static int getDateWidth() {

        /* Check if Cached already */
        if (DATE_WIDTH > 0)
            return DATE_WIDTH;

        /* Calculate and Cache */
        DateFormat dF = getShortDateFormat();
        Calendar cal = Calendar.getInstance();
        cal.set(2006, Calendar.DECEMBER, 12, 12, 12, 12);
        String sampleDate = dF.format(cal.getTime());

        DATE_WIDTH = OwlUI.getTextSize(Display.getDefault(), OwlUI.getBold(HEADLINES_FONT_ID), sampleDate).x;
        DATE_WIDTH += Application.IS_WINDOWS ? 15 : 30; // Bounds of Column requires more space

        return DATE_WIDTH;
    }

    /**
     * @return the width for displaying a state.
     */
    public static int getStateWidth() {

        /* Check if Cached already */
        if (STATE_WIDTH > 0)
            return STATE_WIDTH;

        /* Calculate and Cache */
        String sampleState = Messages.OwlUI_UPDATED;

        STATE_WIDTH = OwlUI.getTextSize(Display.getDefault(), OwlUI.getBold(HEADLINES_FONT_ID), sampleState).x;
        STATE_WIDTH += Application.IS_WINDOWS ? 25 : 30; // Bounds of Column requires more space (arrow indicator)

        return STATE_WIDTH;
    }

    /**
     * Custom Owner Drawn helper to draw a gradient across a Scrollable item.
     *
     * @param event the erase event.
     * @param fg gradient foreground.
     * @param bg gradient background.
     * @param end gradient end.
     */
    public static void codDrawGradient(Event event, Color fg, Color bg, Color end) {
        Scrollable scrollable = (Scrollable) event.widget;
        GC gc = event.gc;

        Rectangle area = scrollable.getClientArea();
        Rectangle rect = event.getBounds();

        /* Paint the selection beyond the end of last column */
        codExpandRegion(event, scrollable, gc, area);

        /* Draw Gradient Rectangle */
        Color oldForeground = gc.getForeground();
        Color oldBackground = gc.getBackground();

        /* Gradient */
        gc.setForeground(fg);
        gc.setBackground(bg);
        gc.fillGradientRectangle(0, rect.y, area.width, rect.height, true);

        /* Bottom Line */
        gc.setForeground(end);
        gc.drawLine(0, rect.y + rect.height - 1, area.width, rect.y + rect.height - 1);

        gc.setForeground(oldForeground);
        gc.setBackground(oldBackground);

        /* Mark as Background being handled */
        event.detail &= ~SWT.BACKGROUND;
    }

    /**
     * Custom Owner Draw helper to expand a drawn region over a scrollable item.
     *
     * @param event the erase event.
     * @param scrollable the scrollable to paint on.
     * @param gc the gc to paint on.
     * @param area the drawable area.
     */
    public static void codExpandRegion(Event event, Scrollable scrollable, GC gc, Rectangle area) {
        int columnCount;
        if (scrollable instanceof Table)
            columnCount = ((Table) scrollable).getColumnCount();
        else
            columnCount = ((Tree) scrollable).getColumnCount();

        if (event.index == columnCount - 1 || columnCount == 0) {
            int width = area.x + area.width - event.x;
            if (width > 0) {
                Region region = new Region();
                gc.getClipping(region);
                region.add(event.x, event.y, width, event.height);
                gc.setClipping(region);
                region.dispose();
            }
        }
    }

    /**
     * @param shell the {@link Shell} as parent of the {@link WizardDialog}.
     * @param wizard the {@link Wizard} to use in the {@link WizardDialog}.
     * @param modal if <code>false</code>, the wizard will not be a modal dialog.
     * @param needsProgressPart <code>true</code> to leave some room for the
     * {@link ProgressMonitorPart} and <code>false</code> otherwise.
     * @param dialogSettingsKey the key to use to store dialog settings.
     */
    public static void openWizard(Shell shell, Wizard wizard, final boolean modal, final boolean needsProgressPart,
            final String dialogSettingsKey) {
        openWizard(shell, wizard, modal, needsProgressPart, dialogSettingsKey, false, null);
    }

    /**
     * @param shell the {@link Shell} as parent of the {@link WizardDialog}.
     * @param wizard the {@link Wizard} to use in the {@link WizardDialog}.
     * @param modal if <code>false</code>, the wizard will not be a modal dialog.
     * @param needsProgressPart <code>true</code> to leave some room for the
     * {@link ProgressMonitorPart} and <code>false</code> otherwise.
     * @param dialogSettingsKey the key to use to store dialog settings.
     * @param pack if <code>true</code>, make the wizard as compact as possible
     * and <code>false</code> otherwise.
     * @param finishLabel the label for the finish button or <code>null</code> to
     * use the default.
     */
    public static void openWizard(Shell shell, Wizard wizard, final boolean modal, final boolean needsProgressPart,
            final String dialogSettingsKey, final boolean pack, final String finishLabel) {
        CustomWizardDialog dialog = new CustomWizardDialog(shell, wizard) {
            private ProgressMonitorPart progressMonitorPart;

            @Override
            protected boolean isResizable() {
                return true;
            }

            @Override
            protected Control createDialogArea(Composite parent) {
                Control control = super.createDialogArea(parent);
                if (progressMonitorPart != null && !needsProgressPart)
                    ((GridData) progressMonitorPart.getLayoutData()).exclude = true;
                return control;
            }

            @Override
            public boolean close() {
                progressMonitorPart = null;
                return super.close();
            }

            @Override
            protected ProgressMonitorPart createProgressMonitorPart(Composite composite, GridLayout pmlayout) {
                progressMonitorPart = super.createProgressMonitorPart(composite, pmlayout);
                return progressMonitorPart;
            }

            @Override
            protected IDialogSettings getDialogBoundsSettings() {
                if (dialogSettingsKey != null) {
                    IDialogSettings settings = Activator.getDefault().getDialogSettings();
                    IDialogSettings section = settings.getSection(dialogSettingsKey);
                    if (section != null)
                        return section;

                    return settings.addNewSection(dialogSettingsKey);
                }

                return super.getDialogBoundsSettings();
            }

            @Override
            protected int getShellStyle() {
                if (modal)
                    return super.getShellStyle();

                return SWT.TITLE | SWT.BORDER | SWT.MIN | SWT.RESIZE | SWT.CLOSE | getDefaultOrientation();
            }

            @Override
            protected int getDialogBoundsStrategy() {
                return DIALOG_PERSISTSIZE;
            }

            @Override
            protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
                if (IDialogConstants.FINISH_ID == id && StringUtils.isSet(finishLabel))
                    label = finishLabel;

                return super.createButton(parent, id, label, defaultButton);
            }

            @Override
            protected Point getInitialSize() {
                if (pack) {
                    int width = Application.IS_WINDOWS ? WINDOWS_PACKED_WIZARD_WIDTH
                            : Application.IS_LINUX ? LINUX_PACKED_WIZARD_WIDTH : MAC_PACKED_WIZARD_WIDTH;
                    return getShell().computeSize(convertHorizontalDLUsToPixels(width), SWT.DEFAULT, true);
                }

                return super.getInitialSize();
            }
        };
        dialog.setMinimumPageSize(0, 0);
        dialog.create();
        dialog.open();
    }

    /**
     * @param folder a selected {@link IFolder}
     * @return the selected {@link IFolder} if part of the currently selected
     * Bookmark Set, or the currently selected Bookmark Set otherwise.
     * @throws PersistenceException in case of an error while loading.
     */
    public static IFolder getSelectedParent(IFolder folder) throws PersistenceException {
        String selectedBookMarkSetPref = BookMarkExplorer.getSelectedBookMarkSetPref(getWindow());
        IPreference preference = DynamicDAO.getDAO(IPreferenceDAO.class).load(selectedBookMarkSetPref);
        if (preference != null) {
            Long selectedRootFolderID = preference.getLong();

            /* Check if available Parent is still valid */
            if (folder != null) {
                if (hasParent(folder, new FolderReference(selectedRootFolderID)))
                    return folder;
            }

            /* Otherwise return visible root-folder */
            return new FolderReference(selectedRootFolderID).resolve();
        }

        Set<IFolder> roots = CoreUtils.loadRootFolders();
        if (!roots.isEmpty())
            return roots.iterator().next();

        return null;
    }

    private static boolean hasParent(IFolder folder, FolderReference folderRef) {
        if (folder == null)
            return false;

        if (folderRef.references(folder))
            return true;

        return hasParent(folder.getParent(), folderRef);
    }

    /**
     * Adjust the bounds of the given Shell to respect the addition or removal of
     * the vertical bar.
     *
     * @param shell the Shell of the container.
     * @param verticalBar the vertical {@link ScrollBar} of the container.
     * @param wasScrollbarShowing <code>true</code> if the vertical scrollbar was
     * showing and <code>false</code> otherwise.
     */
    public static void adjustSizeForScrollbar(Shell shell, ScrollBar verticalBar, boolean wasScrollbarShowing) {
        if (verticalBar == null)
            return;

        /* Ignore for application window */
        if (shell.getParent() == null)
            return;

        int barWidth = verticalBar.getSize().x;
        if (Application.IS_MAC && barWidth == 0)
            barWidth = 16; //Can be 0 on Mac

        if (wasScrollbarShowing != verticalBar.isVisible()) {
            Rectangle shellBounds = shell.getBounds();

            /* Increase if Scrollbar now Visible */
            if (!wasScrollbarShowing)
                shell.setBounds(shellBounds.x, shellBounds.y, shellBounds.width + barWidth, shellBounds.height);

            /* Reduce if Scrollbar now Invisible */
            else
                shell.setBounds(shellBounds.x, shellBounds.y, shellBounds.width - barWidth, shellBounds.height);
        }
    }

    /**
     * @param name the name of the attachment.
     * @param mimeType the mime type of the attachment or <code>null</code> if
     * none.
     * @return an {@link ImageDescriptor} for the attachment. Never
     * <code>null</code>.
     */
    public static ImageDescriptor getAttachmentImage(String name, String mimeType) {

        /* First try to lookup image from Mime Type */
        ImageDescriptor descriptor = getImageForMime(mimeType);
        if (descriptor != null)
            return descriptor;

        /* Second try to lookup image from File Name */
        descriptor = getImageForFile(name);
        if (descriptor != null)
            return descriptor;

        /* Return Default */
        return ATTACHMENT;
    }

    /* Find a Image for the given File Name using Program API from SWT */
    private static ImageDescriptor getImageForFile(String file) {
        if (StringUtils.isSet(file)) {
            int lastIndexOfDot = file.lastIndexOf('.');
            if (lastIndexOfDot != -1 && !file.endsWith(".")) { //$NON-NLS-1$
                String extension = file.substring(lastIndexOfDot + 1);
                return getImageForExtension(extension.toLowerCase());
            }
        }

        return null;
    }

    /* Find a Image for the given Mime Type using Program API from SWT */
    private static ImageDescriptor getImageForMime(String mime) {
        if (StringUtils.isSet(mime)) {
            String extension = getExtensionForMime(mime);
            return getImageForExtension(extension);
        }

        return null;
    }

    /**
     * @param mime the mime type
     * @return the extension for the mime type or <code>null</code> if none
     */
    public static String getExtensionForMime(String mime) {
        if (StringUtils.isSet(mime))
            return fgMapMimeToExtension.get(mime.toLowerCase());

        return null;
    }

    /* Find a Image for the given Extension using Program API from SWT */
    @SuppressWarnings("restriction")
    private static ImageDescriptor getImageForExtension(String extension) {
        if (StringUtils.isSet(extension)) {
            Program p = Program.findProgram(extension);
            if (p != null)
                return new org.eclipse.ui.internal.misc.ExternalProgramImageDescriptor(p);
        }

        return null;
    }

    /**
     * @param seconds the number of seconds.
     * @return the period spanned by the seconds as human readable label.
     */
    public static String getPeriod(int seconds) {
        if (seconds > 0) {
            int hours = seconds / 3600;
            int minutes = (seconds / 60) % 60;

            /* X Hours, Y Minutes */
            if (hours > 0 && minutes > 0) {
                if (hours == 1) {
                    if (minutes == 1)
                        return NLS.bind(Messages.OwlUI_HOUR_MINUTE, hours, minutes);

                    return NLS.bind(Messages.OwlUI_HOUR_MINUTES, hours, minutes);
                }

                if (minutes == 1)
                    return NLS.bind(Messages.OwlUI_HOURS_MINUTE, hours, minutes);

                return NLS.bind(Messages.OwlUI_HOURS_MINUTES, hours, minutes);
            }

            /* X Hours */
            else if (hours > 0)
                return (hours == 1) ? NLS.bind(Messages.OwlUI_HOUR, hours) : NLS.bind(Messages.OwlUI_HOURS, hours);

            /* X Minutes */
            else if (hours == 0 && minutes > 0)
                return (minutes == 1) ? NLS.bind(Messages.OwlUI_MINUTE, minutes)
                        : NLS.bind(Messages.OwlUI_MINUTES, minutes);

            /* X Seconds */
            else if (seconds < 60)
                return (seconds == 1) ? NLS.bind(Messages.OwlUI_SECOND, seconds)
                        : NLS.bind(Messages.OwlUI_SECONDS, seconds);
        }

        return null;
    }

    /**
     * @param bytes the number of bytes.
     * @return a human readable representation of the bytes.
     */
    public static String getSize(long bytes) {
        if (bytes > 0) {
            double gb = bytes / (1024d * 1024d * 1024d);
            double mb = bytes / (1024d * 1024d);
            double kb = bytes / 1024d;

            NumberFormat format = new DecimalFormat(Messages.OwlUI_SIZE_FORMAT);

            if (gb >= 1)
                return NLS.bind(Messages.OwlUI_OwlUI_N_GB, format.format(gb));

            if (mb >= 1)
                return NLS.bind(Messages.OwlUI_N_MB, format.format(mb));

            if (kb >= 1)
                return NLS.bind(Messages.OwlUI_N_KB, format.format(kb));

            return NLS.bind(Messages.OwlUI_N_BYTES, bytes);
        }

        return null;
    }

    /**
     * @return the Size of the {@link Monitor} if only a single monitor is used or
     * <code>null</code> if none.
     */
    public static Point getFirstMonitorSize() {
        Display display = Display.getDefault();
        if (display != null) {
            Monitor[] monitors = display.getMonitors();
            if (monitors.length == 1) {
                Rectangle clientArea = monitors[0].getClientArea();
                return new Point(clientArea.width, clientArea.height);
            }
        }

        return null;
    }

    /**
     * Switch between full-screen and normal screen.
     */
    public static void toggleFullScreen() {
        Shell shell = OwlUI.getActiveShell();
        if (shell != null) {
            shell.setFullScreen(!shell.getFullScreen());

            /* Shell got restored */
            if (!shell.getFullScreen()) {
                ApplicationWorkbenchWindowAdvisor configurer = ApplicationWorkbenchAdvisor.fgPrimaryApplicationWorkbenchWindowAdvisor;
                configurer.setStatusVisible(
                        Owl.getPreferenceService().getGlobalScope().getBoolean(DefaultPreferences.SHOW_STATUS),
                        false);

                shell.layout(); //Need to layout to avoid screen cheese
            }

            /* Shell got fullscreen */
            else {
                ApplicationWorkbenchWindowAdvisor configurer = ApplicationWorkbenchAdvisor.fgPrimaryApplicationWorkbenchWindowAdvisor;
                configurer.setStatusVisible(false, true);
            }
        }
    }

    /**
     * Switch between showing and hiding the Bookmarks View.
     */
    public static void toggleBookmarks() {
        IWorkbenchPage page = OwlUI.getPage();
        if (page != null) {
            IViewPart explorerView = page.findView(BookMarkExplorer.VIEW_ID);

            /* Hide Bookmarks */
            if (explorerView != null)
                page.hideView(explorerView);

            /* Show Bookmarks */
            else {
                try {
                    page.showView(BookMarkExplorer.VIEW_ID);
                } catch (PartInitException e) {
                    Activator.getDefault().logError(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * @param action the dropdown action.
     * @param manager the toolbar containing the action.
     */
    public static void positionDropDownMenu(Action action, ToolBarManager manager) {
        Menu menu = action.getMenuCreator().getMenu(manager.getControl());
        if (menu != null) {

            /* Adjust Location */
            IContributionItem contributionItem = manager.find(action.getId());
            if (contributionItem != null && contributionItem instanceof ActionContributionItem) {
                Widget widget = ((ActionContributionItem) contributionItem).getWidget();
                if (widget != null && widget instanceof ToolItem) {
                    ToolItem item = (ToolItem) widget;
                    Rectangle rect = item.getBounds();
                    Point pt = new Point(rect.x, rect.y + rect.height);
                    pt = manager.getControl().toDisplay(pt);
                    if (Application.IS_MAC)
                        pt.y += 5;
                    menu.setLocation(pt.x, pt.y);
                }
            }

            /* Set Visible */
            menu.setVisible(true);
        }
    }

    /**
     * @param zoomIn
     * @param reset
     */
    @SuppressWarnings("restriction")
    public static void zoomNewsText(boolean zoomIn, boolean reset) {

        /* Retrieve Font */
        ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
        FontRegistry registry = theme.getFontRegistry();
        FontData[] oldFontDatas = registry.getFontData(NEWS_TEXT_FONT_ID);
        FontData[] newFontDatas = new FontData[oldFontDatas.length];

        /* Set Height */
        for (int i = 0; i < oldFontDatas.length; i++) {
            FontData oldFontData = oldFontDatas[i];
            int oldHeight = oldFontData.getHeight();

            if (reset)
                newFontDatas[i] = new FontData(oldFontData.getName(), DEFAULT_NEWS_TEXT_FONT_HEIGHT,
                        oldFontData.getStyle());
            else
                newFontDatas[i] = new FontData(oldFontData.getName(),
                        zoomIn ? oldHeight + 1 : Math.max(oldHeight - 1, 0), oldFontData.getStyle());
        }

        registry.put(NEWS_TEXT_FONT_ID, newFontDatas);

        /* Store in Preferences */
        String key = org.eclipse.ui.internal.themes.ThemeElementHelper.createPreferenceKey(theme,
                NEWS_TEXT_FONT_ID);
        String fdString = PreferenceConverter.getStoredRepresentation(newFontDatas);
        String storeString = org.eclipse.ui.internal.util.PrefUtil.getInternalPreferenceStore().getString(key);
        if (!fdString.equals(storeString))
            org.eclipse.ui.internal.util.PrefUtil.getInternalPreferenceStore().setValue(key, fdString);
    }

    /**
     * @param run the {@link Runnable} to run on selection changes.
     * @param control the control to add selection listener to. Will recursively
     * go into child controls for Composites.
     */
    public static void runOnSelection(final Runnable run, Control... control) {
        for (Control c : control) {

            /* Button */
            if (c instanceof Button) {
                Button button = (Button) c;
                button.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        run.run();
                    }
                });
            }

            /* Combo */
            else if (c instanceof Combo) {
                Combo combo = (Combo) c;
                combo.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        run.run();
                    }
                });
            }

            /* Tree */
            else if (c instanceof Tree) {
                Tree tree = (Tree) c;
                tree.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        if ((e.detail & SWT.CHECK) != 0)
                            run.run();
                    }
                });

                tree.addDragDetectListener(new DragDetectListener() {
                    public void dragDetected(DragDetectEvent e) {
                        run.run();
                    }
                });
            }

            /* Table */
            else if (c instanceof Table) {
                Table table = (Table) c;
                table.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        if ((e.detail & SWT.CHECK) != 0)
                            run.run();
                    }
                });

                table.addDragDetectListener(new DragDetectListener() {
                    public void dragDetected(DragDetectEvent e) {
                        run.run();
                    }
                });
            }

            /* Spinner */
            else if (c instanceof Spinner) {
                Spinner spinner = (Spinner) c;
                spinner.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        run.run();
                    }
                });
            }

            /* Text */
            else if (c instanceof Text) {
                Text text = (Text) c;
                text.addModifyListener(new ModifyListener() {
                    public void modifyText(ModifyEvent e) {
                        run.run();
                    }
                });
            }

            /* Composite */
            else if (c instanceof Composite) {
                Composite composite = (Composite) c;
                runOnSelection(run, composite.getChildren());
            }
        }
    }

    /**
     * @return the {@link DateFormat} used for short dates. Respects the system
     * property to override this value from default.
     */
    public static DateFormat getShortDateFormat() {
        String format = System.getProperty(SHORT_DATE_FORMAT_PROPERTY);
        if (StringUtils.isSet(format)) {
            try {
                return new SimpleDateFormat(format);
            } catch (Exception e) {
                /* Ignore and use Default */
            }
        }

        return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
    }

    /**
     * @return the {@link DateFormat} used for long dates. Respects the system
     * property to override this value from default.
     */
    public static DateFormat getLongDateFormat() {
        String format = System.getProperty(LONG_DATE_FORMAT_PROPERTY);
        if (StringUtils.isSet(format)) {
            try {
                return new SimpleDateFormat(format);
            } catch (Exception e) {
                /* Ignore and use Default */
            }
        }

        return DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.SHORT);
    }

    /**
     * @return the {@link DateFormat} used for short times. Respects the system
     * property to override this value from default.
     */
    public static DateFormat getShortTimeFormat() {
        String format = System.getProperty(SHORT_TIME_FORMAT_PROPERTY);
        if (StringUtils.isSet(format)) {
            try {
                return new SimpleDateFormat(format);
            } catch (Exception e) {
                /* Ignore and use Default */
            }
        }

        return DateFormat.getTimeInstance(DateFormat.SHORT);
    }

    /**
     * @param control the control to provide an accessible name for.
     * @param label the label control to take the label from.
     */
    public static void makeAccessible(Control control, Control label) {
        if (label == null || label.isDisposed())
            return;

        if (label instanceof Button)
            makeAccessible(control, ((Button) label).getText());
        else if (label instanceof Label)
            makeAccessible(control, ((Label) label).getText());
        else if (label instanceof CLabel)
            makeAccessible(control, ((CLabel) label).getText());
    }

    /**
     * @param control the control to provide an accessible name for.
     * @param name the name for the control to be used in accessible environments.
     */
    public static void makeAccessible(final Control control, String name) {

        /* Strip Mnemonics */
        final String accessibleName;
        if (name.contains("&")) //$NON-NLS-1$
            accessibleName = StringUtils.replaceAll(name, "&", ""); //$NON-NLS-1$ //$NON-NLS-2$
        else
            accessibleName = name;

        /* Apply Accessible Name */
        if (control != null && !control.isDisposed()) {
            control.getAccessible().addAccessibleListener(new AccessibleAdapter() {
                @Override
                public void getName(AccessibleEvent e) {
                    if (control instanceof Tree || control instanceof Table) {
                        if (e.childID == ACC.CHILDID_SELF)
                            e.result = accessibleName;
                        else if (!control.isDisposed()) {
                            Widget widget = control.getDisplay().findWidget(control, e.childID);
                            if (widget != null && widget instanceof Item)
                                e.result = NLS.bind(Messages.OwlUI_ACCESSIBLE_NAME, ((Item) widget).getText());
                        }
                    } else
                        e.result = accessibleName;
                }
            });
        }
    }

    /**
     * @return <code>true</code> if tabbed browsing is enabled and
     * <code>false</code> otherwise.
     */
    public static boolean isTabbedBrowsingEnabled() {
        IPreferenceScope preferences = Owl.getPreferenceService().getEclipseScope();
        boolean autoCloseTabs = preferences.getBoolean(DefaultPreferences.ECLIPSE_AUTOCLOSE_TABS);
        int autoCloseTabsThreshold = preferences.getInteger(DefaultPreferences.ECLIPSE_AUTOCLOSE_TABS_THRESHOLD);
        return !autoCloseTabs || autoCloseTabsThreshold > 1;
    }

    /**
     * @return <code>true</code> if RSSOwl is minimized (either its Shell or
     * minimized to tray) and <code>false</code> otherwise.
     */
    public static boolean isMinimized() {
        ApplicationWorkbenchWindowAdvisor advisor = ApplicationWorkbenchAdvisor.fgPrimaryApplicationWorkbenchWindowAdvisor;
        if (advisor != null && (advisor.isMinimizedToTray() || advisor.isMinimized()))
            return true;

        return false;
    }

    /**
     * @return <code>true</code> if the user has configured to use an external
     * browser and <code>false</code> otherwise.
     */
    public static boolean useExternalBrowser() {
        IPreferenceScope preferences = Owl.getPreferenceService().getGlobalScope();
        return preferences.getBoolean(DefaultPreferences.USE_DEFAULT_EXTERNAL_BROWSER)
                || preferences.getBoolean(DefaultPreferences.USE_CUSTOM_EXTERNAL_BROWSER);
    }

    /**
     * @return the currently selected {@link IFolder} as bookmark set from the
     * feeds view or the first root folder otherwise. Falls back to
     * <code>null</code> if neither can be resolved.
     */
    public static IFolder getSelectedBookMarkSet() {
        IPreferenceScope preferences = Owl.getPreferenceService().getGlobalScope();
        IFolderDAO folderDAO = DynamicDAO.getDAO(IFolderDAO.class);

        String selectedBookMarkSetPref = BookMarkExplorer.getSelectedBookMarkSetPref(getWindow());
        long selectedFolderID = preferences.getLong(selectedBookMarkSetPref);
        IFolder selectedSet = folderDAO.load(selectedFolderID);
        if (selectedSet != null)
            return selectedSet;

        Set<IFolder> rootFolders = CoreUtils.loadRootFolders();
        if (!rootFolders.isEmpty())
            return rootFolders.iterator().next();

        return null;
    }

    /**
     * @return <code>true</code> if the preference tell to update duplicate news
     * states when marking as read and <code>false</code> otherwise.
     */
    public static boolean markReadDuplicates() {
        IPreferenceScope preferences = Owl.getPreferenceService().getGlobalScope();
        return preferences.getBoolean(DefaultPreferences.MARK_READ_DUPLICATES);
    }

    /**
     * @param scope the preferences scope to look for the defined layout.
     * @return the selected {@link Layout} from the given preferences scope.
     */
    public static Layout getLayout(IPreferenceScope scope) {
        int layoutOrdinal = scope.getInteger(DefaultPreferences.FV_LAYOUT);
        Layout[] layouts = Layout.values();
        return layoutOrdinal < layouts.length ? layouts[layoutOrdinal] : Layout.CLASSIC;
    }

    /**
     * @param scope the preferences scope to look for the defined page size.
     * @return the selected {@link PageSize} from the given preferences scope.
     */
    public static PageSize getPageSize(IPreferenceScope scope) {
        int pageSize = scope.getInteger(DefaultPreferences.NEWS_BROWSER_PAGE_SIZE);
        return PageSize.from(pageSize);
    }

    /**
     * Safely disposes the provided {@link Menu}.
     *
     * @param menu the {@link Menu} to dispose.
     */
    public static void safeDispose(Menu menu) {
        try {
            menu.dispose();
        } catch (NegativeArraySizeException e) {
            /* Bug in SWT that we can safely ignore */
        }
    }

    /**
     * Opens a file dialog to save the crash report.
     *
     * @param shell the parent {@link Shell} of the dialog that opens.
     * @throws FileNotFoundException in case of an error
     */
    public static void saveCrashReport(Shell shell) throws FileNotFoundException {
        FileDialog dialog = new FileDialog(shell, SWT.SAVE);
        dialog.setText(Messages.OwlUI_SAVE_CRASH_REPORT);
        dialog.setFilterExtensions(new String[] { "*.log" }); //$NON-NLS-1$
        dialog.setFileName("rssowl.log"); //$NON-NLS-1$
        dialog.setOverwrite(true);

        String file = dialog.open();
        if (StringUtils.isSet(file)) {

            /* Check for Log Message from Core to have a complete log */
            String logMessages = CoreUtils.getAndFlushLogMessages();
            if (logMessages != null && logMessages.length() > 0)
                Activator.safeLogError(logMessages, null);

            /* Help to find out where the log is coming from */
            Activator.safeLogInfo("Crash Report Exported"); //$NON-NLS-1$

            /* Export Log File */
            File logFile = Platform.getLogFileLocation().toFile();
            InputStream inS;
            if (logFile.exists())
                inS = new FileInputStream(logFile);
            else
                inS = new ByteArrayInputStream(new byte[0]);

            FileOutputStream outS = new FileOutputStream(new File(file));
            CoreUtils.copy(inS, outS);
        }
    }

    /**
     * Opens the Login Dialog to authenticate against sync services.
     *
     * @param shell the {@link Shell} as parent of the dialog or <code>null</code>
     * if none.
     * @return one of the {@link IDialogConstants} depending on the users choice
     * of closing the dialog with OK or Cancel.
     */
    public static int openSyncLogin(Shell shell) {
        if (shell == null)
            shell = getActiveShell();

        if (shell != null) {
            URI googleLoginUri = URI.create(SyncUtils.GOOGLE_LOGIN_URL);
            LoginDialog dialog = new LoginDialog(shell, googleLoginUri, null, true);
            dialog.setHeader(Messages.OwlUI_SYNC_LOGIN);
            dialog.setSubline(Messages.OwlUI_SYNC_LOGIN_TEXT);
            dialog.setTitleImageDescriptor(OwlUI.getImageDescriptor("icons/wizban/reader_wiz.png")); //$NON-NLS-1$

            return dialog.open();
        }

        return IDialogConstants.CANCEL_ID;
    }

    /**
     * @return <code>true</code> in case the a text control needs an extra cancel
     * control to clear a search and <code>false</code> if the OS provides a
     * native one already.
     */
    public static boolean needsCancelControl() {
        if (Application.IS_WINDOWS)
            return true; //Windows does not support a native cancel button in text fields

        if (Application.IS_MAC)
            return false; //Mac supports native cancel button in text fields

        return SWT.getVersion() < 3700; //Some Linux distros support it with recent SWT version
    }
}