org.springsource.ide.eclipse.dashboard.internal.ui.editors.DashboardMainPage.java Source code

Java tutorial

Introduction

Here is the source code for org.springsource.ide.eclipse.dashboard.internal.ui.editors.DashboardMainPage.java

Source

/*******************************************************************************
 *  Copyright (c) 2012 VMware, Inc.
 *  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.eclipse.org/legal/epl-v10.html
 *
 *  Contributors:
 *      VMware, Inc. - initial API and implementation
 *******************************************************************************/
package org.springsource.ide.eclipse.dashboard.internal.ui.editors;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringEscapeUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.mylyn.commons.ui.CommonImages;
import org.eclipse.mylyn.commons.workbench.search.TextSearchControl;
import org.eclipse.mylyn.tasks.ui.TasksUiUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.NewWizardAction;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.ImageHyperlink;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.forms.widgets.FormFonts;
import org.eclipse.ui.part.PageBook;
import org.osgi.framework.Bundle;
import org.springsource.ide.eclipse.commons.core.ResourceProvider;
import org.springsource.ide.eclipse.commons.core.StatusHandler;
import org.springsource.ide.eclipse.commons.ui.StsUiImages;
import org.springsource.ide.eclipse.dashboard.internal.ui.IIdeUiConstants;
import org.springsource.ide.eclipse.dashboard.internal.ui.IdeUiPlugin;
import org.springsource.ide.eclipse.dashboard.internal.ui.discovery.DashboardExtensionsPage;
import org.springsource.ide.eclipse.dashboard.ui.AbstractDashboardPage;
import org.springsource.ide.eclipse.dashboard.ui.AbstractDashboardPart;

import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;

/**
 * @author Terry Denney
 * @author Christian Dupuis
 * @author Steffen Pingel
 */
public class DashboardMainPage extends AbstractDashboardPage implements PropertyChangeListener {

    public static final String RESOURCE_DASHBOARD_BUG_TRACKER = "dashboard.bug.tracker";

    public static final String RESOURCE_DASHBOARD_FEEDS_BLOGS = "dashboard.feeds.blogs";

    public static final String RESOURCE_DASHBOARD_FEEDS_UPDATE = "dashboard.feeds.update";

    public static final String RESOURCE_DASHBOARD_LOGO = "dashboard.logo";

    public static final String RESOURCE_DASHBOARD_SUBSCRIBE = "dashboard.subscribe";

    public static final String RESOURCE_DASHBOARD_NEW_AND_NOTEWORTHY = "dashboard.new.and.noteworthy";

    public static final String RESOURCE_DASHBOARD_PRODUCT_PAGE = "dashboard.product.page";

    public static final String RESOURCE_DASHBOARD_SEARCH = "dashboard.search";

    public static final String RESOURCE_DASHBOARD_SUPPORT_COMMERCIAL = "dashboard.support.commercial";

    public static final String RESOURCE_DASHBOARD_SUPPORT_COMMUNITY = "dashboard.support.community";

    private static final String LOGO_SPRINGSOURCE = "prod/springsource_logo_transparent.png";

    private static final String SUBSCRIBE_SPRINGSOURCE = "prod/newsletter_subscription.gif";

    public static final String PAGE_ID = "com.springsource.sts.ide.ui.dashboard.page.overview";

    private static final Pattern PATTERN = Pattern.compile("<img.*href=\"(.*?)\"/>");

    private final MultiPageDashboardEditor dashboardEditor;

    private FormToolkit toolkit;

    private ScrolledForm form;

    private Text searchBox;

    private IPreferenceStore prefStore;

    private Color feedColor;

    private Section updateSection;

    private Section helpSection;

    private static final String PROXY_PREF_PAGE_ID = "org.eclipse.ui.net.NetPreferences";

    private static final String ELEMENT_CLASS = "class";

    private static final String ELEMENT_ICON = "icon";

    private static final String ELEMENT_NAME = "name";

    private static final String EXTENSION_ID_NEW_WIZARD = "org.eclipse.ui.newWizards";

    private static final String GRAILS_WIZARD_ID = "org.grails.ide.eclipse.ui.wizard.newGrailsProjectWizard";

    private static final String ROO_WIZARD_ID = "com.springsource.sts.roo.ui.wizard.newRooProjectWizard";

    private static final String GROOVY_WIZARD_ID = "org.codehaus.groovy.eclipse.ui.groovyProjectWizard";

    private static final String TEMPLATE_WIZARD_ID = "com.springsource.sts.wizard.template";

    private static final String SPRING_WIZARD_ID = "org.springframework.ide.eclipse.beans.ui.wizards.newSpringProject";

    private static final String JAVA_WIZARD_ID = "org.eclipse.jdt.ui.wizards.JavaProjectWizard";

    private static final int UPDATE_INDENTATION = 22;

    private static final int UPDATE_TEXT_WRAP_INDENT = 65;

    private static final int FEEDS_TEXT_WRAP_INDENT = 80;

    private final List<SyndEntry> displayedEntries;

    private Composite feedsComposite;

    private ScrolledComposite feedsScrolled;

    private static final String ICON_BLOG_INCOMING = "rss/overlay-incoming.png";

    private static final String ICON_BLOG_BLANK = "rss/blank.png";

    private static final int FEEDS_DESCRIPTION_MAX = 200;

    protected static final String URL_CONFIGURATION_ID = "com.springsource.sts.ide.ui.preferencePage";

    // TODO e3.5 replace by SWT.UNDERLINE_LINK
    public static final int SWT_UNDERLINE_LINK = 4;

    private Set<Control> feedControls;

    private Action refreshFeedsAction;

    private Action refreshUpdatesAction;

    private ScrolledComposite updateScrolled;

    private Set<AggregateFeedJob> unfinishedJobs;

    private List<AbstractDashboardPart> parts;

    public DashboardMainPage(MultiPageDashboardEditor editor) {
        super(editor, PAGE_ID, "Dashboard");
        dashboardEditor = editor;
        displayedEntries = new ArrayList<SyndEntry>();
    }

    public void cancelUnfinishedJobs() {
        for (AggregateFeedJob job : unfinishedJobs) {
            job.cancel();
        }
        unfinishedJobs.clear();
    }

    @Override
    public void dispose() {
        ResourceProvider.getInstance().removePropertyChangeListener(this);
        if (feedColor != null) {
            feedColor.dispose();
        }
    }

    public void propertyChange(PropertyChangeEvent event) {
        String propertyName = event.getPropertyName();

        if (propertyName.equals(RESOURCE_DASHBOARD_FEEDS_BLOGS)) {
            refreshFeedsAction.run();
        } else if (propertyName.equals(RESOURCE_DASHBOARD_FEEDS_UPDATE)) {
            refreshUpdatesAction.run();
        }
    }

    private void addImages(FormText text, String description) {
        if (description.contains("<img")) {
            Matcher matcher = PATTERN.matcher(description);
            while (matcher.find()) {
                String url = matcher.group(1);
                text.setImage(url, IdeUiPlugin.getImage(url));
            }
        }
    }

    private void adjustCollapsableSections() {
        GridDataFactory.fillDefaults().grab(true, updateSection.isExpanded()).applyTo(updateSection);
        for (AbstractDashboardPart part : parts) {
            if (part.getControl() instanceof Section) {
                Section section = (Section) part.getControl();
                GridDataFactory.fillDefaults().grab(true, section.isExpanded()).applyTo(section);
            }
        }
        GridDataFactory.fillDefaults().grab(true,
                !updateSection.isExpanded() && areAllContributedSectionsCollapsed() && helpSection.isExpanded())
                .applyTo(helpSection);

        form.getBody().layout(true, true);
        form.reflow(true);
    }

    private boolean areAllContributedSectionsCollapsed() {
        for (AbstractDashboardPart part : parts) {
            if (part.getControl() instanceof Section) {
                if (((Section) part.getControl()).isExpanded()) {
                    return false;
                }
            }
        }
        return true;
    }

    private void clearText() {
        if (searchBox != null && !searchBox.isDisposed()) {
            searchBox.setText("");
        }
    }

    private void createFeedsSection(Composite parent, final String title, FeedType feedType, String feedName) {
        final Section section = new Section(parent, ExpandableComposite.TITLE_BAR) {
            @Override
            public void redraw() {
                GridData compositeData = (GridData) feedsScrolled.getLayoutData();
                compositeData.widthHint = getSize().x - 35;
                compositeData.heightHint = getSize().y - 40;
                compositeData.grabExcessHorizontalSpace = false;
                compositeData.grabExcessVerticalSpace = false;

                super.redraw();
            }
        };

        form.addControlListener(new ControlAdapter() {

            @Override
            public void controlResized(ControlEvent e) {
                GridData data = (GridData) section.getLayoutData();
                data.heightHint = form.getSize().y - FEEDS_TEXT_WRAP_INDENT;
            }
        });

        toolkit.adapt(section);
        section.setTitleBarForeground(toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
        section.setTitleBarBackground(toolkit.getColors().getColor(IFormColors.TB_BG));
        section.setTitleBarBorderColor(toolkit.getColors().getColor(IFormColors.TB_BORDER));
        section.setFont(FormFonts.getInstance().getBoldFont(getSite().getShell().getDisplay(), section.getFont()));

        section.setText(title);
        section.setLayout(new GridLayout());
        GridDataFactory.fillDefaults().grab(true, true).hint(300, 300).applyTo(section);

        final Composite headerComposite = toolkit.createComposite(section, SWT.NONE);
        RowLayout rowLayout = new RowLayout();
        rowLayout.marginTop = 0;
        rowLayout.marginBottom = 0;
        headerComposite.setLayout(rowLayout);
        headerComposite.setBackground(null);

        ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
        toolBarManager.createControl(headerComposite);

        final PageBook pagebook = new PageBook(section, SWT.NONE);
        toolkit.adapt(pagebook);
        pagebook.setLayoutData(new GridLayout());

        final Composite disclaimer = createDisclaimer(pagebook);

        feedsScrolled = new ScrolledComposite(pagebook, SWT.V_SCROLL);

        feedsScrolled.setExpandVertical(false);
        feedsScrolled.setLayout(new GridLayout());
        feedsScrolled.setAlwaysShowScrollBars(false);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(feedsScrolled);
        toolkit.adapt(feedsScrolled);

        feedsComposite = toolkit.createComposite(feedsScrolled);
        feedsComposite.setLayout(new TableWrapLayout());
        GridDataFactory.fillDefaults().grab(true, false).applyTo(feedsComposite);
        feedsScrolled.setContent(feedsComposite);

        feedsComposite.addPaintListener(new PaintListener() {

            public void paintControl(PaintEvent e) {
                GridData data = (GridData) feedsComposite.getLayoutData();
                data.widthHint = section.getSize().x - FEEDS_TEXT_WRAP_INDENT;
                data.heightHint = form.getSize().y - 50;
            }
        });
        section.addControlListener(new ControlAdapter() {

            @Override
            public void controlResized(ControlEvent e) {
                GridData data = (GridData) feedsScrolled.getLayoutData();
                data.heightHint = form.getSize().y - 50;
                data.grabExcessVerticalSpace = false;
                feedsScrolled.setSize(section.getSize().x - 40, form.getSize().y - 50);

                for (Control feedControl : feedControls) {
                    ((TableWrapData) feedControl.getLayoutData()).maxWidth = section.getSize().x
                            - FEEDS_TEXT_WRAP_INDENT;
                    // Point size = feedControl.computeSize(data.widthHint,
                    // 400);
                    // feedControl.setSize(size);
                    // feedControl.pack(true);
                }

                feedsComposite.pack();
            };
        });

        feedControls = new HashSet<Control>();

        final Map<String, String> springMap = new HashMap<String, String>();
        String[] urls = ResourceProvider.getUrls(RESOURCE_DASHBOARD_FEEDS_BLOGS);
        for (String url : urls) {
            springMap.put(url, null);
        }

        getEditorSite().getShell().getDisplay().asyncExec(new Runnable() {

            public void run() {
                FeedsReader reader = new FeedsReader();
                CachedFeedsManager manager = new CachedFeedsManager(title, springMap, reader);
                try {
                    manager.readCachedFeeds(null);
                    Set<SyndEntry> entries = new HashSet<SyndEntry>();
                    for (SyndFeed entry : reader.getFeeds()) {
                        entries.addAll(entry.getEntries());
                    }

                    if (!getManagedForm().getForm().isDisposed()) {
                        displayFeeds(entries, feedsComposite, feedsScrolled, pagebook, disclaimer, section);
                    }
                } catch (IllegalArgumentException e) {
                    StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
                            "An unexpected error occurred while retrieving feed content from cache.", e));
                } catch (FeedException e) {
                    StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
                            "An unexpected error occurred while retrieving feed content from cache.", e));
                }
            }
        });

        refreshFeedsAction = new Action("Refresh Feeds", CommonImages.REFRESH) {
            @Override
            public void run() {
                Map<String, String> springMap = getFeedsMap();

                displayFeeds(feedsComposite, feedsScrolled, pagebook, disclaimer, springMap, title, section);
            }

        };

        Action configureURLsAction = new Action("Configure URLs", StsUiImages.RSS_CONFIGURE) {
            @Override
            public void run() {
                PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(getSite().getShell(),
                        URL_CONFIGURATION_ID, new String[] { URL_CONFIGURATION_ID }, null);
                dialog.open();
            }
        };

        section.setClient(pagebook);
        section.setTextClient(headerComposite);

        toolBarManager.add(configureURLsAction);
        toolBarManager.add(refreshFeedsAction);
        toolBarManager.update(true);

        refreshFeedsAction.run();
    }

    private Composite createDisclaimer(Composite parent) {
        FormText disclaimer = toolkit.createFormText(parent, true);
        disclaimer.setForeground(feedColor);
        disclaimer.setText(
                "<form><p>No entries found. Ensure <a href=\"proxy\">firewall and proxy settings</a> are appropriately configured.</p></form>",
                true, false);
        disclaimer.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                if ("proxy".equals(e.data)) {
                    PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null, PROXY_PREF_PAGE_ID,
                            null, null);
                    dialog.open();
                }
            }

        });
        return disclaimer;
    }

    private void createHeader() {
        IManagedForm headerForm = dashboardEditor.getHeaderForm();
        Form topForm = headerForm.getForm().getForm();
        final IToolBarManager toolBarManager = topForm.getToolBarManager();

        toolBarManager.removeAll();
        toolBarManager.update(true);

        toolBarManager.add(new ControlContribution("springSource_search") {
            @Override
            protected Control createControl(Composite parent) {
                Composite composite = new Composite(parent, SWT.NONE);
                composite.setBackground(null);

                GridLayout layout = new GridLayout(2, false);
                layout.marginRight = 0;
                layout.marginHeight = 0;
                layout.marginTop = 5;
                layout.verticalSpacing = 1;
                composite.setLayout(layout);

                GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BOTTOM).grab(false, true).applyTo(composite);

                createSearchBox(composite);

                return composite;
            }
        });

        toolBarManager.add(new ControlContribution("subscribe") {
            @Override
            protected Control createControl(Composite parent) {
                Composite composite = new Composite(parent, SWT.NONE);
                composite.setBackground(null);

                GridLayout layout = new GridLayout(2, false);
                layout.marginRight = 2;
                layout.marginLeft = 0;
                layout.marginHeight = 0;
                layout.marginTop = 12;
                layout.verticalSpacing = 1;
                composite.setLayout(layout);

                GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BOTTOM).grab(false, true).applyTo(composite);

                Button subscribeButton = new Button(composite, SWT.PUSH);
                subscribeButton.setText("Subscribe");
                subscribeButton.setImage(IdeUiPlugin.getImageDescriptor(SUBSCRIBE_SPRINGSOURCE).createImage());
                subscribeButton.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));

                subscribeButton.addSelectionListener(new SelectionListener() {
                    public void widgetSelected(SelectionEvent e) {
                        TasksUiUtil.openUrl(ResourceProvider.getUrl(RESOURCE_DASHBOARD_SUBSCRIBE));
                    }

                    public void widgetDefaultSelected(SelectionEvent e) {
                    }
                });

                return composite;
            }
        });

        toolBarManager.add(new Action("SpringSource", IdeUiPlugin.getImageDescriptor(LOGO_SPRINGSOURCE)) {
            @Override
            public void run() {
                TasksUiUtil.openUrl(ResourceProvider.getUrl(RESOURCE_DASHBOARD_LOGO));
            }
        });

        toolBarManager.update(true);
    }

    private void createHelpSection(Composite parent) {
        helpSection = toolkit.createSection(parent, ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE);
        helpSection.setText("Help and Documentation");
        helpSection.setLayout(new GridLayout());
        GridDataFactory.fillDefaults().grab(true, false).applyTo(helpSection);

        Composite composite = toolkit.createComposite(helpSection);
        GridLayout layout = new GridLayout(2, false);
        layout.horizontalSpacing = 10;
        composite.setLayout(layout);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);

        Hyperlink link = toolkit.createHyperlink(composite, "Community Support Forums", SWT_UNDERLINE_LINK);
        GridDataFactory.fillDefaults().applyTo(link);
        link.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                TasksUiUtil.openUrl(ResourceProvider.getUrl(RESOURCE_DASHBOARD_SUPPORT_COMMUNITY));
            }
        });

        link = toolkit.createHyperlink(composite, "New and Noteworthy", SWT_UNDERLINE_LINK);
        GridDataFactory.fillDefaults().applyTo(link);
        link.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                TasksUiUtil.openUrl(ResourceProvider.getUrl(RESOURCE_DASHBOARD_NEW_AND_NOTEWORTHY));
            }
        });

        link = toolkit.createHyperlink(composite, "Issue and Bug Tracker", SWT_UNDERLINE_LINK);
        GridDataFactory.fillDefaults().applyTo(link);
        link.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                TasksUiUtil.openUrl(ResourceProvider.getUrl(RESOURCE_DASHBOARD_BUG_TRACKER));
            }
        });

        link = toolkit.createHyperlink(composite, "Extensions", SWT_UNDERLINE_LINK);
        GridDataFactory.fillDefaults().applyTo(link);
        link.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                try {
                    IWorkbenchPage page = getSite().getWorkbenchWindow().getActivePage();
                    FormEditor editor = (FormEditor) page.openEditor(DashboardEditorInput.INSTANCE,
                            MultiPageDashboardEditor.EDITOR_ID);
                    editor.setActivePage(DashboardExtensionsPage.ID);
                } catch (PartInitException ex) {
                    StatusHandler
                            .log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID, "Could not open dashboard", ex));
                }
            }
        });

        link = toolkit.createHyperlink(composite, "SpringSource Commercial Support", SWT_UNDERLINE_LINK);
        GridDataFactory.fillDefaults().applyTo(link);
        link.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                TasksUiUtil.openUrl(ResourceProvider.getUrl(RESOURCE_DASHBOARD_SUPPORT_COMMERCIAL));
            }
        });

        link = toolkit.createHyperlink(composite, "Product Page", SWT_UNDERLINE_LINK);
        GridDataFactory.fillDefaults().applyTo(link);
        link.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                TasksUiUtil.openUrl(ResourceProvider.getUrl(RESOURCE_DASHBOARD_PRODUCT_PAGE));
            }
        });

        helpSection.setClient(composite);

        setUpExpandableSection(helpSection, IIdeUiConstants.PREF_HELP_SECTION_COLLAPSE, true);
    }

    private void createNewProjectFromExtension(final Composite container, final IConfigurationElement element) {
        if (element == null) {
            return;
        }

        try {
            Object object = WorkbenchPlugin.createExtension(element, ELEMENT_CLASS);
            if (!(object instanceof INewWizard)) {
                StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
                        "Could not load " + object.getClass().getCanonicalName() + " must implement "
                                + INewWizard.class.getCanonicalName()));
                return;
            }

            String title = element.getAttribute(ELEMENT_NAME);

            String pathName = element.getAttribute(ELEMENT_ICON);
            String plugin = element.getContributor().getName();
            Bundle bundle = Platform.getBundle(plugin);
            URL iconLocation = bundle.getResource(pathName);

            Image image = null;
            // TODO: fix this hack...need to read wildcard entries
            if (element.getAttribute("id").equals(JAVA_WIZARD_ID)) {
                image = StsUiImages.getImage(StsUiImages.NEW_JAVA_PROJECT);
            } else {
                if (iconLocation != null) {
                    image = StsUiImages.getImage(ImageDescriptor.createFromURL(iconLocation));
                }
            }

            createNewProjectLink(container, title, image, new HyperlinkAdapter() {
                @Override
                public void linkActivated(HyperlinkEvent e) {
                    Object object;
                    try {
                        object = WorkbenchPlugin.createExtension(element, ELEMENT_CLASS);
                    } catch (CoreException ex) {
                        StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
                                "Could not read dashboard extension", ex));
                        return;
                    }
                    if (!(object instanceof INewWizard)) {
                        StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
                                "Could not load " + object.getClass().getCanonicalName() + " must implement "
                                        + INewWizard.class.getCanonicalName()));
                        return;
                    }

                    INewWizard wizard = (INewWizard) object;
                    wizard.init(PlatformUI.getWorkbench(), new StructuredSelection());
                    WizardDialog dialog = new WizardDialog(container.getShell(), wizard);
                    dialog.open();
                }
            });
        } catch (CoreException e) {
            StatusHandler
                    .log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID, "Could not read dashboard extension", e));
        }

    }

    private void createNewProjectLink(Composite parent, String name, Image image, IHyperlinkListener listener) {
        ImageHyperlink link = toolkit.createImageHyperlink(parent, SWT.NONE);
        link.setText(name);
        link.setImage(image);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(link);

        if (listener != null) {
            link.addHyperlinkListener(listener);
        }
    }

    private void createNewProjectsSection(Composite parent) {
        Section section = toolkit.createSection(parent, ExpandableComposite.TITLE_BAR);
        section.setText("Create");
        GridDataFactory.fillDefaults().grab(false, false).applyTo(section);
        section.setLayout(new GridLayout());

        final Composite headerComposite = toolkit.createComposite(section, SWT.NONE);
        RowLayout rowLayout = new RowLayout();
        rowLayout.marginTop = 0;
        rowLayout.marginBottom = 0;
        headerComposite.setLayout(rowLayout);
        headerComposite.setBackground(null);

        ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
        toolBarManager.createControl(headerComposite);
        section.setTextClient(headerComposite);

        toolBarManager.add(new NewWizardAction(getSite().getWorkbenchWindow()));
        toolBarManager.update(true);

        Composite container = toolkit.createComposite(section);
        container.setLayout(new GridLayout(2, false));
        GridDataFactory.fillDefaults().grab(true, false).applyTo(container);

        IExtensionRegistry registry = Platform.getExtensionRegistry();
        IExtensionPoint extensionPoint = registry.getExtensionPoint(EXTENSION_ID_NEW_WIZARD);
        IExtension[] extensions = extensionPoint.getExtensions();

        IConfigurationElement[] foundElements = new IConfigurationElement[6];
        String[] ids = new String[] { JAVA_WIZARD_ID, SPRING_WIZARD_ID, TEMPLATE_WIZARD_ID, ROO_WIZARD_ID,
                GROOVY_WIZARD_ID, GRAILS_WIZARD_ID };

        for (IExtension extension : extensions) {
            IConfigurationElement[] elements = extension.getConfigurationElements();
            for (IConfigurationElement element : elements) {
                String id = element.getAttribute("id");
                for (int i = 0; i < ids.length; i++) {
                    if (ids[i].equals(id) && element.getAttribute(ELEMENT_CLASS) != null
                            && element.getAttribute(ELEMENT_NAME) != null
                            && element.getAttribute(ELEMENT_ICON) != null) {
                        foundElements[i] = element;
                    }
                }
            }
        }

        for (IConfigurationElement element : foundElements) {
            createNewProjectFromExtension(container, element);
        }

        section.setClient(container);
    }

    private void createSearchBox(Composite composite) {
        TextSearchControl searchControl = new TextSearchControl(composite, false, null);
        searchControl.getTextControl().setMessage("Search springsource.org");
        GridDataFactory.fillDefaults().grab(false, true).align(SWT.FILL, SWT.CENTER).hint(200, SWT.DEFAULT)
                .applyTo(searchControl);
        searchBox = searchControl.getTextControl();

        searchBox.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.character == SWT.ESC && e.doit) {
                    clearText();
                }
            }
        });

        searchControl.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                if (e.detail == TextSearchControl.ICON_CANCEL) {
                    clearText();
                } else {
                    searchSpringSource();
                }
            }
        });
    }

    private void createUpdateSection(final Composite parent) {
        updateSection = new Section(parent, ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE) {
            @Override
            public void redraw() {
                if (getStoredExpandedState(IIdeUiConstants.PREF_UPDATE_SECTION_COLLAPSE, true)) {
                    GridData compositeData = (GridData) updateScrolled.getLayoutData();
                    compositeData.widthHint = getSize().x - 55;
                    compositeData.heightHint = getSize().y - 40;
                    compositeData.grabExcessHorizontalSpace = false;
                    compositeData.grabExcessVerticalSpace = false;
                }

                super.redraw();
            }
        };
        // updateSection = toolkit.createSection(parent,
        // ExpandableComposite.TITLE_BAR);
        toolkit.adapt(updateSection);
        updateSection.setTitleBarForeground(toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
        updateSection.setTitleBarBackground(toolkit.getColors().getColor(IFormColors.TB_BG));
        updateSection.setTitleBarBorderColor(toolkit.getColors().getColor(IFormColors.TB_BORDER));
        updateSection.setFont(
                FormFonts.getInstance().getBoldFont(getSite().getShell().getDisplay(), updateSection.getFont()));

        updateSection.setText("Updates");
        updateSection.setLayout(new GridLayout());
        GridDataFactory.fillDefaults()
                .grab(true, getStoredExpandedState(IIdeUiConstants.PREF_UPDATE_SECTION_COLLAPSE, true))
                .applyTo(updateSection);

        setUpExpandableSection(updateSection, IIdeUiConstants.PREF_UPDATE_SECTION_COLLAPSE, true);

        final Composite headerComposite = toolkit.createComposite(updateSection, SWT.NONE);
        RowLayout rowLayout = new RowLayout();
        rowLayout.marginTop = 0;
        rowLayout.marginBottom = 0;
        headerComposite.setLayout(rowLayout);
        headerComposite.setBackground(null);

        ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
        toolBarManager.createControl(headerComposite);

        updateSection.setTextClient(headerComposite);

        final PageBook pagebook = new PageBook(updateSection, SWT.NONE);
        toolkit.adapt(pagebook);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(pagebook);

        final Composite disclaimer = createDisclaimer(pagebook);

        updateScrolled = new ScrolledComposite(pagebook, SWT.V_SCROLL);
        updateScrolled.setExpandVertical(false);
        updateScrolled.setLayout(new GridLayout());
        updateScrolled.setAlwaysShowScrollBars(false);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(updateScrolled);
        toolkit.adapt(updateScrolled);

        final Composite composite = toolkit.createComposite(updateScrolled);
        updateScrolled.setContent(composite);
        composite.setLayout(new TableWrapLayout());
        GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
        displayUpdates(composite, pagebook, disclaimer);

        refreshUpdatesAction = new Action("Refresh Updates", CommonImages.REFRESH) {
            @Override
            public void run() {
                displayUpdates(composite, pagebook, disclaimer);
            }
        };
        toolBarManager.add(refreshUpdatesAction);
        toolBarManager.update(true);

        pagebook.showPage(composite);

        updateSection.addControlListener(new ControlAdapter() {

            @Override
            public void controlResized(ControlEvent e) {
                if (updateSection.isExpanded()) {
                    GridData data = (GridData) updateScrolled.getLayoutData();
                    data.grabExcessVerticalSpace = false;
                    updateScrolled.setSize(updateSection.getSize().x - 14, updateSection.getSize().y - 33);

                    for (Control child : composite.getChildren()) {
                        ((TableWrapData) child.getLayoutData()).maxWidth = updateSection.getSize().x
                                - UPDATE_TEXT_WRAP_INDENT;
                    }

                    composite.pack();
                }
            }
        });

        updateSection.setClient(pagebook);
    }

    private void displayFeed(SyndEntry entry, Composite composite, final Section section, final int pos,
            Control[] children) {
        ImageHyperlink link;
        FormText text;

        if (pos < children.length / 2) {
            link = (ImageHyperlink) children[pos * 2];
            link.setVisible(true);

            text = (FormText) children[pos * 2 + 1];
            text.setVisible(true);
        } else {
            final ImageHyperlink newLink = toolkit.createImageHyperlink(composite, SWT.NONE);
            feedControls.add(newLink);
            link = newLink;
            link.addHyperlinkListener(new HyperlinkAdapter() {
                @Override
                public void linkActivated(HyperlinkEvent e) {
                    Object source = e.getSource();
                    if (source instanceof ImageHyperlink && ((ImageHyperlink) source).getData() != null) {
                        SyndEntry entry = (SyndEntry) ((ImageHyperlink) source).getData();
                        String url = entry.getLink();

                        if (url == null) {
                            return;
                        }
                        int urlPos = url.indexOf("?");
                        String newUrl = url;
                        if (urlPos > 0) {
                            newUrl = url.substring(0, urlPos + 1)
                                    + url.substring(urlPos + 1).replaceAll("\\?", "&");
                        }

                        TasksUiUtil.openUrl(url);
                        prefStore.setValue(IIdeUiConstants.PREF_FEED_ENTRY_READ_STATE + ":" + newUrl, true);
                        IdeUiPlugin.getDefault().savePluginPreferences();
                        newLink.setImage(IdeUiPlugin.getImage(ICON_BLOG_BLANK));
                    }
                }
            });

            // text = new Text(composite, SWT.WRAP | SWT.MULTI |
            // SWT.NO_BACKGROUND);
            text = new FormText(composite, SWT.WRAP | SWT.MULTI | SWT.NO_BACKGROUND | SWT.NO_FOCUS);
            text.setHyperlinkSettings(toolkit.getHyperlinkGroup());
            feedControls.add(text);
            final TableWrapData data = new TableWrapData();
            data.indent = UPDATE_INDENTATION;
            data.maxWidth = section.getSize().x - FEEDS_TEXT_WRAP_INDENT;
            data.grabVertical = true;
            text.setLayoutData(data);
            text.addPaintListener(new PaintListener() {

                public void paintControl(PaintEvent e) {
                    data.maxWidth = section.getSize().x - FEEDS_TEXT_WRAP_INDENT;
                }
            });

            text.setForeground(feedColor);
            text.setBackground(toolkit.getColors().getBackground());
        }

        String title = entry.getTitle();

        Date entryDate = new Date(0);
        if (entry.getUpdatedDate() != null) {
            entryDate = entry.getUpdatedDate();
        } else {
            entryDate = entry.getPublishedDate();
        }

        String dateString = "";
        if (entryDate != null) {
            dateString = DateFormat.getDateInstance(DateFormat.MEDIUM).format(entryDate);
        }

        String entryAuthor = "";
        if (entry.getAuthor() != null && entry.getAuthor().trim() != "") {
            entryAuthor = " by " + entry.getAuthor();
        }

        if (dateString.length() > 0 && entryAuthor.length() > 0) {
            link.setText(removeHtmlEntities(title));
        }

        TableWrapData linkData = new TableWrapData();
        if (!prefStore.getBoolean(IIdeUiConstants.PREF_FEED_ENTRY_READ_STATE + ":" + entry.getLink())) {
            link.setImage(IdeUiPlugin.getImage(ICON_BLOG_INCOMING));
            linkData.indent = 0;
        } else {
            // link.setImage(IdeUiPlugin.getImage(ICON_BLOG_BLANK));
            linkData.indent = UPDATE_INDENTATION - 1;
        }
        link.setLayoutData(linkData);
        link.setData(entry);

        String description = trimText(getDescription(entry));
        text.setText(description + " (" + dateString + entryAuthor + ")", false, false);
    }

    private void displayFeeds(final Composite composite, final ScrolledComposite scrolled, final PageBook pagebook,
            final Control disclaimer, Map<String, String> map, String feedName, final Section section) {
        final AggregateFeedJob job = new AggregateFeedJob(map, feedName);
        job.addJobChangeListener(new JobChangeAdapter() {
            @Override
            public void done(IJobChangeEvent event) {
                unfinishedJobs.remove(job);
                IWorkbenchPartSite site = getSite();
                if (site != null && site.getShell() != null && !site.getShell().isDisposed()
                        && site.getShell().getDisplay() != null && !site.getShell().getDisplay().isDisposed()) {
                    site.getShell().getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            Map<SyndEntry, SyndFeed> entryToFeed = job.getFeedReader().getFeedsWithEntries();
                            Set<SyndEntry> entries = entryToFeed.keySet();

                            if (!getManagedForm().getForm().isDisposed()) {
                                displayFeeds(entries, composite, scrolled, pagebook, disclaimer, section);
                            }
                        }
                    });
                }
            }
        });
        unfinishedJobs.add(job);
        job.schedule();
    }

    private void displayFeeds(Set<SyndEntry> entries, Composite composite, ScrolledComposite scrolled,
            PageBook pagebook, Control disclaimer, Section section) {

        // make sure the entries are sorted correctly
        List<SyndEntry> sortedEntries = new ArrayList<SyndEntry>(entries);
        Collections.sort(sortedEntries, new Comparator<SyndEntry>() {

            public int compare(SyndEntry o1, SyndEntry o2) {
                return o2.getPublishedDate().compareTo(o1.getPublishedDate());
            }
        });

        if (sortedEntries.isEmpty()) {
            pagebook.showPage(disclaimer);
            return;
        }

        if (displayedEntries.containsAll(entries) && entries.containsAll(displayedEntries)) {
            return;
        } else {
            displayedEntries.clear();
            displayedEntries.addAll(entries);
        }

        Control[] children = composite.getChildren();

        int counter = 0;
        for (SyndEntry entry : sortedEntries) {
            displayFeed(entry, composite, section, counter, children);
            counter++;
        }

        for (int i = counter * 2; i < children.length; i++) {
            children[i].dispose();
        }

        pagebook.showPage(scrolled);
        composite.pack(true);

    }

    private void displayUpdate(final SyndEntry entry, String severity, Composite composite, int pos,
            Control[] children) {
        ImageHyperlink link;
        FormText text;

        if (pos < children.length / 2) {
            link = (ImageHyperlink) children[pos * 2];
            link.setVisible(true);
            text = (FormText) children[pos * 2 + 1];
            text.setVisible(true);
        } else {
            link = toolkit.createImageHyperlink(composite, SWT.NONE);
            text = new FormText(composite, SWT.WRAP | SWT.MULTI | SWT.NO_BACKGROUND | SWT.NO_FOCUS);
            text.setHyperlinkSettings(toolkit.getHyperlinkGroup());
            final TableWrapData data = new TableWrapData();
            data.indent = UPDATE_INDENTATION;
            data.maxWidth = updateSection.getSize().x - UPDATE_TEXT_WRAP_INDENT;
            text.setLayoutData(data);
            text.addPaintListener(new PaintListener() {

                public void paintControl(PaintEvent e) {
                    data.maxWidth = updateSection.getSize().x - UPDATE_TEXT_WRAP_INDENT;
                }
            });

            text.setBackground(toolkit.getColors().getBackground());
            text.addHyperlinkListener(new HyperlinkAdapter() {

                @Override
                public void linkActivated(HyperlinkEvent e) {
                    if (e.data instanceof String) {
                        TasksUiUtil.openUrl((String) e.data);
                    }
                }
            });
        }

        link.setText(entry.getTitle());

        TableWrapData linkData = new TableWrapData();
        if ("important".equals(severity)) {
            link.setImage(StsUiImages.getImage(StsUiImages.IMPORTANT));
            linkData.indent = 0;
        } else if ("warning".equals(severity)) {
            link.setImage(StsUiImages.getImage(StsUiImages.WARNING));
            linkData.indent = 0;
        } else {
            // link.setImage(IdeUiPlugin.getImage(ICON_BLOG_BLANK));
            linkData.indent = UPDATE_INDENTATION - 1;
        }
        link.setLayoutData(linkData);

        link.addHyperlinkListener(new HyperlinkAdapter() {
            @Override
            public void linkActivated(HyperlinkEvent e) {
                String url = entry.getLink();

                if (url == null) {
                    return;
                }
                TasksUiUtil.openUrl(url);
            }
        });

        String description = getDescription(entry);
        if (entry.getPublishedDate() != null && description.endsWith("</p></form>")) {
            String dateString = DateFormat.getDateInstance(DateFormat.MEDIUM).format(entry.getPublishedDate());
            description = description.replace("</p></form>", " (" + dateString + ")</p></form>");
        } else if (entry.getPublishedDate() != null) {
            String dateString = DateFormat.getDateInstance(DateFormat.MEDIUM).format(entry.getPublishedDate());
            description = description + " (" + dateString + ")";
        }

        text.setText(description, description.startsWith("<form>"), true);
        text.setForeground(feedColor);
        text.setBackground(toolkit.getColors().getBackground());
        addImages(text, description);
    }

    private void displayUpdates(final Composite composite, final PageBook pagebook, final Composite disclaimer) {
        Map<String, String> map = new HashMap<String, String>();
        map.put(ResourceProvider.getUrl(RESOURCE_DASHBOARD_FEEDS_UPDATE), null);

        final AggregateFeedJob job = new AggregateFeedJob(map, "Updates");
        job.addJobChangeListener(new JobChangeAdapter() {
            @Override
            public void done(IJobChangeEvent event) {
                unfinishedJobs.remove(job);

                Display display = PlatformUI.getWorkbench().getDisplay();
                if (display != null && !display.isDisposed()) {
                    display.asyncExec(new Runnable() {
                        public void run() {
                            if (getManagedForm().getForm().isDisposed()) {
                                return;
                            }

                            List<UpdateNotification> notifications = job.getNotifications();
                            if (notifications.isEmpty()) {
                                pagebook.showPage(disclaimer);
                                return;
                            }

                            // make sure the entries are sorted correctly
                            Collections.sort(notifications, new Comparator<UpdateNotification>() {
                                public int compare(UpdateNotification o1, UpdateNotification o2) {
                                    return o2.getEntry().getPublishedDate()
                                            .compareTo(o1.getEntry().getPublishedDate());
                                }
                            });

                            int counter = 0;
                            Control[] children = composite.getChildren();
                            for (UpdateNotification notification : notifications) {
                                displayUpdate(notification.getEntry(), notification.getSeverity(), composite,
                                        counter, children);
                                counter++;
                            }

                            for (int i = counter * 2; i < children.length; i++) {
                                children[i].dispose();
                            }

                            composite.changed(composite.getChildren());
                            composite.pack(true);
                            composite.redraw();
                            composite.getParent().redraw();
                        }
                    });
                }
            }
        });
        unfinishedJobs.add(job);
        job.schedule();
    }

    private int findEndOfWord(StringBuilder sb, int pos) {
        Pattern pattern = Pattern.compile("\\w");
        while (pos < sb.length()) {
            if (pattern.matcher(sb.subSequence(pos, pos + 1)).matches()) {
                pos++;
            } else {
                return pos;
            }
        }
        return pos;
    }

    private String getDescription(SyndEntry entry) {
        SyndContent content = entry.getDescription();
        if (content == null) {
            return "";
        }

        String value = content.getValue();
        if (value == null) {
            return "";
        }

        if (value.startsWith("<form>")) {
            return value;
        }

        return removeHtmlEntities(value);
    }

    private boolean getStoredExpandedState(String prefId, boolean defaultExpanded) {
        int storedState = prefStore.getInt(prefId);
        if (storedState == IIdeUiConstants.SECTION_EXPANDED) {
            return true;
        } else if (storedState == IIdeUiConstants.SECTION_COLLAPSED) {
            return false;
        } else {
            return defaultExpanded;
        }

    }

    private String removeHtmlEntities(String value) {
        StringBuilder result = new StringBuilder();
        boolean tagOpened = false;
        for (char currChar : value.toCharArray()) {
            if (currChar == '<') {
                tagOpened = true;
            } else if (currChar == '>') {
                tagOpened = false;
            } else {
                if (!tagOpened) {
                    result.append(currChar);
                }
            }
        }
        return StringEscapeUtils.unescapeHtml(result.toString());

    }

    private void searchSpringSource() {
        String searchTerms = searchBox.getText();
        String url = ResourceProvider.getUrl(RESOURCE_DASHBOARD_SEARCH) + searchTerms.replaceAll(" ", "+");
        TasksUiUtil.openUrl(url);
    }

    private void setUpExpandableSection(final Section section, final String prefId, boolean defaultExpanded) {
        section.setExpanded(getStoredExpandedState(prefId, defaultExpanded));

        section.addExpansionListener(new ExpansionAdapter() {

            @Override
            public void expansionStateChanged(ExpansionEvent e) {
                if (e.getState()) {
                    prefStore.setValue(prefId, IIdeUiConstants.SECTION_EXPANDED);
                } else {
                    prefStore.setValue(prefId, IIdeUiConstants.SECTION_COLLAPSED);
                }

                if (isContributedSection(section)) {
                    // If we're expanding a contributed section, close the
                    // update section...
                    updateSection.setExpanded(false);
                    prefStore.setValue(IIdeUiConstants.PREF_UPDATE_SECTION_COLLAPSE,
                            IIdeUiConstants.SECTION_COLLAPSED);

                }

                // ... close all contributed section that are not the section of
                // interest
                for (AbstractDashboardPart part : parts) {
                    if (!section.equals(part.getControl()) && part.getControl() instanceof Section) {
                        ((Section) part.getControl()).setExpanded(false);
                        prefStore.setValue(getExpansionPropertyId(part), IIdeUiConstants.SECTION_COLLAPSED);
                    }
                }

                adjustCollapsableSections();
            }
        });
    }

    private boolean isContributedSection(Section section) {
        for (AbstractDashboardPart part : parts) {
            if (section.equals(part.getControl())) {
                return true;
            }
        }
        return false;
    }

    private String trimText(String s) {
        // Remove html encoded entities
        s = StringEscapeUtils.unescapeHtml(s);

        // Remove line breaks and tabs
        s = s.replace("\n", " ");
        s = s.replace("\t", " ");

        // Remove whitespace between text
        String[] vals = s.split(" ");
        StringBuilder sb = new StringBuilder();
        for (String v : vals) {
            if (v.trim().length() > 0) {
                sb.append(v).append(" ");
            }
        }

        if (sb.length() > FEEDS_DESCRIPTION_MAX) {
            return sb.substring(0, findEndOfWord(sb, FEEDS_DESCRIPTION_MAX)) + " ...";
            // return sb.substring(0, FEEDS_DESCRIPTION_MAX) + " ...";
        }
        return sb.toString();
    }

    @Override
    protected void createFormContent(IManagedForm managedForm) {
        toolkit = managedForm.getToolkit();
        form = managedForm.getForm();
        unfinishedJobs = new CopyOnWriteArraySet<AggregateFeedJob>();

        // get dark gray color as FormText display it lighter as other widgets
        feedColor = new Color(Display.getDefault(), 70, 70, 70);

        // getHeaderForm().setText(null);// "SpringSource Tool Suite");
        toolkit.decorateFormHeading(form.getForm());

        prefStore = IdeUiPlugin.getDefault().getPreferenceStore();

        GridLayout compositeLayout = new GridLayout(2, true);
        compositeLayout.marginHeight = 0;
        compositeLayout.marginTop = 5;
        compositeLayout.verticalSpacing = 0;

        Composite body = form.getBody();
        body.setLayout(compositeLayout);

        Composite leftComposite = toolkit.createComposite(body);
        leftComposite.setLayout(new GridLayout());
        GridDataFactory.fillDefaults().grab(true, true).applyTo(leftComposite);

        Composite rightComposite = toolkit.createComposite(body);
        rightComposite.setLayout(new GridLayout());
        GridDataFactory.fillDefaults().grab(true, true).applyTo(rightComposite);

        createHeader();

        createNewProjectsSection(leftComposite);
        createUpdateSection(leftComposite);

        parts = contributeParts(leftComposite, AbstractDashboardPart.ID_PATH_DOC);
        for (AbstractDashboardPart part : parts) {
            if (part.getControl() instanceof Section) {
                String expansionProp = getExpansionPropertyId(part);
                Section section = (Section) part.getControl();
                GridDataFactory.fillDefaults().grab(false, getStoredExpandedState(expansionProp, false))
                        .applyTo(section);
                setUpExpandableSection(section, expansionProp, false);
            }
        }

        createHelpSection(leftComposite);

        createFeedsSection(rightComposite, "Feeds", FeedType.BLOG, "blog");

        searchBox.setFocus();

        ResourceProvider.getInstance().addPropertyChangeListener(this);
    }

    private String getExpansionPropertyId(AbstractDashboardPart part) {
        return part.getId() + ".expansion";
    }

    protected ScrolledForm getHeaderForm() {
        if (dashboardEditor == null || dashboardEditor.getHeaderForm() == null) {
            return null;
        }
        return dashboardEditor.getHeaderForm().getForm();
    }

    public static Map<String, String> getFeedsMap() {
        Map<String, String> springMap = new HashMap<String, String>();
        String[] urls = ResourceProvider.getUrls(RESOURCE_DASHBOARD_FEEDS_BLOGS);
        for (String url : urls) {
            if (url != null && url.length() > 0) {
                springMap.put(url, null);
            }
        }
        return springMap;
    }

    public enum FeedType {
        BLOG, DOWNLOAD, BUZZ, RESEARCH
    }

}