/******************************************************************************* * Copyright Technophobia Ltd 2012 * * This file is part of the Substeps Eclipse Plugin. * * The Substeps Eclipse Plugin is free software: you can redistribute it and/or modify * it under the terms of the Eclipse Public License v1.0. * * The Substeps Eclipse Plugin is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Eclipse Public License for more details. * * You should have received a copy of the Eclipse Public License * along with the Substeps Eclipse Plugin. If not, see <http://www.eclipse.org/legal/epl-v10.html>. ******************************************************************************/ package com.technophobia.substeps.editor.preferences.page; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.BooleanFieldEditor; import org.eclipse.jface.preference.FieldEditor; import org.eclipse.jface.preference.IPersistentPreferenceStore; import org.eclipse.jface.preference.StringButtonFieldEditor; import org.eclipse.jface.preference.StringFieldEditor; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; import org.eclipse.ui.IWorkbenchPropertyPage; import org.eclipse.ui.dialogs.ElementTreeSelectionDialog; import org.eclipse.ui.dialogs.ISelectionStatusValidator; import org.eclipse.ui.dialogs.PropertyPage; import org.eclipse.ui.model.BaseWorkbenchContentProvider; import org.eclipse.ui.model.WorkbenchLabelProvider; import com.technophobia.eclipse.project.ProjectObserver; import com.technophobia.substeps.FeatureEditorPlugin; import com.technophobia.substeps.editor.preferences.OverridableProjectLocalPreferenceStore; import com.technophobia.substeps.event.SubstepsFolderChangedListener; import com.technophobia.substeps.preferences.SubstepsPreferences; public class SubstepsPropertyPage extends PropertyPage implements IWorkbenchPropertyPage { private enum ConfirmationStatus { YES, NO, CANCEL }; private FieldEditor controlEditor; private FieldEditor hasNatureEditor; private final String pluginId; private final Map<String, FieldEditor> fieldEditorsByKey; private final Map<FieldEditor, Composite> fieldToParentMap; private final ProjectObserver projectObserver; private final Collection<SubstepsFolderChangedListener> substepsFolderChangeListeners; public SubstepsPropertyPage() { this.fieldEditorsByKey = new LinkedHashMap<String, FieldEditor>(); this.fieldToParentMap = new HashMap<FieldEditor, Composite>(); this.projectObserver = FeatureEditorPlugin.instance().getProjectObserver(); this.substepsFolderChangeListeners = FeatureEditorPlugin.instance().substepsFolderChangeListeners(); this.pluginId = FeatureEditorPlugin.PLUGIN_ID; } @Override public boolean performOk() { final boolean result = super.performOk(); doValidation(); if (isValid()) { hasNatureEditor.store(); controlEditor.store(); for (final FieldEditor fieldEditor : fieldEditorsByKey.values()) { oldSubstepsFolderLocation(); fieldEditor.store(); } savePreferenceStore(); updateProject(); return result; } return false; } @Override public void setElement(final IAdaptable element) { super.setElement(element); setPreferenceStore(createResourceLocalPreferenceStore((IProject) element.getAdapter(IProject.class), (IPersistentPreferenceStore) FeatureEditorPlugin.instance().getPreferenceStore())); } @Override protected Control createContents(final Composite parent) { final Composite composite = createComposite(parent); createProjectNatureControl(composite); createProjectOverrideControl(composite); createProblemsGroup(composite); createFoldersGroup(composite); final boolean hasNature = getPreferenceStore().getBoolean(SubstepsPreferences.PROJECT_NATURE.key()); final boolean projectOverride = getPreferenceStore().getBoolean(SubstepsPreferences.PROJECT_OVERRIDE.key()); setFieldsEnabled(hasNature && projectOverride); return composite; } @Override protected void performApply() { super.performApply(); savePreferenceStore(); updateProject(); } protected void savePreferenceStore() { try { ((IPersistentPreferenceStore) getPreferenceStore()).save(); } catch (final IOException ex) { FeatureEditorPlugin.instance().error("Could not save preferences", ex); } } protected void setFieldsEnabledStatus() { final boolean hasNature = ((BooleanFieldEditor) hasNatureEditor).getBooleanValue(); final boolean projectOverride = ((BooleanFieldEditor) controlEditor).getBooleanValue(); setFieldsEnabled(hasNature && projectOverride); } protected void setFieldsEnabled(final boolean enabled) { for (final FieldEditor field : fieldEditorsByKey.values()) { field.setEnabled(enabled, fieldToParentMap.get(field)); } } private void createProjectNatureControl(final Composite composite) { hasNatureEditor = new BooleanFieldEditor(SubstepsPreferences.PROJECT_NATURE.key(), "&Is this a substeps project?", composite); hasNatureEditor.setPage(this); hasNatureEditor.setPreferenceStore(getPreferenceStore()); hasNatureEditor.load(); hasNatureEditor.setPropertyChangeListener(new IPropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent event) { setFieldsEnabledStatus(); } }); // addField(hasNatureEditor, composite); } private void createProjectOverrideControl(final Composite composite) { controlEditor = new BooleanFieldEditor(SubstepsPreferences.PROJECT_OVERRIDE.key(), "&Enable project specific settings", composite); controlEditor.setPage(this); controlEditor.setPreferenceStore(getPreferenceStore()); controlEditor.setPropertyChangeListener(new IPropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent event) { setFieldsEnabledStatus(); } }); controlEditor.load(); } private void createProblemsGroup(final Composite composite) { final Group group = new Group(composite, SWT.NONE); group.setFont(composite.getFont()); group.setText("Problems"); final GridData layoutData = new GridData(GridData.FILL, GridData.FILL, true, false); layoutData.verticalIndent = 20; group.setLayoutData(layoutData); group.setLayout(new GridLayout(1, false)); final FieldEditor enableProblems = new BooleanFieldEditor(SubstepsPreferences.ENABLE_PROBLEMS.key(), "&Show substeps problems", group); enableProblems.setPage(this); enableProblems.setPreferenceStore(getPreferenceStore()); enableProblems.load(); addField(enableProblems, group); } private void createFoldersGroup(final Composite composite) { final Group group = new Group(composite, SWT.NONE); group.setFont(composite.getFont()); group.setText("Folders"); final GridData layoutData = new GridData(GridData.FILL, GridData.FILL, true, false); layoutData.verticalIndent = 20; group.setLayoutData(layoutData); group.setLayout(new GridLayout(1, false)); addField(createStringFieldEditor(SubstepsPreferences.FEATURE_FOLDER, "&Feature folder", group), group); addField(createStringFieldEditor(SubstepsPreferences.SUBSTEPS_FOLDER, "&Substeps folder", group), group); } private FieldEditor createStringFieldEditor(final SubstepsPreferences preference, final String label, final Group group) { final StringButtonFieldEditor fieldEditor = new StringButtonFieldEditor(preference.key(), label, group) { @Override protected String changePressed() { final String newLocation = handleBrowseFolderClick(); return newLocation != null ? newLocation : ""; } }; fieldEditor.setChangeButtonText("Browse"); fieldEditor.setPage(this); fieldEditor.setPreferenceStore(getPreferenceStore()); fieldEditor.load(); return fieldEditor; } private Composite createComposite(final Composite parent) { final Composite composite = new Composite(parent, SWT.NULL); final GridLayout layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; composite.setLayout(layout); final GridData gridData = new GridData(GridData.FILL_HORIZONTAL); composite.setLayoutData(gridData); return composite; } private void addField(final FieldEditor field, final Composite parent) { fieldEditorsByKey.put(field.getPreferenceName(), field); fieldToParentMap.put(field, parent); } private IPersistentPreferenceStore createResourceLocalPreferenceStore(final IProject project, final IPersistentPreferenceStore preferenceStore) { final OverridableProjectLocalPreferenceStore overridableProjectLocalPreferenceStore = new OverridableProjectLocalPreferenceStore( pluginId, SubstepsPreferences.PROJECT_OVERRIDE.key(), "true", project, preferenceStore, SubstepsPreferences.PROJECT_NATURE.key()); return new SubstepsPreferenceStore(overridableProjectLocalPreferenceStore, getProject()); } private void updateProject() { final IProject project = getProject(); projectObserver.preferencesChanged(project); } private IProject getProject() { return (IProject) getElement().getAdapter(IProject.class); } private String handleBrowseFolderClick() { final IResource resource = chooseFolderResource(); if (resource == null) { return ""; } return projectLocalisedPathFor(resource); } private IResource chooseFolderResource() { final ILabelProvider labelProvider = new WorkbenchLabelProvider(); final ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), labelProvider, new BaseWorkbenchContentProvider()); dialog.setTitle("Choose folder"); dialog.setMessage("Please select a folder"); dialog.setInput(getElement().getAdapter(IProject.class)); dialog.setAllowMultiple(false); dialog.setValidator(new ISelectionStatusValidator() { @Override public IStatus validate(final Object[] selection) { if (selection.length > 0) { final Object item = selection[0]; if (item instanceof IFolder) { return new Status(IStatus.OK, FeatureEditorPlugin.PLUGIN_ID, ""); } } return new Status(IStatus.ERROR, FeatureEditorPlugin.PLUGIN_ID, "Not a folder"); } }); if (dialog.open() == Window.OK) { return (IResource) dialog.getFirstResult(); } return null; } /** * Convert a resource to its os-specific project localised path string * * @param resource * to be localised * @return localised path */ protected String projectLocalisedPathFor(final IResource resource) { return resource.getFullPath().removeFirstSegments(1).toOSString(); } private void doValidation() { setValid(isSafeSubstepsFolderChange()); } private boolean isSafeSubstepsFolderChange() { final IPath previousPath = oldSubstepsFolderLocation(); final IPath newPath = newSubstepsFolderLocation(); final Collection<SubstepsFolderChangedListener> listenersRequiringConfirmationAction = new ArrayList<SubstepsFolderChangedListener>(); boolean cancelled = false; if (!previousPath.equals(newPath)) { for (final SubstepsFolderChangedListener folderChangeListener : substepsFolderChangeListeners) { final ConfirmationStatus confirmationStatus = handleFolderChanged(previousPath, newPath, folderChangeListener); if (ConfirmationStatus.YES.equals(confirmationStatus)) { listenersRequiringConfirmationAction.add(folderChangeListener); } else if (ConfirmationStatus.CANCEL.equals(confirmationStatus)) { listenersRequiringConfirmationAction.clear(); cancelled = true; break; } } } for (final SubstepsFolderChangedListener folderChangedListener : listenersRequiringConfirmationAction) { folderChangedListener.onConfirmation(previousPath, newPath); } return !cancelled; } private IPath oldSubstepsFolderLocation() { final IProject project = getProject(); return project.getFolder(getPreferenceStore().getString(SubstepsPreferences.SUBSTEPS_FOLDER.key())) .getFullPath().removeFirstSegments(1); } private IPath newSubstepsFolderLocation() { final IProject project = getProject(); return project .getFolder( ((StringFieldEditor) fieldEditorsByKey.get(SubstepsPreferences.SUBSTEPS_FOLDER.key())) .getStringValue()).getFullPath().removeFirstSegments(1); } private ConfirmationStatus handleFolderChanged(final IPath previousPath, final IPath newPath, final SubstepsFolderChangedListener folderChangeListener) { final boolean confirmationRequired = folderChangeListener.isConfirmationRequired(previousPath, newPath); if (confirmationRequired) { return getConfirmationStatus(previousPath, newPath, folderChangeListener); } return ConfirmationStatus.NO; } private ConfirmationStatus getConfirmationStatus(final IPath previousPath, final IPath newPath, final SubstepsFolderChangedListener folderChangeListener) { final MessageDialog messageDialog = new MessageDialog(getShell(), "Confirm", null, folderChangeListener.confirmationMessage(previousPath, newPath), MessageDialog.QUESTION_WITH_CANCEL, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL }, 0); final int status = messageDialog.open(); return ConfirmationStatus.values()[status]; } }