/******************************************************************************* * Copyright (c) 2012, 2013 Pivotal Software, 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: * Pivotal Software, 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 * @author Leo Dos Santos */ 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/spring_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 SPRING_WIZARD_ID = "com.springsource.sts.wizard.template"; 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) { if (!feedControl.isDisposed()) { ((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("Spring", 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, 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 spring.io"); 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) { Date o1Date = o1.getPublishedDate() != null ? o1.getPublishedDate() : o1.getUpdatedDate(); Date o2Date = o2.getPublishedDate() != null ? o2.getPublishedDate() : o2.getUpdatedDate(); if (o1Date == null && o2Date == null) { return 0; } else if (o1Date == null) { return -1; } else if (o2Date == null) { return 1; } else { return o2Date.compareTo(o1Date); } } }); 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 getDate(o2).compareTo(getDate(o1)); } /** * Make sure 'date' is never null so we have something to pass to * 'compare'. * <p> * See https://issuetracker.springsource.com/browse/STS-3844 */ private Date getDate(UpdateNotification o) { if (o!=null) { SyndEntry e = o.getEntry(); if (e!=null) { Date d = e.getPublishedDate(); if (d!=null) { return d; } } } //Treat anything that has no date as very very old. return new Date(0); } }); 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) { List nestedContent = entry.getContents(); if (!nestedContent.isEmpty()) { Object obj = nestedContent.get(0); if (obj instanceof SyndContent) { content = (SyndContent) obj; } } } 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 } }