/*******************************************************************************
* Copyright (c) 2008, 2012 itemis AG (http://www.itemis.eu) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* itemis AG - initial API and implementation
* Cloudsmith Inc - changes to store 'use project settings' in project
*
*******************************************************************************/
package org.cloudsmith.geppetto.pp.dsl.ui.preferences.editors;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* This is a reworked implementation the class with the same name in org.eclipse.xtext.ui.editor.preferences.
* This implementation works with a {@link ProjectAwareScopedPreferenceStore} as oppoes to the
* ScopedPreferenceStore used by Xtext < 2.3, and FixedScopedPreferenceStore used in Xtext > 2.3.
*
* <p>
* The following changes have been made:
* <ul>
* <li>'useProjectSettings' is now stored in the project preference store as opposed to the project meta-data stored in the workspace. This is done so
* that import of a project automatically gets the intended 'useProjectSettings'.</li>
* </ul>
*/
public abstract class AbstractPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage,
IWorkbenchPropertyPage {
private static final Logger log = Logger.getLogger(AbstractPreferencePage.class);
private static final String USE_PROJECT_SETTINGS = "useProjectSettings"; //$NON-NLS-1$
private IWorkbench workbench;
private IProject project;
private Button useProjectSettingsButton;
private final List<FieldEditor> editors = new ArrayList<FieldEditor>();
private Link link;
@Inject
private IPreferenceStoreAccess preferenceStoreAccess;
@Inject
@Named("languageName")
private String languageName;
public AbstractPreferencePage() {
super(GRID);
}
@Override
protected void addField(FieldEditor editor) {
editors.add(editor);
super.addField(editor);
}
@Override
protected Control createContents(Composite parent) {
if(isPropertyPage())
createUseProjectSettingsControls(parent);
return super.createContents(parent);
}
@Override
public void createControl(Composite parent) {
super.createControl(parent);
if(isPropertyPage())
handleUseProjectSettings();
}
private void createUseProjectSettingsControls(Composite parent) {
Composite projectSettingsParent = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
layout.marginHeight = 0;
layout.marginWidth = 0;
projectSettingsParent.setLayout(layout);
projectSettingsParent.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// use project settings button
useProjectSettingsButton = new Button(projectSettingsParent, SWT.CHECK);
useProjectSettingsButton.setText("Enable project specific settings");
useProjectSettingsButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleUseProjectSettings();
log.debug("AbstractPreferencePage.widgetSelected()"); //$NON-NLS-1$
}
});
// configure ws settings link
link = new Link(projectSettingsParent, SWT.NONE);
link.setFont(projectSettingsParent.getFont());
link.setText("<A>" + "Configure Workspace Settings..." + "</A>"); //$NON-NLS-1$
link.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
String id = qualifiedName();
PreferencesUtil.createPreferenceDialogOn(getShell(), id, new String[] { id }, null).open();
updateFieldEditors(false);
}
});
link.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false));
// separator line
Label horizontalLine = new Label(projectSettingsParent, SWT.SEPARATOR | SWT.HORIZONTAL);
horizontalLine.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 2, 1));
horizontalLine.setFont(projectSettingsParent.getFont());
useProjectSettingsButton.setSelection(getUseProjectSettings());
}
private IProject currentProject() {
if(project == null)
throw new IllegalStateException("Not a property page case, but current project was requested."); //$NON-NLS-1$
return project;
}
@Override
protected IPreferenceStore doGetPreferenceStore() {
if(isPropertyPage()) {
return preferenceStoreAccess.getWritablePreferenceStore(currentProject());
}
return preferenceStoreAccess.getWritablePreferenceStore();
}
public IAdaptable getElement() {
return project;
}
protected String getLanguageName() {
return this.languageName;
}
/**
* Gets the 'useProjectSettings' flag in the project preferences.
*
* @return true if the settings on this page are project specific
*/
private Boolean getUseProjectSettings() {
return Boolean.valueOf(getPreferenceStore().getBoolean(getUseProjectSettingsName()));
}
/**
* Produces the preference key to use for the 'use project settings' flag when this preference page
* is is used as a properties page.
*
* @return the concatenation of {@link #qualifiedName()}, "." and {@link #USE_PROJECT_SETTINGS}.
*/
protected String getUseProjectSettingsName() {
return qualifiedName() + "." + USE_PROJECT_SETTINGS;
}
protected IWorkbench getWorkbench() {
return workbench;
}
/**
* Switches the search scope of the preference store to use [Project, Instance, Configuration] if
* values are project specific, and [Instance, Configuration] otherwise. This implementation requires that the
* given preference store is based on the Project preference store when the page is used as
* a Properties page. (This is done in {@link #doGetPreferenceStore()}).
*/
@SuppressWarnings("deprecation")
private void handleUseProjectSettings() {
// Note: uses the pre Eclipse 3.6 way of specifying search scopes (deprecated since 3.6)
boolean isUseProjectSettings = useProjectSettingsButton.getSelection();
link.setEnabled(!isUseProjectSettings);
if(!isUseProjectSettings) {
setSearchContexts(
getPreferenceStore(), new IScopeContext[] { new InstanceScope(), new ConfigurationScope() });
}
else {
// copy instance values to project specific values
setSearchContexts(getPreferenceStore(), new IScopeContext[] {
new ProjectScope(currentProject()), new InstanceScope(), new ConfigurationScope() });
setProjectSpecificValues();
}
updateFieldEditors(isUseProjectSettings);
}
public void init(IWorkbench workbench) {
this.workbench = workbench;
}
public boolean isPropertyPage() {
return project != null;
}
@Override
public boolean performOk() {
boolean retVal = super.performOk();
if(retVal && isPropertyPage()) {
try {
saveUseProjectSettings(useProjectSettingsButton.getSelection());
}
catch(Exception e) {
log.error("Error", e); //$NON-NLS-1$
retVal = false;
}
}
return retVal;
}
/**
* @return prefix for preference keys
*/
protected String qualifiedName() {
return languageName;
}
/**
* Saves the 'use project settings' as a preference in the store using
* the {@link #getUseProjectSettingsName()} as the key.
* If not project specific, all affected keys are removed from the project preferences.
*/
private void saveUseProjectSettings(boolean isProjectSpecific) throws Exception {
final ProjectAwareScopedPreferenceStore store = (ProjectAwareScopedPreferenceStore) getPreferenceStore();
if(!isProjectSpecific) {
// remove all the keys (written by various field editors)
IEclipsePreferences storePreferences = store.getStorePreferences();
for(FieldEditor field : editors) {
storePreferences.remove(field.getPreferenceName());
}
// Also remove the master key (i.e. use default/default == false when later reading).
storePreferences.remove(getUseProjectSettingsName());
}
else {
store.setValue(getUseProjectSettingsName(), Boolean.toString(isProjectSpecific));
}
store.save();
}
public void setElement(IAdaptable element) {
this.project = (IProject) element.getAdapter(IProject.class);
}
/**
* Copies all the project specific values (except master key) to the values of the instance preference
* store.
*/
private void setProjectSpecificValues() {
ProjectAwareScopedPreferenceStore store = (ProjectAwareScopedPreferenceStore) getPreferenceStore();
IEclipsePreferences storePreferences = store.getStorePreferences();
for(FieldEditor field : editors) {
String key = field.getPreferenceName();
storePreferences.put(key, store.getString(key));
}
}
/**
* Sets the given search scopes in the preference store.
* This implementation requires use of {@link ProjectAwareScopedPreferenceStore} - see {@link PPPreferenceStoreAccess} and its binding in the UI
* module.
*
* @param store
* @param scopes
*/
private void setSearchContexts(IPreferenceStore store, IScopeContext[] scopes) {
// Note: Eclipse default uses ScopedPreferenceStore, Xtext >= 2.3 uses FixedScopedPreferenceStore
((ProjectAwareScopedPreferenceStore) store).setSearchContexts(scopes);
}
/**
* Loads values of all field editors using current search scopes in the preference store.
* Also updates fields enabled status. (The effect is that fields show project specific values
* when enabled, and instance scoped/default values when disabled).
*
* @param enabled
*/
protected void updateFieldEditors(boolean enabled) {
Composite parent = getFieldEditorParent();
for(FieldEditor editor : editors) {
editor.load();
editor.setEnabled(enabled, parent);
}
getDefaultsButton().setEnabled(enabled);
}
}