| /******************************************************************************* |
| * 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(() -> 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; |
| } |
| }; |
| } |
| } |