blob: cb25f628747f83d53d5a3fa53c40bbc5b4325c40 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 The Eclipse Foundation 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:
* The Eclipse Foundation - initial API and implementation
* Yatta Solutions - error handling (bug 374105), header layout (bug 341014),
* news (bug 401721), public API (bug 432803)
*******************************************************************************/
package org.eclipse.epp.internal.mpc.ui.wizards;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.epp.internal.mpc.core.service.Identifiable;
import org.eclipse.epp.internal.mpc.ui.MarketplaceClientUi;
import org.eclipse.epp.internal.mpc.ui.MarketplaceClientUiPlugin;
import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceCatalog;
import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceCategory;
import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceCategory.Contents;
import org.eclipse.epp.mpc.core.model.ICategory;
import org.eclipse.epp.mpc.core.model.IIdentifiable;
import org.eclipse.epp.mpc.core.model.IMarket;
import org.eclipse.epp.mpc.core.model.INode;
import org.eclipse.epp.mpc.ui.CatalogDescriptor;
import org.eclipse.epp.mpc.ui.Operation;
import org.eclipse.equinox.internal.p2.discovery.Catalog;
import org.eclipse.equinox.internal.p2.discovery.model.CatalogCategory;
import org.eclipse.equinox.internal.p2.discovery.model.CatalogItem;
import org.eclipse.equinox.internal.p2.discovery.model.Tag;
import org.eclipse.equinox.internal.p2.ui.discovery.util.ControlListItem;
import org.eclipse.equinox.internal.p2.ui.discovery.util.FilteredViewer;
import org.eclipse.equinox.internal.p2.ui.discovery.util.GradientCanvas;
import org.eclipse.equinox.internal.p2.ui.discovery.util.PatternFilter;
import org.eclipse.equinox.internal.p2.ui.discovery.util.TextSearchControl;
import org.eclipse.equinox.internal.p2.ui.discovery.wizards.CatalogFilter;
import org.eclipse.equinox.internal.p2.ui.discovery.wizards.CatalogViewer;
import org.eclipse.equinox.internal.p2.ui.discovery.wizards.CategoryItem;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.statushandlers.StatusManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/**
* @author Steffen Pingel
* @author David Green
* @author Benjamin Muskalla
* @author Carsten Reckord
*/
public class MarketplaceViewer extends CatalogViewer {
public enum ContentType {
SEARCH, RECENT, POPULAR, INSTALLED, SELECTION, RELATED
}
public static class MarketplaceCatalogContentProvider extends CatalogContentProvider {
private static final Object[] NO_ELEMENTS = new Object[0];
@Override
public Catalog getCatalog() {
return super.getCatalog();
}
@Override
public Object[] getElements(Object inputElement) {
if (getCatalog() != null) {
// don't provide any categories unless it's featured
List<Object> items = new ArrayList<Object>(getCatalog().getItems());
for (CatalogCategory category : getCatalog().getCategories()) {
if (category instanceof MarketplaceCategory) {
MarketplaceCategory marketplaceCategory = (MarketplaceCategory) category;
if (marketplaceCategory.getContents() == Contents.FEATURED) {
items.add(0, category);
}
}
}
return items.toArray();
}
return NO_ELEMENTS;
}
}
private ViewerFilter[] filters;
private ContentType contentType = ContentType.SEARCH;
public static String CONTENT_TYPE_PROPERTY = "contentType"; //$NON-NLS-1$
private final SelectionModel selectionModel;
private String queryText;
private IMarket queryMarket;
private ICategory queryCategory;
private ContentType queryContentType;
private final IMarketplaceWebBrowser browser;
private String findText;
private final MarketplaceWizard wizard;
private final List<IPropertyChangeListener> listeners = new LinkedList<IPropertyChangeListener>();
private IDiscoveryItemFactory discoveryItemFactory;
private boolean inUpdate;
public MarketplaceViewer(Catalog catalog, IShellProvider shellProvider, MarketplaceWizard wizard) {
super(catalog, shellProvider, wizard.getContainer(), wizard.getConfiguration());
this.browser = wizard;
this.selectionModel = wizard.getSelectionModel();
this.wizard = wizard;
setAutomaticFind(false);
}
@Override
protected void doCreateHeaderControls(Composite parent) {
final int originalChildCount = parent.getChildren().length;
for (CatalogFilter filter : getConfiguration().getFilters()) {
if (filter instanceof MarketplaceFilter) {
MarketplaceFilter marketplaceFilter = (MarketplaceFilter) filter;
marketplaceFilter.createControl(parent);
marketplaceFilter.addPropertyChangeListener(new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (AbstractTagFilter.PROP_SELECTED.equals(event.getProperty())) {
doQuery();
}
}
});
}
}
Control[] children = parent.getChildren();
for (int x = originalChildCount; x < children.length; ++x) {
Control child = children[x];
GridDataFactory.swtDefaults().hint(135, SWT.DEFAULT).applyTo(child);
}
Button goButton = new Button(parent, SWT.PUSH);
goButton.setText(Messages.MarketplaceViewer_go);
goButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doQuery();
}
});
}
@Override
protected CatalogContentProvider doCreateContentProvider() {
return new MarketplaceCatalogContentProvider();
}
@Override
protected void catalogUpdated(final boolean wasCancelled, final boolean wasError) {
runUpdate(new Runnable() {
public void run() {
MarketplaceViewer.super.catalogUpdated(wasCancelled, wasError);
for (CatalogFilter filter : getConfiguration().getFilters()) {
if (filter instanceof MarketplaceFilter) {
((MarketplaceFilter) filter).catalogUpdated(wasCancelled);
}
}
setFilters(queryMarket, queryCategory, queryText);
}
});
}
@Override
protected void filterTextChanged() {
doFind(getFilterText());
}
@Override
protected void doFind(String text) {
findText = text;
doQuery();
}
@Override
protected ControlListItem<?> doCreateViewerItem(Composite parent, Object element) {
if (element instanceof CatalogItem) {
CatalogItem catalogItem = (CatalogItem) element;
if (catalogItem.getData() instanceof CatalogDescriptor) {
CatalogDescriptor catalogDescriptor = (CatalogDescriptor) catalogItem.getData();
return new BrowseCatalogItem(parent, getResources(), shellProvider, browser,
(MarketplaceCategory) catalogItem.getCategory(), catalogDescriptor, this);
} else {
DiscoveryItem<CatalogItem> discoveryItem = createDiscoveryItem(parent, catalogItem);
discoveryItem.setSelected(getCheckedItems().contains(catalogItem));
return discoveryItem;
}
} else if (element instanceof MarketplaceCategory) {
MarketplaceCategory category = (MarketplaceCategory) element;
if (category.getContents() == Contents.FEATURED) {
category.setName(Messages.MarketplaceViewer_featured);
} else {
throw new IllegalStateException();
}
CategoryItem<?> categoryItem = (CategoryItem<?>) super.doCreateViewerItem(parent, element);
setSeparatorVisible(categoryItem, false);
fixLayout(categoryItem);
return categoryItem;
}
return super.doCreateViewerItem(parent, element);
}
private DiscoveryItem<CatalogItem> createDiscoveryItem(Composite parent, CatalogItem catalogItem) {
if (discoveryItemFactory != null) {
return discoveryItemFactory.createDiscoveryItem(catalogItem, this, parent, getResources(), shellProvider,
browser);
}
return new DiscoveryItem<CatalogItem>(parent, SWT.NONE, getResources(), browser, catalogItem,
this);
}
public void show(Set<? extends INode> nodes) {
ContentType newContentType = ContentType.SEARCH;
ContentType oldContentType = contentType;
contentType = newContentType;
fireContentTypeChange(oldContentType, newContentType);
doQuery(null, null, null, nodes);
}
public void search(IMarket market, ICategory category, String query) {
ContentType newContentType = ContentType.SEARCH;
ContentType oldContentType = contentType;
contentType = newContentType;
fireContentTypeChange(oldContentType, newContentType);
setFilters(market, category, query);
queryMarket = market;
queryCategory = category;
queryText = query;
doQuery(market, category, query, null);
}
private void setFilters(IMarket market, ICategory category, String query) {
setFindText(query == null ? "" : query); //$NON-NLS-1$
for (CatalogFilter filter : getConfiguration().getFilters()) {
if (filter instanceof AbstractTagFilter) {
AbstractTagFilter tagFilter = (AbstractTagFilter) filter;
if (tagFilter.getTagClassification() == ICategory.class) {
List<Tag> choices = tagFilter.getChoices();
Tag tag = choices.isEmpty() ? null : choices.get(0);
if (tag != null) {
IIdentifiable data = null;
if (tag.getTagClassifier() == IMarket.class) {
data = market;
} else if (tag.getTagClassifier() == ICategory.class) {
data = category;
} else {
continue;
}
tag = null;
if (data != null) {
for (Tag choice : choices) {
final Object choiceData = choice.getData();
if (choiceData == data || matches(data, choiceData)) {
tag = choice;
break;
}
}
}
tagFilter.setSelected(tag == null ? Collections.<Tag> emptySet() : Collections.singleton(tag));
//we expect a query to happen next, so don't fire a property change resulting in an additional query
tagFilter.updateUi();
}
}
}
}
}
private static boolean matches(IIdentifiable data, final Object tagData) {
return tagData instanceof IIdentifiable && Identifiable.matches((IIdentifiable) tagData, data);
}
private void doQuery() {
initQueryFromFilters();
doQuery(queryMarket, queryCategory, queryText, null);
}
private void initQueryFromFilters() {
queryMarket = null;
queryCategory = null;
queryText = null;
findText = getFilterText();
for (CatalogFilter filter : getConfiguration().getFilters()) {
if (filter instanceof AbstractTagFilter) {
AbstractTagFilter tagFilter = (AbstractTagFilter) filter;
if (tagFilter.getTagClassification() == ICategory.class) {
Tag tag = tagFilter.getSelected().isEmpty() ? null : tagFilter.getSelected().iterator().next();
if (tag != null) {
if (tag.getTagClassifier() == IMarket.class) {
queryMarket = (IMarket) tag.getData();
} else if (tag.getTagClassifier() == ICategory.class) {
queryCategory = (ICategory) tag.getData();
}
}
}
}
}
queryText = findText;
}
public void doQueryForTag(String tag) {
setFindText(tag);
doSetContentType(ContentType.SEARCH);
}
private void setFindText(String tag) {
try {
Field filterTextField = FilteredViewer.class.getDeclaredField("filterText"); //$NON-NLS-1$
filterTextField.setAccessible(true);
TextSearchControl textSearchControl = (TextSearchControl) filterTextField.get(this);
textSearchControl.getTextControl().setText(tag);
} catch (Exception e) {
StatusManager.getManager()
.handle(new Status(IStatus.WARNING, MarketplaceClientUi.BUNDLE_ID,
Messages.MarketplaceViewer_Could_not_change_find_text, e));
}
}
private void fireContentTypeChange(ContentType oldValue, ContentType newValue) {
Object source = this;
String property = CONTENT_TYPE_PROPERTY;
firePropertyChangeEvent(new PropertyChangeEvent(source, property, oldValue, newValue));
}
private void doQuery(final IMarket market, final ICategory category, final String queryText,
final Set<? extends INode> nodes) {
try {
final ContentType queryType = contentType;
queryContentType = queryType;
final IStatus[] result = new IStatus[1];
context.run(true, true, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
switch (queryType) {
case POPULAR:
result[0] = getCatalog().popular(monitor);
break;
case RECENT:
result[0] = getCatalog().recent(monitor);
break;
case INSTALLED:
result[0] = getCatalog().installed(monitor);
break;
case SELECTION:
Set<String> nodeIds = new HashSet<String>();
for (CatalogItem item : getSelectionModel().getItemToSelectedOperation().keySet()) {
nodeIds.add(((INode) item.getData()).getId());
}
result[0] = getCatalog().performQuery(monitor, nodeIds);
break;
case SEARCH:
default:
if (nodes != null && !nodes.isEmpty()) {
result[0] = getCatalog().performNodeQuery(monitor, nodes);
} else if (queryText != null && queryText.length() > 0) {
result[0] = getCatalog().performQuery(market, category, queryText, monitor);
} else {
result[0] = getCatalog().featured(monitor, market, category);
}
break;
}
if (!monitor.isCanceled() && result[0] != null && result[0].getSeverity() != IStatus.CANCEL) {
getCatalog().checkForUpdates(monitor);
}
MarketplaceViewer.this.getControl().getDisplay().syncExec(new Runnable() {
public void run() {
updateViewer(queryText);
}
});
}
});
if (result[0] != null && !result[0].isOK() && result[0].getSeverity() != IStatus.CANCEL) {
StatusManager.getManager().handle(result[0],
StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG);
} else {
verifyUpdateSiteAvailability();
}
} catch (InvocationTargetException e) {
IStatus status = computeStatus(e, Messages.MarketplaceViewer_unexpectedException);
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG);
} catch (InterruptedException e) {
// cancelled by user so nothing to do here.
}
}
private void updateViewer(final String queryText) {
runUpdate(new Runnable() {
public void run() {
if (contentType == ContentType.INSTALLED) {
getViewer().setSorter(new MarketplaceViewerSorter());
} else {
getViewer().setSorter(null);
}
MarketplaceViewer.super.doFind(queryText);
// bug 305274: scrollbars don't always appear after switching tabs, so we re-do the layout
getViewer().getControl().getParent().layout(true, true);
}
});
}
private void runUpdate(Runnable r) {
if (inUpdate) {
r.run();
return;
}
inUpdate = true;
getViewer().getControl().setRedraw(false);
try {
r.run();
} finally {
inUpdate = false;
getViewer().getControl().setRedraw(true);
}
}
public void showSelected() {
contentType = ContentType.SELECTION;
queryMarket = null;
queryCategory = null;
queryText = null;
findText = null;
runUpdate(new Runnable() {
public void run() {
setHeaderVisible(true);
doQuery(null, null, findText, null);
}
});
contentType = ContentType.SEARCH;
}
@Override
public MarketplaceCatalogConfiguration getConfiguration() {
return (MarketplaceCatalogConfiguration) super.getConfiguration();
}
@Override
public MarketplaceCatalog getCatalog() {
return (MarketplaceCatalog) super.getCatalog();
}
public ContentType getContentType() {
return contentType;
}
@Override
protected PatternFilter doCreateFilter() {
return new MarketplacePatternFilter();
}
public void setContentType(final ContentType contentType) {
if (this.contentType != contentType) {
doSetContentType(contentType);
}
}
private void doSetContentType(final ContentType contentType) {
ContentType oldContentType = this.contentType;
this.contentType = contentType;
fireContentTypeChange(oldContentType, contentType);
runUpdate(new Runnable() {
public void run() {
setHeaderVisible(contentType == ContentType.SEARCH || contentType == ContentType.SELECTION);
doQuery();
}
});
}
public void addPropertyChangeListener(IPropertyChangeListener listener) {
listeners.add(listener);
}
public void removePropertyChangeListener(IPropertyChangeListener listener) {
listeners.remove(listener);
}
private void firePropertyChangeEvent(PropertyChangeEvent event) {
for (IPropertyChangeListener listener : listeners) {
listener.propertyChange(event);
}
}
@Override
public void setHeaderVisible(final boolean visible) {
if (visible != isHeaderVisible()) {
runUpdate(new Runnable() {
public void run() {
if (!visible) {
filters = getViewer().getFilters();
getViewer().resetFilters();
} else {
if (filters != null) {
getViewer().setFilters(filters);
filters = null;
}
}
MarketplaceViewer.super.setHeaderVisible(visible);
}
});
}
}
@Override
protected boolean doFilter(CatalogItem item) {
// all filtering is done server-side, so never filter here
return true;
}
@Override
protected StructuredViewer doCreateViewer(Composite container) {
ServiceReference<IDiscoveryItemFactory> serviceReference = null;
final BundleContext bundleContext = MarketplaceClientUiPlugin.getInstance().getBundle().getBundleContext();
try {
serviceReference = bundleContext.getServiceReference(IDiscoveryItemFactory.class);
if (serviceReference != null) {
discoveryItemFactory = bundleContext.getService(serviceReference);
}
} catch (Exception ex) {
//fall back
MarketplaceClientUi.error(ex);
}
StructuredViewer viewer = super.doCreateViewer(container);
viewer.setSorter(null);
if (serviceReference != null) {
final ServiceReference<IDiscoveryItemFactory> ref = serviceReference;
viewer.getControl().addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
bundleContext.ungetService(ref);
}
});
}
return viewer;
}
/**
* not supported, instead usee {@link #modifySelection(CatalogItem, Operation)}
*/
@Override
protected void modifySelection(CatalogItem connector, boolean selected) {
throw new UnsupportedOperationException();
}
/**
* @deprecated use {@link #modifySelection(CatalogItem, Operation)} instead
*/
@Deprecated
protected void modifySelection(CatalogItem connector, org.eclipse.epp.internal.mpc.ui.wizards.Operation operation) {
modifySelection(connector, operation == null ? null : operation.getOperation());
}
protected void modifySelection(CatalogItem connector, Operation operation) {
if (operation == null) {
throw new IllegalArgumentException();
}
selectionModel.select(connector, operation);
super.modifySelection(connector, operation != Operation.NONE);
}
@Override
protected void postDiscovery() {
// nothing to do
}
@Override
public void updateCatalog() {
if (getWizard().wantInitializeInitialSelection()) {
try {
getWizard().initializeInitialSelection();
catalogUpdated(false, false);
} catch (CoreException e) {
boolean wasCancelled = e.getStatus().getSeverity() == IStatus.CANCEL;
if (!wasCancelled) {
StatusManager.getManager().handle(e.getStatus(),
StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG);
}
catalogUpdated(wasCancelled, !wasCancelled);
}
} else {
super.updateCatalog();
}
refresh();
}
@Override
protected IStatus computeStatus(InvocationTargetException e, String message) {
return MarketplaceClientUi.computeStatus(e, message);
}
private MarketplaceWizard getWizard() {
return wizard;
}
@Override
public List<CatalogItem> getCheckedItems() {
List<CatalogItem> items = new ArrayList<CatalogItem>();
for (Entry<CatalogItem, Operation> entry : getSelectionModel().getItemToSelectedOperation().entrySet()) {
if (entry.getValue() != Operation.NONE) {
items.add(entry.getKey());
}
}
return items;
}
@Override
public IStructuredSelection getSelection() {
return new StructuredSelection(getCheckedItems());
}
public SelectionModel getSelectionModel() {
return selectionModel;
}
/**
* the text for the current query
*/
public String getQueryText() {
return queryText;
}
/**
* the category for the current query
*
* @return the market or null if no category has been selected
*/
public ICategory getQueryCategory() {
return queryCategory;
}
/**
* the market for the current query
*
* @return the market or null if no market has been selected
*/
public IMarket getQueryMarket() {
return queryMarket;
}
/**
* the content type for the current query
*
* @return the content type or null if it's unknown
*/
ContentType getQueryContentType() {
return queryContentType;
}
@Override
protected Set<String> getInstalledFeatures(IProgressMonitor monitor) {
return MarketplaceClientUi.computeInstalledFeatures(monitor);
}
@Override
public void refresh() {
runUpdate(new Runnable() {
public void run() {
MarketplaceViewer.super.refresh();
}
});
}
protected static void fixLayout(CategoryItem<?> categoryItem) {
//FIXME remove once the layout has been fixed upstream
CatalogCategory category = categoryItem.getData();
boolean hasDescription = category.getDescription() != null;
int valignTitle = hasDescription ? SWT.BEGINNING : SWT.CENTER;
int totalRows = hasDescription ? 2 : 1;
final Control[] children = categoryItem.getChildren();
Composite categoryHeaderContainer = (Composite) children[0];
GridLayoutFactory.fillDefaults()
.numColumns(3)
.margins(5, hasDescription ? 5 : 10)
.equalWidth(false)
.applyTo(categoryHeaderContainer);
final Control[] headerChildren = categoryHeaderContainer.getChildren();
final Control iconLabel = headerChildren[0];
final Control nameLabel = headerChildren[1];
final Control tooltip = headerChildren[2];
GridDataFactory.swtDefaults().align(SWT.CENTER, valignTitle).span(1, totalRows).applyTo(iconLabel);
GridDataFactory.fillDefaults().grab(true, false).align(SWT.BEGINNING, valignTitle).applyTo(nameLabel);
if (tooltip instanceof Label) {
GridDataFactory.fillDefaults().align(SWT.END, valignTitle).applyTo(tooltip);
}
}
protected static void setSeparatorVisible(CategoryItem<?> categoryItem, boolean visible) {
//FIXME introduce API in CategoryItem and then get rid of this
final Control childControl = categoryItem.getChildren()[0];
if (childControl instanceof GradientCanvas) {
GradientCanvas canvas = (GradientCanvas) childControl;
canvas.setSeparatorVisible(visible);
}
}
}