### Eclipse Workspace Patch 1.0 #P org.eclipse.mylyn.tasks.ui Index: src/org/eclipse/mylyn/internal/tasks/ui/editors/TaskEditorExtensions.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.mylyn/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/editors/TaskEditorExtensions.java,v retrieving revision 1.1 diff -u -r1.1 TaskEditorExtensions.java --- src/org/eclipse/mylyn/internal/tasks/ui/editors/TaskEditorExtensions.java 17 Oct 2008 03:22:28 -0000 1.1 +++ src/org/eclipse/mylyn/internal/tasks/ui/editors/TaskEditorExtensions.java 29 Oct 2008 05:23:02 -0000 @@ -86,8 +86,7 @@ init(); String id = taskRepository.getProperty(REPOSITORY_PROPERTY_EDITOR_EXTENSION); if (id == null) { - // TODO 3.1 disabled until bug 244653 is resolved - //id = getDefaultTaskEditorExtensionId(taskRepository); + id = getDefaultTaskEditorExtensionId(taskRepository); } return id; } @@ -96,9 +95,27 @@ repository.setProperty(REPOSITORY_PROPERTY_EDITOR_EXTENSION, editorExtensionId); } + /** + * Get the default task editor extension id for the given task repository + * + * @param taskRepository + * @return the default task editor extension id or null if there is no default + */ public static String getDefaultTaskEditorExtensionId(TaskRepository taskRepository) { + return getDefaultTaskEditorExtensionId(taskRepository.getConnectorKind()); + } + + /** + * Get the default task editor extension id for the given kind of connector + * + * @param connectorKind + * the kind of connector + * + * @return the default task editor extension id or null if there is no default + */ + public static String getDefaultTaskEditorExtensionId(String connectorKind) { init(); - return associationByConnectorKind.get(taskRepository.getConnectorKind()); + return associationByConnectorKind.get(connectorKind); } /** Index: src/org/eclipse/mylyn/internal/tasks/ui/wizards/EditRepositoryWizard.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.mylyn/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/wizards/EditRepositoryWizard.java,v retrieving revision 1.34 diff -u -r1.34 EditRepositoryWizard.java --- src/org/eclipse/mylyn/internal/tasks/ui/wizards/EditRepositoryWizard.java 7 Oct 2008 05:09:48 -0000 1.34 +++ src/org/eclipse/mylyn/internal/tasks/ui/wizards/EditRepositoryWizard.java 29 Oct 2008 05:23:02 -0000 @@ -18,6 +18,7 @@ import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector; import org.eclipse.mylyn.internal.tasks.ui.RefactorRepositoryUrlOperation; import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; import org.eclipse.mylyn.tasks.core.TaskRepository; @@ -72,7 +73,9 @@ repository.flushAuthenticationCredentials(); - repository.setRepositoryUrl(newUrl); + if (!repository.getConnectorKind().equals(LocalRepositoryConnector.CONNECTOR_KIND)) { + repository.setRepositoryUrl(newUrl); + } settingsPage.applyTo(repository); if (oldUrl != null && newUrl != null && !oldUrl.equals(newUrl)) { TasksUiPlugin.getRepositoryManager().notifyRepositoryUrlChanged(repository, oldUrl); @@ -90,8 +93,8 @@ @Override public void addPages() { AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin.getConnectorUi(repository.getConnectorKind()); - // TODO 3.1 pass repository - settingsPage = connectorUi.getSettingsPage(null); + + settingsPage = connectorUi.getSettingsPage(repository); if (settingsPage instanceof AbstractRepositorySettingsPage) { ((AbstractRepositorySettingsPage) settingsPage).setRepository(repository); ((AbstractRepositorySettingsPage) settingsPage).setVersion(repository.getVersion()); Index: src/org/eclipse/mylyn/tasks/ui/editors/AbstractTaskEditorPage.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.mylyn/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/editors/AbstractTaskEditorPage.java,v retrieving revision 1.86 diff -u -r1.86 AbstractTaskEditorPage.java --- src/org/eclipse/mylyn/tasks/ui/editors/AbstractTaskEditorPage.java 17 Oct 2008 05:38:21 -0000 1.86 +++ src/org/eclipse/mylyn/tasks/ui/editors/AbstractTaskEditorPage.java 29 Oct 2008 05:23:03 -0000 @@ -159,6 +159,30 @@ */ public abstract class AbstractTaskEditorPage extends FormPage implements ISelectionProvider, ISelectionChangedListener { + /** + * a resize listener that causes the form page to reflow + */ + private final class ParentResizeHandler implements Listener { + private int generation = 0; + + public void handleEvent(Event event) { + // bug 237503: delay the reflow as an optimization + ++generation; + + Display.getCurrent().timerExec(150, new Runnable() { + int scheduledGeneration = generation; + + public void run() { + // Only reflow if this is the latest generation. This prevents + // unnecessary reflows + if (scheduledGeneration == generation) { + getManagedForm().reflow(true); + } + } + }); + } + } + private class SubmitTaskJobListener extends SubmitJobListener { private final boolean attachContext; @@ -497,11 +521,7 @@ @Override public void createPartControl(Composite parent) { - parent.addListener(SWT.Resize, new Listener() { - public void handleEvent(Event event) { - getManagedForm().reflow(true); - } - }); + parent.addListener(SWT.Resize, new ParentResizeHandler()); super.createPartControl(parent); } Index: src/org/eclipse/mylyn/tasks/ui/wizards/TaskAttachmentPage.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.mylyn/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/wizards/TaskAttachmentPage.java,v retrieving revision 1.15 diff -u -r1.15 TaskAttachmentPage.java --- src/org/eclipse/mylyn/tasks/ui/wizards/TaskAttachmentPage.java 13 Sep 2008 03:28:17 -0000 1.15 +++ src/org/eclipse/mylyn/tasks/ui/wizards/TaskAttachmentPage.java 29 Oct 2008 05:23:04 -0000 @@ -16,14 +16,32 @@ import java.util.LinkedList; import java.util.List; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.ITextListener; +import org.eclipse.jface.text.TextEvent; +import org.eclipse.jface.text.source.AnnotationModel; +import org.eclipse.jface.text.source.IAnnotationAccess; +import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.mylyn.context.core.ContextCore; import org.eclipse.mylyn.internal.provisional.commons.ui.CommonImages; import org.eclipse.mylyn.internal.tasks.core.data.FileTaskAttachmentSource; +import org.eclipse.mylyn.internal.tasks.ui.editors.AbstractHyperlinkTextPresentationManager; +import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil; +import org.eclipse.mylyn.internal.tasks.ui.editors.HighlightingHyperlinkTextPresentationManager; +import org.eclipse.mylyn.internal.tasks.ui.editors.RepositoryTextViewer; +import org.eclipse.mylyn.internal.tasks.ui.editors.RepositoryTextViewerConfiguration; +import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorExtensions; +import org.eclipse.mylyn.internal.tasks.ui.editors.TaskHyperlinkTextPresentationManager; +import org.eclipse.mylyn.internal.tasks.ui.editors.RepositoryTextViewerConfiguration.Mode; +import org.eclipse.mylyn.tasks.core.TaskRepository; import org.eclipse.mylyn.tasks.core.data.TaskAttachmentMapper; import org.eclipse.mylyn.tasks.core.data.TaskAttachmentModel; import org.eclipse.mylyn.tasks.ui.TasksUiImages; +import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorExtension; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; @@ -36,6 +54,14 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.contexts.IContextActivation; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.texteditor.AnnotationPreference; +import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; +import org.eclipse.ui.texteditor.MarkerAnnotationPreferences; +import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; /** * A wizard page to enter details of a new attachment. @@ -43,6 +69,7 @@ * @author Jeff Pound * @author Mik Kersten * @author Steffen Pingel + * @author David Green task editor extension integration * @since 3.0 */ public class TaskAttachmentPage extends WizardPage { @@ -62,7 +89,7 @@ private Button attachContextButton; - private Text commentText; + private SourceViewer comment; private Text descriptionText; @@ -80,6 +107,10 @@ private boolean first = true; + private IContextActivation commentContext; + + private IContextService contextService; + public TaskAttachmentPage(TaskAttachmentModel model) { super("AttachmentDetails"); this.model = model; @@ -118,14 +149,31 @@ Label label = new Label(composite, SWT.NONE); label.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, false)); label.setText("Comment"); - commentText = new Text(composite, SWT.V_SCROLL | SWT.BORDER | SWT.WRAP); - commentText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); - commentText.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { + + AbstractTaskEditorExtension extension = TaskEditorExtensions.getTaskEditorExtension(model.getTaskRepository()); + if (extension != null) { + // TODO: stack layout with preview + comment = extension.createEditor(model.getTaskRepository(), composite, SWT.V_SCROLL | SWT.MULTI + | SWT.BORDER); + String contextId = extension.getEditorContextId(); + if (contextId != null) { + // FIXME: keybindings don't work. How do we use commands with a dialog/wizard? + // related: http://dev.eclipse.org/newslists/news.eclipse.platform.rcp/msg14539.html + contextService = (IContextService) PlatformUI.getWorkbench().getService(IContextService.class); + if (contextService != null) { + commentContext = contextService.activateContext(contextId); + } + } + } else { + comment = createDefaultEditor(composite, SWT.V_SCROLL | SWT.MULTI | SWT.BORDER); + } + configure(comment); + comment.addTextListener(new ITextListener() { + public void textChanged(TextEvent event) { apply(); } - }); + comment.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); new Label(composite, SWT.NONE).setText("Content Type");// .setBackground(parent.getBackground()); @@ -219,10 +267,75 @@ if (descriptionText != null) { descriptionText.setFocus(); } else { - commentText.setFocus(); + comment.getTextWidget().setFocus(); } } + private RepositoryTextViewerConfiguration installHyperlinkPresenter(SourceViewer viewer) { + RepositoryTextViewerConfiguration configuration = new RepositoryTextViewerConfiguration( + model.getTaskRepository(), false); + configuration.setMode(Mode.DEFAULT); + + // do not configure viewer, this has already been done in extension + + AbstractHyperlinkTextPresentationManager manager; + + manager = new HighlightingHyperlinkTextPresentationManager(); + manager.setHyperlinkDetectors(configuration.getDefaultHyperlinkDetectors(viewer, null)); + manager.install(viewer); + + manager = new TaskHyperlinkTextPresentationManager(); + manager.setHyperlinkDetectors(configuration.getDefaultHyperlinkDetectors(viewer, Mode.TASK)); + manager.install(viewer); + + return configuration; + } + + private SourceViewer configure(final SourceViewer viewer) { + // do this before setting the document to not require invalidating the presentation + installHyperlinkPresenter(viewer); + + configureAsEditor(viewer, new Document()); + + // enable cut/copy/paste + EditorUtil.setTextViewer(viewer.getTextWidget(), viewer); + viewer.setEditable(true); + + return viewer; + } + + private void configureAsEditor(SourceViewer viewer, Document document) { + AnnotationModel annotationModel = new AnnotationModel(); + viewer.showAnnotations(false); + viewer.showAnnotationsOverview(false); + IAnnotationAccess annotationAccess = new DefaultMarkerAnnotationAccess(); + final SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(viewer, null, annotationAccess, + EditorsUI.getSharedTextColors()); + Iterator e = new MarkerAnnotationPreferences().getAnnotationPreferences().iterator(); + while (e.hasNext()) { + support.setAnnotationPreference((AnnotationPreference) e.next()); + } + support.install(EditorsUI.getPreferenceStore()); + viewer.getTextWidget().addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + support.uninstall(); + } + }); + //viewer.getTextWidget().setIndent(2); + viewer.setDocument(document, annotationModel); + } + + private SourceViewer createDefaultEditor(Composite parent, int styles) { + TaskRepository taskRepository = model.getTaskRepository(); + SourceViewer defaultEditor = new RepositoryTextViewer(taskRepository, parent, styles | SWT.WRAP); + + RepositoryTextViewerConfiguration viewerConfig = new RepositoryTextViewerConfiguration(taskRepository, true); + viewerConfig.setMode(Mode.DEFAULT); + defaultEditor.configure(viewerConfig); + + return defaultEditor; + } + private void validate() { apply(); if (fileNameText != null && "".equals(fileNameText.getText().trim())) { @@ -243,7 +356,7 @@ private void apply() { taskAttachment.applyTo(model.getAttribute()); - model.setComment(commentText.getText()); + model.setComment(comment.getDocument().get()); model.setAttachContext(attachContextButton.getSelection()); model.setContentType(taskAttachment.getContentType()); } @@ -298,10 +411,22 @@ if (descriptionText != null) { descriptionText.setFocus(); } else { - commentText.setFocus(); + comment.getTextWidget().setFocus(); } first = false; } } + @Override + public void dispose() { + deactivateCommentContext(); + super.dispose(); + } + + private void deactivateCommentContext() { + if (contextService != null && commentContext != null) { + contextService.deactivateContext(commentContext); + commentContext = null; + } + } } Index: src/org/eclipse/mylyn/tasks/ui/wizards/AbstractRepositorySettingsPage.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.mylyn/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/wizards/AbstractRepositorySettingsPage.java,v retrieving revision 1.59 diff -u -r1.59 AbstractRepositorySettingsPage.java --- src/org/eclipse/mylyn/tasks/ui/wizards/AbstractRepositorySettingsPage.java 12 Sep 2008 04:19:26 -0000 1.59 +++ src/org/eclipse/mylyn/tasks/ui/wizards/AbstractRepositorySettingsPage.java 29 Oct 2008 05:23:04 -0000 @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2004, 2008 Tasktop Technologies and others. + * Copyright (c) 2004, 2008 Tasktop Technologies 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 @@ -28,7 +28,6 @@ import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.preference.PreferenceDialog; import org.eclipse.jface.preference.StringFieldEditor; -import org.eclipse.jface.wizard.WizardPage; import org.eclipse.mylyn.commons.core.StatusHandler; import org.eclipse.mylyn.commons.net.AuthenticationCredentials; import org.eclipse.mylyn.commons.net.AuthenticationType; @@ -67,7 +66,6 @@ import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.events.IHyperlinkListener; import org.eclipse.ui.forms.widgets.ExpandableComposite; -import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Hyperlink; /** @@ -78,8 +76,10 @@ * @author Rob Elves * @author Steffen Pingel * @author Frank Becker + * @author David Green */ -public abstract class AbstractRepositorySettingsPage extends WizardPage implements ITaskRepositoryPage { +public abstract class AbstractRepositorySettingsPage extends AbstractExtensibleRepositorySettingsPage implements + ITaskRepositoryPage { protected static final String PREFS_PAGE_ID_NET_PROXY = "org.eclipse.ui.net.NetPreferences"; @@ -205,18 +205,12 @@ private Button disconnectedButton; - // TODO 3.1 make accessible to subclasses - private FormToolkit toolkit; - /** * @since 3.0 */ public AbstractRepositorySettingsPage(String title, String description, TaskRepository taskRepository) { - super(title); - this.repository = taskRepository; + super(title, description, taskRepository); this.connector = TasksUi.getRepositoryManager().getRepositoryConnector(getConnectorKind()); - setTitle(title); - setDescription(description); setNeedsAnonymousLogin(false); setNeedsEncoding(true); setNeedsTimeZone(true); @@ -228,20 +222,21 @@ /** * @since 3.0 */ + @Override public abstract String getConnectorKind(); @Override public void dispose() { super.dispose(); - if (toolkit != null) { - toolkit.dispose(); - toolkit = null; - } } - public void createControl(Composite parent) { - toolkit = new FormToolkit(TasksUiPlugin.getDefault().getFormColors(parent.getDisplay())); + @Override + protected void createContents(Composite parent) { + createSettingControls(parent); + } + @Override + protected void createSettingControls(Composite parent) { if (repository != null) { originalUrl = repository.getRepositoryUrl(); AuthenticationCredentials oldCredentials = repository.getCredentials(AuthenticationType.REPOSITORY); @@ -628,6 +623,8 @@ addStatusSection(); + addContributionSection(); + Composite managementComposite = new Composite(compositeContainer, SWT.NULL); GridLayout managementLayout = new GridLayout(4, false); managementLayout.marginHeight = 0; @@ -701,7 +698,6 @@ updateHyperlinks(); - setControl(compositeContainer); } private void addProxySection() { @@ -855,6 +851,20 @@ proxyExpComposite.setExpanded(!systemProxyButton.getSelection()); } + private void addContributionSection() { + Composite composite = toolkit.createComposite(compositeContainer); + GridDataFactory.fillDefaults().grab(true, false).span(2, SWT.DEFAULT).applyTo(composite); + + GridLayout layout = new GridLayout(1, false); + layout.marginWidth = 0; + layout.marginTop = -5; + composite.setLayout(layout); + + composite.setBackground(compositeContainer.getBackground()); + + addContributions(composite); + } + private void addStatusSection() { ExpandableComposite statusComposite = toolkit.createExpandableComposite(compositeContainer, ExpandableComposite.COMPACT | ExpandableComposite.TWISTIE | ExpandableComposite.TITLE_BAR); @@ -1269,6 +1279,7 @@ /** * @since 2.2 */ + @Override public void applyTo(TaskRepository repository) { repository.setVersion(getVersion()); if (needsEncoding()) { @@ -1307,6 +1318,8 @@ } repository.setOffline(disconnectedButton.getSelection()); + + super.applyTo(repository); } public AbstractRepositoryConnector getConnector() { @@ -1413,6 +1426,9 @@ } } + /** + * validate settings provided by the {@link #getValidator(TaskRepository) validator}, typically the server settings. + */ protected void validateSettings() { final Validator validator = getValidator(createTaskRepository()); if (validator == null) { @@ -1453,6 +1469,14 @@ getWizard().getContainer().updateButtons(); } + /** + * @since 3.1 + */ + @Override + protected IStatus validate() { + return null; + } + protected void applyValidatorResult(Validator validator) { IStatus status = validator.getStatus(); String message = status.getMessage(); Index: plugin.xml =================================================================== RCS file: /cvsroot/tools/org.eclipse.mylyn/org.eclipse.mylyn.tasks.ui/plugin.xml,v retrieving revision 1.341 diff -u -r1.341 plugin.xml --- plugin.xml 17 Oct 2008 03:22:28 -0000 1.341 +++ plugin.xml 29 Oct 2008 05:23:02 -0000 @@ -8,6 +8,7 @@ + @@ -1410,18 +1411,9 @@ value="1"> - - - - - - - - + + @@ -1780,4 +1772,10 @@ type="org.eclipse.mylyn.tasks.core.TaskRepository"> + + + + Index: src/org/eclipse/mylyn/internal/tasks/ui/LocalRepositoryConnectorUi.java =================================================================== RCS file: /cvsroot/tools/org.eclipse.mylyn/org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/internal/tasks/ui/LocalRepositoryConnectorUi.java,v retrieving revision 1.2 diff -u -r1.2 LocalRepositoryConnectorUi.java --- src/org/eclipse/mylyn/internal/tasks/ui/LocalRepositoryConnectorUi.java 12 Sep 2008 04:19:24 -0000 1.2 +++ src/org/eclipse/mylyn/internal/tasks/ui/LocalRepositoryConnectorUi.java 29 Oct 2008 05:23:02 -0000 @@ -14,6 +14,7 @@ import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.wizard.IWizard; import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector; +import org.eclipse.mylyn.internal.tasks.ui.wizards.LocalRepositorySettingsPage; import org.eclipse.mylyn.internal.tasks.ui.wizards.NewLocalTaskWizard; import org.eclipse.mylyn.tasks.core.IRepositoryQuery; import org.eclipse.mylyn.tasks.core.ITask; @@ -50,7 +51,7 @@ @Override public ITaskRepositoryPage getSettingsPage(TaskRepository taskRepository) { - return null; + return new LocalRepositorySettingsPage(taskRepository); } @Override Index: schema/taskRepositoryPageContribution.exsd =================================================================== RCS file: schema/taskRepositoryPageContribution.exsd diff -N schema/taskRepositoryPageContribution.exsd --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ schema/taskRepositoryPageContribution.exsd 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,112 @@ + + + + + + + + + This extension point enables plug-ins to contribute user interface to the task repository settings dialog. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the kind of repository connector for which this contributor should be used, or "" (the empty string) if it applies to all connectors + + + + + + + + + + + + 3.1 + + + + + + + + + <extension + point="org.eclipse.mylyn.tasks.ui.taskRepositoryPageContribution"> + <taskRepositoryPageContribution class="org.eclipse.mylyn.internal.tasks.ui.wizards.TaskEditorExtensionSettingsContribution"/> + </extension> + + + + + + + + + [Enter API information here.] + + + + + + + + + [Enter information about supplied implementation of this extension point.] + + + + + Index: src/org/eclipse/mylyn/tasks/ui/wizards/AbstractExtensibleRepositorySettingsPage.java =================================================================== RCS file: src/org/eclipse/mylyn/tasks/ui/wizards/AbstractExtensibleRepositorySettingsPage.java diff -N src/org/eclipse/mylyn/tasks/ui/wizards/AbstractExtensibleRepositorySettingsPage.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/mylyn/tasks/ui/wizards/AbstractExtensibleRepositorySettingsPage.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,347 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Mylyn project committers 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 + *******************************************************************************/ + +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.IMessageProvider; +import org.eclipse.jface.layout.GridDataFactory; +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.tasks.core.TaskRepository; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +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 taskRepositoryPageContribution + * 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 + * + * @since 3.1 + * + * @author David Green + */ +public abstract class AbstractExtensibleRepositorySettingsPage extends WizardPage implements ITaskRepositoryPage { + + private static final String KIND = "connectorKind"; + + private static final String TASK_REPOSITORY_PAGE_CONTRIBUTION = "taskRepositoryPageContribution"; + + private static final String TASK_REPOSITORY_PAGE_CONTRIBUTION_EXTENSION = "org.eclipse.mylyn.tasks.ui.taskRepositoryPageContribution"; + + private static final Comparator CONTRIBUTION_COMPARATOR = new ContributionComparator(); + + protected final TaskRepository repository; + + private final List contributions = new ArrayList(); + + protected FormToolkit toolkit; + + protected Composite compositeContainer; + + private final AbstractTaskRepositoryPageContribution.Listener contributionListener = new AbstractTaskRepositoryPageContribution.Listener() { + public void validationRequired(AbstractTaskRepositoryPageContribution contribution) { + validatePageSettings(); + } + }; + + public AbstractExtensibleRepositorySettingsPage(String title, String description, TaskRepository repository) { + super(title); + if (repository != null && !repository.getConnectorKind().equals(getConnectorKind())) { + throw new IllegalArgumentException(); + } + this.repository = repository; + setTitle(title); + setDescription(description); + } + + /** + * Get the kind of connector supported by this page. + * + * @return the kind of connector, never null + */ + public abstract String getConnectorKind(); + + @Override + public void dispose() { + if (toolkit != null) { + toolkit.dispose(); + toolkit = null; + } + super.dispose(); + } + + public void createControl(Composite parent) { + toolkit = new FormToolkit(TasksUiPlugin.getDefault().getFormColors(parent.getDisplay())); + + compositeContainer = new Composite(parent, SWT.NULL); + GridLayout layout = new GridLayout(1, true); + compositeContainer.setLayout(layout); + + createContents(compositeContainer); + + setControl(compositeContainer); + } + + /** + * Create the contents of the page. Subclasses may override this method to change where the contributions are added. + */ + protected void createContents(Composite parent) { + createSettingControls(parent); + + addContributions(parent); + } + + /** + * create the controls of this page + */ + 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 + */ + protected void addContributions(Composite parentControl) { + contributions.clear(); + contributions.addAll(findApplicableContributors()); + + if (!contributions.isEmpty()) { + for (AbstractTaskRepositoryPageContribution contribution : contributions) { + contribution.init(getConnectorKind(), repository); + contribution.addListener(contributionListener); + } + + Collections.sort(contributions, CONTRIBUTION_COMPARATOR); + + for (AbstractTaskRepositoryPageContribution contribution : contributions) { + + ExpandableComposite section = toolkit.createExpandableComposite(parentControl, + ExpandableComposite.COMPACT | ExpandableComposite.TWISTIE | ExpandableComposite.TITLE_BAR); + section.clientVerticalSpacing = 0; + section.setBackground(parentControl.getBackground()); + section.setFont(parentControl.getFont()); + section.addExpansionListener(new ExpansionAdapter() { + @Override + public void expansionStateChanged(ExpansionEvent e) { + getControl().getShell().pack(); + } + }); + section.setText(contribution.getTitle()); + section.setToolTipText(contribution.getDescription()); + + GridDataFactory.fillDefaults().grab(true, false).applyTo(section); + + Composite sectionContentsContainer = toolkit.createComposite(section); + sectionContentsContainer.setBackground(parentControl.getBackground()); + contribution.createControl(sectionContentsContainer, toolkit); + + section.setClient(sectionContentsContainer); + } + } + } + + /** + * 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() + */ + protected abstract IStatus validate(); + + /** + * Overriding methods should call super.applyTo(repository) + */ + public void applyTo(TaskRepository repository) { + applyContributionSettingsTo(repository); + } + + private void applyContributionSettingsTo(TaskRepository repository) { + for (AbstractTaskRepositoryPageContribution contribution : contributions) { + contribution.applyTo(repository); + } + } + + /** + * compute the validation + * + * @return a status if there is a message to display, otherwise null + */ + private IStatus computeValidation() { + if (Display.getCurrent() == null) { + throw new IllegalStateException(); + } + IStatus cumulativeResult; + + // validate the page + cumulativeResult = validate(); + + // validate contributions + for (AbstractTaskRepositoryPageContribution contribution : contributions) { + IStatus result = contribution.validate(); + if (result != null) { + if (cumulativeResult == null) { + cumulativeResult = result; + } else if (cumulativeResult instanceof MultiStatus) { + ((MultiStatus) cumulativeResult).add(result); + } else { + cumulativeResult = new MultiStatus(cumulativeResult.getPlugin(), cumulativeResult.getCode(), + new IStatus[] { cumulativeResult, result }, null, null); + } + } + } + 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[]) + */ + protected 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 + */ + protected void applyValidationResult(IStatus status) { + if (status == null) { + 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 findApplicableContributors() { + List contributors = new ArrayList(); + + 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 || "".equals(kind) || getConnectorKind().equals(kind)) { + try { + Object contributor = element.createExecutableExtension("class"); + contributors.add((AbstractTaskRepositoryPageContribution) contributor); + } catch (Exception e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Could not load " + + TASK_REPOSITORY_PAGE_CONTRIBUTION, e)); + } + } + } + } + } + + return contributors; + } + + private static class ContributionComparator implements Comparator { + + 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 = new Integer(System.identityHashCode(o1)).compareTo(System.identityHashCode(o2)); + } + return i; + } + + } +} Index: src/org/eclipse/mylyn/tasks/ui/wizards/AbstractTaskRepositoryPageContribution.java =================================================================== RCS file: src/org/eclipse/mylyn/tasks/ui/wizards/AbstractTaskRepositoryPageContribution.java diff -N src/org/eclipse/mylyn/tasks/ui/wizards/AbstractTaskRepositoryPageContribution.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/mylyn/tasks/ui/wizards/AbstractTaskRepositoryPageContribution.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 Mylyn project committers 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 + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.ui.wizards; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.dialogs.IDialogPage; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * A contribution to a {@link ITaskRepositoryPage}, which enables plug-ins to contribute UI to the task repository + * settings. + * + * subclasses must have a default public constructor. + * + * @since 3.1 + * + * @author David Green + */ +public abstract class AbstractTaskRepositoryPageContribution { + + /** + * a listener interface that should be implemented by classes wishing to be notified of changes that occur within + * the contribution. + */ + public interface Listener { + /** + * Called when the state of the contribution changes such that validation should be performed + * + * @param contribution + * the contribution that changed + * + * @see ITaskRepositoryPageContribution#validate(IProgressMonitor) + */ + public void validationRequired(AbstractTaskRepositoryPageContribution contribution); + } + + private final List listeners = new CopyOnWriteArrayList(); + + private final String title; + + private final String description; + + /** + * the repository for which this contribution was created, or null if it was created for a new repository + */ + protected TaskRepository repository; + + /** + * the kind of connector for which this contribution was created + */ + protected String connectorKind; + + /** + * + * @param title + * the title of the contribution, as displayed to the user, usually used as a section heading + * @param description + * the description of the contribution, as displayed to the user, typically as a tool-tip + */ + protected AbstractTaskRepositoryPageContribution(String title, String description) { + this.title = title; + this.description = description; + } + + /** + * Initialize the contribution + * + * @param connectorKind + * the kind of connector for which this is a contribution + * @param repository + * the repository for which this contribution was created, or null if the repository is not yet available + */ + public void init(String connectorKind, TaskRepository repository) { + this.connectorKind = connectorKind; + this.repository = repository; + } + + /** + * Add a listener to this contribution. The contribution must notify the listener at the appropriate times, for + * example when a setting has changed in the UI. + * + * @see #removeListener(Listener) + */ + public void addListener(Listener listener) { + listeners.add(listener); + } + + /** + * Remove a listener from this contribution. + * + * @see #addListener(Listener) + */ + public void removeListener(Listener listener) { + listeners.remove(listener); + } + + /** + * @see IDialogPage#createControl(Composite) + */ + public abstract void createControl(Composite parent, FormToolkit toolkit); + + /** + * @see IDialogPage#getTitle() + */ + public String getTitle() { + return title; + } + + /** + * @see IDialogPage#getDescription() + */ + public String getDescription() { + return description; + } + + /** + * @see IWizardPage#isPageComplete() + */ + public abstract boolean isPageComplete(); + + /** + * @see IWizardPage#canFlipToNextPage() + */ + public abstract boolean canFlipToNextPage(); + + /** + * Validate the settings of the contribution. Contributions should expect this method to be called often and should + * thus return quickly. Always called on the UI thread. + * + * @return the status (errors) on the contribution, or null if there are none. A MultiStatus should be used to + * return multiple error messages or warnings. + */ + public abstract IStatus validate(); + + /** + * Apply the settings in the contribution to the given repository. + * + * @param repository + * the repository to which settings should be applied + * + * @see ITaskRepositoryPage#applyTo(TaskRepository) + */ + public abstract void applyTo(TaskRepository repository); + + /** + * fire the validation required event. + */ + protected void fireValidationRequired() { + for (Listener l : listeners) { + l.validationRequired(this); + } + } +} Index: src/org/eclipse/mylyn/internal/tasks/ui/wizards/TaskEditorExtensionSettingsContribution.java =================================================================== RCS file: src/org/eclipse/mylyn/internal/tasks/ui/wizards/TaskEditorExtensionSettingsContribution.java diff -N src/org/eclipse/mylyn/internal/tasks/ui/wizards/TaskEditorExtensionSettingsContribution.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/mylyn/internal/tasks/ui/wizards/TaskEditorExtensionSettingsContribution.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 Mylyn project committers 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 + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.ui.wizards; + +import java.util.SortedSet; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.resource.FontRegistry; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorExtensions; +import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorExtensions.RegisteredTaskEditorExtension; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.ui.wizards.AbstractTaskRepositoryPageContribution; +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.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * A contribution that adds a section for 'Editor' on the task repository settings page. + * + * @author David Green + */ +public class TaskEditorExtensionSettingsContribution extends AbstractTaskRepositoryPageContribution { + + private static final String LABEL_NONE = "Plain Text"; + + private static final String LABEL_DEFAULT_SUFFIX = " (default)"; + + private static final String DATA_EDITOR_EXTENSION = "editorExtension"; + + private final SelectionListener listener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + selectedExtensionId = (String) ((Widget) e.getSource()).getData(DATA_EDITOR_EXTENSION); + fireValidationRequired(); + } + }; + + private String selectedExtensionId = null; + + public TaskEditorExtensionSettingsContribution() { + super("Editor", "Select the capabilities of the the task editor"); + } + + @Override + public void applyTo(TaskRepository repository) { + TaskEditorExtensions.setTaskEditorExtensionId(repository, selectedExtensionId == null ? "none" + : selectedExtensionId); + } + + @Override + public boolean canFlipToNextPage() { + return true; + } + + @Override + public boolean isPageComplete() { + return true; + } + + @Override + public void createControl(Composite parent, FormToolkit toolkit) { + parent.setLayout(new GridLayout(1, true)); + + String defaultExtensionId = TaskEditorExtensions.getDefaultTaskEditorExtensionId(connectorKind); + selectedExtensionId = repository == null ? defaultExtensionId + : TaskEditorExtensions.getTaskEditorExtensionId(repository); + + Button noneButton; + { // configure a 'Plain Text' (none) button + String noneTitle = LABEL_NONE; + + boolean isDefault = defaultExtensionId == null || defaultExtensionId.length() == 0; + if (isDefault) { + noneTitle += LABEL_DEFAULT_SUFFIX; + } + noneButton = toolkit.createButton(parent, noneTitle, SWT.RADIO); + if (isDefault) { + adjustForDefault(noneButton); + } + + noneButton.addSelectionListener(listener); + } + + boolean foundSelection = false; + + // now add selection buttons for all registered extensions + SortedSet allEditorExtensions = TaskEditorExtensions.getTaskEditorExtensions(); + for (RegisteredTaskEditorExtension editorExtension : allEditorExtensions) { + String name = editorExtension.getName(); + + boolean isDefault = editorExtension.getId().equals(defaultExtensionId); + if (isDefault) { + name += LABEL_DEFAULT_SUFFIX; + } + Button button = toolkit.createButton(parent, name, SWT.RADIO); + if (isDefault) { + adjustForDefault(button); + } + + if (editorExtension.getId().equals(selectedExtensionId)) { + foundSelection = true; + button.setSelection(true); + } + button.setText(name); + button.setData(DATA_EDITOR_EXTENSION, editorExtension.getId()); + button.addSelectionListener(listener); + } + if (!foundSelection) { + noneButton.setSelection(true); + } + } + + private void adjustForDefault(Button button) { + Font font = button.getFont(); + button.setFont(getBold(font)); + } + + private Font getBold(Font font) { + FontData[] originalFontData = font.getFontData(); + FontData fontData = originalFontData[0]; + if ((fontData.getStyle() & SWT.BOLD) != 0) { + return font; + } + + FontRegistry fontRegistry = JFaceResources.getFontRegistry(); + String key = fontData.getName() + '-' + fontData.getHeight() + "-" + fontData.getLocale() + "-" + + fontData.getStyle() + "-bold"; + + if (!fontRegistry.hasValueFor(key)) { + FontData[] boldFontDatas = new FontData[originalFontData.length]; + int index = -1; + for (FontData fd : originalFontData) { + boldFontDatas[++index] = new FontData(fd.getName(), fd.getHeight(), fd.getStyle() | SWT.BOLD); + } + fontRegistry.put(key, boldFontDatas); + } + return fontRegistry.get(key); + } + + @Override + public IStatus validate() { + // nothing to validate + return null; + } +} Index: src/org/eclipse/mylyn/internal/tasks/ui/wizards/LocalRepositorySettingsPage.java =================================================================== RCS file: src/org/eclipse/mylyn/internal/tasks/ui/wizards/LocalRepositorySettingsPage.java diff -N src/org/eclipse/mylyn/internal/tasks/ui/wizards/LocalRepositorySettingsPage.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/mylyn/internal/tasks/ui/wizards/LocalRepositorySettingsPage.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Mylyn project committers 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 + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.ui.wizards; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.ui.wizards.AbstractExtensibleRepositorySettingsPage; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.ExpandableComposite; + +/** + * A settings page for the local repository properties dialog. Local repositories have no settings, however they may + * have settings contributed via the taskRepositoryPageContribution. + * + * @author David Green + */ +public class LocalRepositorySettingsPage extends AbstractExtensibleRepositorySettingsPage { + + public LocalRepositorySettingsPage(TaskRepository taskRepository) { + super("Local Repository Settings", "Configure the local repository", taskRepository); + } + + @Override + public String getConnectorKind() { + return LocalRepositoryConnector.CONNECTOR_KIND; + } + + public String getRepositoryUrl() { + return null; + } + + @Override + protected void createSettingControls(Composite parent) { + // nothing to do, since the local repository has no settings + } + + @Override + protected IStatus validate() { + // nothing to do + return null; + } + + @Override + protected void addContributions(Composite parentControl) { + super.addContributions(parentControl); + // expand the first contribution since we have no other settings + Control[] children = parentControl.getChildren(); + if (children.length > 0) { + if (children[0] instanceof ExpandableComposite) { + ((ExpandableComposite) children[0]).setExpanded(true); + } + } + } +}