blob: 084d5cafeb01adcfc2d11cba0dbed197f2f72c93 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Sonatype, Inc. - ongoing development
*******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.dialogs;
import java.net.URI;
import java.util.ArrayList;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.equinox.internal.p2.ui.*;
import org.eclipse.equinox.internal.p2.ui.model.*;
import org.eclipse.equinox.internal.p2.ui.query.IUViewQueryContext;
import org.eclipse.equinox.internal.p2.ui.viewers.*;
import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.repository.IRepositoryManager;
import org.eclipse.equinox.p2.ui.LoadMetadataRepositoryJob;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.eclipse.jface.viewers.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
/**
* An AvailableIUGroup is a reusable UI component that displays the
* IU's available for installation. By default, content from all available
* repositories is shown.
*
* @since 3.4
*/
public class AvailableIUGroup extends StructuredIUGroup {
/**
* Show contents from all repositories
*/
public static final int AVAILABLE_ALL = 1;
/**
* Don't show any repository content
*/
public static final int AVAILABLE_NONE = 2;
/**
* Show local repository content
*/
public static final int AVAILABLE_LOCAL = 3;
/**
* Show content for a specific repository
*/
public static final int AVAILABLE_SPECIFIED = 4;
IUViewQueryContext queryContext;
int filterConstant = AVAILABLE_ALL;
URI repositoryFilter;
QueryableMetadataRepositoryManager queryableManager;
// We restrict the type of the filter used because PatternFilter does
// unnecessary accesses of children that cause problems with the deferred
// tree.
AvailableIUPatternFilter filter;
private boolean useBold = false;
private IUDetailsLabelProvider labelProvider;
private int repoFlags;
Display display;
DelayedFilterCheckboxTree filteredTree;
Job lastRequestedLoadJob;
/**
* Create a group that represents the available IU's from all available
* repositories. The default policy controls the visibility flags for
* repositories and IU's.
*
* @param parent the parent composite for the group
*/
public AvailableIUGroup(ProvisioningUI ui, final Composite parent) {
this(ui, parent, parent.getFont(), null, getDefaultColumnConfig(), AVAILABLE_ALL);
}
private static IUColumnConfig[] getDefaultColumnConfig() {
// increase primary column width because we might be nesting names under categories and require more space than a flat list
IUColumnConfig nameColumn = new IUColumnConfig(ProvUIMessages.ProvUI_NameColumnTitle, IUColumnConfig.COLUMN_NAME, ILayoutConstants.DEFAULT_PRIMARY_COLUMN_WIDTH + 15);
IUColumnConfig versionColumn = new IUColumnConfig(ProvUIMessages.ProvUI_VersionColumnTitle, IUColumnConfig.COLUMN_VERSION, ILayoutConstants.DEFAULT_COLUMN_WIDTH);
return new IUColumnConfig[] {nameColumn, versionColumn};
}
/**
* Create a group that represents the available IU's.
*
* @param ui the policy to use for deciding what should be shown
* @param parent the parent composite for the group
* @param font The font to use for calculating pixel sizes. This font is
* not managed by the receiver.
* @param queryContext the IUViewQueryContext that determines additional
* information about what is shown, such as the visible repositories
* @param columnConfig the description of the columns that should be shown. If <code>null</code>, a default
* will be used.
* @param filterConstant a constant specifying which repositories are used when showing content
*/
public AvailableIUGroup(ProvisioningUI ui, final Composite parent, Font font, IUViewQueryContext queryContext, IUColumnConfig[] columnConfig, int filterConstant) {
super(ui, parent, font, columnConfig);
this.display = parent.getDisplay();
if (queryContext == null)
this.queryContext = ProvUI.getQueryContext(getPolicy());
else
this.queryContext = queryContext;
repoFlags = ui.getRepositoryTracker().getMetadataRepositoryFlags();
this.queryableManager = new QueryableMetadataRepositoryManager(ui, false);
this.filterConstant = filterConstant;
this.filter = new AvailableIUPatternFilter(getColumnConfig());
this.filter.setIncludeLeadingWildcard(true);
createGroupComposite(parent);
}
@Override
protected StructuredViewer createViewer(Composite parent) {
// Table of available IU's
filteredTree = new DelayedFilterCheckboxTree(parent, SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER, filter, getPreFilterJobProvider());
final TreeViewer availableIUViewer = filteredTree.getViewer();
availableIUViewer.getTree().setFont(parent.getFont());
filteredTree.getFilterControl().setFont(parent.getFont());
// If the user expanded or collapsed anything while we were loading a repo
// in the background, we would not want to disrupt their work by making
// a newly loaded visible and expanding it. Setting the load job to null
// will take care of this.
availableIUViewer.getTree().addTreeListener(new TreeListener() {
@Override
public void treeCollapsed(TreeEvent e) {
lastRequestedLoadJob = null;
}
@Override
public void treeExpanded(TreeEvent e) {
lastRequestedLoadJob = null;
}
});
labelProvider = new IUDetailsLabelProvider(filteredTree, getColumnConfig(), getShell());
labelProvider.setUseBoldFontForFilteredItems(useBold);
labelProvider.setToolTipProperty(IInstallableUnit.PROP_DESCRIPTION);
// Filters and sorters before establishing content, so we don't refresh unnecessarily.
IUComparator comparator = new IUComparator(IUComparator.IU_NAME);
comparator.useColumnConfig(getColumnConfig());
availableIUViewer.setComparator(comparator);
availableIUViewer.setComparer(new ProvElementComparer());
// Now the content provider.
DeferredQueryContentProvider contentProvider = new DeferredQueryContentProvider();
availableIUViewer.setContentProvider(contentProvider);
// Now the presentation, columns before label provider.
setTreeColumns(availableIUViewer.getTree());
availableIUViewer.setLabelProvider(labelProvider);
// Notify the filtered tree so that it can hook listeners on the
// content provider. This is needed so that filtering is only allowed
// after content has been retrieved.
filteredTree.contentProviderSet(contentProvider);
final StructuredViewerProvisioningListener listener = new StructuredViewerProvisioningListener(getClass().getName(), availableIUViewer, ProvUIProvisioningListener.PROV_EVENT_METADATA_REPOSITORY, getProvisioningUI().getOperationRunner()) {
@Override
protected void repositoryAdded(final RepositoryEvent event) {
makeRepositoryVisible(event.getRepositoryLocation());
}
@Override
protected void refreshViewer() {
final TreeViewer treeViewer = filteredTree.getViewer();
final Tree tree = treeViewer.getTree();
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench.isClosing())
return;
if (tree != null && !tree.isDisposed()) {
updateAvailableViewState();
}
}
};
ProvUI.getProvisioningEventBus(getProvisioningUI().getSession()).addListener(listener);
availableIUViewer.getControl().addDisposeListener(e -> ProvUI.getProvisioningEventBus(getProvisioningUI().getSession()).removeListener(listener));
updateAvailableViewState();
return availableIUViewer;
}
private void setTreeColumns(Tree tree) {
tree.setHeaderVisible(true);
IUColumnConfig[] cols = getColumnConfig();
for (int i = 0; i < cols.length; i++) {
TreeColumn tc = new TreeColumn(tree, SWT.NONE, i);
tc.setResizable(true);
tc.setText(cols[i].getColumnTitle());
tc.setWidth(cols[i].getWidthInPixels(tree));
}
}
Object getNewInput() {
if (repositoryFilter != null) {
return new MetadataRepositoryElement(queryContext, getProvisioningUI(), repositoryFilter, true);
} else if (filterConstant == AVAILABLE_NONE) {
// Dummy object that explains empty site list
return new ProvElement(null) {
@Override
public Object[] getChildren(Object o) {
String description;
String name;
int severity;
if (!getPolicy().getRepositoriesVisible()) {
// shouldn't get here ideally. No sites and no way to add any.
severity = IStatus.ERROR;
name = ProvUIMessages.AvailableIUGroup_NoSitesConfiguredExplanation;
description = ProvUIMessages.AvailableIUGroup_NoSitesConfiguredDescription;
} else {
severity = IStatus.INFO;
name = ProvUIMessages.AvailableIUGroup_NoSitesExplanation;
description = ProvUIMessages.ColocatedRepositoryManipulator_NoContentExplanation;
}
return new Object[] {new EmptyElementExplanation(null, severity, name, description)};
}
@Override
public String getLabel(Object o) {
// Label not needed for input
return null;
}
};
} else {
queryableManager.setRespositoryFlags(repoFlags);
return new MetadataRepositories(queryContext, getProvisioningUI(), queryableManager);
}
}
/**
* Set a boolean indicating whether a bold font should be used when
* showing filtered items. This method does not refresh the tree or
* labels, so that must be done explicitly by the caller.
* @param useBoldFont
*/
public void setUseBoldFontForFilteredItems(boolean useBoldFont) {
if (labelProvider != null)
labelProvider.setUseBoldFontForFilteredItems(useBoldFont);
}
/**
* Return the composite that contains the controls in this group.
* @return the composite
*/
@Override
public Composite getComposite() {
return super.getComposite();
}
/**
* Get the viewer used to represent the available IU's
* @return the viewer
*/
@Override
public StructuredViewer getStructuredViewer() {
return super.getStructuredViewer();
}
/**
* Get the selected IU's
* @return the array of selected IU's
*/
// overridden for visibility in the public package
@Override
public java.util.List<IInstallableUnit> getSelectedIUs() {
return super.getSelectedIUs();
}
// overridden to weed out non-IU elements, such as repositories or empty explanations
@Override
public Object[] getSelectedIUElements() {
Object[] elements = viewer.getStructuredSelection().toArray();
ArrayList<Object> list = new ArrayList<>(elements.length);
for (Object element : elements)
if (ElementUtils.getIU(element) != null)
list.add(element);
return list.toArray();
}
public CheckboxTreeViewer getCheckboxTreeViewer() {
return filteredTree.getCheckboxTreeViewer();
}
/**
* Get the selected IU's
* @return the array of checked IU's
*/
public IInstallableUnit[] getCheckedLeafIUs() {
Object[] selections = filteredTree.getCheckedElements(); // Get all the elements that have been selected, not just the visible ones
if (selections.length == 0)
return new IInstallableUnit[0];
ArrayList<IInstallableUnit> leaves = new ArrayList<>(selections.length);
for (Object selection : selections) {
if (!getCheckboxTreeViewer().getGrayed(selection)) {
IInstallableUnit iu = ProvUI.getAdapter(selection, IInstallableUnit.class);
if (iu != null && !ProvUI.isCategory(iu) && !leaves.contains(iu))
leaves.add(iu);
}
}
return leaves.toArray(new IInstallableUnit[leaves.size()]);
}
public Tree getTree() {
if (viewer == null)
return null;
return ((TreeViewer) viewer).getTree();
}
/*
* Make the repository with the specified location visible in the viewer.
*/
void makeRepositoryVisible(final URI location) {
// If we are viewing by anything other than site, there is no specific way
// to make a repo visible.
if (!(queryContext.getViewType() == IUViewQueryContext.AVAILABLE_VIEW_BY_REPO)) {
if (Display.getCurrent() == null)
display.asyncExec(this::updateAvailableViewState);
else
updateAvailableViewState();
return;
}
// First reset the input so that the new repo shows up
Runnable runnable = () -> {
final TreeViewer treeViewer = filteredTree.getViewer();
final Tree tree = treeViewer.getTree();
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench.isClosing())
return;
if (tree != null && !tree.isDisposed()) {
updateAvailableViewState();
}
};
if (Display.getCurrent() == null)
display.asyncExec(runnable);
else
runnable.run();
// We don't know if loading will be a fast or slow operation.
// We do it in a job to be safe, and when it's done, we update
// the UI.
Job job = new Job(NLS.bind(ProvUIMessages.AvailableIUGroup_LoadingRepository, URIUtil.toUnencodedString(location))) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
getProvisioningUI().loadMetadataRepository(location, true, monitor);
return Status.OK_STATUS;
} catch (ProvisionException e) {
return e.getStatus();
} catch (OperationCanceledException e) {
return Status.CANCEL_STATUS;
}
}
};
job.setPriority(Job.LONG);
job.setSystem(true);
job.setUser(false);
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(final IJobChangeEvent event) {
if (event.getResult().isOK())
display.asyncExec(() -> {
final TreeViewer treeViewer = filteredTree.getViewer();
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench.isClosing())
return;
// Expand only if there have been no other jobs started for other repos.
if (event.getJob() == lastRequestedLoadJob) {
final Tree tree = treeViewer.getTree();
if (tree != null && !tree.isDisposed()) {
for (TreeItem item : tree.getItems()) {
if (item.getData() instanceof IRepositoryElement) {
URI url = ((IRepositoryElement<?>) item.getData()).getLocation();
if (url.equals(location)) {
treeViewer.expandToLevel(item.getData(), AbstractTreeViewer.ALL_LEVELS);
tree.select(item);
return;
}
}
}
}
}
});
}
});
lastRequestedLoadJob = job;
job.schedule();
}
public void updateAvailableViewState() {
if (getTree() == null || getTree().isDisposed())
return;
final Composite parent = getComposite().getParent();
setUseBoldFontForFilteredItems(queryContext.getViewType() != IUViewQueryContext.AVAILABLE_VIEW_FLAT);
BusyIndicator.showWhile(display, () -> {
parent.setRedraw(false);
getCheckboxTreeViewer().setInput(getNewInput());
parent.layout(true);
parent.setRedraw(true);
});
}
@Override
public Control getDefaultFocusControl() {
if (filteredTree != null)
return filteredTree.getFilterControl();
return null;
}
protected String getFilterString() {
return filteredTree.getFilterString();
}
@Override
protected GridData getViewerGridData() {
GridData data = super.getViewerGridData();
data.heightHint = convertHeightInCharsToPixels(ILayoutConstants.DEFAULT_TABLE_HEIGHT);
return data;
}
/**
* Set the checked elements to the specified selections. This method
* does not force visibility/expansion of the checked elements. If they are not
* visible, they will not be checked.
* @param selections
*/
public void setChecked(Object[] selections) {
filteredTree.getCheckboxTreeViewer().setCheckedElements(selections);
// TODO HACK ALERT!
// Since We don't have API for setAllChecked(boolean), clients have to use this method.
// We need to signal DelayedFilterCheckboxTree when everything needs to be deselected since
// we aren't firing an event for each item.
Object element = selections.length == 0 ? DelayedFilterCheckboxTree.ALL_ITEMS_HACK : selections[0];
filteredTree.getCheckboxTreeViewer().fireCheckStateChanged(element, selections.length > 0);
}
public void setRepositoryFilter(int filterFlag, URI repoLocation) {
// If there has been no change, don't do anything. We will be
// clearing out selection caches in this method and should not do
// so if there's really no change.
if (filterConstant == filterFlag) {
if (filterConstant != AVAILABLE_SPECIFIED)
return;
if (repoLocation != null && repoLocation.equals(repositoryFilter))
return;
}
filterConstant = filterFlag;
switch (filterFlag) {
case AVAILABLE_ALL :
case AVAILABLE_NONE :
repositoryFilter = null;
repoFlags &= ~IRepositoryManager.REPOSITORIES_LOCAL;
break;
case AVAILABLE_LOCAL :
repositoryFilter = null;
repoFlags |= IRepositoryManager.REPOSITORIES_LOCAL;
break;
default :
repositoryFilter = repoLocation;
break;
}
updateAvailableViewState();
filteredTree.clearCheckStateCache();
}
private IPreFilterJobProvider getPreFilterJobProvider() {
return () -> {
switch (filterConstant) {
case AVAILABLE_ALL :
Job preFilterJob = new LoadMetadataRepositoryJob(getProvisioningUI());
preFilterJob.setProperty(LoadMetadataRepositoryJob.SUPPRESS_REPOSITORY_EVENTS, Boolean.toString(true));
return preFilterJob;
case AVAILABLE_NONE :
case AVAILABLE_LOCAL :
return null;
default :
if (repositoryFilter == null)
return null;
Job job = new Job("Repository Load Job") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
getProvisioningUI().loadMetadataRepository(repositoryFilter, false, monitor);
return Status.OK_STATUS;
} catch (ProvisionException e) {
return e.getStatus();
}
}
};
job.setPriority(Job.SHORT);
return job;
}
};
}
}