Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian de Alwis2016-01-22 21:10:13 +0000
committerBrian de Alwis2016-01-22 21:48:42 +0000
commit8124ef1b34163de3a3e4a6f3c39f2045a25ceffc (patch)
tree86bb4a0c9ba294dd571f7165a152205c9bc01662 /org.eclipse.ui.intro.quicklinks/src
parentde5846610979bdc09d2cc46a2e0eb352707a3322 (diff)
downloadeclipse.platform.ua-8124ef1b34163de3a3e4a6f3c39f2045a25ceffc.tar.gz
eclipse.platform.ua-8124ef1b34163de3a3e4a6f3c39f2045a25ceffc.tar.xz
eclipse.platform.ua-8124ef1b34163de3a3e4a6f3c39f2045a25ceffc.zip
Bug 466370 - [UX] Welcome Screen redesign
Add a new Intro viewer component, Quicklinks, to show a set of useful actions from the Welcome. Change-Id: Icc3d94a6efc0d593b3158b56188befdbfb0e1f68
Diffstat (limited to 'org.eclipse.ui.intro.quicklinks/src')
-rw-r--r--org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/FilteringContentProvider.java51
-rw-r--r--org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/QuicklinksViewer.java394
-rw-r--r--org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/URLLabelProvider.java60
3 files changed, 505 insertions, 0 deletions
diff --git a/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/FilteringContentProvider.java b/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/FilteringContentProvider.java
new file mode 100644
index 000000000..f1c40cde4
--- /dev/null
+++ b/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/FilteringContentProvider.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Manumitting Technologies Inc and others.
+ * 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:
+ * Manumitting Technologies Inc - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.intro.quicklinks;
+
+import java.util.Collection;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * A structured content provider that first transforms elements with a function.
+ *
+ * @param <O>
+ * the object type
+ */
+public class FilteringContentProvider<O> implements IStructuredContentProvider {
+
+ private Function<Object, O> function;
+
+ public FilteringContentProvider(Function<Object, O> f) {
+ this.function = f;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ if (inputElement instanceof Object[]) {
+ return Stream.of((Object[]) inputElement).map(function).toArray();
+ } else if (inputElement instanceof Collection) {
+ return ((Collection<?>) inputElement).stream().map(function).toArray();
+ }
+ return Stream.of(inputElement).map(function).toArray();
+ }
+}
diff --git a/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/QuicklinksViewer.java b/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/QuicklinksViewer.java
new file mode 100644
index 000000000..12a3edc19
--- /dev/null
+++ b/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/QuicklinksViewer.java
@@ -0,0 +1,394 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Manumitting Technologies Inc and others.
+ * 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:
+ * Manumitting Technologies Inc - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.intro.quicklinks;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import org.eclipse.core.commands.CommandManager;
+import org.eclipse.core.commands.ParameterizedCommand;
+import org.eclipse.core.commands.SerializationException;
+import org.eclipse.core.commands.common.NotDefinedException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.ImageLoader;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.commands.ICommandImageService;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPartImplementation;
+import org.eclipse.ui.internal.intro.impl.model.IntroTheme;
+import org.eclipse.ui.internal.menus.MenuHelper;
+import org.eclipse.ui.intro.config.IIntroContentProvider;
+import org.eclipse.ui.intro.config.IIntroContentProviderSite;
+import org.eclipse.ui.services.IServiceLocator;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * An Intro content provider that populates a list of frequently-used commands
+ * from an extension point. The appearance of these quicklinks is normally taken
+ * from the command metadata, including the image icon, but can be tailored.
+ * These tailorings can be made optional depending on the current theme.
+ *
+ * This is still experimental and subject to change.
+ */
+public class QuicklinksViewer implements IIntroContentProvider {
+
+ /** Represents the importance of an element */
+ enum Importance {
+ HIGH("high", 0), MEDIUM("medium", 1), LOW("low", 2);
+
+ String id;
+ int level;
+
+ Importance(String text, int importance) {
+ this.id = text;
+ this.level = importance;
+ }
+
+ public static Importance forId(String id) {
+ for (Importance i : values()) {
+ if (i.id.equals(id)) {
+ return i;
+ }
+ }
+ return LOW;
+ }
+ }
+
+ /** Model holding the relevant attributes of a Quicklink element */
+ class Quicklink {
+ String commandSpec;
+ String label;
+ String description;
+ String iconUrl;
+ boolean standby = true;
+ Importance importance = Importance.LOW;
+
+ String bundleSymbolicName;
+ }
+
+ /**
+ * Responsible for retrieving Quicklinks and applying any icon overrides
+ */
+ class ModelReader implements Supplier<Stream<Quicklink>> {
+ private static final String QL_EXT_PT = "org.eclipse.ui.intro.quicklinks"; //$NON-NLS-1$
+ private static final String ELMT_QUICKLINK = "quicklink"; //$NON-NLS-1$
+ private static final String ATT_COMMAND = "command"; //$NON-NLS-1$
+ private static final String ATT_LABEL = "label"; //$NON-NLS-1$
+ private static final String ATT_DESCRIPTION = "description"; //$NON-NLS-1$
+ private static final String ATT_ICON = "icon"; //$NON-NLS-1$
+ private static final String ATT_IMPORTANCE = "importance"; //$NON-NLS-1$
+ private static final String ATT_STANDBY = "standby"; //$NON-NLS-1$
+ private static final String ELMT_OVERRIDE = "override"; //$NON-NLS-1$
+ private static final String ATT_THEME = "theme"; //$NON-NLS-1$
+
+ private List<Quicklink> quicklinks = new ArrayList<>();
+
+ public Stream<Quicklink> get() {
+ CommandManager manager = locator.getService(CommandManager.class);
+
+ for (IConfigurationElement ce : getExtensionRegistry().getConfigurationElementsFor(QL_EXT_PT)) {
+ if (!ELMT_QUICKLINK.equals(ce.getName())) {
+ continue;
+ }
+ String commandSpec = ce.getAttribute(ATT_COMMAND);
+ try {
+ ParameterizedCommand pc = manager.deserialize(commandSpec);
+ if (pc != null && pc.getCommand().isDefined()) {
+ Quicklink ql = new Quicklink();
+ ql.bundleSymbolicName = ce.getContributor().getName();
+ ql.commandSpec = commandSpec;
+ ql.label = Optional.ofNullable(ce.getAttribute(ATT_LABEL)).orElse(pc.getCommand().getName());
+ ql.description = Optional.ofNullable(ce.getAttribute(ATT_DESCRIPTION))
+ .orElse(pc.getCommand().getDescription());
+ ql.iconUrl = QuicklinksViewer.this.getImageURL(ce, ATT_ICON, commandSpec);
+ if (ce.getAttribute(ATT_IMPORTANCE) != null) {
+ ql.importance = Importance.forId(ce.getAttribute(ATT_IMPORTANCE));
+ }
+ if (ce.getAttribute(ATT_STANDBY) != null) {
+ ql.standby = Boolean.valueOf(ce.getAttribute(ATT_STANDBY));
+ }
+ quicklinks.add(ql);
+ }
+ } catch (NotDefinedException | SerializationException e) {
+ /* skip */
+ System.err.printf("Skipping '%s': %s\n", commandSpec, e);
+ }
+ }
+
+ for (IConfigurationElement ce : getExtensionRegistry().getConfigurationElementsFor(QL_EXT_PT)) {
+ if (!ELMT_OVERRIDE.equals(ce.getName())) {
+ continue;
+ }
+ String theme = ce.getAttribute(ATT_THEME);
+ String commandSpecPattern = ce.getAttribute(ATT_COMMAND);
+ String icon = ce.getAttribute(ATT_ICON);
+ if (theme != null && icon != null && Objects.equals(theme, getCurrentThemeId()) && commandSpecPattern != null) {
+ findMatchingQuicklinks(commandSpecPattern)
+ .forEach(ql -> ql.iconUrl = QuicklinksViewer.this.getImageURL(ce, ATT_ICON, null));
+ }
+ }
+ return quicklinks.stream();
+ }
+
+ private Stream<Quicklink> findMatchingQuicklinks(String commandSpecPattern) {
+ commandSpecPattern = commandSpecPattern.replace(".", "\\.").replace("(", "\\(").replace(")", "\\)")
+ .replace("*", ".*");
+ final Pattern pattern = Pattern.compile(commandSpecPattern);
+ return quicklinks.stream().filter(ql -> pattern.matcher(ql.commandSpec).matches());
+ }
+ }
+
+ /** Source: http://stackoverflow.com/a/417184 */
+ private static final int MAX_URL_LENGTH = 2083;
+
+ private IIntroContentProviderSite site;
+ private IServiceLocator locator;
+ private Map<String, Long> bundleIds;
+ private Bundle[] bundles;
+
+ public void init(IIntroContentProviderSite site) {
+ this.site = site;
+ // IIntroContentProviderSite should provide services.
+ if (site instanceof AbstractIntroPartImplementation) {
+ this.locator = ((AbstractIntroPartImplementation) site).getIntroPart().getIntroSite();
+ } else {
+ this.locator = PlatformUI.getWorkbench();
+ }
+ }
+
+ public String getCurrentThemeId() {
+ if (site instanceof AbstractIntroPartImplementation) {
+ IntroTheme theme = ((AbstractIntroPartImplementation) site).getModel().getTheme();
+ return theme.getId();
+ }
+ return null;
+ }
+
+ public IExtensionRegistry getExtensionRegistry() {
+ return locator.getService(IExtensionRegistry.class);
+ }
+
+ public void createContent(String id, PrintWriter out) {
+ // Content is already embedded within a <div id="...">
+ getQuicklinks().forEach(ql -> {
+ try {
+ // ah how lovely to embed HTML in code
+ String urlEncodedCommand = URLEncoder.encode(ql.commandSpec, "UTF-8");
+ out.append("<a class='content-link' id='");
+ out.append(asCSSId(ql.commandSpec));
+ out.append("' ");
+ out.append(" href='http://org.eclipse.ui.intro/execute?command=");
+ out.append(urlEncodedCommand);
+ out.append("&standby=");
+ out.append(Boolean.toString(ql.standby));
+ out.append("'>");
+ if (ql.iconUrl != null) {
+ out.append("<img class='background-image' src='").append(ql.iconUrl).append("'>");
+ }
+ out.append("\n<div class='link-extra-div'></div>\n"); // UNKNOWN
+ out.append("<span class='link-label'>");
+ out.append(ql.label);
+ out.append("</span>");
+ if (ql.description != null) {
+ out.append("\n<p><span class='text'>");
+ out.append(ql.description);
+ out.append("</span></p>");
+ }
+ out.append("</a>");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ private String asCSSId(String commandSpec) {
+ int indexOf = commandSpec.indexOf('(');
+ if (indexOf > 0) {
+ commandSpec = commandSpec.substring(0, indexOf);
+ }
+ return commandSpec.replace('.', '_');
+ }
+
+ /**
+ * @return URL to image, suitable for using in an external browser; may be a
+ * <code>data:</code> URL; may be null
+ */
+ private String getImageURL(IConfigurationElement ce, String attr, String commandId) {
+ String iconURL = MenuHelper.getIconURI(ce, attr);
+ if (iconURL != null) {
+ return asBrowserURL(iconURL);
+ }
+
+ if (commandId == null) {
+ return null;
+ }
+ ICommandImageService images = locator.getService(ICommandImageService.class);
+ if (images == null) {
+ return null;
+ }
+ ImageDescriptor descriptor = images.getImageDescriptor(commandId);
+ iconURL = MenuHelper.getImageUrl(descriptor);
+ if (iconURL != null) {
+ return asBrowserURL(iconURL);
+ }
+ return asDataURL(descriptor);
+ }
+
+ private String asBrowserURL(String iconURL) {
+ if (iconURL.startsWith("file:") || iconURL.startsWith("http:")) {
+ return iconURL;
+ }
+ try {
+ URL original = new URL(iconURL);
+ URL toLocal = FileLocator.toFileURL(original);
+ if (!toLocal.sameFile(original)) {
+ return toLocal.toString();
+ }
+ } catch (IOException e1) {
+ /* ignore */
+ }
+
+ // extract content
+ try {
+ return asDataURL(ImageDescriptor.createFromURL(new URL(iconURL)));
+ } catch (MalformedURLException e) {
+ // should probably log this
+ return iconURL;
+ }
+ }
+
+ /**
+ * Write out the image as a data: URL if possible or to the file-system.
+ *
+ * @param descriptor
+ * @return URL with the resulting image
+ */
+ private String asDataURL(ImageDescriptor descriptor) {
+ if (descriptor == null) {
+ return null;
+ }
+ ImageData data = descriptor.getImageData();
+ if (data == null) {
+ return null;
+ }
+ ImageLoader loader = new ImageLoader();
+ loader.data = new ImageData[] { data };
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ loader.save(output, SWT.IMAGE_PNG);
+ if (output.size() * 4 / 3 < MAX_URL_LENGTH) {
+ // You'd think there was a more efficient way to do this...
+ return "data:image/png;base64," + Base64.getUrlEncoder().encodeToString(output.toByteArray());
+ }
+ try {
+ File tempFile = File.createTempFile("qlink", "png");
+ FileOutputStream fos = new FileOutputStream(tempFile);
+ fos.write(output.toByteArray());
+ fos.close();
+ tempFile.deleteOnExit();
+ return tempFile.toURI().toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public void createContent(String id, Composite parent, FormToolkit toolkit) {
+ Section section = toolkit.createSection(parent, Section.EXPANDED);
+ TableViewer tableViewer = new TableViewer(toolkit.createTable(section, SWT.FULL_SELECTION));
+ tableViewer.setLabelProvider(new URLLabelProvider() {
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Quicklink) {
+ return ((Quicklink) element).label;
+ }
+ return super.getText(element);
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof Quicklink) {
+ return super.getImage(((Quicklink) element).iconUrl);
+ }
+ return super.getImage(element);
+ }
+ });
+ tableViewer.setContentProvider(new ArrayContentProvider());
+ tableViewer.setInput(getQuicklinks().toArray());
+ }
+
+ private Stream<Quicklink> getQuicklinks() {
+ return new ModelReader().get().sorted(this::compareQuicklinks);
+ }
+
+ public void dispose() {
+ }
+
+ private int compareQuicklinks(Quicklink a, Quicklink b) {
+ int impA = a.importance.level;
+ int impB = b.importance.level;
+ if (impA != impB) {
+ return impA - impB;
+ }
+ long diff = getRank(a) - getRank(b);
+ if (diff > 0) {
+ return 1;
+ }
+ if (diff < 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ private long getRank(Quicklink ql) {
+ if (bundleIds == null) {
+ Bundle bundle = FrameworkUtil.getBundle(getClass());
+ bundleIds = new HashMap<>();
+ bundles = bundle.getBundleContext().getBundles();
+ }
+ return bundleIds.computeIfAbsent(ql.bundleSymbolicName, bsn -> {
+ for (Bundle b : bundles) {
+ if (bsn.equals(b.getSymbolicName()) && (b.getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) {
+ return b.getBundleId();
+ }
+ }
+ return Long.MAX_VALUE;
+ });
+ }
+}
diff --git a/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/URLLabelProvider.java b/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/URLLabelProvider.java
new file mode 100644
index 000000000..20ec3abd1
--- /dev/null
+++ b/org.eclipse.ui.intro.quicklinks/src/org/eclipse/ui/intro/quicklinks/URLLabelProvider.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Manumitting Technologies Inc and others.
+ * 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:
+ * Manumitting Technologies Inc - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.intro.quicklinks;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * Simple label provider that knows how to load images from a URL.
+ */
+public class URLLabelProvider extends LabelProvider {
+ private LocalResourceManager registry;
+
+ public URLLabelProvider() {
+ this.registry = new LocalResourceManager(JFaceResources.getResources());
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ try {
+ ImageDescriptor descriptor = null;
+ if (element instanceof String) {
+ descriptor = ImageDescriptor.createFromURL(new URL((String) element));
+ } else if (element instanceof URL) {
+ descriptor = ImageDescriptor.createFromURL((URL) element);
+ }
+ if (descriptor == null) {
+ return null;
+ }
+ return registry.createImage(descriptor);
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public String getText(Object element) {
+ return (String) element;
+ }
+
+ @Override
+ public void dispose() {
+ registry.dispose();
+ super.dispose();
+ }
+}

Back to the top