1 /*******************************************************************************
2  * Copyright (c) 2004, 2015 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  *     Tom Hochstein (Freescale) - Bug 409996 - 'Restore Defaults' does not work properly on Project Properties > Resource tab
14  *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 472784, 474273
15  *******************************************************************************/
16 package org.eclipse.ui.ide.dialogs;
17 
18 import org.eclipse.core.resources.IContainer;
19 import org.eclipse.core.resources.IFile;
20 import org.eclipse.core.resources.IProject;
21 import org.eclipse.core.resources.IResource;
22 import org.eclipse.core.resources.IWorkspaceRoot;
23 import org.eclipse.core.resources.ProjectScope;
24 import org.eclipse.core.resources.ResourcesPlugin;
25 import org.eclipse.core.runtime.Assert;
26 import org.eclipse.core.runtime.CoreException;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.Platform;
29 import org.eclipse.core.runtime.Status;
30 import org.eclipse.core.runtime.content.IContentDescription;
31 import org.eclipse.core.runtime.content.IContentType;
32 import org.eclipse.core.runtime.jobs.Job;
33 import org.eclipse.jface.dialogs.DialogPage;
34 import org.eclipse.jface.dialogs.IDialogConstants;
35 import org.eclipse.jface.dialogs.MessageDialog;
36 import org.eclipse.osgi.util.NLS;
37 import org.eclipse.swt.SWT;
38 import org.eclipse.swt.layout.GridData;
39 import org.eclipse.swt.widgets.Button;
40 import org.eclipse.swt.widgets.Composite;
41 import org.eclipse.swt.widgets.Control;
42 import org.eclipse.swt.widgets.Group;
43 import org.eclipse.swt.widgets.Label;
44 import org.eclipse.swt.widgets.Shell;
45 import org.eclipse.ui.WorkbenchEncoding;
46 import org.eclipse.ui.ide.IDEEncoding;
47 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
48 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
49 import org.osgi.service.prefs.BackingStoreException;
50 import org.osgi.service.prefs.Preferences;
51 
52 /**
53  * The ResourceEncodingFieldEditor is a field editor for editing the encoding of
54  * a resource and does not use a preference store.
55  * <p>
56  * This class may be instantiated; it is not intended to be subclassed.
57  * </p>
58  *
59  * @since 3.1
60  */
61 public final class ResourceEncodingFieldEditor extends AbstractEncodingFieldEditor {
62 
63 
64 	private static boolean DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS = ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
65 
66 
67 	/**
68 	 * The resource being edited.
69 	 */
70 	private IResource resource;
71 
72 	private Composite group;
73 
74 	private Button separateDerivedEncodingsButton = null;
75 
76 	/**
77 	 * Creates a new encoding field editor for setting the encoding on the given
78 	 * resource.
79 	 *
80 	 * @param labelText
81 	 *            the label text of the field editor
82 	 * @param parent
83 	 *            the parent of the field editor's control
84 	 * @param charsetResource
85 	 *            must be an <code>IContainer</code> or an <code>IFile</code>.
86 	 *
87 	 * @see org.eclipse.core.resources.IContainer#getDefaultCharset()
88 	 * @see org.eclipse.core.resources.IFile#getCharset()
89 	 */
ResourceEncodingFieldEditor(String labelText, Composite parent, IResource charsetResource)90 	public ResourceEncodingFieldEditor(String labelText, Composite parent,
91 			IResource charsetResource) {
92 		super();
93 		setLabelAndResource(labelText, charsetResource);
94 		createControl(parent);
95 	}
96 
97 	/**
98 	 * Creates a new encoding field editor for setting the encoding on the given
99 	 * resource.
100 	 *
101 	 * @param labelText
102 	 *            the label text of the field editor
103 	 * @param parent
104 	 *            the parent of the field editor's control
105 	 * @param charsetResource
106 	 *            must be an <code>IContainer</code> or an <code>IFile</code>.
107 	 *  @param groupTitle
108 	 *  		  the title for the field editor's control. If groupTitle is
109 	 *            <code>null</code> the control will be unlabelled
110 	 *            (by default a {@link Composite} instead of a {@link Group}.
111 	 *
112 	 * @see org.eclipse.core.resources.IContainer#getDefaultCharset()
113 	 * @see org.eclipse.core.resources.IFile#getCharset()
114 	 * @see AbstractEncodingFieldEditor#setGroupTitle(String)
115 	 * @since 3.3
116 	 */
ResourceEncodingFieldEditor(String labelText, Composite parent, IResource charsetResource,String groupTitle)117 	public ResourceEncodingFieldEditor(String labelText, Composite parent,
118 			IResource charsetResource,String groupTitle) {
119 		super();
120 		setLabelAndResource(labelText, charsetResource);
121 		setGroupTitle(groupTitle);
122 		createControl(parent);
123 	}
124 
125 	/**
126 	 * Set the label text and the resource we are editing.
127 	 * @param labelText
128 	 * @param charsetResource
129 	 * @since 3.3
130 	 */
setLabelAndResource(String labelText, IResource charsetResource)131 	private void setLabelAndResource(String labelText, IResource charsetResource) {
132 		Assert.isTrue(charsetResource instanceof IContainer
133 				|| charsetResource instanceof IFile);
134 		setLabelText(labelText);
135 		this.resource = charsetResource;
136 	}
137 
138 	@Override
getStoredValue()139 	protected String getStoredValue() {
140 		try {
141 			if (resource instanceof IContainer) {
142 				return ((IContainer) resource).getDefaultCharset(false);
143 			}
144 			return ((IFile) resource).getCharset(false);
145 
146 		} catch (CoreException e) {// If there is an error return the default
147 			IDEWorkbenchPlugin
148 					.log(
149 							IDEWorkbenchMessages.ResourceEncodingFieldEditor_ErrorLoadingMessage,
150 							e.getStatus());
151 			return WorkbenchEncoding.getWorkbenchDefaultEncoding();
152 		}
153 
154 	}
155 
getStoredSeparateDerivedEncodingsValue()156 	private boolean getStoredSeparateDerivedEncodingsValue() {
157 		// be careful looking up for our node so not to create any nodes as side effect
158 		Preferences node = Platform.getPreferencesService().getRootNode()
159 				.node(ProjectScope.SCOPE);
160 		String projectName = resource.getName();
161 		try {
162 			//TODO once bug 90500 is fixed, should be as simple as this:
163 			//			String path = projectName + IPath.SEPARATOR + ResourcesPlugin.PI_RESOURCES;
164 			//			return node.nodeExists(path) ? node.node(path).getBoolean(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS, false) : false;
165 			// for now, take the long way
166 			if (!node.nodeExists(projectName))
167 				return DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
168 			node = node.node(projectName);
169 			if (!node.nodeExists(ResourcesPlugin.PI_RESOURCES))
170 				return DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
171 			node = node.node(ResourcesPlugin.PI_RESOURCES);
172 			return node.getBoolean(
173 					ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS,
174 					DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS);
175 		} catch (BackingStoreException e) {
176 			// default value
177 			return DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
178 		}
179 	}
180 
hasSameSeparateDerivedEncodings()181 	private boolean hasSameSeparateDerivedEncodings() {
182 		return separateDerivedEncodingsButton == null
183 			|| separateDerivedEncodingsButton.getSelection() == getStoredSeparateDerivedEncodingsValue();
184 	}
185 
186 	@Override
doStore()187 	protected void doStore() {
188 
189 		String encoding = getSelectedEncoding();
190 
191 		// Clear the value if nothing is selected
192 		if (isDefaultSelected()) {
193 			encoding = null;
194 		}
195 		// Don't update if the same thing is selected
196 		final boolean hasSameEncoding = hasSameEncoding(encoding);
197 		final boolean hasSameSeparateDerivedEncodings = hasSameSeparateDerivedEncodings();
198 		if (hasSameEncoding && hasSameSeparateDerivedEncodings) {
199 			return;
200 		}
201 
202 		String descriptionCharset = getCharsetFromDescription();
203 		if (descriptionCharset != null
204 				&& !(descriptionCharset.equals(encoding)) && encoding != null) {
205 			Shell shell = null;
206 			DialogPage page = getPage();
207 			if (page != null) {
208 				shell = page.getShell();
209 			}
210 
211 			MessageDialog dialog = new MessageDialog(
212 					shell,
213 					IDEWorkbenchMessages.ResourceEncodingFieldEditor_EncodingConflictTitle,
214 					null,
215 					NLS.bind(IDEWorkbenchMessages.ResourceEncodingFieldEditor_EncodingConflictMessage, encoding,
216 							descriptionCharset),
217 					MessageDialog.WARNING, 0, IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL) {
218 				@Override
219 				protected int getShellStyle() {
220 					return super.getShellStyle() | SWT.SHEET;
221 				}
222 			}; // yes is the
223 			// default
224 			if (dialog.open() > 0) {
225 				return;
226 			}
227 		}
228 
229 		IDEEncoding.addIDEEncoding(encoding);
230 
231 		final String finalEncoding = encoding;
232 
233 		Job charsetJob = Job.create(IDEWorkbenchMessages.IDEEncoding_EncodingJob, monitor -> {
234 			try {
235 				if (!hasSameEncoding) {
236 					if (resource instanceof IContainer) {
237 						((IContainer) resource).setDefaultCharset(
238 								finalEncoding, monitor);
239 					} else {
240 						((IFile) resource).setCharset(finalEncoding,
241 								monitor);
242 					}
243 				}
244 				if (!hasSameSeparateDerivedEncodings) {
245 					Preferences prefs = new ProjectScope((IProject) resource).getNode(ResourcesPlugin.PI_RESOURCES);
246 					boolean newValue = !getStoredSeparateDerivedEncodingsValue();
247 					// Remove the pref if it's the default, otherwise store it.
248 					if (newValue == DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS)
249 						prefs.remove(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS);
250 					else
251 						prefs.putBoolean(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS, newValue);
252 					prefs.flush();
253 				}
254 				return Status.OK_STATUS;
255 			} catch (CoreException e1) {// If there is an error return the
256 				// default
257 				IDEWorkbenchPlugin
258 						.log(
259 								IDEWorkbenchMessages.ResourceEncodingFieldEditor_ErrorStoringMessage,
260 								e1.getStatus());
261 				return e1.getStatus();
262 			} catch (BackingStoreException e2) {
263 				IDEWorkbenchPlugin.log(IDEWorkbenchMessages.ResourceEncodingFieldEditor_ErrorStoringMessage, e2);
264 				return new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, e2.getMessage(), e2);
265 			}
266 		});
267 
268 		charsetJob.schedule();
269 
270 	}
271 
272 	@Override
store()273 	public void store() {
274 		// Override the store method as we are not using a preference store
275 		doStore();
276 	}
277 
278 	@Override
load()279 	public void load() {
280 		// Override the load method as we are not using a preference store
281 		setPresentsDefaultValue(false);
282 		doLoad();
283 	}
284 
285 	@Override
loadDefault()286 	public void loadDefault() {
287 		// Override the loadDefault method as we are not using a preference store
288 		setPresentsDefaultValue(true);
289 		doLoadDefault();
290 		refreshValidState();
291 	}
292 
293 	@Override
doLoadDefault()294 	protected void doLoadDefault() {
295 		super.doLoadDefault();
296 		if (separateDerivedEncodingsButton != null)
297 			separateDerivedEncodingsButton.setSelection(DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS);
298 	}
299 
300 	@Override
findDefaultEncoding()301 	protected String findDefaultEncoding() {
302 
303 		if (resource instanceof IWorkspaceRoot) {
304 			return super.findDefaultEncoding();
305 		}
306 
307 		String defaultCharset = getCharsetFromDescription();
308 
309 		if (defaultCharset != null && defaultCharset.length() > 0) {
310 			return defaultCharset;
311 		}
312 		try {
313 			// Query up the whole hierarchy
314 			defaultCharset = resource.getParent().getDefaultCharset(true);
315 		} catch (CoreException exception) {
316 			// If there is an exception try again
317 		}
318 
319 		if (defaultCharset != null && defaultCharset.length() > 0) {
320 			return defaultCharset;
321 		}
322 
323 		return super.findDefaultEncoding();
324 	}
325 
326 	/**
327 	 * Returns the charset from the content description if there is one.
328 	 *
329 	 * @return the charset from the content description, or <code>null</code>
330 	 */
getCharsetFromDescription()331 	private String getCharsetFromDescription() {
332 		IContentDescription description = getContentDescription();
333 		if (description != null) {
334 			return description.getCharset();
335 		}
336 		return null;
337 	}
338 
339 	@Override
defaultButtonText()340 	protected String defaultButtonText() {
341 
342 		if (resource instanceof IWorkspaceRoot) {
343 			return super.defaultButtonText();
344 		}
345 
346 		if (resource instanceof IFile) {
347 			try {
348 				IContentDescription description = ((IFile) resource)
349 						.getContentDescription();
350 				// If we can find a charset from the description then derive
351 				// from that
352 				if (description == null || description.getCharset() == null) {
353 					return NLS
354 							.bind(
355 									IDEWorkbenchMessages.ResourceInfo_fileContainerEncodingFormat,
356 									getDefaultEnc());
357 				}
358 
359 				IContentType contentType = description.getContentType();
360 				if (contentType != null && contentType.getDefaultCharset() == description.getCharset()) {
361 					return NLS
362 							.bind(
363 									IDEWorkbenchMessages.ResourceInfo_fileContentTypeEncodingFormat,
364 									getDefaultEnc());
365 				}
366 
367 				return NLS
368 						.bind(
369 								IDEWorkbenchMessages.ResourceInfo_fileContentEncodingFormat,
370 								getDefaultEnc());
371 
372 			} catch (CoreException exception) {
373 				// Do nothing here as we will just try to derive from the
374 				// container
375 			}
376 		}
377 
378 		return NLS.bind(
379 				IDEWorkbenchMessages.ResourceInfo_containerEncodingFormat,
380 				getDefaultEnc());
381 
382 	}
383 
384 	@Override
createEncodingGroup(Composite parent, int numColumns)385 	protected Composite createEncodingGroup(Composite parent, int numColumns) {
386 		group = super.createEncodingGroup(parent, numColumns);
387 		String byteOrderLabel = IDEEncoding
388 				.getByteOrderMarkLabel(getContentDescription());
389 		if (byteOrderLabel != null) {
390 			Label label = new Label(group, SWT.NONE);
391 			label
392 					.setText(NLS
393 							.bind(
394 									IDEWorkbenchMessages.WorkbenchPreference_encoding_encodingMessage,
395 									byteOrderLabel));
396 			GridData layoutData = new GridData();
397 			layoutData.horizontalSpan = numColumns + 1;
398 			label.setLayoutData(layoutData);
399 
400 		}
401 
402 		if (resource.getType() == IResource.PROJECT) {
403 			separateDerivedEncodingsButton = new Button(group, SWT.CHECK);
404 			GridData data = new GridData();
405 			data.horizontalSpan = 2;
406 			separateDerivedEncodingsButton.setLayoutData(data);
407 			separateDerivedEncodingsButton
408 					.setText(IDEWorkbenchMessages.ResourceEncodingFieldEditor_SeparateDerivedEncodingsLabel);
409 			separateDerivedEncodingsButton
410 					.setSelection(getStoredSeparateDerivedEncodingsValue());
411 		}
412 		return group;
413 	}
414 
415 	/**
416 	 * Returns the content description of the resource if it is a file and it
417 	 * has a content description.
418 	 *
419 	 * @return the content description or <code>null</code> if resource is not
420 	 *         an <code>IFile</code> or it does not have a description
421 	 */
getContentDescription()422 	private IContentDescription getContentDescription() {
423 		try {
424 			if (resource instanceof IFile) {
425 				return (((IFile) resource).getContentDescription());
426 			}
427 		} catch (CoreException exception) {
428 			// If we cannot find it return null
429 		}
430 		return null;
431 	}
432 
433 	@Override
setEnabled(boolean enabled, Composite parent)434 	public void setEnabled(boolean enabled, Composite parent) {
435 		super.setEnabled(enabled, parent);
436 		group.setEnabled(enabled);
437 		for (Control child : group.getChildren()) {
438 			child.setEnabled(enabled);
439 
440 		}
441 	}
442 
443 }
444