/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation 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: * IBM Corporation - initial API and implementation * Mickael Istria (Red Hat Inc.) - Bug 486901 *******************************************************************************/ package org.eclipse.ui.dialogs; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.ide.dialogs.ResourceTreeAndListGroup; import org.eclipse.ui.internal.ide.DialogUtil; import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; import org.eclipse.ui.model.WorkbenchContentProvider; import org.eclipse.ui.model.WorkbenchLabelProvider; /** * Abstract superclass for a typical export wizard's main page. * <p> * Clients may subclass this page to inherit its common destination resource * selection facilities. * </p> * <p> * Subclasses must implement * <ul> * <li><code>createDestinationGroup</code></li> * </ul> * </p> * <p> * Subclasses may override * <ul> * <li><code>allowNewContainerName</code></li> * </ul> * </p> * <p> * Subclasses may extend * <ul> * <li><code>handleEvent</code></li> * <li><code>internalSaveWidgetValues</code></li> * <li><code>updateWidgetEnablements</code></li> * </ul> * </p> */ public abstract class WizardExportResourcesPage extends WizardDataTransferPage { private IStructuredSelection initialResourceSelection; private List selectedTypes = new ArrayList(); // widgets private ResourceTreeAndListGroup resourceGroup; private boolean showLinkedResources; private final static String SELECT_TYPES_TITLE = IDEWorkbenchMessages.WizardTransferPage_selectTypes; private final static String SELECT_ALL_TITLE = IDEWorkbenchMessages.WizardTransferPage_selectAll; private final static String DESELECT_ALL_TITLE = IDEWorkbenchMessages.WizardTransferPage_deselectAll; /** * Creates an export wizard page. If the current resource selection * is not empty then it will be used as the initial collection of resources * selected for export. * * @param pageName the name of the page * @param selection {@link IStructuredSelection} of {@link IResource} * @see IDE#computeSelectedResources(IStructuredSelection) */ protected WizardExportResourcesPage(String pageName, IStructuredSelection selection) { super(pageName); this.initialResourceSelection = selection; } /** * The <code>addToHierarchyToCheckedStore</code> implementation of this * <code>WizardDataTransferPage</code> method returns <code>false</code>. * Subclasses may override this method. */ @Override protected boolean allowNewContainerName() { return false; } /** * Creates a new button with the given id. * <p> * The <code>Dialog</code> implementation of this framework method * creates a standard push button, registers for selection events * including button presses and registers * default buttons with its shell. * The button id is stored as the buttons client data. * Note that the parent's layout is assumed to be a GridLayout and * the number of columns in this layout is incremented. * Subclasses may override. * </p> * * @param parent the parent composite * @param id the id of the button (see * <code>IDialogConstants.*_ID</code> constants * for standard dialog button ids) * @param label the label from the button * @param defaultButton <code>true</code> if the button is to be the * default button, and <code>false</code> otherwise */ protected Button createButton(Composite parent, int id, String label, boolean defaultButton) { // increment the number of columns in the button bar ((GridLayout) parent.getLayout()).numColumns++; Button button = new Button(parent, SWT.PUSH); GridData buttonData = new GridData(GridData.FILL_HORIZONTAL); button.setLayoutData(buttonData); button.setData(id); button.setText(label); button.setFont(parent.getFont()); if (defaultButton) { Shell shell = parent.getShell(); if (shell != null) { shell.setDefaultButton(button); } button.setFocus(); } button.setFont(parent.getFont()); setButtonLayoutData(button); return button; } /** * Creates the buttons for selecting specific types or selecting all or none of the * elements. * * @param parent the parent control */ protected final void createButtonsGroup(Composite parent) { Font font = parent.getFont(); // top level group Composite buttonComposite = new Composite(parent, SWT.NONE); buttonComposite.setFont(parent.getFont()); GridLayout layout = new GridLayout(); layout.numColumns = 3; layout.makeColumnsEqualWidth = true; buttonComposite.setLayout(layout); buttonComposite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); // types edit button Button selectTypesButton = createButton(buttonComposite, IDialogConstants.SELECT_TYPES_ID, SELECT_TYPES_TITLE, false); SelectionListener listener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { handleTypesEditButtonPressed(); } }; selectTypesButton.addSelectionListener(listener); selectTypesButton.setFont(font); setButtonLayoutData(selectTypesButton); Button selectButton = createButton(buttonComposite, IDialogConstants.SELECT_ALL_ID, SELECT_ALL_TITLE, false); listener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { resourceGroup.setAllSelections(true); updateWidgetEnablements(); } }; selectButton.addSelectionListener(listener); selectButton.setFont(font); setButtonLayoutData(selectButton); Button deselectButton = createButton(buttonComposite, IDialogConstants.DESELECT_ALL_ID, DESELECT_ALL_TITLE, false); listener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { resourceGroup.setAllSelections(false); updateWidgetEnablements(); } }; deselectButton.addSelectionListener(listener); deselectButton.setFont(font); setButtonLayoutData(deselectButton); } @Override public void createControl(Composite parent) { initializeDialogUnits(parent); Composite composite = new Composite(parent, SWT.NULL); composite.setLayout(new GridLayout()); composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); composite.setFont(parent.getFont()); createResourcesGroup(composite); createButtonsGroup(composite); createDestinationGroup(composite); createOptionsGroup(composite); restoreResourceSpecificationWidgetValues(); // ie.- local restoreWidgetValues(); // ie.- subclass hook if (initialResourceSelection != null) { setupBasedOnInitialSelections(); } updateWidgetEnablements(); setPageComplete(determinePageCompletion()); setErrorMessage(null); // should not initially have error message setControl(composite); } /** * Creates the export destination specification visual components. * <p> * Subclasses must implement this method. * </p> * * @param parent the parent control */ protected abstract void createDestinationGroup(Composite parent); /** * Creates the checkbox tree and list for selecting resources. * * @param parent the parent control */ protected final void createResourcesGroup(Composite parent) { //create the input element, which has the root resource //as its only child List input = new ArrayList(); IProject[] projects = ResourcesPlugin.getWorkspace().getRoot() .getProjects(); for (IProject project : projects) { if (project.isOpen()) { input.add(project); } } showLinkedResources = getShowLinkedResources(); this.resourceGroup = new ResourceTreeAndListGroup(parent, input, getResourceProvider(IResource.FOLDER | IResource.PROJECT, showLinkedResources), WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider(), getResourceProvider(IResource.FILE, showLinkedResources), WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider(), SWT.NONE, DialogUtil.inRegularFontMode(parent)); ICheckStateListener listener = event -> updateWidgetEnablements(); this.resourceGroup.addCheckStateListener(listener); } /* * @see WizardDataTransferPage.getErrorDialogTitle() */ @Override protected String getErrorDialogTitle() { return IDEWorkbenchMessages.WizardExportPage_errorDialogTitle; } /** * Obsolete method. This was implemented to handle the case where ensureLocal() * needed to be called but it doesn't use it any longer. * * @deprecated Only retained for backwards compatibility. */ @Deprecated protected boolean ensureResourcesLocal(List resources) { return true; } /** * Returns a new subcollection containing only those resources which are not * local. * * @param originalList the original list of resources (element type: * <code>IResource</code>) * @return the new list of non-local resources (element type: * <code>IResource</code>) */ protected List extractNonLocalResources(List originalList) { Vector result = new Vector(originalList.size()); Iterator resourcesEnum = originalList.iterator(); while (resourcesEnum.hasNext()) { IResource currentResource = (IResource) resourcesEnum.next(); if (!currentResource.isLocal(IResource.DEPTH_ZERO)) { result.addElement(currentResource); } } return result; } private static class ResourceProvider extends WorkbenchContentProvider { private static final Object[] EMPTY = new Object[0]; private int resourceType; private boolean showLinkedResources; public ResourceProvider(int resourceType, boolean showLinkedResources) { super(); this.resourceType = resourceType; this.showLinkedResources = showLinkedResources; } @Override public Object[] getChildren(Object o) { if (o instanceof IContainer) { IContainer container = (IContainer) o; if (!showLinkedResources && container.isLinked(IResource.CHECK_ANCESTORS)) { // just return an empty set of children return EMPTY; } IResource[] members = null; try { members = container.members(); } catch (CoreException e) { // just return an empty set of children return EMPTY; } // filter out the desired resource types List<IResource> results = new ArrayList<>(); for (IResource resource : members) { if (!showLinkedResources && resource.isLinked()) { continue; } if ((resource.getType() & resourceType) > 0) { results.add(resource); } } return results.toArray(); } // input element case if (o instanceof ArrayList) { return ((List<?>) o).toArray(); } return EMPTY; } } /** * Returns a content provider for <code>IResource</code>s that returns * only children of the given resource type. */ private ITreeContentProvider getResourceProvider(int resourceType, boolean showLinkedResources) { return new ResourceProvider(resourceType, showLinkedResources); } /** * Returns {<code>true</code> if the page tree and list providers should * show linked resources. Default is false. * * @since 3.12 */ protected boolean getShowLinkedResources() { return showLinkedResources; } /** * Updates the resources tree to show or hide linked resources * * @param showLinked * {<code>true</code> if the page should show linked resources * @since 3.12 */ protected void updateContentProviders(boolean showLinked) { showLinkedResources = showLinked; resourceGroup.setTreeProviders( getResourceProvider(IResource.FOLDER | IResource.PROJECT, showLinkedResources), WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider()); resourceGroup.setListProviders(getResourceProvider(IResource.FILE, showLinkedResources), WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider()); } /** * Returns this page's collection of currently-specified resources to be * exported. This is the primary resource selection facility accessor for * subclasses. * * @return a collection of resources currently selected * for export (element type: <code>IResource</code>) */ protected List getSelectedResources() { Iterator resourcesToExportIterator = this .getSelectedResourcesIterator(); List resourcesToExport = new ArrayList(); while (resourcesToExportIterator.hasNext()) { resourcesToExport.add(resourcesToExportIterator.next()); } return resourcesToExport; } /** * Returns this page's collection of currently-specified resources to be * exported. This is the primary resource selection facility accessor for * subclasses. * * @return an iterator over the collection of resources currently selected * for export (element type: <code>IResource</code>). This will include * white checked folders and individually checked files. */ protected Iterator getSelectedResourcesIterator() { return this.resourceGroup.getAllCheckedListItems().iterator(); } /** * Returns the resource extensions currently specified to be exported. * * @return the resource extensions currently specified to be exported (element * type: <code>String</code>) */ protected List getTypesToExport() { return selectedTypes; } /** * Returns this page's collection of currently-specified resources to be * exported. This returns both folders and files - for just the files use * getSelectedResources. * * @return a collection of resources currently selected * for export (element type: <code>IResource</code>) */ protected List getWhiteCheckedResources() { return this.resourceGroup.getAllWhiteCheckedItems(); } /** * Queries the user for the types of resources to be exported and selects * them in the checkbox group. */ protected void handleTypesEditButtonPressed() { Object[] newSelectedTypes = queryResourceTypesToExport(); if (newSelectedTypes != null) { // ie.- did not press Cancel this.selectedTypes = new ArrayList(newSelectedTypes.length); for (Object newSelectedType : newSelectedTypes) { this.selectedTypes.add(newSelectedType); } setupSelectionsBasedOnSelectedTypes(); } } /** * Returns whether the extension of the given resource name is an extension that * has been specified for export by the user. * * @param resourceName the resource name * @return <code>true</code> if the resource name is suitable for export based * upon its extension */ protected boolean hasExportableExtension(String resourceName) { if (selectedTypes == null) { return true; } int separatorIndex = resourceName.lastIndexOf("."); //$NON-NLS-1$ if (separatorIndex == -1) { return false; } String extension = resourceName.substring(separatorIndex + 1); Iterator it = selectedTypes.iterator(); while (it.hasNext()) { if (extension.equalsIgnoreCase((String) it.next())) { return true; } } return false; } /** * Persists additional setting that are to be restored in the next instance of * this page. * <p> * The <code>WizardImportPage</code> implementation of this method does * nothing. Subclasses may extend to persist additional settings. * </p> */ protected void internalSaveWidgetValues() { } /** * Queries the user for the resource types that are to be exported and returns * these types as an array. * * @return the resource types selected for export (element type: * <code>String</code>), or <code>null</code> if the user canceled the * selection */ protected Object[] queryResourceTypesToExport() { TypeFilteringDialog dialog = new TypeFilteringDialog(getContainer() .getShell(), getTypesToExport()); dialog.open(); return dialog.getResult(); } /** * Restores resource specification control settings that were persisted * in the previous instance of this page. Subclasses wishing to restore * persisted values for their controls may extend. */ protected void restoreResourceSpecificationWidgetValues() { } /** * Persists resource specification control setting that are to be restored * in the next instance of this page. Subclasses wishing to persist additional * setting for their controls should extend hook method * <code>internalSaveWidgetValues</code>. */ @Override protected void saveWidgetValues() { // allow subclasses to save values internalSaveWidgetValues(); } /** * Set the initial selections in the resource group. */ protected void setupBasedOnInitialSelections() { Iterator it = this.initialResourceSelection.iterator(); while (it.hasNext()) { IResource currentResource = (IResource) it.next(); if (currentResource.getType() == IResource.FILE) { this.resourceGroup.initialCheckListItem(currentResource); } else { this.resourceGroup.initialCheckTreeItem(currentResource); } } } /** * Update the tree to only select those elements that match the selected types */ private void setupSelectionsBasedOnSelectedTypes() { Runnable runnable = () -> { Map selectionMap = new Hashtable(); //Only get the white selected ones Iterator resourceIterator = resourceGroup .getAllWhiteCheckedItems().iterator(); while (resourceIterator.hasNext()) { //handle the files here - white checked containers require recursion IResource resource = (IResource) resourceIterator.next(); if (resource.getType() == IResource.FILE) { if (hasExportableExtension(resource.getName())) { List resourceList = new ArrayList(); IContainer parent = resource.getParent(); if (selectionMap.containsKey(parent)) { resourceList = (List) selectionMap.get(parent); } resourceList.add(resource); selectionMap.put(parent, resourceList); } } else { setupSelectionsBasedOnSelectedTypes(selectionMap, (IContainer) resource); } } resourceGroup.updateSelections(selectionMap); }; BusyIndicator.showWhile(getShell().getDisplay(), runnable); } /** * Set up the selection values for the resources and put them in the selectionMap. * If a resource is a file see if it matches one of the selected extensions. If not * then check the children. */ private void setupSelectionsBasedOnSelectedTypes(Map selectionMap, IContainer parent) { List selections = new ArrayList(); IResource[] resources; boolean hasFiles = false; try { resources = parent.members(); } catch (CoreException exception) { //Just return if we can't get any info return; } for (IResource resource : resources) { if (resource.getType() == IResource.FILE) { if (hasExportableExtension(resource.getName())) { hasFiles = true; selections.add(resource); } } else { setupSelectionsBasedOnSelectedTypes(selectionMap, (IContainer) resource); } } //Only add it to the list if there are files in this folder if (hasFiles) { selectionMap.put(parent, selections); } } /** * Save any editors that the user wants to save before export. * @return boolean if the save was successful. */ protected boolean saveDirtyEditors() { return IDEWorkbenchPlugin.getDefault().getWorkbench().saveAllEditors( true); } /** * Check if widgets are enabled or disabled by a change in the dialog. */ @Override protected void updateWidgetEnablements() { boolean pageComplete = determinePageCompletion(); setPageComplete(pageComplete); if (pageComplete) { setMessage(null); } super.updateWidgetEnablements(); } }