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