1 /******************************************************************************* 2 * Copyright (c) 2000, 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 *******************************************************************************/ 14 package org.eclipse.ui.actions; 15 16 import java.util.Iterator; 17 import java.util.List; 18 19 import org.eclipse.core.resources.IContainer; 20 import org.eclipse.core.resources.IResource; 21 import org.eclipse.core.resources.IWorkspace; 22 import org.eclipse.core.resources.IWorkspaceRoot; 23 import org.eclipse.core.runtime.Assert; 24 import org.eclipse.core.runtime.IPath; 25 import org.eclipse.jface.viewers.IStructuredSelection; 26 import org.eclipse.jface.window.IShellProvider; 27 import org.eclipse.swt.widgets.Shell; 28 import org.eclipse.ui.PlatformUI; 29 import org.eclipse.ui.dialogs.ContainerSelectionDialog; 30 import org.eclipse.ui.dialogs.ISelectionValidator; 31 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; 32 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; 33 import org.eclipse.ui.internal.ide.IIDEHelpContextIds; 34 35 /** 36 * Standard action for copying the currently selected resources elsewhere 37 * in the workspace. All resources being copied as a group must be siblings. 38 * <p> 39 * This class may be instantiated; it is not intended to be subclassed. 40 * </p> 41 * @noextend This class is not intended to be subclassed by clients. 42 */ 43 public class CopyResourceAction extends SelectionListenerAction implements 44 ISelectionValidator { 45 46 /** 47 * The id of this action. 48 */ 49 public static final String ID = PlatformUI.PLUGIN_ID 50 + ".CopyResourceAction"; //$NON-NLS-1$ 51 52 /** 53 * The IShellProvider in which to show any dialogs. 54 */ 55 protected IShellProvider shellProvider; 56 57 /** 58 * The operation to run. This is created only during the life-cycle of the 59 * run method. 60 */ 61 protected CopyFilesAndFoldersOperation operation; 62 63 private String[] modelProviderIds; 64 65 /** 66 * Returns a new name for a copy of the resource at the given path in the given 67 * workspace. This name could be determined either automatically or by querying 68 * the user. This name will <b>not</b> be verified by the caller, so it must be 69 * valid and unique. 70 * <p> 71 * Note this method is for internal use only. 72 * </p> 73 * 74 * @param originalName the full path of the resource 75 * @param workspace the workspace 76 * @return the new full path for the copy, or <code>null</code> if the resource 77 * should not be copied 78 */ getNewNameFor(IPath originalName, IWorkspace workspace)79 public static IPath getNewNameFor(IPath originalName, IWorkspace workspace) { 80 return CopyFilesAndFoldersOperation.getAutoNewNameFor(originalName, 81 workspace); 82 } 83 84 /** 85 * Creates a new action. 86 * 87 * @param shell the shell for any dialogs 88 * 89 * @deprecated {@link #CopyResourceAction(IShellProvider)} 90 */ 91 @Deprecated CopyResourceAction(Shell shell)92 public CopyResourceAction(Shell shell) { 93 this(shell, IDEWorkbenchMessages.CopyResourceAction_title); 94 } 95 96 /** 97 * Creates a new action 98 * 99 * @param provider the shell for any dialogs 100 * @since 3.4 101 */ CopyResourceAction(IShellProvider provider)102 public CopyResourceAction(IShellProvider provider){ 103 this(provider, IDEWorkbenchMessages.CopyResourceAction_title); 104 } 105 106 /** 107 * Creates a new action with the given text. 108 * 109 * @param shell the shell for any dialogs 110 * @param name the string used as the name for the action, 111 * or <code>null</code> if there is no name 112 * 113 * @deprecated {@link #CopyResourceAction(IShellProvider, String)} 114 */ 115 @Deprecated CopyResourceAction(final Shell shell, String name)116 CopyResourceAction(final Shell shell, String name) { 117 super(name); 118 Assert.isNotNull(shell); 119 shellProvider = () -> shell; 120 initAction(); 121 } 122 123 /** 124 * Creates a new action with the given text 125 * 126 * @param provider the shell for any dialogs 127 * @param name the string used as the name for the action, 128 * or <code>null</code> if there is no name 129 */ CopyResourceAction(IShellProvider provider, String name)130 CopyResourceAction(IShellProvider provider, String name){ 131 super(name); 132 Assert.isNotNull(provider); 133 shellProvider = provider; 134 initAction(); 135 } 136 137 /** 138 * Returns the operation to perform when this action runs. 139 * 140 * @return the operation to perform when this action runs. 141 */ createOperation()142 protected CopyFilesAndFoldersOperation createOperation() { 143 return new CopyFilesAndFoldersOperation(getShell()); 144 } 145 initAction()146 private void initAction(){ 147 setToolTipText(IDEWorkbenchMessages.CopyResourceAction_toolTip); 148 setId(CopyResourceAction.ID); 149 PlatformUI.getWorkbench().getHelpSystem().setHelp(this, 150 IIDEHelpContextIds.COPY_RESOURCE_ACTION); 151 } 152 153 /** 154 * Returns the path of the container to initially select in the container 155 * selection dialog, or <code>null</code> if there is no initial selection 156 * @return The initial container; <code>null</code> if none. 157 */ getInitialContainer()158 IContainer getInitialContainer() { 159 List<? extends IResource> resources = getSelectedResources(); 160 if (resources.size() > 0) { 161 IResource resource = resources.get(0); 162 return resource.getParent(); 163 } 164 return null; 165 } 166 167 /** 168 * Returns an array of resources to use for the operation from 169 * the provided list. 170 * 171 * @param resourceList The list of resources to converted into an array. 172 * @return an array of resources to use for the operation 173 */ getResources(List<? extends IResource> resourceList)174 protected IResource[] getResources(List<? extends IResource> resourceList) { 175 return resourceList.toArray(new IResource[resourceList 176 .size()]); 177 } 178 179 /** 180 * Returns the shell in which to show any dialogs 181 * @return The shell for parenting dialogs; never <code>null</code>. 182 */ getShell()183 Shell getShell() { 184 return shellProvider.getShell(); 185 } 186 187 /** 188 * The <code>CopyResourceAction</code> implementation of this 189 * <code>ISelectionValidator</code> method checks whether the given path 190 * is a good place to copy the selected resources. 191 */ 192 @Override isValid(Object destination)193 public String isValid(Object destination) { 194 IWorkspaceRoot root = IDEWorkbenchPlugin.getPluginWorkspace().getRoot(); 195 IContainer container = (IContainer) root 196 .findMember((IPath) destination); 197 198 if (container != null) { 199 // create a new operation here. 200 // isValid is API and may be called in any context. 201 CopyFilesAndFoldersOperation newOperation = createOperation(); 202 List<? extends IResource> sources = getSelectedResources(); 203 IResource[] resources = sources 204 .toArray(new IResource[sources.size()]); 205 return newOperation.validateDestination(container, resources); 206 } 207 return null; 208 } 209 210 /** 211 * Asks the user for the destination of this action. 212 * 213 * @return the path on an existing or new resource container, or 214 * <code>null</code> if the operation should be abandoned 215 */ queryDestinationResource()216 IPath queryDestinationResource() { 217 // start traversal at root resource, should probably start at a 218 // better location in the tree 219 ContainerSelectionDialog dialog = new ContainerSelectionDialog(shellProvider.getShell(), 220 getInitialContainer(), true, IDEWorkbenchMessages.CopyResourceAction_selectDestination); 221 dialog.setValidator(this); 222 dialog.showClosedProjects(false); 223 dialog.open(); 224 Object[] result = dialog.getResult(); 225 if (result != null && result.length == 1) { 226 return (IPath) result[0]; 227 } 228 return null; 229 } 230 231 @Override run()232 public void run() { 233 try { 234 operation = createOperation(); 235 operation.setModelProviderIds(getModelProviderIds()); 236 237 // WARNING: do not query the selected resources more than once 238 // since the selection may change during the run, 239 // e.g. due to window activation when the prompt dialog is dismissed. 240 // For more details, see Bug 60606 [Navigator] (data loss) Navigator deletes/moves the wrong file 241 List<? extends IResource> sources = getSelectedResources(); 242 243 IPath destination = queryDestinationResource(); 244 if (destination == null) { 245 return; 246 } 247 248 IWorkspaceRoot root = IDEWorkbenchPlugin.getPluginWorkspace() 249 .getRoot(); 250 IContainer container = (IContainer) root.findMember(destination); 251 if (container == null) { 252 return; 253 } 254 255 runOperation(getResources(sources), container); 256 } finally { 257 operation = null; 258 } 259 } 260 261 /** 262 * Runs the operation created in <code>createOperation</code> 263 * 264 * @param resources source resources to pass to the operation 265 * @param destination destination container to pass to the operation 266 */ runOperation(IResource[] resources, IContainer destination)267 protected void runOperation(IResource[] resources, IContainer destination) { 268 operation.copyResources(resources, destination); 269 } 270 271 /** 272 * The <code>CopyResourceAction</code> implementation of this 273 * <code>SelectionListenerAction</code> method enables this action only if 274 * all of the one or more selections are sibling resources which are 275 * local (depth infinity). 276 */ 277 @Override updateSelection(IStructuredSelection selection)278 protected boolean updateSelection(IStructuredSelection selection) { 279 if (!super.updateSelection(selection)) { 280 return false; 281 } 282 if (getSelectedNonResources().size() > 0) { 283 return false; 284 } 285 286 // to enable this command all selected resources must be siblings 287 List<? extends IResource> selectedResources = getSelectedResources(); 288 if (selectedResources.isEmpty()) { 289 return false; 290 } 291 IContainer firstParent = ((IResource) selectedResources.get(0)) 292 .getParent(); 293 if (firstParent == null) { 294 return false; 295 } 296 Iterator<? extends IResource> resourcesEnum = selectedResources.iterator(); 297 while (resourcesEnum.hasNext()) { 298 IResource currentResource = resourcesEnum.next(); 299 if (!currentResource.exists()) { 300 return false; 301 } 302 if (currentResource.getType() == IResource.PROJECT) { 303 return false; 304 } 305 IContainer parent = currentResource.getParent(); 306 if ((parent != null) && (!parent.equals(firstParent))) { 307 return false; 308 } 309 } 310 return true; 311 } 312 313 /** 314 * Returns the model provider ids that are known to the client 315 * that instantiated this operation. 316 * 317 * @return the model provider ids that are known to the client 318 * that instantiated this operation. 319 * @since 3.2 320 */ getModelProviderIds()321 public String[] getModelProviderIds() { 322 return modelProviderIds; 323 } 324 325 /** 326 * Sets the model provider ids that are known to the client 327 * that instantiated this operation. Any potential side effects 328 * reported by these models during validation will be ignored. 329 * 330 * @param modelProviderIds the model providers known to the client 331 * who is using this operation. 332 * @since 3.2 333 */ setModelProviderIds(String[] modelProviderIds)334 public void setModelProviderIds(String[] modelProviderIds) { 335 this.modelProviderIds = modelProviderIds; 336 } 337 } 338