blob: 5c755a3b55711d2cfaa5e3c3b5b31f89f5d7b25b [file] [log] [blame]
/*******************************************************************************
* 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());
}
}
}