| /******************************************************************************* |
| * Copyright (c) 2009, 2013 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0, which accompanies this distribution |
| * and is available at https://www.eclipse.org/legal/epl-2.0/. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.common.ui.internal.prefs; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Map; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jdt.internal.ui.preferences.PropertyAndPreferencePage; |
| import org.eclipse.jdt.internal.ui.preferences.ScrolledPageContent; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.dialogs.ProgressMonitorDialog; |
| import org.eclipse.jface.layout.PixelConverter; |
| import org.eclipse.jface.operation.IRunnableContext; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jpt.common.core.internal.utility.WorkspaceRunnableAdapter; |
| import org.eclipse.jpt.common.core.utility.ValidationMessage; |
| import org.eclipse.jpt.common.ui.JptCommonUiMessages; |
| import org.eclipse.jpt.common.ui.internal.JptUIPlugin; |
| import org.eclipse.jpt.common.ui.internal.jface.RunnableWithProgressAdapter; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Combo; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.ui.forms.events.ExpansionAdapter; |
| import org.eclipse.ui.forms.events.ExpansionEvent; |
| import org.eclipse.ui.forms.widgets.ExpandableComposite; |
| import org.eclipse.wst.validation.internal.provisional.core.IMessage; |
| |
| /** |
| * Abstract problem severities page that supports |
| * workspace- and project-level severities. |
| */ |
| @SuppressWarnings("restriction") |
| public abstract class JptProblemSeveritiesPage |
| extends PropertyAndPreferencePage |
| { |
| /** |
| * Changed severities are stored in this map and either committed |
| * (e.g. when the user presses the 'OK' button) or discarded |
| * (e.g. when the user presses the 'Cancel' button).<ul> |
| * <li> key = preference key (which is the |
| * {@link ValidationMessage#getID() validation message ID}) |
| * <li> value = preference severity level (which is the |
| * {@link IMessage#getSeverity() validation message severity}):<ul> |
| * <li>{@link IMessage#HIGH_SEVERITY} |
| * <li>{@link IMessage#NORMAL_SEVERITY} |
| * <li>{@link IMessage#LOW_SEVERITY} |
| * <li>{@link ValidationMessage#IGNORE_SEVERITY} |
| * </ul> |
| * </ul> |
| */ |
| private final HashMap<String, Integer> changedSeverities = new HashMap<String, Integer>(); |
| |
| /** |
| * Cache the {@link Combo}s so we can revert the settings. |
| */ |
| private final ArrayList<Combo> combos = new ArrayList<Combo>(); |
| |
| /** |
| * Cache the {@link ExpandableComposite}s so we can save |
| * and restore the page's expansion state. |
| */ |
| private final ArrayList<ExpandableComposite> expandablePanes = new ArrayList<ExpandableComposite>(); |
| |
| /** |
| * The key used to store and retrieve a combo's validation message. |
| * @see org.eclipse.swt.widgets.Widget#getData(String) |
| */ |
| /* CU private */ static final String VALIDATION_MESSAGE = ValidationMessage.class.getName(); |
| |
| /** |
| * The scrollable pane used to show the content of this page. |
| */ |
| private ScrolledPageContent mainComposite; |
| |
| /** |
| * The possible choices which describes the severity of a single problem. |
| */ |
| private final PreferenceSeverity[] preferenceSeverities; |
| private final String[] severityDisplayStrings; |
| |
| private Boolean hasProjectSpecificPreferences = null; |
| |
| /** |
| * Constant used to store the expansion state of each expandable pane. |
| */ |
| public static final String SETTINGS_EXPANDED = "expanded"; //$NON-NLS-1$ |
| |
| /** |
| * The preference key used to retrieve the dialog settings where the expansion |
| * states have been stored. |
| */ |
| public static final String SETTINGS_SECTION_NAME = "JpaProblemSeveritiesPage"; //$NON-NLS-1$ |
| |
| |
| public JptProblemSeveritiesPage() { |
| super(); |
| this.preferenceSeverities = this.buildPreferenceSeverities(); |
| this.severityDisplayStrings = this.buildSeverityDisplayStrings(); |
| } |
| |
| @Override |
| protected IPreferenceStore doGetPreferenceStore() { |
| return this.getUIPlugin().getPreferenceStore(); |
| } |
| |
| protected abstract JptUIPlugin getUIPlugin(); |
| |
| protected PreferenceSeverity[] buildPreferenceSeverities() { |
| return DEFAULT_PREFERENCE_SEVERITIES; |
| } |
| |
| protected static final PreferenceSeverity[] DEFAULT_PREFERENCE_SEVERITIES = buildDefaultPreferenceSeverities(); |
| protected static PreferenceSeverity[] buildDefaultPreferenceSeverities() { |
| ArrayList<PreferenceSeverity> severities = new ArrayList<PreferenceSeverity>(); |
| severities.add(new PreferenceSeverity(IMessage.HIGH_SEVERITY, JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__ERROR)); |
| severities.add(new PreferenceSeverity(IMessage.NORMAL_SEVERITY, JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__WARNING)); |
| severities.add(new PreferenceSeverity(IMessage.LOW_SEVERITY, JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__INFO)); |
| severities.add(new PreferenceSeverity(ValidationMessage.IGNORE_SEVERITY, JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__IGNORE)); |
| return severities.toArray(new PreferenceSeverity[severities.size()]); |
| } |
| |
| /** |
| * Pair a preference value with its localized display string |
| * (e.g. <code>"error"</code> and <code>"Error"</code>). |
| */ |
| public static class PreferenceSeverity { |
| public final int preferenceValue; |
| public final String displayString; |
| public PreferenceSeverity(int preferenceValue, String displayString) { |
| super(); |
| this.preferenceValue = preferenceValue; |
| this.displayString = displayString; |
| } |
| } |
| |
| /** |
| * Pre-condition: {@link #preferenceSeverities} is already built. |
| */ |
| protected String[] buildSeverityDisplayStrings() { |
| int len = this.preferenceSeverities.length; |
| String[] displayStrings = new String[len]; |
| for (int i = 0; i < len; i++) { |
| displayStrings[i] = this.preferenceSeverities[i].displayString; |
| } |
| return displayStrings; |
| } |
| |
| @Override |
| protected Control createPreferenceContent(Composite parent) { |
| PixelConverter pixelConverter = new PixelConverter(parent); |
| // create a container because the caller will set the GridData and we need |
| // to change the heightHint of the first child and we also need to set the |
| // font otherwise the layout won't be calculated correctly |
| Composite container = new Composite(parent, SWT.NONE); |
| container.setFont(parent.getFont()); |
| GridLayout layout = new GridLayout(1, false); |
| layout.marginHeight = 0; |
| layout.marginWidth = 0; |
| container.setLayout(layout); |
| |
| // the page's main composite |
| this.mainComposite = new ScrolledPageContent(container); |
| |
| GridData gridData = new GridData(GridData.FILL, GridData.FILL, true, true); |
| gridData.heightHint = pixelConverter.convertHeightInCharsToPixels(20); |
| this.mainComposite.setLayoutData(gridData); |
| |
| parent = this.mainComposite.getBody(); |
| parent.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true)); |
| layout = new GridLayout(1, false); |
| layout.marginHeight = 0; |
| layout.marginWidth = 0; |
| parent.setLayout(layout); |
| |
| this.addCombos(parent); |
| |
| this.restoreSectionExpansionStates(); |
| |
| return container; |
| } |
| |
| protected abstract void addCombos(Composite parent); |
| |
| protected void restoreSectionExpansionStates() { |
| IDialogSettings settings = this.getDialogPreferences(); |
| for (int index = this.expandablePanes.size(); index-- > 0; ) { |
| ExpandableComposite expandablePane = this.expandablePanes.get(index); |
| if (settings == null) { |
| expandablePane.setExpanded(index == 0); // only expand the first node by default |
| } else { |
| expandablePane.setExpanded(settings.getBoolean(SETTINGS_EXPANDED + index)); |
| } |
| } |
| } |
| |
| @Override |
| public Point computeSize() { |
| return this.doComputeSize(); |
| } |
| |
| protected Composite addExpandableSection(Composite parent, String text) { |
| return this.addExpandableSection(parent, text, new GridData(GridData.FILL, GridData.FILL, true, false)); |
| } |
| |
| protected Composite addSubExpandableSection(Composite parent, String text) { |
| return this.addExpandableSection(parent, text, new GridData(GridData.FILL, GridData.FILL, true, false, 2, 1)); |
| } |
| |
| /** |
| * Creates and adds to the given <code>Composite</code> an expandable pane |
| * where its content can be shown or hidden depending on the expansion state. |
| * |
| * @param parent The parent container |
| * @param text The title of the expandable section |
| * @return The container to which widgets can be added, which is a child of |
| * the expandable pane |
| */ |
| private Composite addExpandableSection(Composite parent, String text, GridData gridData) { |
| int expansionStype = ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT; |
| ExpandableComposite expandablePane = new ExpandableComposite(parent, SWT.NONE, expansionStype); |
| |
| expandablePane.setText(text); |
| expandablePane.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DIALOG_FONT)); |
| expandablePane.setLayoutData(gridData); |
| expandablePane.addExpansionListener(this.buildExpansionListener()); |
| |
| this.mainComposite.adaptChild(expandablePane); |
| this.expandablePanes.add(expandablePane); |
| |
| parent = new Composite(expandablePane, SWT.NONE); |
| parent.setLayout(new GridLayout(2, false)); |
| expandablePane.setClient(parent); |
| |
| return parent; |
| } |
| |
| private ExpansionAdapter buildExpansionListener() { |
| return new ExpansionListener(); |
| } |
| |
| /* CU private */ class ExpansionListener |
| extends ExpansionAdapter |
| { |
| @Override |
| public void expansionStateChanged(ExpansionEvent e) { |
| JptProblemSeveritiesPage.this.expansionStateChanged(); |
| } |
| } |
| |
| /** |
| * Revalidates the layout in order to show or hide the vertical scroll bar |
| * after a section was either expanded or collapsed. This unfortunately does |
| * not happen automatically. |
| */ |
| protected void expansionStateChanged() { |
| this.mainComposite.reflow(true); |
| } |
| |
| /** |
| * Creates and adds to the given parent a labeled combo where the possible |
| * choices are "Ignore", "Error" and "Warning". |
| * |
| * @param parent The parent to which the widgets are added |
| * @param validationMessage The corresponding validation message |
| */ |
| protected void addLabeledCombo(Composite parent, ValidationMessage validationMessage) { |
| Label label = new Label(parent, SWT.NONE); |
| label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); |
| label.setText(validationMessage.getDescription() + ':'); |
| |
| Combo combo = new Combo(parent, SWT.READ_ONLY); |
| combo.setItems(this.severityDisplayStrings); |
| combo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL)); |
| combo.setData(VALIDATION_MESSAGE, validationMessage); |
| combo.select(this.getInitialComboIndex(validationMessage)); |
| combo.addSelectionListener(this.buildComboSelectionListener()); |
| |
| this.mainComposite.adaptChild(combo); |
| this.combos.add(combo); |
| } |
| |
| /** |
| * Return the combo index corresponding to the specified validation message's |
| * current preference value. |
| * <p> |
| * Only called during initialization. |
| */ |
| protected int getInitialComboIndex(ValidationMessage validationMessage) { |
| return this.convertPreferenceValueToComboIndex(this.getPreferenceValue(validationMessage)); |
| } |
| |
| /** |
| * Return the current preference value for the specified validation message. |
| * <p> |
| * Only called during initialization. |
| */ |
| protected int getPreferenceValue(ValidationMessage validationMessage) { |
| int prefValue = this.getPreferenceValue_(validationMessage); |
| return (prefValue != ValidationMessage.UNSET_SEVERITY_PREFERENCE) ? prefValue : validationMessage.getDefaultSeverity(); |
| } |
| |
| protected int getPreferenceValue_(ValidationMessage validationMessage) { |
| String prefKey = validationMessage.getID(); |
| // useProjectSettings() won't work here since the page is still being initialized |
| return (this.isProjectPreferencePage() && this.hasProjectSpecificOptions(this.getProject())) ? |
| this.getValidationMessageSeverityPreference(this.getProject(), prefKey) : |
| this.getValidationMessageSeverityPreference(prefKey); |
| } |
| |
| protected int convertPreferenceValueToComboIndex(int prefValue) { |
| for (int i = 0; i < this.preferenceSeverities.length; i++) { |
| if (prefValue == this.preferenceSeverities[i].preferenceValue) { |
| return i; |
| } |
| } |
| throw new IllegalArgumentException("unknown preference value: " + prefValue); //$NON-NLS-1$ |
| } |
| |
| private SelectionListener buildComboSelectionListener() { |
| return new ComboSelectionListener(); |
| } |
| |
| /* CU private */ class ComboSelectionListener |
| extends SelectionAdapter |
| { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| JptProblemSeveritiesPage.this.comboSelected((Combo) e.widget); |
| } |
| } |
| |
| protected void comboSelected(Combo combo) { |
| ValidationMessage msg = (ValidationMessage) combo.getData(VALIDATION_MESSAGE); |
| String prefKey = msg.getID(); |
| int prefValue = this.preferenceSeverities[combo.getSelectionIndex()].preferenceValue; |
| this.changedSeverities.put(prefKey, Integer.valueOf(prefValue)); |
| } |
| |
| @Override |
| protected boolean hasProjectSpecificOptions(IProject project) { |
| return this.getWorkspaceValidationPreferencesOverridden(project); |
| } |
| |
| /** |
| * For a project properties page, the superclass implementation calls |
| * {@link #enableProjectSpecificSettings(boolean)}, with an argument of |
| * <code>false</code>; so we need handle only the workspace preferences |
| * page case here. |
| */ |
| @Override |
| protected void performDefaults() { |
| if ( ! this.isProjectPreferencePage()) { |
| this.revertWorkspaceToDefaultPreferences(); |
| } |
| super.performDefaults(); |
| } |
| |
| /** |
| * This is called only when the page is a workspace preferences page. |
| */ |
| protected void revertWorkspaceToDefaultPreferences() { |
| for (Combo combo : this.combos) { |
| this.revertWorkspaceToDefaultPreference(combo); |
| } |
| } |
| |
| /** |
| * This is called only when the page is a workspace preferences page. |
| */ |
| protected void revertWorkspaceToDefaultPreference(Combo combo) { |
| ValidationMessage validationMessage = (ValidationMessage) combo.getData(VALIDATION_MESSAGE); |
| String prefKey = validationMessage.getID(); |
| int prefValue = validationMessage.getDefaultSeverity(); |
| combo.select(this.convertPreferenceValueToComboIndex(prefValue)); |
| // force the workspace-level preference to be removed |
| this.changedSeverities.put(prefKey, Integer.valueOf(ValidationMessage.UNSET_SEVERITY_PREFERENCE)); |
| } |
| |
| /** |
| * This is called only when the page is a project properties page. |
| */ |
| @Override |
| protected void enableProjectSpecificSettings(boolean useProjectSpecificSettings) { |
| super.enableProjectSpecificSettings(useProjectSpecificSettings); |
| if (this.getDefaultsButton() == null) { |
| //@Hack("If the defaults button is null the control is currently being built," + |
| // "otherwise the 'enable project specific settings' checkbox is being pressed") |
| return; |
| } |
| |
| this.hasProjectSpecificPreferences = Boolean.valueOf(useProjectSpecificSettings); |
| |
| if (useProjectSpecificSettings){ |
| this.copyCurrentPreferencesToProject(); |
| } else { |
| this.revertProjectPreferences(); |
| } |
| } |
| |
| /** |
| * Copy <em>all</em> the current settings to the project-level settings. |
| * This locks down all the settings; otherwise future changes to the |
| * workspace-level settings would affect any project-level settings |
| * that were not copied over. |
| * <p> |
| * This is called only when the page is a project properties page. |
| */ |
| protected void copyCurrentPreferencesToProject() { |
| for (Combo combo : this.combos) { |
| this.copyCurrentPreferenceToProject(combo); |
| } |
| } |
| |
| /** |
| * This is called only when the page is a project properties page. |
| */ |
| protected void copyCurrentPreferenceToProject(Combo combo) { |
| ValidationMessage validationMessage = (ValidationMessage) combo.getData(VALIDATION_MESSAGE); |
| String prefKey = validationMessage.getID(); |
| int prefValue = this.getValidationMessageSeverityPreference(prefKey); |
| if (prefValue == ValidationMessage.UNSET_SEVERITY_PREFERENCE) { |
| prefValue = validationMessage.getDefaultSeverity(); |
| } |
| combo.select(this.convertPreferenceValueToComboIndex(prefValue)); |
| // combo does not fire a selection event when set programmatically... |
| this.changedSeverities.put(prefKey, Integer.valueOf(prefValue)); |
| } |
| |
| /** |
| * This is called only when the page is a project properties page. |
| */ |
| protected void revertProjectPreferences() { |
| for (Combo combo : this.combos) { |
| this.revertProjectPreference(combo); |
| } |
| } |
| |
| /** |
| * This is called only when the page is a project properties page. |
| */ |
| protected void revertProjectPreference(Combo combo) { |
| ValidationMessage validationMessage = (ValidationMessage) combo.getData(VALIDATION_MESSAGE); |
| String prefKey = validationMessage.getID(); |
| int prefValue = this.getValidationMessageSeverityPreference(prefKey); |
| if (prefValue == ValidationMessage.UNSET_SEVERITY_PREFERENCE) { |
| prefValue = validationMessage.getDefaultSeverity(); |
| } |
| combo.select(this.convertPreferenceValueToComboIndex(prefValue)); |
| // force the project-level preference to be removed |
| this.changedSeverities.put(prefKey, Integer.valueOf(ValidationMessage.UNSET_SEVERITY_PREFERENCE)); |
| } |
| |
| @Override |
| protected void noDefaultAndApplyButton() { |
| throw new IllegalStateException("Don't call this, see enableProjectSpecificSettings for the hack that looks for the defaultsButton being null"); //$NON-NLS-1$ |
| } |
| |
| |
| // ********** plug-in preferences ********** |
| |
| protected abstract int getValidationMessageSeverityPreference(IProject project, String prefKey); |
| |
| protected abstract int getValidationMessageSeverityPreference(String prefKey); |
| |
| protected abstract boolean getWorkspaceValidationPreferencesOverridden(IProject project); |
| |
| protected abstract void setWorkspaceValidationPreferencesOverridden(IProject project, boolean value); |
| |
| protected abstract void setValidationMessageSeverityPreference(String prefKey, int value); |
| |
| protected abstract void setValidationMessageSeverityPreference(IProject project, String prefKey, int value); |
| |
| |
| // ********** OK/Revert/Apply behavior ********** |
| |
| @Override |
| public boolean performOk() { |
| super.performOk(); |
| if (this.hasProjectSpecificPreferences != null) { |
| this.setWorkspaceValidationPreferencesOverridden(this.getProject(), this.hasProjectSpecificPreferences.booleanValue()); |
| } |
| for (Map.Entry<String, Integer> entry : this.changedSeverities.entrySet()) { |
| this.setProblemSeverityPreference(entry.getKey(), entry.getValue().intValue()); |
| } |
| try { |
| // true=fork; false=uncancellable |
| this.buildOKDialog().run(true, false, this.buildOKRunnable()); |
| } catch (InterruptedException ex) { |
| // should *not* happen... |
| Thread.currentThread().interrupt(); |
| return false; |
| } catch (InvocationTargetException ex) { |
| throw new RuntimeException(ex.getTargetException()); |
| } |
| |
| return true; |
| } |
| |
| protected void setProblemSeverityPreference(String prefKey, int value) { |
| if (this.isProjectPreferencePage()) { |
| this.setValidationMessageSeverityPreference(this.getProject(), prefKey, value); |
| } else { |
| this.setValidationMessageSeverityPreference(prefKey, value); |
| } |
| } |
| |
| private IRunnableContext buildOKDialog() { |
| return new ProgressMonitorDialog(this.getShell()); |
| } |
| |
| private IRunnableWithProgress buildOKRunnable() { |
| return new OKRunnable(); |
| } |
| |
| /* CU private */ class OKRunnable |
| extends RunnableWithProgressAdapter |
| { |
| @Override |
| public void run(IProgressMonitor monitor) throws InvocationTargetException { |
| try { |
| this.run_(monitor); |
| } catch (CoreException ex) { |
| throw new InvocationTargetException(ex); |
| } |
| } |
| /** |
| * {@link #performOk_(IProgressMonitor)} triggers a build that locks the |
| * workspace root, so we need to use the workspace root as our |
| * scheduling rule here |
| */ |
| private void run_(IProgressMonitor monitor) throws CoreException { |
| IWorkspace ws = ResourcesPlugin.getWorkspace(); |
| ws.run( |
| JptProblemSeveritiesPage.this.buildOkWorkspaceRunnable(), |
| ws.getRoot(), |
| IWorkspace.AVOID_UPDATE, |
| monitor |
| ); |
| } |
| } |
| |
| IWorkspaceRunnable buildOkWorkspaceRunnable() { |
| return new OKWorkspaceRunnable(); |
| } |
| |
| /* CU private */ class OKWorkspaceRunnable |
| extends WorkspaceRunnableAdapter |
| { |
| @Override |
| public void run(IProgressMonitor monitor) throws CoreException { |
| JptProblemSeveritiesPage.this.performOk_(monitor); |
| } |
| } |
| |
| void performOk_(IProgressMonitor monitor) throws CoreException { |
| int buildKind = IncrementalProjectBuilder.FULL_BUILD; |
| IProject project = this.getProject(); |
| if (project != null) { |
| // project preference page |
| project.build(buildKind, monitor); |
| } else { |
| // workspace preference page |
| ResourcesPlugin.getWorkspace().build(buildKind, monitor); |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| this.storeSectionExpansionStates(this.getDialogPreferences()); |
| super.dispose(); |
| } |
| |
| protected IDialogSettings getDialogPreferences() { |
| return this.getUIPlugin().getDialogSettings(SETTINGS_SECTION_NAME); |
| } |
| |
| protected void storeSectionExpansionStates(IDialogSettings settings) { |
| for (int index = this.expandablePanes.size(); index-- > 0; ) { |
| ExpandableComposite expandablePane = this.expandablePanes.get(index); |
| settings.put(SETTINGS_EXPANDED + index, expandablePane.isExpanded()); |
| } |
| } |
| |
| } |