1 /******************************************************************************* 2 * Copyright (c) 2006, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Dina Sayed, dsayed@eg.ibm.com, IBM - bug 269844 14 * Andrey Loskutov <loskutov@gmx.de> - generified interface, bug 462760 15 * Mickael Istria (Red Hat Inc.) - Bug 486901 16 * Lucas Bullen (Red Hat Inc.) - Bug 522096 - "Close Projects" on working set 17 *******************************************************************************/ 18 package org.eclipse.ui.actions; 19 20 import java.util.ArrayList; 21 import java.util.Collections; 22 import java.util.List; 23 24 import org.eclipse.core.resources.IProject; 25 import org.eclipse.core.resources.IResource; 26 import org.eclipse.core.resources.IResourceChangeEvent; 27 import org.eclipse.core.resources.IResourceDelta; 28 import org.eclipse.core.resources.ResourcesPlugin; 29 import org.eclipse.core.runtime.CoreException; 30 import org.eclipse.jface.dialogs.IDialogConstants; 31 import org.eclipse.jface.dialogs.MessageDialogWithToggle; 32 import org.eclipse.jface.preference.IPreferenceStore; 33 import org.eclipse.jface.window.IShellProvider; 34 import org.eclipse.osgi.util.NLS; 35 import org.eclipse.swt.widgets.Shell; 36 import org.eclipse.ui.PlatformUI; 37 import org.eclipse.ui.ide.IDEActionFactory; 38 import org.eclipse.ui.internal.ide.IDEInternalPreferences; 39 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; 40 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; 41 import org.eclipse.ui.internal.ide.IIDEHelpContextIds; 42 import org.eclipse.ui.internal.ide.misc.DisjointSet; 43 44 /** 45 * This action closes all projects that are unrelated to the selected projects. A 46 * project is unrelated if it is not directly or transitively referenced by one 47 * of the selected projects, and does not directly or transitively reference 48 * one of the selected projects. 49 * <p> 50 * This class may be instantiated; it is not intended to be subclassed. 51 * </p> 52 * 53 * @see IDEActionFactory#CLOSE_UNRELATED_PROJECTS 54 * @since 3.3 55 */ 56 public class CloseUnrelatedProjectsAction extends CloseResourceAction { 57 /** 58 * The id of this action. 59 */ 60 public static final String ID = PlatformUI.PLUGIN_ID + ".CloseUnrelatedProjectsAction"; //$NON-NLS-1$ 61 62 private List<IResource> projectsToClose = new ArrayList<>(); 63 64 private boolean selectionDirty = true; 65 66 private List<? extends IResource> oldSelection = Collections.emptyList(); 67 68 69 /** 70 * Builds the connected component set for the input projects. 71 * The result is a DisjointSet where all related projects belong 72 * to the same set. 73 */ buildConnectedComponents(IProject[] projects)74 private static DisjointSet<IProject> buildConnectedComponents(IProject[] projects) { 75 //initially each vertex is in a set by itself 76 DisjointSet<IProject> set = new DisjointSet<>(); 77 for (IProject project : projects) { 78 set.makeSet(project); 79 } 80 for (IProject project : projects) { 81 try { 82 IProject[] references = project.getReferencedProjects(); 83 //each reference represents an edge in the project reference 84 //digraph from projects[i] -> references[j] 85 for (IProject reference : references) { 86 IProject setOne = set.findSet(project); 87 //note that referenced projects may not exist in the workspace 88 IProject setTwo = set.findSet(reference); 89 //these two projects are related, so join their sets 90 if (setOne != null && setTwo != null && setOne != setTwo) { 91 set.union(setOne, setTwo); 92 } 93 } 94 } catch (CoreException e) { 95 //assume inaccessible projects have no references 96 } 97 } 98 return set; 99 } 100 101 /** 102 * Creates this action. 103 * 104 * @param shell 105 * The shell to use for parenting any dialogs created by this 106 * action. 107 * 108 * @deprecated {@link #CloseUnrelatedProjectsAction(IShellProvider)} 109 */ 110 @Deprecated CloseUnrelatedProjectsAction(Shell shell)111 public CloseUnrelatedProjectsAction(Shell shell) { 112 super(shell, IDEWorkbenchMessages.CloseUnrelatedProjectsAction_text); 113 initAction(); 114 } 115 116 /** 117 * Creates this action. 118 * 119 * @param provider 120 * The shell to use for parenting any dialogs created by this 121 * action. 122 * @since 3.4 123 */ CloseUnrelatedProjectsAction(IShellProvider provider)124 public CloseUnrelatedProjectsAction(IShellProvider provider){ 125 super(provider, IDEWorkbenchMessages.CloseUnrelatedProjectsAction_text, 126 IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip, 127 IDEWorkbenchMessages.CloseUnrelatedProjectsAction_text_plural, 128 IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip_plural); 129 initAction(); 130 } 131 132 @Override run()133 public void run() { 134 if (promptForConfirmation()) { 135 super.run(); 136 } 137 } 138 139 /** 140 * Returns whether to close unrelated projects. 141 * Consults the preference and prompts the user if necessary. 142 * 143 * @return <code>true</code> if unrelated projects should be closed, and 144 * <code>false</code> otherwise. 145 */ promptForConfirmation()146 private boolean promptForConfirmation() { 147 IPreferenceStore store = IDEWorkbenchPlugin.getDefault().getPreferenceStore(); 148 if (store.getBoolean(IDEInternalPreferences.CLOSE_UNRELATED_PROJECTS)) { 149 return true; 150 } 151 152 // get first project name 153 List<? extends IResource> selection = super.getSelectedResources(); 154 int selectionSize = selection.size(); 155 if (selectionSize == 0) { 156 return true; 157 } 158 159 String message = null; 160 if (selectionSize == 1) { // if one project is selected then print its name 161 IResource firstSelected = selection.get(0); 162 String projectName = null; 163 if (firstSelected instanceof IProject) { 164 projectName = ((IProject) firstSelected).getName(); 165 } 166 message = NLS.bind(IDEWorkbenchMessages.CloseUnrelatedProjectsAction_confirmMsg1, projectName); 167 } else // if more then one project is selected then print there number 168 message = NLS.bind(IDEWorkbenchMessages.CloseUnrelatedProjectsAction_confirmMsgN, selectionSize); 169 170 MessageDialogWithToggle dialog = MessageDialogWithToggle.openOkCancelConfirm( 171 getShell(), IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip, 172 message, IDEWorkbenchMessages.CloseUnrelatedProjectsAction_AlwaysClose, 173 false, null, null); 174 if (dialog.getReturnCode() != IDialogConstants.OK_ID) { 175 return false; 176 } 177 store.setValue(IDEInternalPreferences.CLOSE_UNRELATED_PROJECTS, dialog.getToggleState()); 178 return true; 179 } 180 181 /** 182 * Initializes for the constructor. 183 */ initAction()184 private void initAction(){ 185 setId(ID); 186 setToolTipText(IDEWorkbenchMessages.CloseUnrelatedProjectsAction_toolTip); 187 PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IIDEHelpContextIds.CLOSE_UNRELATED_PROJECTS_ACTION); 188 } 189 190 @Override clearCache()191 protected void clearCache() { 192 super.clearCache(); 193 oldSelection = Collections.emptyList(); 194 selectionDirty = true; 195 } 196 197 /** 198 * Computes the related projects of the selection. 199 */ computeRelated(List<? extends IResource> selection)200 private List<IResource> computeRelated(List<? extends IResource> selection) { 201 if (selection.contains(ResourcesPlugin.getWorkspace().getRoot())) { 202 return new ArrayList<>(); 203 } 204 //build the connected component set for all projects in the workspace 205 DisjointSet<IProject> set = buildConnectedComponents(ResourcesPlugin.getWorkspace().getRoot().getProjects()); 206 //remove the connected components that the selected projects are in 207 for (IResource resource : selection) { 208 IProject project = resource.getProject(); 209 if (project != null) { 210 set.removeSet(project); 211 } 212 } 213 //the remainder of the projects in the disjoint set are unrelated to the selection 214 List<IResource> projects = new ArrayList<>(); 215 set.toList(projects); 216 return projects; 217 } 218 219 @Override getSelectedResources()220 protected List<? extends IResource> getSelectedResources() { 221 if (selectionDirty) { 222 List<? extends IResource> newSelection = super.getSelectedResources(); 223 if (!oldSelection.equals(newSelection)) { 224 oldSelection = newSelection; 225 projectsToClose = computeRelated(newSelection); 226 } 227 selectionDirty = false; 228 } 229 return projectsToClose; 230 } 231 232 /** 233 * Handles a resource changed event by updating the enablement 234 * when projects change. 235 * <p> 236 * This method overrides the super-type implementation to update 237 * the selection when the open state or description of any project changes. 238 */ 239 @Override resourceChanged(IResourceChangeEvent event)240 public void resourceChanged(IResourceChangeEvent event) { 241 // don't bother looking at delta if selection not applicable 242 if (selectionIsOfType(IResource.PROJECT)) { 243 IResourceDelta delta = event.getDelta(); 244 if (delta != null) { 245 IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED); 246 for (IResourceDelta projDelta : projDeltas) { 247 //changing either the description or the open state can affect enablement 248 if ((projDelta.getFlags() & (IResourceDelta.OPEN | IResourceDelta.DESCRIPTION)) != 0) { 249 selectionChanged(getStructuredSelection()); 250 return; 251 } 252 } 253 } 254 } 255 } 256 } 257