1 /*******************************************************************************
2  * Copyright (c) 2010, 2016 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  *     Serge Beauchamp (Freescale Semiconductor) - initial API and implementation
13  *     Mickael Istria (Red Hat Inc.) - Bug 486901
14  *******************************************************************************/
15 package org.eclipse.ui.ide.dialogs;
16 
17 import java.io.File;
18 
19 import org.eclipse.core.resources.IContainer;
20 import org.eclipse.core.resources.IResource;
21 import org.eclipse.jface.dialogs.IDialogConstants;
22 import org.eclipse.jface.dialogs.TrayDialog;
23 import org.eclipse.jface.preference.IPreferenceStore;
24 import org.eclipse.swt.SWT;
25 import org.eclipse.swt.dnd.DND;
26 import org.eclipse.swt.events.SelectionAdapter;
27 import org.eclipse.swt.events.SelectionEvent;
28 import org.eclipse.swt.events.SelectionListener;
29 import org.eclipse.swt.layout.GridData;
30 import org.eclipse.swt.layout.GridLayout;
31 import org.eclipse.swt.widgets.Button;
32 import org.eclipse.swt.widgets.Composite;
33 import org.eclipse.swt.widgets.Control;
34 import org.eclipse.swt.widgets.Label;
35 import org.eclipse.swt.widgets.Link;
36 import org.eclipse.swt.widgets.Shell;
37 import org.eclipse.ui.PlatformUI;
38 import org.eclipse.ui.dialogs.PreferencesUtil;
39 import org.eclipse.ui.internal.ide.IDEInternalPreferences;
40 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
41 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
42 import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
43 import org.eclipse.ui.internal.ide.dialogs.LinkedResourcesPreferencePage;
44 import org.eclipse.ui.internal.ide.dialogs.RelativePathVariableGroup;
45 
46 
47 /**
48  * Dialog to let the user customise how files and resources are created in a project
49  * hierarchy after the user drag and drop items on a workspace container.
50  *
51  * Files and folders can be created either by copying the source objects, creating
52  * linked resources, and/or creating virtual folders.
53  * @noextend This class is not intended to be subclassed by clients.
54  * @since 3.6
55  *
56  */
57 public class ImportTypeDialog extends TrayDialog {
58 
59 	/**
60 	 * Copy the files and folders to the destination
61 	 */
62 	public static final int IMPORT_COPY 			= 1;
63 	/**
64 	 * Import only files
65 	 */
66 	public static final int IMPORT_FILES_ONLY		= 16;
67 	/**
68 	 * Create linked resources for each file and folder
69 	 */
70 	public static final int IMPORT_LINK 			= 4;
71 	/**
72 	 * Move the files and folders to the destination
73 	 */
74 	public static final int IMPORT_MOVE 			= 8;
75 	/**
76 	 * Do not perform an import operation
77 	 */
78 	public static final int IMPORT_NONE 			= 0;
79 	/**
80 	 * Recreate the file and folder hierarchy using groups and links
81 	 */
82 	public static final int IMPORT_VIRTUAL_FOLDERS_AND_LINKS = 2;
83 
84 	private Button copyButton = null;
85 
86 	private int currentSelection;
87 
88 	private Button linkButton = null;
89 
90 	private Button moveButton = null;
91 
92 	private int operationMask;
93 	private String preferredVariable;
94 	private IResource receivingResource = null;
95 	private Button shadowCopyButton = null;
96 	private String variable = null;
97 
98 	private RelativePathVariableGroup relativePathVariableGroup;
99 
100 	/**
101 	 * Creates the Import Type Dialog when resources are dragged and dropped from an Eclipse
102 	 * view.
103 	 *
104 	 * @param shell
105 	 * 			the parent Shell
106 	 * @param dropOperation
107 	 * 		The dropOperation that was used by the user
108 	 * @param sources
109 	 * 		The list of resources that were dragged
110 	 * @param target
111 	 * 		The target container onto which the resources were dropped
112 	 */
ImportTypeDialog(Shell shell, int dropOperation, IResource[] sources, IContainer target)113 	public ImportTypeDialog(Shell shell, int dropOperation,
114 			IResource[] sources, IContainer target) {
115 		this(shell, selectAppropriateMask(dropOperation, sources, target), RelativePathVariableGroup.getPreferredVariable(sources, target));
116 	}
117 
118 	/**
119 	 * Creates the Import Type Dialog when files are dragged and dropped from the
120 	 * operating system's shell (Windows Explorer on Windows Platform, for example).
121 	 *
122 	 * @param shell
123 	 * 			the parent Shell
124 	 * @param dropOperation
125 	 * 		The dropOperation that was used by the user
126 	 * @param names
127 	 * 		The list of files that were dragged
128 	 * @param target
129 	 * 		The target container onto which the files were dropped
130 	 */
ImportTypeDialog(Shell shell, int dropOperation, String[] names, IContainer target)131 	public ImportTypeDialog(Shell shell, int dropOperation, String[] names, IContainer target) {
132 		this(shell, selectAppropriateMask(dropOperation, names, target), RelativePathVariableGroup.getPreferredVariable(names, target));
133 	}
134 
135 	/**
136 	 * @param parentShell
137 	 * @param operationMask
138 	 */
ImportTypeDialog(Shell parentShell, int operationMask, String preferredVariable)139 	private ImportTypeDialog(Shell parentShell, int operationMask, String preferredVariable) {
140 		super(parentShell);
141 
142 		this.preferredVariable = preferredVariable;
143 		this.operationMask = operationMask;
144 		currentSelection = 0;
145 		String tmp = readContextPreference(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_TYPE);
146 		if (tmp.length() > 0)
147 			currentSelection = Integer.parseInt(tmp);
148 		currentSelection = currentSelection & operationMask;
149 		if (currentSelection == 0) {
150 			if (hasFlag(IMPORT_COPY))
151 				currentSelection = IMPORT_COPY;
152 			else
153 				currentSelection = IMPORT_MOVE;
154 		}
155 
156 		IPreferenceStore store = IDEWorkbenchPlugin.getDefault().getPreferenceStore();
157 		if (store.getBoolean(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_RELATIVE))
158 			variable = preferredVariable;
159 	}
160 
161 	@Override
close()162 	public boolean close() {
163 		return super.close();
164 	}
165 
166 	/**
167 	 * Get the user selection from the dialog.
168 	 * @return The current selection (one of IMPORT_COPY, IMPORT_VIRTUAL_FOLDERS_AND_LINKS, IMPORT_LINK and IMPORT_MOVE)
169 	 */
getSelection()170 	public int getSelection() {
171 		return currentSelection;
172 	}
173 
174 	/**
175 	 * Get the selected variable if the selection is either IMPORT_VIRTUAL_FOLDERS_AND_LINKS or IMPORT_LINK
176 	 * @return The currently selected variable, or AUTOMATIC or ABSOLUTE_PATH
177 	 */
getVariable()178 	public String getVariable() {
179 		return variable;
180 	}
181 
182 	/** Set the project that is the destination of the import operation
183 	 * @param resource the resource
184 	 */
setResource(IResource resource)185 	public void setResource(IResource resource) {
186 		receivingResource = resource;
187 	}
188 
hasFlag(int flag)189 	private boolean hasFlag(int flag) {
190 		return (operationMask & flag) != 0;
191 	}
192 
193 	// the format of the context is operationMask,value:operationMask,value:operationMask,value
readContextPreference(String key)194 	private String readContextPreference(String key) {
195 		String value = IDEWorkbenchPlugin.getDefault().getPreferenceStore().getString(key);
196 		for (String keyPair : value.split(":")) { //$NON-NLS-1$
197 			String [] element = keyPair.split(","); //$NON-NLS-1$
198 			if (element.length == 2) {
199 				if (element[0].equals(Integer.toString(operationMask)))
200 					return element[1];
201 			}
202 		}
203 		return ""; //$NON-NLS-1$
204 	}
205 
refreshSelection()206 	private void refreshSelection() {
207 		if (copyButton != null)
208 			copyButton.setSelection(currentSelection == IMPORT_COPY);
209 		if (shadowCopyButton != null)
210 			shadowCopyButton.setSelection(currentSelection == IMPORT_VIRTUAL_FOLDERS_AND_LINKS);
211 		if (linkButton != null)
212 			linkButton.setSelection(currentSelection == IMPORT_LINK);
213 		if (moveButton != null)
214 			moveButton.setSelection(currentSelection == IMPORT_MOVE);
215 		if (relativePathVariableGroup != null) {
216 			relativePathVariableGroup.setEnabled((currentSelection & (IMPORT_VIRTUAL_FOLDERS_AND_LINKS | IMPORT_LINK)) != 0);
217 		}
218 	}
219 
writeContextPreference(String key, String value)220 	private void writeContextPreference(String key, String value) {
221 		String oldValue = IDEWorkbenchPlugin.getDefault().getPreferenceStore().getString(key);
222 		StringBuilder buffer = new StringBuilder();
223 		String [] keyPairs = oldValue.split(":"); //$NON-NLS-1$
224 		boolean found = false;
225 		for (int i = 0; i < keyPairs.length; i++) {
226 			if (i > 0)
227 				buffer.append(":"); //$NON-NLS-1$
228 			String [] element = keyPairs[i].split(","); //$NON-NLS-1$
229 			if (element.length == 2) {
230 				if (element[0].equals(Integer.toString(operationMask))) {
231 					buffer.append(element[0] + "," + value); //$NON-NLS-1$
232 					found = true;
233 				}
234 				else
235 					buffer.append(keyPairs[i]);
236 			}
237 		}
238 		if (!found) {
239 			if (buffer.length() > 0)
240 				buffer.append(":"); //$NON-NLS-1$
241 			buffer.append(operationMask + "," + value); //$NON-NLS-1$
242 		}
243 		String newValue = buffer.toString();
244 		IDEWorkbenchPlugin.getDefault().getPreferenceStore().setValue(key, newValue);
245 	}
246 
247 	@Override
buttonPressed(int buttonId)248 	protected void buttonPressed(int buttonId) {
249 		if (buttonId == IDialogConstants.OK_ID) {
250 			writeContextPreference(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_TYPE, Integer.toString(currentSelection));
251 
252 			IPreferenceStore store = IDEWorkbenchPlugin.getDefault().getPreferenceStore();
253 			store.putValue(IDEInternalPreferences.IMPORT_FILES_AND_FOLDERS_RELATIVE, Boolean.toString(variable != null));
254 		}
255 		super.buttonPressed(buttonId);
256 	}
257 
258 	@Override
configureShell(Shell shell)259 	protected void configureShell(Shell shell) {
260 		super.configureShell(shell);
261 		String title = (operationMask & IMPORT_FILES_ONLY) != 0 ? IDEWorkbenchMessages.ImportTypeDialog_titleFilesOnly:
262 			IDEWorkbenchMessages.ImportTypeDialog_title;
263 		shell.setText(title);
264 		PlatformUI.getWorkbench().getHelpSystem().setHelp(shell,
265 				IIDEHelpContextIds.IMPORT_TYPE_DIALOG);
266 	}
267 
268 	@Override
createDialogArea(Composite parent)269 	protected Control createDialogArea(Composite parent) {
270 		boolean linkIsOnlyChoice = hasFlag(IMPORT_LINK) && !(hasFlag(IMPORT_COPY | IMPORT_MOVE) || (hasFlag(IMPORT_VIRTUAL_FOLDERS_AND_LINKS) && !hasFlag(IMPORT_FILES_ONLY)));
271 
272 		if (!linkIsOnlyChoice)
273 			createMessageArea(parent);
274 		Composite composite = new Composite(parent, 0);
275 		GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
276 		composite.setLayoutData(gridData);
277 		composite.setFont(parent.getFont());
278 
279 
280 		GridLayout layout = new GridLayout();
281 		layout.numColumns = 1;
282 		layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
283 		layout.verticalSpacing= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
284 		layout.horizontalSpacing= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
285 
286 		int indent= linkIsOnlyChoice ? 0: convertWidthInCharsToPixels(3);
287 
288 		layout.marginWidth += indent;
289 		composite.setLayout(layout);
290 		SelectionListener listener = new SelectionListener() {
291 			@Override
292 			public void widgetDefaultSelected(SelectionEvent e) {
293 				currentSelection = ((Integer) e.widget.getData()).intValue();
294 				refreshSelection();
295 			}
296 
297 			@Override
298 			public void widgetSelected(SelectionEvent e) {
299 				currentSelection = ((Integer) e.widget.getData()).intValue();
300 				refreshSelection();
301 			}
302 		};
303 
304 		if (hasFlag(IMPORT_COPY)) {
305 			copyButton = new Button(composite, SWT.RADIO);
306 			copyButton.setText(hasFlag(IMPORT_FILES_ONLY) ? IDEWorkbenchMessages.ImportTypeDialog_copyFiles: IDEWorkbenchMessages.ImportTypeDialog_copyFilesAndDirectories);
307 			gridData = new GridData(GridData.FILL_HORIZONTAL);
308 			copyButton.setLayoutData(gridData);
309 			copyButton.setData(IMPORT_COPY);
310 			copyButton.addSelectionListener(listener);
311 			copyButton.setFont(parent.getFont());
312 		}
313 
314 		if (hasFlag(IMPORT_MOVE)) {
315 			moveButton = new Button(composite, SWT.RADIO);
316 			moveButton.setText(hasFlag(IMPORT_FILES_ONLY) ? IDEWorkbenchMessages.ImportTypeDialog_moveFiles:IDEWorkbenchMessages.ImportTypeDialog_moveFilesAndDirectories);
317 			gridData = new GridData(GridData.FILL_HORIZONTAL);
318 			moveButton.setLayoutData(gridData);
319 			moveButton.setData(IMPORT_MOVE);
320 			moveButton.addSelectionListener(listener);
321 			moveButton.setFont(parent.getFont());
322 		}
323 
324 		if (hasFlag(IMPORT_LINK) && !linkIsOnlyChoice) {
325 			linkButton = new Button(composite, SWT.RADIO);
326 			linkButton.setText(hasFlag(IMPORT_FILES_ONLY) ? IDEWorkbenchMessages.ImportTypeDialog_linkFiles:IDEWorkbenchMessages.ImportTypeDialog_createLinks);
327 			gridData = new GridData(GridData.FILL_HORIZONTAL);
328 			linkButton.setLayoutData(gridData);
329 			linkButton.setData(IMPORT_LINK);
330 			linkButton.addSelectionListener(listener);
331 			linkButton.setFont(parent.getFont());
332 		}
333 
334 		if (hasFlag(IMPORT_VIRTUAL_FOLDERS_AND_LINKS) && !hasFlag(IMPORT_FILES_ONLY)) {
335 			shadowCopyButton = new Button(composite, SWT.RADIO);
336 			shadowCopyButton.setText(IDEWorkbenchMessages.ImportTypeDialog_recreateFilesAndDirectories);
337 			gridData = new GridData(GridData.FILL_HORIZONTAL);
338 			shadowCopyButton.setLayoutData(gridData);
339 			shadowCopyButton.setData(IMPORT_VIRTUAL_FOLDERS_AND_LINKS);
340 			shadowCopyButton.addSelectionListener(listener);
341 			shadowCopyButton.setFont(parent.getFont());
342 		}
343 
344 		if (hasFlag(IMPORT_VIRTUAL_FOLDERS_AND_LINKS | IMPORT_LINK)) {
345 			relativePathVariableGroup = new RelativePathVariableGroup(new RelativePathVariableGroup.IModel() {
346 				@Override
347 				public IResource getResource() {
348 					return receivingResource;
349 				}
350 				@Override
351 				public void setVariable(String string) {
352 					variable = string;
353 				}
354 				@Override
355 				public String getVariable() {
356 					return variable;
357 				}
358 			});
359 
360 			int groupIndent = 0;
361 
362 			if (!linkIsOnlyChoice) {
363 				Button tmp = new Button(composite, SWT.CHECK);
364 				tmp.setText("."); //$NON-NLS-1$
365 				groupIndent = tmp.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
366 				tmp.dispose();
367 
368 				Label tmpLabel = new Label(composite, SWT.NONE);
369 				tmpLabel.setText("."); //$NON-NLS-1$
370 				groupIndent -= tmpLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
371 				tmpLabel.dispose();
372 			}
373 
374 			Composite variableGroup = new Composite(composite, 0);
375 			gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
376 			gridData.horizontalIndent = groupIndent;
377 			variableGroup.setLayoutData(gridData);
378 			variableGroup.setFont(parent.getFont());
379 
380 			layout = new GridLayout();
381 			layout.numColumns = 2;
382 			layout.marginWidth= 0;
383 			variableGroup.setLayout(layout);
384 
385 			relativePathVariableGroup.createContents(variableGroup);
386 			relativePathVariableGroup.setSelection(variable != null);
387 			if (variable != null)
388 				relativePathVariableGroup.selectVariable(variable);
389 			else
390 				relativePathVariableGroup.selectVariable(preferredVariable);
391 		}
392 
393 		if (linkIsOnlyChoice) {
394 			currentSelection = IMPORT_LINK;
395 			parent.getShell().setText(IDEWorkbenchMessages.ImportTypeDialog_titleFilesLinking);
396 		}
397 		createLinkControl(parent);
398 		refreshSelection();
399 		return composite;
400 	}
401 
createLinkControl(Composite composite)402 	private Control createLinkControl(Composite composite) {
403 		Link link= new Link(composite, SWT.WRAP | SWT.RIGHT);
404 		link.setText(IDEWorkbenchMessages.ImportTypeDialog_configureSettings);
405 		link.addSelectionListener(new SelectionAdapter() {
406 			@Override
407 			public void widgetSelected(SelectionEvent e) {
408 				openSettingsPage();
409 			}
410 		});
411 		GridData gridData= new GridData(GridData.FILL, GridData.CENTER, true, false);
412 		gridData.horizontalIndent = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
413 		link.setLayoutData(gridData);
414 		link.setFont(composite.getFont());
415 
416 		return link;
417 	}
418 
openSettingsPage()419 	protected void openSettingsPage() {
420 		String prefID = LinkedResourcesPreferencePage.PREF_ID;
421 		PreferencesUtil.createPreferenceDialogOn(getShell(), prefID, new String[] {prefID}, null).open();
422 	}
423 
createMessageArea(Composite parent)424 	protected Control createMessageArea(Composite parent) {
425 		Composite composite = new Composite(parent, 0);
426 		GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
427 		composite.setLayoutData(gridData);
428 		composite.setFont(parent.getFont());
429 
430 
431 		GridLayout layout = new GridLayout();
432 		layout.numColumns = 1;
433 		layout.marginTop= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
434 		layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
435 		composite.setLayout(layout);
436 
437 		String message = (operationMask & IMPORT_FILES_ONLY) != 0 ? IDEWorkbenchMessages.ImportTypeDialog_questionFilesOnly:
438 			IDEWorkbenchMessages.ImportTypeDialog_question;
439 
440 		// create message
441 		if (message != null) {
442 			Label messageLabel = new Label(composite, SWT.WRAP);
443 			messageLabel.setFont(parent.getFont());
444 			messageLabel.setText(message);
445 			gridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
446 			messageLabel.setLayoutData(gridData);
447 		}
448 		return composite;
449 	}
450 
451 	/**
452 	 * @param resources
453 	 * 		The list of items that were dragged
454 	 * @return true if a set of paths are files only or a mix of files and folders, false otherwise
455 	 */
areOnlyFiles(IResource[] resources)456 	private static boolean areOnlyFiles(IResource[] resources) {
457 		for (IResource resource : resources) {
458 			if (resource.getType() != IResource.FILE)
459 				return false;
460 		}
461 		return true;
462 	}
463 
464 	/**
465 	 * @param names
466 	 * 		The list of items that were dragged
467 	 * @return true if a set of paths are files only or a mix of files and folders, false otherwise
468 	 */
areOnlyFiles(String[] names)469 	private static boolean areOnlyFiles(String[] names) {
470 		for (String name : names) {
471 			File file = new File(name);
472 			if (file.exists() && !file.isFile())
473 				return false;
474 		}
475 		return true;
476 	}
477 
478 	/**
479 	 * Select the most appropriate mode that should be used for the dialog given
480 	 * the items dropped on the container, the container type, and the drop operation.
481 	 *
482 	 * @param dropOperation
483 	 * @param resources
484 	 * 		The list of items that were dragged
485 	 * @param target
486 	 * 		The target container onto which the items were dropped
487 	 * @return the appropriate import mask given the files dropped on the target
488 	 */
selectAppropriateMask(int dropOperation, IResource[] resources, IContainer target)489 	private static int selectAppropriateMask(int dropOperation, IResource[] resources, IContainer target) {
490 		int mask = ImportTypeDialog.IMPORT_VIRTUAL_FOLDERS_AND_LINKS | ImportTypeDialog.IMPORT_LINK;
491 		if (!target.isVirtual() && (dropOperation != DND.DROP_LINK))
492 			mask |= ImportTypeDialog.IMPORT_COPY;
493 		if (areOnlyFiles(resources))
494 			mask |= ImportTypeDialog.IMPORT_FILES_ONLY;
495 		return mask;
496 	}
497 
498 	/**
499 	 * Select the most appropriate mode that should be used for the dialog given
500 	 * the items dropped on the container, the container type, and the drop operation.
501 	 *
502 	 * @param dropOperation
503 	 * @param names
504 	 * 		The list of items that were dragged
505 	 * @param target
506 	 * 		The target container onto which the items were dropped
507 	 * @return the appropriate import mask given the files dropped on the target
508 	 */
selectAppropriateMask(int dropOperation, String[] names, IContainer target)509 	private static int selectAppropriateMask(int dropOperation, String[] names, IContainer target) {
510 		int mask = ImportTypeDialog.IMPORT_VIRTUAL_FOLDERS_AND_LINKS | ImportTypeDialog.IMPORT_LINK;
511 		if (!target.isVirtual() && (dropOperation != DND.DROP_LINK))
512 			mask |= ImportTypeDialog.IMPORT_COPY;
513 		if (areOnlyFiles(names))
514 			mask |= ImportTypeDialog.IMPORT_FILES_ONLY;
515 		return mask;
516 	}
517 }
518