1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 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  *     Igor Fedorenko <igorfie@yahoo.com> -
14  *     		Fix for Bug 136921 [IDE] New File dialog locks for 20 seconds
15  *******************************************************************************/
16 package org.eclipse.ui.internal.ide.misc;
17 
18 import java.util.ArrayList;
19 import java.util.List;
20 
21 import org.eclipse.core.resources.IContainer;
22 import org.eclipse.core.resources.ResourcesPlugin;
23 import org.eclipse.core.runtime.IPath;
24 import org.eclipse.core.runtime.Path;
25 import org.eclipse.equinox.bidi.StructuredTextTypeHandlerFactory;
26 import org.eclipse.jface.dialogs.Dialog;
27 import org.eclipse.jface.util.BidiUtils;
28 import org.eclipse.jface.viewers.ISelection;
29 import org.eclipse.jface.viewers.IStructuredSelection;
30 import org.eclipse.jface.viewers.StructuredSelection;
31 import org.eclipse.jface.viewers.TreeViewer;
32 import org.eclipse.jface.viewers.ViewerComparator;
33 import org.eclipse.osgi.util.TextProcessor;
34 import org.eclipse.swt.SWT;
35 import org.eclipse.swt.layout.GridData;
36 import org.eclipse.swt.layout.GridLayout;
37 import org.eclipse.swt.widgets.Composite;
38 import org.eclipse.swt.widgets.Event;
39 import org.eclipse.swt.widgets.Label;
40 import org.eclipse.swt.widgets.Listener;
41 import org.eclipse.swt.widgets.Text;
42 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
43 import org.eclipse.ui.model.WorkbenchLabelProvider;
44 import org.eclipse.ui.part.DrillDownComposite;
45 
46 /**
47  * Workbench-level composite for choosing a container.
48  */
49 public class ContainerSelectionGroup extends Composite {
50 	// The listener to notify of events
51 	private Listener listener;
52 
53 	// Enable user to type in new container name
54 	private boolean allowNewContainerName = true;
55 
56 	// show all projects by default
57 	private boolean showClosedProjects = true;
58 
59 	// Last selection made by user
60 	private IContainer selectedContainer;
61 
62 	// handle on parts
63 	private Text containerNameField;
64 
65 	TreeViewer treeViewer;
66 
67 	// the message to display at the top of this dialog
68 	private static final String DEFAULT_MSG_NEW_ALLOWED = IDEWorkbenchMessages.ContainerGroup_message;
69 
70 	private static final String DEFAULT_MSG_SELECT_ONLY = IDEWorkbenchMessages.ContainerGroup_selectFolder;
71 
72 	// sizing constants
73 	private static final int SIZING_SELECTION_PANE_WIDTH = 320;
74 
75 	private static final int SIZING_SELECTION_PANE_HEIGHT = 300;
76 
77 	/**
78 	 * Creates a new instance of the widget.
79 	 *
80 	 * @param parent
81 	 *            The parent widget of the group.
82 	 * @param listener
83 	 *            A listener to forward events to. Can be null if no listener is
84 	 *            required.
85 	 * @param allowNewContainerName
86 	 *            Enable the user to type in a new container name instead of
87 	 *            just selecting from the existing ones.
88 	 */
ContainerSelectionGroup(Composite parent, Listener listener, boolean allowNewContainerName)89 	public ContainerSelectionGroup(Composite parent, Listener listener,
90 			boolean allowNewContainerName) {
91 		this(parent, listener, allowNewContainerName, null);
92 	}
93 
94 	/**
95 	 * Creates a new instance of the widget.
96 	 *
97 	 * @param parent
98 	 *            The parent widget of the group.
99 	 * @param listener
100 	 *            A listener to forward events to. Can be null if no listener is
101 	 *            required.
102 	 * @param allowNewContainerName
103 	 *            Enable the user to type in a new container name instead of
104 	 *            just selecting from the existing ones.
105 	 * @param message
106 	 *            The text to present to the user.
107 	 */
ContainerSelectionGroup(Composite parent, Listener listener, boolean allowNewContainerName, String message)108 	public ContainerSelectionGroup(Composite parent, Listener listener,
109 			boolean allowNewContainerName, String message) {
110 		this(parent, listener, allowNewContainerName, message, true);
111 	}
112 
113 	/**
114 	 * Creates a new instance of the widget.
115 	 *
116 	 * @param parent
117 	 *            The parent widget of the group.
118 	 * @param listener
119 	 *            A listener to forward events to. Can be null if no listener is
120 	 *            required.
121 	 * @param allowNewContainerName
122 	 *            Enable the user to type in a new container name instead of
123 	 *            just selecting from the existing ones.
124 	 * @param message
125 	 *            The text to present to the user.
126 	 * @param showClosedProjects
127 	 *            Whether or not to show closed projects.
128 	 */
ContainerSelectionGroup(Composite parent, Listener listener, boolean allowNewContainerName, String message, boolean showClosedProjects)129 	public ContainerSelectionGroup(Composite parent, Listener listener,
130 			boolean allowNewContainerName, String message,
131 			boolean showClosedProjects) {
132 		this(parent, listener, allowNewContainerName, message,
133 				showClosedProjects, SIZING_SELECTION_PANE_HEIGHT,
134 				SIZING_SELECTION_PANE_WIDTH);
135 	}
136 
137 	/**
138 	 * Creates a new instance of the widget.
139 	 *
140 	 * @param parent
141 	 *            The parent widget of the group.
142 	 * @param listener
143 	 *            A listener to forward events to. Can be null if no listener is
144 	 *            required.
145 	 * @param allowNewContainerName
146 	 *            Enable the user to type in a new container name instead of
147 	 *            just selecting from the existing ones.
148 	 * @param message
149 	 *            The text to present to the user.
150 	 * @param showClosedProjects
151 	 *            Whether or not to show closed projects.
152 	 * @param heightHint
153 	 *            height hint for the drill down composite
154 	 * @param widthHint
155 	 *            width hint for the drill down composite
156 	 */
ContainerSelectionGroup(Composite parent, Listener listener, boolean allowNewContainerName, String message, boolean showClosedProjects, int heightHint, int widthHint)157 	public ContainerSelectionGroup(Composite parent, Listener listener,
158 			boolean allowNewContainerName, String message,
159 			boolean showClosedProjects, int heightHint, int widthHint) {
160 		super(parent, SWT.NONE);
161 		this.listener = listener;
162 		this.allowNewContainerName = allowNewContainerName;
163 		this.showClosedProjects = showClosedProjects;
164 		if (message != null) {
165 			createContents(message, heightHint, widthHint);
166 		} else if (allowNewContainerName) {
167 			createContents(DEFAULT_MSG_NEW_ALLOWED, heightHint, widthHint);
168 		} else {
169 			createContents(DEFAULT_MSG_SELECT_ONLY, heightHint, widthHint);
170 		}
171 	}
172 
173 	/**
174 	 * The container selection has changed in the tree view. Update the
175 	 * container name field value and notify all listeners.
176 	 *
177 	 * @param container
178 	 *            The container that changed
179 	 */
containerSelectionChanged(IContainer container)180 	public void containerSelectionChanged(IContainer container) {
181 		selectedContainer = container;
182 
183 		if (allowNewContainerName) {
184 			if (container == null) {
185 				containerNameField.setText("");//$NON-NLS-1$
186 			} else {
187 				String text = TextProcessor.process(container.getFullPath()
188 						.makeRelative().toString());
189 				containerNameField.setText(text);
190 				containerNameField.setToolTipText(text);
191 			}
192 		}
193 
194 		// fire an event so the parent can update its controls
195 		if (listener != null) {
196 			Event changeEvent = new Event();
197 			changeEvent.type = SWT.Selection;
198 			changeEvent.widget = this;
199 			listener.handleEvent(changeEvent);
200 		}
201 	}
202 
203 	/**
204 	 * Creates the contents of the composite.
205 	 *
206 	 * @param message
207 	 */
createContents(String message)208 	public void createContents(String message) {
209 		createContents(message, SIZING_SELECTION_PANE_HEIGHT,
210 				SIZING_SELECTION_PANE_WIDTH);
211 	}
212 
213 	/**
214 	 * Creates the contents of the composite.
215 	 *
216 	 * @param message
217 	 * @param heightHint
218 	 * @param widthHint
219 	 */
createContents(String message, int heightHint, int widthHint)220 	public void createContents(String message, int heightHint, int widthHint) {
221 		GridLayout layout = new GridLayout();
222 		layout.marginWidth = 0;
223 		setLayout(layout);
224 		setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
225 
226 		Label label = new Label(this, SWT.WRAP);
227 		label.setText(message);
228 		label.setFont(this.getFont());
229 
230 		if (allowNewContainerName) {
231 			containerNameField = new Text(this, SWT.SINGLE | SWT.BORDER);
232 			GridData gd = new GridData(GridData.FILL_HORIZONTAL);
233 			gd.widthHint = widthHint;
234 			containerNameField.setLayoutData(gd);
235 			containerNameField.addListener(SWT.Modify, listener);
236 			containerNameField.setFont(this.getFont());
237 			BidiUtils.applyBidiProcessing(containerNameField, StructuredTextTypeHandlerFactory.FILE);
238 		} else {
239 			// filler...
240 			new Label(this, SWT.NONE);
241 		}
242 
243 		createTreeViewer(heightHint);
244 		Dialog.applyDialogFont(this);
245 	}
246 
247 	/**
248 	 * Returns a new drill down viewer for this dialog.
249 	 *
250 	 * @param heightHint
251 	 *            height hint for the drill down composite
252 	 */
createTreeViewer(int heightHint)253 	protected void createTreeViewer(int heightHint) {
254 		// Create drill down.
255 		DrillDownComposite drillDown = new DrillDownComposite(this, SWT.BORDER);
256 		GridData spec = new GridData(SWT.FILL, SWT.FILL, true, true);
257 		spec.widthHint = SIZING_SELECTION_PANE_WIDTH;
258 		spec.heightHint = heightHint;
259 		drillDown.setLayoutData(spec);
260 
261 		// Create tree viewer inside drill down.
262 		treeViewer = new TreeViewer(drillDown, SWT.NONE);
263 		drillDown.setChildTree(treeViewer);
264 		ContainerContentProvider cp = new ContainerContentProvider();
265 		cp.showClosedProjects(showClosedProjects);
266 		treeViewer.setContentProvider(cp);
267 		treeViewer.setLabelProvider(WorkbenchLabelProvider
268 				.getDecoratingWorkbenchLabelProvider());
269 		treeViewer.setComparator(new ViewerComparator());
270 		treeViewer.setUseHashlookup(true);
271 		treeViewer.addSelectionChangedListener(event -> {
272 			IStructuredSelection selection = event.getStructuredSelection();
273 			containerSelectionChanged((IContainer) selection
274 					.getFirstElement()); // allow null
275 		});
276 		treeViewer.addDoubleClickListener(event -> {
277 			ISelection selection = event.getSelection();
278 			if (selection instanceof IStructuredSelection) {
279 				Object item = ((IStructuredSelection) selection)
280 						.getFirstElement();
281 				if (item == null) {
282 					return;
283 				}
284 				if (treeViewer.getExpandedState(item)) {
285 					treeViewer.collapseToLevel(item, 1);
286 				} else {
287 					treeViewer.expandToLevel(item, 1);
288 				}
289 			}
290 		});
291 
292 		// This has to be done after the viewer has been laid out
293 		treeViewer.setInput(ResourcesPlugin.getWorkspace());
294 	}
295 
296 	/**
297 	 * Returns the currently entered container name. Null if the field is empty.
298 	 * Note that the container may not exist yet if the user entered a new
299 	 * container name in the field.
300 	 *
301 	 * @return IPath
302 	 */
getContainerFullPath()303 	public IPath getContainerFullPath() {
304 		if (allowNewContainerName) {
305 			String pathName = containerNameField.getText();
306 			if (pathName == null || pathName.length() < 1) {
307 				return null;
308 			}
309 			// The user may not have made this absolute so do it for them
310 			return (new Path(TextProcessor.deprocess(pathName))).makeAbsolute();
311 
312 		}
313 		if (selectedContainer == null)
314 			return null;
315 		return selectedContainer.getFullPath();
316 
317 	}
318 
319 	/**
320 	 * Gives focus to one of the widgets in the group, as determined by the
321 	 * group.
322 	 */
setInitialFocus()323 	public void setInitialFocus() {
324 		if (allowNewContainerName) {
325 			containerNameField.setFocus();
326 		} else {
327 			treeViewer.getTree().setFocus();
328 		}
329 	}
330 
331 	/**
332 	 * Sets the selected existing container.
333 	 *
334 	 * @param container
335 	 */
setSelectedContainer(IContainer container)336 	public void setSelectedContainer(IContainer container) {
337 		selectedContainer = container;
338 
339 		// expand to and select the specified container
340 		List<IContainer> itemsToExpand = new ArrayList<>();
341 		IContainer parent = container.getParent();
342 		while (parent != null) {
343 			itemsToExpand.add(0, parent);
344 			parent = parent.getParent();
345 		}
346 		treeViewer.setExpandedElements(itemsToExpand.toArray());
347 		treeViewer.setSelection(new StructuredSelection(container), true);
348 	}
349 }
350