blob: 1637001b181c357340a69b2f57618595adc06333 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2013 David Green and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* David Green - initial API and implementation
* Tasktop Technologies - improvements
* Helen Bershadskaya - improvements for bug 242445
*******************************************************************************/
package org.eclipse.mylyn.tasks.ui.wizards;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.wizards.Messages;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
/**
* An abstract base class for repository settings page that supports the <code>taskRepositoryPageContribution</code>
* extension point. {@link ITaskRepositoryPage} implementations are encouraged to extend
* {@link AbstractRepositorySettingsPage} if possible as it provides a standard UI for managing server settings.
*
* @see AbstractRepositorySettingsPage
* @author David Green
* @author Steffen Pingel
* @since 3.1
*/
public abstract class AbstractTaskRepositoryPage extends WizardPage implements ITaskRepositoryPage {
private static final String CLASS = "class"; //$NON-NLS-1$
private static final String ID = "id"; //$NON-NLS-1$
private static final String KIND = "connectorKind"; //$NON-NLS-1$
private static final String TASK_REPOSITORY_PAGE_CONTRIBUTION = "taskRepositoryPageContribution"; //$NON-NLS-1$
private static final String TASK_REPOSITORY_PAGE_CONTRIBUTION_EXTENSION = "org.eclipse.mylyn.tasks.ui.taskRepositoryPageContribution"; //$NON-NLS-1$
private static final Comparator<AbstractTaskRepositoryPageContribution> CONTRIBUTION_COMPARATOR = new ContributionComparator();
private final TaskRepository repository;
private final List<AbstractTaskRepositoryPageContribution> contributions;
FormToolkit toolkit;
private final AbstractTaskRepositoryPageContribution.Listener contributionListener = new AbstractTaskRepositoryPageContribution.Listener() {
public void validationRequired(AbstractTaskRepositoryPageContribution contribution) {
validatePageSettings();
}
};
/**
* @since 3.1
*/
public AbstractTaskRepositoryPage(String title, String description, TaskRepository repository) {
super(title);
this.repository = repository;
this.contributions = new ArrayList<AbstractTaskRepositoryPageContribution>();
setTitle(title);
setDescription(description);
}
/**
* Get the kind of connector supported by this page.
*
* @return the kind of connector, never null
* @since 3.1
*/
public abstract String getConnectorKind();
@Override
public void dispose() {
if (toolkit != null) {
toolkit.dispose();
toolkit = null;
}
super.dispose();
}
/**
* Creates the contents of the page. Subclasses may override this method to change where the contributions are
* added.
*
* @since 2.0
*/
public void createControl(Composite parent) {
initializeDialogUnits(parent);
toolkit = new FormToolkit(TasksUiPlugin.getDefault().getFormColors(parent.getDisplay()));
Composite compositeContainer = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(3, false);
compositeContainer.setLayout(layout);
// Composite compositeContainer = new Composite(parent, SWT.NULL);
// Layout layout = new FillLayout();
// compositeContainer.setLayout(layout);
createSettingControls(compositeContainer);
createContributionControls(compositeContainer);
Dialog.applyDialogFont(compositeContainer);
setControl(compositeContainer);
//getControl().getShell().pack();
}
/**
* Creates the controls of this page.
*
* @since 3.1
*/
protected abstract void createSettingControls(Composite parent);
@Override
public boolean isPageComplete() {
return super.isPageComplete() && conributionsIsPageComplete();
}
@Override
public boolean canFlipToNextPage() {
return super.canFlipToNextPage() && contributionsCanFlipToNextPage();
}
private boolean contributionsCanFlipToNextPage() {
for (AbstractTaskRepositoryPageContribution contribution : contributions) {
if (!contribution.canFlipToNextPage()) {
return false;
}
}
return true;
}
private boolean conributionsIsPageComplete() {
for (AbstractTaskRepositoryPageContribution contribution : contributions) {
if (!contribution.isPageComplete()) {
return false;
}
}
return true;
}
/**
* Subclasses should only call this method if they override {@link #createContents(Composite)}.
*
* @param parentControl
* the container into which the contributions will create their UI
* @since 3.1
*/
protected void createContributionControls(final Composite parentControl) {
contributions.clear();
contributions.addAll(findApplicableContributors());
if (!contributions.isEmpty()) {
final List<AbstractTaskRepositoryPageContribution> badContributions = new ArrayList<AbstractTaskRepositoryPageContribution>();
for (final AbstractTaskRepositoryPageContribution contribution : contributions) {
SafeRunnable.run(new SafeRunnable() {
public void run() throws Exception {
contribution.init(getConnectorKind(), repository);
contribution.addListener(contributionListener);
}
@Override
public void handleException(Throwable e) {
badContributions.add(contribution);
StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, NLS.bind(
"Problems occured when initializing contribution \"{0}\"", contribution.getId()), e)); //$NON-NLS-1$
}
});
}
contributions.removeAll(badContributions);
Collections.sort(contributions, CONTRIBUTION_COMPARATOR);
for (final AbstractTaskRepositoryPageContribution contribution : contributions) {
final ExpandableComposite section = createSection(parentControl, contribution.getTitle());
section.setToolTipText(contribution.getDescription());
SafeRunnable.run(new SafeRunnable() {
public void run() throws Exception {
Control control = contribution.createControl(section);
section.setClient(control);
}
@Override
public void handleException(Throwable e) {
section.dispose();
StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, NLS.bind(
"Problems occured when creating control for contribution \"{0}\"", //$NON-NLS-1$
contribution.getId()), e));
}
});
}
}
}
/**
* @since 3.1
*/
protected ExpandableComposite createSection(final Composite parentControl, String title) {
final ExpandableComposite section = toolkit.createExpandableComposite(parentControl,
ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT | ExpandableComposite.COMPACT);
section.clientVerticalSpacing = 0;
section.setBackground(parentControl.getBackground());
section.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DIALOG_FONT));
section.addExpansionListener(new ExpansionAdapter() {
@Override
public void expansionStateChanged(ExpansionEvent e) {
parentControl.layout(true);
getControl().getShell().pack();
}
});
section.setText(title);
GridDataFactory.fillDefaults().indent(0, 5).grab(true, false).span(3, SWT.DEFAULT).applyTo(section);
return section;
}
/**
* Validate the settings of this page, not including contributions. This method should not be called directly by
* page implementations. Always run on a UI thread.
*
* @return the status, or null if there are no messages.
* @see #validatePageSettings()
* @since 3.1
*/
protected abstract IStatus validate();
/**
* Overriding methods should call <code>super.applyTo(repository)</code>
*
* @since 3.1
*/
public void applyTo(TaskRepository repository) {
applyContributionSettingsTo(repository);
}
private void applyContributionSettingsTo(TaskRepository repository) {
for (AbstractTaskRepositoryPageContribution contribution : contributions) {
contribution.applyTo(repository);
}
}
/**
* {@inheritDoc}
* <p>
* Invokes {@link #applyTo(TaskRepository)} by default. Client may override.
*
* @since 3.6
*/
public void performFinish(TaskRepository repository) {
applyTo(repository);
}
/**
* {@inheritDoc}
* <p>
* Invokes {@link #applyTo(TaskRepository)} by default. Client may override.
*
* @since 3.7
* @return true to indicate the finish request was accepted, and false to indicate that the finish request was
* refused
*/
public boolean preFinish(TaskRepository repository) {
return true;
}
/**
* Returns a status if there is a message to display, otherwise null.
*/
private IStatus computeValidation() {
final MultiStatus cumulativeResult = new MultiStatus(TasksUiPlugin.ID_PLUGIN, IStatus.OK,
Messages.AbstractTaskRepositoryPage_Validation_failed, null);
// validate the page
IStatus result = validate();
if (result != null) {
cumulativeResult.add(result);
}
// validate contributions
for (final AbstractTaskRepositoryPageContribution contribution : contributions) {
SafeRunnable.run(new SafeRunnable() {
public void run() throws Exception {
IStatus result = contribution.validate();
if (result != null) {
cumulativeResult.add(result);
}
}
@Override
public void handleException(Throwable e) {
StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, NLS.bind(
"Problems occured when validating contribution \"{0}\"", contribution.getId()), e)); //$NON-NLS-1$
}
});
}
return cumulativeResult;
}
/**
* Validate all settings in the page including contributions. This method should be called whenever a setting is
* changed on the page. The results of validation are applied and the buttons of the page are updated.
*
* @see #validate(IProgressMonitor)
* @see #applyValidationResult(IStatus[])
*/
private void validatePageSettings() {
IStatus validationStatus = computeValidation();
applyValidationResult(validationStatus);
getWizard().getContainer().updateButtons();
}
/**
* Apply the results of validation to the page. The implementation finds the most {@link IStatus#getSeverity()
* severe} status and {@link #setMessage(String, int) applies the message} to the page.
*
* @param status
* the status of the validation, or null
*/
private void applyValidationResult(IStatus status) {
if (status == null || status.isOK()) {
setMessage(null, IMessageProvider.INFORMATION);
setErrorMessage(null);
} else {
// find the most severe status
int messageType;
switch (status.getSeverity()) {
case IStatus.OK:
case IStatus.INFO:
messageType = IMessageProvider.INFORMATION;
break;
case IStatus.WARNING:
messageType = IMessageProvider.WARNING;
break;
case IStatus.ERROR:
default:
messageType = IMessageProvider.ERROR;
break;
}
setErrorMessage(null);
setMessage(status.getMessage(), messageType);
}
}
private List<AbstractTaskRepositoryPageContribution> findApplicableContributors() {
List<AbstractTaskRepositoryPageContribution> contributors = new ArrayList<AbstractTaskRepositoryPageContribution>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint editorExtensionPoint = registry.getExtensionPoint(TASK_REPOSITORY_PAGE_CONTRIBUTION_EXTENSION);
IExtension[] editorExtensions = editorExtensionPoint.getExtensions();
for (IExtension extension : editorExtensions) {
IConfigurationElement[] elements = extension.getConfigurationElements();
for (IConfigurationElement element : elements) {
if (element.getName().equals(TASK_REPOSITORY_PAGE_CONTRIBUTION)) {
String kind = element.getAttribute(KIND);
if (kind == null || kind.length() == 0 || kind.equals(getConnectorKind())) {
String id = element.getAttribute(ID);
try {
if (id == null || id.length() == 0) {
throw new IllegalStateException(TASK_REPOSITORY_PAGE_CONTRIBUTION + "/@" + ID //$NON-NLS-1$
+ " is required"); //$NON-NLS-1$
}
Object contributor = element.createExecutableExtension(CLASS);
AbstractTaskRepositoryPageContribution pageContributor = (AbstractTaskRepositoryPageContribution) contributor;
pageContributor.setId(id);
if (pageContributor.isEnabled()) {
contributors.add(pageContributor);
}
} catch (Throwable e) {
StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Could not load " //$NON-NLS-1$
+ TASK_REPOSITORY_PAGE_CONTRIBUTION + " '" + id + "' from plug-in " //$NON-NLS-1$//$NON-NLS-2$
+ element.getContributor().getName(), e));
}
}
}
}
}
return contributors;
}
private static class ContributionComparator implements Comparator<AbstractTaskRepositoryPageContribution> {
public int compare(AbstractTaskRepositoryPageContribution o1, AbstractTaskRepositoryPageContribution o2) {
if (o1 == o2) {
return 0;
}
String s1 = o1.getTitle();
String s2 = o2.getTitle();
int i = s1.compareTo(s2);
if (i == 0) {
i = o1.getId().compareTo(o2.getId());
}
return i;
}
}
}