| /******************************************************************************* |
| * Copyright (c) 2004, 2015 Tasktop Technologies 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: |
| * Tasktop Technologies - initial API and implementation |
| * David Green - fixes for bug 237503 |
| * Frank Becker - fixes for bug 252300 |
| * Kevin Sawicki - fixes for bug 306029 |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.tasks.ui.editors; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IMenuCreator; |
| import org.eclipse.jface.action.IToolBarManager; |
| import org.eclipse.jface.action.MenuManager; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.dialogs.IMessageProvider; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.text.TextSelection; |
| import org.eclipse.jface.util.LocalSelectionTransfer; |
| import org.eclipse.jface.util.SafeRunnable; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.ISelectionProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.mylyn.commons.core.StatusHandler; |
| import org.eclipse.mylyn.commons.ui.CommonImages; |
| import org.eclipse.mylyn.commons.ui.CommonUiUtil; |
| import org.eclipse.mylyn.commons.ui.GradientCanvas; |
| import org.eclipse.mylyn.commons.workbench.editors.CommonTextSupport; |
| import org.eclipse.mylyn.commons.workbench.forms.CommonFormUtil; |
| import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer; |
| import org.eclipse.mylyn.internal.tasks.core.ITaskListChangeListener; |
| import org.eclipse.mylyn.internal.tasks.core.ITaskListRunnable; |
| import org.eclipse.mylyn.internal.tasks.core.TaskContainerDelta; |
| import org.eclipse.mylyn.internal.tasks.core.data.ITaskDataManagerListener; |
| import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManagerEvent; |
| import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.ClearOutgoingAction; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.DeleteTaskEditorAction; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.NewSubTaskAction; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.OpenWithBrowserAction; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.SynchronizeEditorAction; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.AbstractTaskEditorSection; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.FocusTracker; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.Messages; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskAttachmentDropListener; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorActionContributor; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorActionPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorAttachmentPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorAttributePart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorContributionExtensionReader; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorDescriptionPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorFindSupport; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorNewCommentPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorOutlineNode; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorOutlinePage; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorPeoplePart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorPlanningPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorRichTextPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorSummaryPart; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.TaskMigrator; |
| import org.eclipse.mylyn.internal.tasks.ui.editors.ToolBarButtonContribution; |
| import org.eclipse.mylyn.internal.tasks.ui.util.AttachmentUtil; |
| import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal; |
| import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; |
| import org.eclipse.mylyn.tasks.core.IRepositoryElement; |
| import org.eclipse.mylyn.tasks.core.ITask; |
| import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; |
| import org.eclipse.mylyn.tasks.core.RepositoryStatus; |
| import org.eclipse.mylyn.tasks.core.TaskRepository; |
| import org.eclipse.mylyn.tasks.core.data.ITaskDataWorkingCopy; |
| import org.eclipse.mylyn.tasks.core.data.TaskAttribute; |
| import org.eclipse.mylyn.tasks.core.data.TaskData; |
| import org.eclipse.mylyn.tasks.core.data.TaskDataModel; |
| import org.eclipse.mylyn.tasks.core.data.TaskDataModelEvent; |
| import org.eclipse.mylyn.tasks.core.data.TaskDataModelListener; |
| import org.eclipse.mylyn.tasks.core.data.TaskRelation; |
| import org.eclipse.mylyn.tasks.core.sync.SubmitJob; |
| import org.eclipse.mylyn.tasks.core.sync.SubmitJobEvent; |
| import org.eclipse.mylyn.tasks.core.sync.SubmitJobListener; |
| import org.eclipse.mylyn.tasks.ui.AbstractRepositoryConnectorUi; |
| import org.eclipse.mylyn.tasks.ui.TasksUi; |
| import org.eclipse.mylyn.tasks.ui.TasksUiImages; |
| import org.eclipse.mylyn.tasks.ui.TasksUiUtil; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.dnd.DND; |
| import org.eclipse.swt.dnd.DropTarget; |
| import org.eclipse.swt.dnd.FileTransfer; |
| import org.eclipse.swt.dnd.TextTransfer; |
| import org.eclipse.swt.dnd.Transfer; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.swt.widgets.ScrollBar; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorSite; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.actions.ActionFactory; |
| import org.eclipse.ui.forms.FormColors; |
| import org.eclipse.ui.forms.IFormColors; |
| import org.eclipse.ui.forms.IFormPart; |
| import org.eclipse.ui.forms.IManagedForm; |
| import org.eclipse.ui.forms.editor.FormPage; |
| import org.eclipse.ui.forms.events.HyperlinkAdapter; |
| import org.eclipse.ui.forms.events.HyperlinkEvent; |
| import org.eclipse.ui.forms.widgets.ExpandableComposite; |
| import org.eclipse.ui.forms.widgets.FormToolkit; |
| import org.eclipse.ui.forms.widgets.ScrolledForm; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.services.IDisposable; |
| import org.eclipse.ui.views.contentoutline.IContentOutlinePage; |
| |
| /** |
| * Extend to provide a task editor page. |
| * |
| * @author Mik Kersten |
| * @author Rob Elves |
| * @author Steffen Pingel |
| * @since 3.0 |
| */ |
| public abstract class AbstractTaskEditorPage extends TaskFormPage |
| implements ISelectionProvider, ISelectionChangedListener { |
| |
| /** |
| * Causes the form page to reflow on resize. |
| */ |
| private final class ParentResizeHandler implements Listener { |
| private int generation; |
| |
| public void handleEvent(Event event) { |
| ++generation; |
| |
| Display.getCurrent().timerExec(300, new Runnable() { |
| int scheduledGeneration = generation; |
| |
| public void run() { |
| if (getManagedForm().getForm().isDisposed()) { |
| return; |
| } |
| |
| // only reflow if this is the latest generation to prevent |
| // unnecessary reflows while the form is being resized |
| if (scheduledGeneration == generation) { |
| getManagedForm().reflow(true); |
| } |
| } |
| }); |
| } |
| } |
| |
| private class SubmitTaskJobListener extends SubmitJobListener { |
| |
| private final boolean attachContext; |
| |
| private final boolean expandLastComment; |
| |
| public SubmitTaskJobListener(boolean attachContext, boolean expandLastComment) { |
| this.attachContext = attachContext; |
| this.expandLastComment = expandLastComment; |
| } |
| |
| @Override |
| public void done(SubmitJobEvent event) { |
| final SubmitJob job = event.getJob(); |
| PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { |
| |
| private void addTask(ITask newTask) { |
| AbstractTaskContainer parent = null; |
| AbstractTaskEditorPart actionPart = getPart(ID_PART_ACTIONS); |
| if (actionPart instanceof TaskEditorActionPart) { |
| parent = ((TaskEditorActionPart) actionPart).getCategory(); |
| } |
| TasksUiInternal.getTaskList().addTask(newTask, parent); |
| } |
| |
| public void run() { |
| try { |
| if (job.getStatus() == null) { |
| TasksUiInternal.synchronizeRepositoryInBackground(getTaskRepository()); |
| if (job.getTask().equals(getTask())) { |
| refresh(); |
| } else { |
| ITask oldTask = getTask(); |
| ITask newTask = job.getTask(); |
| addTask(newTask); |
| |
| TaskMigrator migrator = new TaskMigrator(oldTask); |
| migrator.setDelete(true); |
| migrator.setEditor(getTaskEditor()); |
| migrator.setMigrateDueDate( |
| !connector.hasRepositoryDueDate(getTaskRepository(), newTask, taskData)); |
| migrator.execute(newTask); |
| } |
| if (expandLastComment) { |
| expandLastComment(); |
| } |
| } |
| handleTaskSubmitted(new SubmitJobEvent(job)); |
| } finally { |
| showEditorBusy(false); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void taskSubmitted(SubmitJobEvent event, IProgressMonitor monitor) throws CoreException { |
| if (!getModel().getTaskData().isNew() && attachContext) { |
| TaskData taskData = getModel().getTaskData(); |
| TaskAttribute taskAttribute = null; |
| if (taskData != null) { |
| taskAttribute = taskData.getRoot().createMappedAttribute(TaskAttribute.NEW_ATTACHMENT); |
| } |
| AttachmentUtil.postContext(connector, getModel().getTaskRepository(), task, "", taskAttribute, monitor); //$NON-NLS-1$ |
| } |
| } |
| |
| @Override |
| public void taskSynchronized(SubmitJobEvent event, IProgressMonitor monitor) { |
| } |
| |
| } |
| |
| private final ITaskDataManagerListener TASK_DATA_LISTENER = new ITaskDataManagerListener() { |
| |
| public void taskDataUpdated(final TaskDataManagerEvent event) { |
| ITask task = event.getTask(); |
| if (task.equals(AbstractTaskEditorPage.this.getTask()) && event.getTaskDataUpdated()) { |
| refresh(task); |
| } |
| } |
| |
| private void refresh(final ITask task) { |
| PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { |
| public void run() { |
| if (refreshDisabled || busy) { |
| return; |
| } |
| |
| if (!isDirty() && task.getSynchronizationState() == SynchronizationState.SYNCHRONIZED) { |
| // automatically refresh if the user has not made any changes and there is no chance of missing incomings |
| AbstractTaskEditorPage.this.refresh(); |
| } else { |
| getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Task_has_incoming_changes, |
| IMessageProvider.WARNING, new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| AbstractTaskEditorPage.this.refresh(); |
| } |
| }); |
| setSubmitEnabled(false); |
| } |
| } |
| }); |
| } |
| |
| public void editsDiscarded(TaskDataManagerEvent event) { |
| if (event.getTask().equals(AbstractTaskEditorPage.this.getTask())) { |
| refresh(event.getTask()); |
| } |
| } |
| }; |
| |
| private class NotInTaskListListener extends HyperlinkAdapter implements ITaskListChangeListener, IDisposable { |
| |
| public NotInTaskListListener() { |
| TasksUiPlugin.getTaskList().addChangeListener(this); |
| } |
| |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| TasksUiPlugin.getTaskList().addTaskIfAbsent(task); |
| getTaskEditor().setMessage(null, IMessageProvider.NONE); |
| } |
| |
| public void containersChanged(Set<TaskContainerDelta> containers) { |
| // clears message if task is added to Task List. |
| for (TaskContainerDelta taskContainerDelta : containers) { |
| if (task.equals(taskContainerDelta.getElement())) { |
| if (taskContainerDelta.getKind().equals(TaskContainerDelta.Kind.ADDED)) { |
| PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { |
| public void run() { |
| getTaskEditor().setMessage(null, IMessageProvider.NONE); |
| } |
| }); |
| } |
| } |
| } |
| } |
| |
| public void dispose() { |
| TasksUiPlugin.getTaskList().removeChangeListener(this); |
| } |
| |
| }; |
| |
| private class MenuCreator implements IMenuCreator { |
| |
| private MenuManager menuManager; |
| |
| private Menu menu; |
| |
| public MenuCreator() { |
| } |
| |
| public void dispose() { |
| if (menu != null) { |
| menu.dispose(); |
| menu = null; |
| } |
| if (menuManager != null) { |
| menuManager.dispose(); |
| menuManager = null; |
| } |
| } |
| |
| public Menu getMenu(Control parent) { |
| if (menuManager == null) { |
| menuManager = new MenuManager(); |
| initialize(menuManager); |
| } |
| return menuManager.createContextMenu(parent); |
| } |
| |
| public Menu getMenu(Menu parent) { |
| return null; |
| } |
| |
| protected void initialize(MenuManager menuManager) { |
| } |
| |
| } |
| |
| private static final String ERROR_NOCONNECTIVITY = Messages.AbstractTaskEditorPage_Unable_to_submit_at_this_time; |
| |
| public static final String ID_PART_ACTIONS = "org.eclipse.mylyn.tasks.ui.editors.parts.actions"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_ATTACHMENTS = "org.eclipse.mylyn.tasks.ui.editors.parts.attachments"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_ATTRIBUTES = "org.eclipse.mylyn.tasks.ui.editors.parts.attributes"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_COMMENTS = "org.eclipse.mylyn.tasks.ui.editors.parts.comments"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_DESCRIPTION = "org.eclipse.mylyn.tasks.ui.editors.parts.descriptions"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_NEW_COMMENT = "org.eclipse.mylyn.tasks.ui.editors.parts.newComment"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_PEOPLE = "org.eclipse.mylyn.tasks.ui.editors.parts.people"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_PLANNING = "org.eclipse.mylyn.tasks.ui.editors.parts.planning"; //$NON-NLS-1$ |
| |
| public static final String ID_PART_SUMMARY = "org.eclipse.mylyn.tasks.ui.editors.parts.summary"; //$NON-NLS-1$ |
| |
| public static final String PATH_ACTIONS = "actions"; //$NON-NLS-1$ |
| |
| /** |
| * @since 3.7 |
| */ |
| public static final String PATH_ASSOCIATIONS = "associations"; //$NON-NLS-1$ |
| |
| public static final String PATH_ATTACHMENTS = "attachments"; //$NON-NLS-1$ |
| |
| public static final String PATH_ATTRIBUTES = "attributes"; //$NON-NLS-1$ |
| |
| public static final String PATH_COMMENTS = "comments"; //$NON-NLS-1$ |
| |
| public static final String PATH_HEADER = "header"; //$NON-NLS-1$ |
| |
| public static final String PATH_PEOPLE = "people"; //$NON-NLS-1$ |
| |
| public static final String PATH_PLANNING = "planning"; //$NON-NLS-1$ |
| |
| private AttributeEditorFactory attributeEditorFactory; |
| |
| private AttributeEditorToolkit attributeEditorToolkit; |
| |
| private AbstractRepositoryConnector connector; |
| |
| private final String connectorKind; |
| |
| private StructuredSelection defaultSelection; |
| |
| private Composite editorComposite; |
| |
| private ScrolledForm form; |
| |
| private boolean busy; |
| |
| private ISelection lastSelection; |
| |
| private TaskDataModel model; |
| |
| private boolean needsAddToCategory; |
| |
| private boolean reflow; |
| |
| private volatile boolean refreshDisabled; |
| |
| private final ListenerList selectionChangedListeners; |
| |
| private SynchronizeEditorAction synchronizeEditorAction; |
| |
| private ITask task; |
| |
| private TaskData taskData; |
| |
| private FormToolkit toolkit; |
| |
| private TaskEditorOutlinePage outlinePage; |
| |
| private TaskAttachmentDropListener defaultDropListener; |
| |
| private CommonTextSupport textSupport; |
| |
| private Composite partControl; |
| |
| private GradientCanvas footerComposite; |
| |
| private boolean needsFooter; |
| |
| private Button submitButton; |
| |
| private boolean submitEnabled; |
| |
| private boolean needsSubmit; |
| |
| private boolean needsSubmitButton; |
| |
| private boolean needsPrivateSection; |
| |
| private FocusTracker focusTracker; |
| |
| private TaskEditorFindSupport findSupport; |
| |
| /** |
| * @since 3.1 |
| */ |
| public AbstractTaskEditorPage(TaskEditor editor, String id, String label, String connectorKind) { |
| super(editor, id, label); |
| Assert.isNotNull(connectorKind); |
| this.connectorKind = connectorKind; |
| this.reflow = true; |
| this.selectionChangedListeners = new ListenerList(); |
| this.submitEnabled = true; |
| this.needsSubmit = true; |
| } |
| |
| public AbstractTaskEditorPage(TaskEditor editor, String connectorKind) { |
| this(editor, "id", "label", connectorKind); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * @since 3.1 |
| * @see FormPage#getEditor() |
| */ |
| @Override |
| public TaskEditor getEditor() { |
| return (TaskEditor) super.getEditor(); |
| } |
| |
| public void addSelectionChangedListener(ISelectionChangedListener listener) { |
| selectionChangedListeners.add(listener); |
| } |
| |
| public void appendTextToNewComment(String text) { |
| AbstractTaskEditorPart newCommentPart = getPart(ID_PART_NEW_COMMENT); |
| if (newCommentPart instanceof TaskEditorRichTextPart) { |
| ((TaskEditorRichTextPart) newCommentPart).appendText(text); |
| newCommentPart.setFocus(); |
| } |
| } |
| |
| public boolean canPerformAction(String actionId) { |
| if (findSupport != null && actionId.equals(ActionFactory.FIND.getId())) { |
| return true; |
| } |
| return CommonTextSupport.canPerformAction(actionId, EditorUtil.getFocusControl(this)); |
| } |
| |
| public void close() { |
| if (Display.getCurrent() != null) { |
| getSite().getPage().closeEditor(getTaskEditor(), false); |
| } else { |
| // TODO consider removing asyncExec() |
| Display activeDisplay = getSite().getShell().getDisplay(); |
| activeDisplay.asyncExec(new Runnable() { |
| public void run() { |
| if (getSite() != null && getSite().getPage() != null && !getManagedForm().getForm().isDisposed()) { |
| if (getTaskEditor() != null) { |
| getSite().getPage().closeEditor(getTaskEditor(), false); |
| } else { |
| getSite().getPage().closeEditor(AbstractTaskEditorPage.this, false); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| protected AttributeEditorFactory createAttributeEditorFactory() { |
| return new AttributeEditorFactory(getModel(), getTaskRepository(), getEditorSite()); |
| } |
| |
| AttributeEditorToolkit createAttributeEditorToolkit() { |
| return new AttributeEditorToolkit(textSupport); |
| } |
| |
| @Override |
| public void createPartControl(Composite parent) { |
| parent.addListener(SWT.Resize, new ParentResizeHandler()); |
| |
| if (needsFooter()) { |
| partControl = getEditor().getToolkit().createComposite(parent); |
| GridLayout partControlLayout = new GridLayout(1, false); |
| partControlLayout.marginWidth = 0; |
| partControlLayout.marginHeight = 0; |
| partControlLayout.verticalSpacing = 0; |
| partControl.setLayout(partControlLayout); |
| |
| super.createPartControl(partControl); |
| getManagedForm().getForm().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| |
| footerComposite = new GradientCanvas(partControl, SWT.NONE); |
| footerComposite.setSeparatorVisible(true); |
| footerComposite.setSeparatorAlignment(SWT.TOP); |
| GridLayout headLayout = new GridLayout(); |
| headLayout.marginHeight = 0; |
| headLayout.marginWidth = 0; |
| headLayout.horizontalSpacing = 0; |
| headLayout.verticalSpacing = 0; |
| headLayout.numColumns = 1; |
| footerComposite.setLayout(headLayout); |
| footerComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| |
| FormColors colors = getEditor().getToolkit().getColors(); |
| Color top = colors.getColor(IFormColors.H_GRADIENT_END); |
| Color bottom = colors.getColor(IFormColors.H_GRADIENT_START); |
| footerComposite.setBackgroundGradient(new Color[] { bottom, top }, new int[] { 100 }, true); |
| |
| footerComposite.putColor(IFormColors.H_BOTTOM_KEYLINE1, colors.getColor(IFormColors.H_BOTTOM_KEYLINE1)); |
| footerComposite.putColor(IFormColors.H_BOTTOM_KEYLINE2, colors.getColor(IFormColors.H_BOTTOM_KEYLINE2)); |
| footerComposite.putColor(IFormColors.H_HOVER_LIGHT, colors.getColor(IFormColors.H_HOVER_LIGHT)); |
| footerComposite.putColor(IFormColors.H_HOVER_FULL, colors.getColor(IFormColors.H_HOVER_FULL)); |
| footerComposite.putColor(IFormColors.TB_TOGGLE, colors.getColor(IFormColors.TB_TOGGLE)); |
| footerComposite.putColor(IFormColors.TB_TOGGLE_HOVER, colors.getColor(IFormColors.TB_TOGGLE_HOVER)); |
| footerComposite.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false)); |
| |
| createFooterContent(footerComposite); |
| } else { |
| super.createPartControl(parent); |
| } |
| } |
| |
| @Override |
| protected void createFormContent(final IManagedForm managedForm) { |
| super.createFormContent(managedForm); |
| form = managedForm.getForm(); |
| |
| toolkit = managedForm.getToolkit(); |
| registerDefaultDropListener(form); |
| CommonFormUtil.disableScrollingOnFocus(form); |
| |
| try { |
| setReflow(false); |
| |
| editorComposite = form.getBody(); |
| // TODO consider using TableWrapLayout, it makes resizing much faster |
| GridLayout editorLayout = new GridLayout(); |
| editorLayout.verticalSpacing = 0; |
| editorComposite.setLayout(editorLayout); |
| editorComposite.setMenu(getTaskEditor().getMenu()); |
| |
| AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin.getConnectorUi(getConnectorKind()); |
| if (connectorUi == null) { |
| PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { |
| public void run() { |
| getTaskEditor().setMessage( |
| Messages.AbstractTaskEditorPage_Synchronize_to_update_editor_contents, |
| IMessageProvider.INFORMATION, new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| AbstractTaskEditorPage.this.refresh(); |
| } |
| }); |
| } |
| }); |
| } |
| |
| if (taskData != null) { |
| createFormContentInternal(); |
| } |
| |
| updateHeaderMessage(); |
| } finally { |
| setReflow(true); |
| |
| // if the editor is restored as part of workbench startup then we must reflow() asynchronously |
| // otherwise the editor layout is incorrect |
| boolean reflowRequired = calculateReflowRequired(form); |
| |
| if (reflowRequired) { |
| Display.getCurrent().asyncExec(new Runnable() { |
| public void run() { |
| // this fixes a problem with layout that occurs when an editor |
| // is restored before the workbench is fully initialized |
| reflow(); |
| } |
| }); |
| } |
| } |
| } |
| |
| private boolean calculateReflowRequired(ScrolledForm form) { |
| Composite stopComposite = getEditor().getEditorParent().getParent().getParent(); |
| Composite composite = form.getParent(); |
| while (composite != null) { |
| Rectangle clientArea = composite.getClientArea(); |
| if (clientArea.width > 1) { |
| return false; |
| } |
| if (composite == stopComposite) { |
| return true; |
| } |
| composite = composite.getParent(); |
| } |
| return true; |
| } |
| |
| private void createFormContentInternal() { |
| // end life-cycle of previous editor controls |
| if (attributeEditorToolkit != null) { |
| attributeEditorToolkit.dispose(); |
| } |
| |
| // start life-cycle of previous editor controls |
| if (attributeEditorFactory == null) { |
| attributeEditorFactory = createAttributeEditorFactory(); |
| Assert.isNotNull(attributeEditorFactory); |
| } |
| attributeEditorToolkit = createAttributeEditorToolkit(); |
| Assert.isNotNull(attributeEditorToolkit); |
| attributeEditorToolkit.setMenu(editorComposite.getMenu()); |
| attributeEditorFactory.setEditorToolkit(attributeEditorToolkit); |
| |
| createParts(); |
| |
| focusTracker = new FocusTracker(); |
| focusTracker.track(editorComposite); |
| } |
| |
| protected TaskDataModel createModel(TaskEditorInput input) throws CoreException { |
| ITaskDataWorkingCopy taskDataState; |
| try { |
| taskDataState = TasksUi.getTaskDataManager().getWorkingCopy(task); |
| } catch (OperationCanceledException e) { |
| // XXX retry once to work around bug 235479 |
| taskDataState = TasksUi.getTaskDataManager().getWorkingCopy(task); |
| } |
| TaskRepository taskRepository = TasksUi.getRepositoryManager().getRepository(taskDataState.getConnectorKind(), |
| taskDataState.getRepositoryUrl()); |
| |
| return new TaskDataModel(taskRepository, input.getTask(), taskDataState); |
| } |
| |
| /** |
| * To suppress a section, just remove its descriptor from the list. To add your own section in a specific order on |
| * the page, use the path value for where you want it to appear (your descriptor will appear after previously added |
| * descriptors with the same path), and add it to the descriptors list in your override of this method. |
| */ |
| protected Set<TaskEditorPartDescriptor> createPartDescriptors() { |
| Set<TaskEditorPartDescriptor> descriptors = new LinkedHashSet<TaskEditorPartDescriptor>(); |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_SUMMARY) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorSummaryPart(); |
| } |
| }.setPath(PATH_HEADER)); |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_ATTRIBUTES) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorAttributePart(); |
| } |
| }.setPath(PATH_ATTRIBUTES)); |
| if (!taskData.isNew() && connector.getTaskAttachmentHandler() != null |
| && (AttachmentUtil.canDownloadAttachment(task) || AttachmentUtil.canUploadAttachment(task))) { |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_ATTACHMENTS) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorAttachmentPart(); |
| } |
| }.setPath(PATH_ATTACHMENTS)); |
| } |
| if (needsPrivateSection() || taskData.isNew()) { |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_PLANNING) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorPlanningPart(); |
| } |
| }.setPath(PATH_PLANNING)); |
| } |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_DESCRIPTION) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| TaskEditorDescriptionPart part = new TaskEditorDescriptionPart(); |
| if (getModel().getTaskData().isNew()) { |
| part.setExpandVertically(true); |
| part.setSectionStyle(ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED); |
| } |
| return part; |
| } |
| }.setPath(PATH_COMMENTS)); |
| if (!taskData.isNew()) { |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_COMMENTS) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorCommentPart(); |
| } |
| }.setPath(PATH_COMMENTS)); |
| } |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_NEW_COMMENT) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorNewCommentPart(); |
| } |
| }.setPath(PATH_COMMENTS)); |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_ACTIONS) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorActionPart(); |
| } |
| }.setPath(PATH_ACTIONS)); |
| descriptors.add(new TaskEditorPartDescriptor(ID_PART_PEOPLE) { |
| @Override |
| public AbstractTaskEditorPart createPart() { |
| return new TaskEditorPeoplePart(); |
| } |
| }.setPath(PATH_PEOPLE)); |
| |
| descriptors.addAll(getContributionPartDescriptors()); |
| return descriptors; |
| } |
| |
| private Collection<TaskEditorPartDescriptor> getContributionPartDescriptors() { |
| return TaskEditorContributionExtensionReader.getRepositoryEditorContributions(); |
| } |
| |
| protected void createParts() { |
| List<TaskEditorPartDescriptor> descriptors = new LinkedList<TaskEditorPartDescriptor>(createPartDescriptors()); |
| // single column |
| createParts(PATH_HEADER, editorComposite, descriptors); |
| createParts(PATH_ASSOCIATIONS, editorComposite, descriptors); |
| createParts(PATH_ATTRIBUTES, editorComposite, descriptors); |
| createParts(PATH_ATTACHMENTS, editorComposite, descriptors); |
| createParts(PATH_PLANNING, editorComposite, descriptors); |
| createParts(PATH_COMMENTS, editorComposite, descriptors); |
| // two column |
| Composite bottomComposite = toolkit.createComposite(editorComposite); |
| bottomComposite.setLayout(GridLayoutFactory.fillDefaults().numColumns(2).create()); |
| GridDataFactory.fillDefaults().grab(true, false).applyTo(bottomComposite); |
| createParts(PATH_ACTIONS, bottomComposite, descriptors); |
| createParts(PATH_PEOPLE, bottomComposite, descriptors); |
| bottomComposite.pack(true); |
| |
| } |
| |
| private void createParts(String path, final Composite parent, |
| final Collection<TaskEditorPartDescriptor> descriptors) { |
| for (Iterator<TaskEditorPartDescriptor> it = descriptors.iterator(); it.hasNext();) { |
| final TaskEditorPartDescriptor descriptor = it.next(); |
| if (path == null || path.equals(descriptor.getPath())) { |
| SafeRunner.run(new ISafeRunnable() { |
| public void handleException(Throwable e) { |
| StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, |
| "Error creating task editor part: \"" + descriptor.getId() + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| public void run() throws Exception { |
| AbstractTaskEditorPart part = descriptor.createPart(); |
| part.setPartId(descriptor.getId()); |
| initializePart(parent, part, descriptors); |
| } |
| }); |
| it.remove(); |
| } |
| } |
| } |
| |
| private void createSubParts(final AbstractTaskEditorSection parentPart, |
| final Collection<TaskEditorPartDescriptor> descriptors) { |
| for (final TaskEditorPartDescriptor descriptor : descriptors) { |
| int i; |
| String path = descriptor.getPath(); |
| if (path != null && (i = path.indexOf("/")) != -1) { //$NON-NLS-1$ |
| String parentId = path.substring(0, i); |
| final String subPath = path.substring(i + 1); |
| if (parentId.equals(parentPart.getPartId())) { |
| SafeRunner.run(new ISafeRunnable() { |
| public void handleException(Throwable e) { |
| StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, |
| "Error creating task editor part: \"" + descriptor.getId() + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| public void run() throws Exception { |
| AbstractTaskEditorPart part = descriptor.createPart(); |
| part.setPartId(descriptor.getId()); |
| getManagedForm().addPart(part); |
| part.initialize(AbstractTaskEditorPage.this); |
| parentPart.addSubPart(subPath, part); |
| } |
| }); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| if (textSupport != null) { |
| textSupport.dispose(); |
| } |
| if (attributeEditorToolkit != null) { |
| attributeEditorToolkit.dispose(); |
| } |
| TasksUiPlugin.getTaskDataManager().removeListener(TASK_DATA_LISTENER); |
| super.dispose(); |
| } |
| |
| public void doAction(String actionId) { |
| if (findSupport != null && actionId.equals(ActionFactory.FIND.getId())) { |
| findSupport.toggleFind(); |
| } |
| CommonTextSupport.doAction(actionId, EditorUtil.getFocusControl(this)); |
| } |
| |
| @Override |
| public void doSave(IProgressMonitor monitor) { |
| if (!isDirty()) { |
| return; |
| } |
| |
| getManagedForm().commit(true); |
| |
| if (model.isDirty()) { |
| try { |
| model.save(monitor); |
| } catch (final CoreException e) { |
| StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Error saving task", e)); //$NON-NLS-1$ |
| getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Could_not_save_task, IMessageProvider.ERROR, |
| new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent event) { |
| TasksUiInternal.displayStatus(Messages.AbstractTaskEditorPage_Save_failed, |
| e.getStatus()); |
| } |
| }); |
| } |
| } |
| // update the summary of unsubmitted repository tasks |
| if (getTask().getSynchronizationState() == SynchronizationState.OUTGOING_NEW) { |
| String summary = connector.getTaskMapping(model.getTaskData()).getSummary(); |
| try { |
| TasksUiPlugin.getTaskList().run(new ITaskListRunnable() { |
| public void execute(IProgressMonitor monitor) throws CoreException { |
| task.setSummary(summary); |
| } |
| }); |
| TasksUiPlugin.getTaskList().notifyElementChanged(task); |
| } catch (CoreException e) { |
| StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, |
| "Failed to set summary for task \"" + task + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| TasksUiPlugin.getTaskList().addTaskIfAbsent(task); |
| |
| updateHeaderMessage(); |
| getManagedForm().dirtyStateChanged(); |
| getTaskEditor().updateHeaderToolBar(); |
| } |
| |
| @Override |
| public void doSaveAs() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public void doSubmit() { |
| if (!submitEnabled || !needsSubmit()) { |
| return; |
| } |
| |
| try { |
| showEditorBusy(true); |
| |
| doSave(new NullProgressMonitor()); |
| |
| TaskAttribute newCommentAttribute = getModel().getTaskData() |
| .getRoot() |
| .getMappedAttribute(TaskAttribute.COMMENT_NEW); |
| boolean expandLastComment = newCommentAttribute != null |
| && getModel().getChangedAttributes().contains(newCommentAttribute); |
| |
| SubmitJob submitJob = TasksUiInternal.getJobFactory().createSubmitTaskJob(connector, |
| getModel().getTaskRepository(), task, getModel().getTaskData(), |
| getModel().getChangedOldAttributes()); |
| submitJob.addSubmitJobListener(new SubmitTaskJobListener(getAttachContext(), expandLastComment)); |
| submitJob.schedule(); |
| } catch (RuntimeException e) { |
| showEditorBusy(false); |
| throw e; |
| } |
| |
| TasksUiPlugin.getTaskList().addTaskIfAbsent(task); |
| } |
| |
| /** |
| * Override for customizing the tool bar. |
| */ |
| @Override |
| public void fillToolBar(IToolBarManager toolBarManager) { |
| final TaskRepository taskRepository = (model != null) ? getModel().getTaskRepository() : null; |
| |
| if (taskData == null) { |
| synchronizeEditorAction = new SynchronizeEditorAction(); |
| synchronizeEditorAction.selectionChanged(new StructuredSelection(getTaskEditor())); |
| toolBarManager.appendToGroup("repository", synchronizeEditorAction); //$NON-NLS-1$ |
| } else { |
| if (taskData.isNew()) { |
| DeleteTaskEditorAction deleteAction = new DeleteTaskEditorAction(getTask()); |
| deleteAction.setImageDescriptor(CommonImages.CLEAR); |
| toolBarManager.appendToGroup("new", deleteAction); //$NON-NLS-1$ |
| } else if (taskRepository != null) { |
| ClearOutgoingAction clearOutgoingAction = new ClearOutgoingAction( |
| Collections.singletonList((IRepositoryElement) task)); |
| (clearOutgoingAction).setTaskEditorPage(this); |
| if (clearOutgoingAction.isEnabled()) { |
| toolBarManager.appendToGroup("new", clearOutgoingAction); //$NON-NLS-1$ |
| } |
| |
| if (task.getSynchronizationState() != SynchronizationState.OUTGOING_NEW) { |
| synchronizeEditorAction = new SynchronizeEditorAction(); |
| synchronizeEditorAction.selectionChanged(new StructuredSelection(getTaskEditor())); |
| toolBarManager.appendToGroup("repository", synchronizeEditorAction); //$NON-NLS-1$ |
| } |
| |
| NewSubTaskAction newSubTaskAction = new NewSubTaskAction(); |
| newSubTaskAction.selectionChanged(newSubTaskAction, new StructuredSelection(task)); |
| if (newSubTaskAction.isEnabled()) { |
| toolBarManager.appendToGroup("new", newSubTaskAction); //$NON-NLS-1$ |
| } |
| |
| AbstractRepositoryConnectorUi connectorUi = TasksUiPlugin.getConnectorUi(taskData.getConnectorKind()); |
| if (connectorUi != null) { |
| final String historyUrl = connectorUi.getTaskHistoryUrl(taskRepository, task); |
| if (historyUrl != null) { |
| final Action historyAction = new Action() { |
| @Override |
| public void run() { |
| TasksUiUtil.openUrl(historyUrl); |
| } |
| }; |
| |
| historyAction.setText(Messages.AbstractTaskEditorPage_History); |
| historyAction.setImageDescriptor(TasksUiImages.TASK_REPOSITORY_HISTORY); |
| historyAction.setToolTipText(Messages.AbstractTaskEditorPage_History); |
| if (getEditor().openWithBrowserAction != null) { |
| getEditor().openWithBrowserAction.setMenuCreator(new MenuCreator() { |
| @Override |
| protected void initialize(MenuManager menuManager) { |
| OpenWithBrowserAction openWithBrowserAction = new OpenWithBrowserAction(); |
| openWithBrowserAction.selectionChanged(new StructuredSelection(task)); |
| menuManager.add(openWithBrowserAction); |
| menuManager.add(new Separator()); |
| menuManager.add(historyAction); |
| }; |
| }); |
| } else { |
| toolBarManager.prependToGroup("open", historyAction); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| if (needsSubmitButton()) { |
| ToolBarButtonContribution submitButtonContribution = new ToolBarButtonContribution( |
| "org.eclipse.mylyn.tasks.toolbars.submit") { //$NON-NLS-1$ |
| @Override |
| protected Control createButton(Composite composite) { |
| submitButton = new Button(composite, SWT.FLAT); |
| submitButton.setText(Messages.TaskEditorActionPart_Submit + " "); //$NON-NLS-1$ |
| submitButton.setImage(CommonImages.getImage(TasksUiImages.REPOSITORY_SUBMIT)); |
| submitButton.setBackground(null); |
| submitButton.addListener(SWT.Selection, new Listener() { |
| public void handleEvent(Event e) { |
| doSubmit(); |
| } |
| }); |
| return submitButton; |
| } |
| }; |
| submitButtonContribution.marginLeft = 10; |
| toolBarManager.add(submitButtonContribution); |
| } |
| if (findSupport != null) { |
| findSupport.addFindAction(toolBarManager); |
| } |
| } |
| } |
| |
| protected void fireSelectionChanged(ISelection selection) { |
| // create an event |
| final SelectionChangedEvent event = new SelectionChangedEvent(this, selection); |
| |
| // fire the event |
| Object[] listeners = selectionChangedListeners.getListeners(); |
| for (Object listener : listeners) { |
| final ISelectionChangedListener l = (ISelectionChangedListener) listener; |
| SafeRunner.run(new SafeRunnable() { |
| public void run() { |
| l.selectionChanged(event); |
| } |
| }); |
| } |
| } |
| |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| @Override |
| public Object getAdapter(Class adapter) { |
| if (adapter == IContentOutlinePage.class) { |
| updateOutlinePage(); |
| return outlinePage; |
| } |
| // TODO 3.5 replace by getTextSupport() method |
| if (adapter == CommonTextSupport.class) { |
| return textSupport; |
| } |
| return super.getAdapter(adapter); |
| } |
| |
| private void updateOutlinePage() { |
| if (outlinePage == null) { |
| outlinePage = new TaskEditorOutlinePage(); |
| outlinePage.addSelectionChangedListener(new ISelectionChangedListener() { |
| public void selectionChanged(SelectionChangedEvent event) { |
| ISelection selection = event.getSelection(); |
| if (selection instanceof StructuredSelection) { |
| Object select = ((StructuredSelection) selection).getFirstElement(); |
| selectReveal(select); |
| getEditor().setActivePage(getId()); |
| } |
| } |
| }); |
| } |
| if (getModel() != null) { |
| TaskEditorOutlineNode node = TaskEditorOutlineNode.parse(getModel().getTaskData(), false); |
| outlinePage.setInput(getTaskRepository(), node); |
| } else { |
| outlinePage.setInput(null, null); |
| } |
| } |
| |
| private boolean getAttachContext() { |
| AbstractTaskEditorPart actionPart = getPart(ID_PART_ACTIONS); |
| if (actionPart instanceof TaskEditorActionPart) { |
| return ((TaskEditorActionPart) actionPart).getAttachContext(); |
| } |
| return false; |
| } |
| |
| public AttributeEditorFactory getAttributeEditorFactory() { |
| return attributeEditorFactory; |
| } |
| |
| public AttributeEditorToolkit getAttributeEditorToolkit() { |
| return attributeEditorToolkit; |
| } |
| |
| public AbstractRepositoryConnector getConnector() { |
| return connector; |
| } |
| |
| public String getConnectorKind() { |
| return connectorKind; |
| } |
| |
| /** |
| * @return The composite for the whole editor. |
| */ |
| public Composite getEditorComposite() { |
| return editorComposite; |
| } |
| |
| public TaskDataModel getModel() { |
| return model; |
| } |
| |
| public AbstractTaskEditorPart getPart(String partId) { |
| Assert.isNotNull(partId); |
| if (getManagedForm() != null) { |
| for (IFormPart part : getManagedForm().getParts()) { |
| if (part instanceof AbstractTaskEditorPart) { |
| AbstractTaskEditorPart taskEditorPart = (AbstractTaskEditorPart) part; |
| if (partId.equals(taskEditorPart.getPartId())) { |
| return taskEditorPart; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| public ISelection getSelection() { |
| return lastSelection; |
| } |
| |
| public ITask getTask() { |
| return task; |
| } |
| |
| public TaskEditor getTaskEditor() { |
| return getEditor(); |
| } |
| |
| public TaskRepository getTaskRepository() { |
| // FIXME model can be null |
| return getModel().getTaskRepository(); |
| } |
| |
| /** |
| * Invoked after task submission has completed. This method is invoked on the UI thread in all cases whether |
| * submission was successful, canceled or failed. The value returned by <code>event.getJob().getStatus()</code> |
| * indicates the result of the submit job. Sub-classes may override but are encouraged to invoke the super method. |
| * |
| * @since 3.2 |
| * @see SubmitJob |
| */ |
| protected void handleTaskSubmitted(SubmitJobEvent event) { |
| IStatus status = event.getJob().getStatus(); |
| if (status != null && status.getSeverity() != IStatus.CANCEL) { |
| handleSubmitError(event.getJob()); |
| } |
| } |
| |
| private void handleSubmitError(SubmitJob job) { |
| if (form != null && !form.isDisposed()) { |
| final IStatus status = job.getStatus(); |
| String message = null; |
| if (status.getCode() == RepositoryStatus.REPOSITORY_COMMENT_REQUIRED) { |
| TasksUiInternal.displayStatus(Messages.AbstractTaskEditorPage_Comment_required, status); |
| AbstractTaskEditorPart newCommentPart = getPart(ID_PART_NEW_COMMENT); |
| if (newCommentPart != null) { |
| newCommentPart.setFocus(); |
| } |
| return; |
| } else if (status.getCode() == RepositoryStatus.ERROR_REPOSITORY_LOGIN) { |
| if (TasksUiUtil.openEditRepositoryWizard(getTaskRepository()) == Window.OK) { |
| submitEnabled = true; |
| doSubmit(); |
| return; |
| } else { |
| message = getMessageFromStatus(status); |
| } |
| } else if (status.getCode() == RepositoryStatus.ERROR_IO) { |
| message = ERROR_NOCONNECTIVITY; |
| } else { |
| message = getMessageFromStatus(status); |
| } |
| getTaskEditor().setMessage(message, IMessageProvider.ERROR, new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| TasksUiInternal.displayStatus(Messages.AbstractTaskEditorPage_Submit_failed, status); |
| } |
| }); |
| } |
| } |
| |
| private String getMessageFromStatus(final IStatus status) { |
| String message; |
| if (status.getMessage().length() > 0) { |
| if (status.getMessage().length() < 256) { |
| message = Messages.AbstractTaskEditorPage_Submit_failed_ + status.getMessage(); |
| } else { |
| message = Messages.AbstractTaskEditorPage_Submit_failed_ + status.getMessage().substring(0, 256) |
| + "..."; //$NON-NLS-1$ |
| } |
| } else { |
| message = Messages.AbstractTaskEditorPage_Submit_failed; |
| } |
| return message.replaceAll("\n", " ").replaceAll("\r", " "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| |
| @Override |
| public void init(IEditorSite site, IEditorInput input) { |
| super.init(site, input); |
| |
| TaskEditorInput taskEditorInput = (TaskEditorInput) input; |
| this.task = taskEditorInput.getTask(); |
| this.defaultSelection = new StructuredSelection(task); |
| this.lastSelection = defaultSelection; |
| IHandlerService handlerService = (IHandlerService) getSite().getService(IHandlerService.class); |
| this.textSupport = new CommonTextSupport(handlerService); |
| this.textSupport.setSelectionChangedListener(this); |
| createFindSupport(); |
| |
| initModel(taskEditorInput); |
| |
| TasksUiPlugin.getTaskDataManager().addListener(TASK_DATA_LISTENER); |
| } |
| |
| private void initModel(TaskEditorInput input) { |
| Assert.isTrue(model == null); |
| try { |
| this.model = createModel(input); |
| this.connector = TasksUi.getRepositoryManager().getRepositoryConnector(getConnectorKind()); |
| setTaskData(model.getTaskData()); |
| model.addModelListener(new TaskDataModelListener() { |
| @Override |
| public void attributeChanged(TaskDataModelEvent event) { |
| IManagedForm form = getManagedForm(); |
| if (form != null && !form.isDirty()) { |
| form.dirtyStateChanged(); |
| } |
| } |
| }); |
| setNeedsAddToCategory(model.getTaskData().isNew()); |
| } catch (final CoreException e) { |
| StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Error opening task", e)); //$NON-NLS-1$ |
| getTaskEditor().setStatus(Messages.AbstractTaskEditorPage_Error_opening_task, |
| Messages.AbstractTaskEditorPage_Open_failed, e.getStatus()); |
| } |
| } |
| |
| private void initializePart(Composite parent, AbstractTaskEditorPart part, |
| Collection<TaskEditorPartDescriptor> descriptors) { |
| getManagedForm().addPart(part); |
| part.initialize(this); |
| if (part instanceof AbstractTaskEditorSection) { |
| createSubParts((AbstractTaskEditorSection) part, descriptors); |
| } |
| if (parent != null) { |
| part.createControl(parent, toolkit); |
| if (part.getControl() != null) { |
| if (ID_PART_ACTIONS.equals(part.getPartId())) { |
| // do not expand horizontally |
| GridDataFactory.fillDefaults() |
| .align(SWT.FILL, SWT.FILL) |
| .grab(false, false) |
| .applyTo(part.getControl()); |
| } else { |
| if (part.getExpandVertically()) { |
| GridDataFactory.fillDefaults() |
| .align(SWT.FILL, SWT.FILL) |
| .grab(true, true) |
| .applyTo(part.getControl()); |
| } else { |
| GridDataFactory.fillDefaults() |
| .align(SWT.FILL, SWT.TOP) |
| .grab(true, false) |
| .applyTo(part.getControl()); |
| } |
| } |
| // for outline |
| if (ID_PART_COMMENTS.equals(part.getPartId())) { |
| EditorUtil.setMarker(part.getControl(), TaskEditorOutlineNode.LABEL_COMMENTS); |
| } else if (ID_PART_ATTACHMENTS.equals(part.getPartId())) { |
| EditorUtil.setMarker(part.getControl(), TaskEditorOutlineNode.LABEL_ATTACHMENTS); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Subclasses may override to disable the task editor find functionality. |
| * |
| * @since 3.11 |
| */ |
| protected void createFindSupport() { |
| this.findSupport = new TaskEditorFindSupport(this); |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return isModelDirty() || isFormDirty(); |
| } |
| |
| private boolean isFormDirty() { |
| IManagedForm form = getManagedForm(); |
| return form != null && form.isDirty(); |
| } |
| |
| private boolean isModelDirty() { |
| TaskDataModel model = getModel(); |
| return model != null && model.isDirty(); |
| } |
| |
| @Override |
| public boolean isSaveAsAllowed() { |
| return false; |
| } |
| |
| public boolean needsAddToCategory() { |
| return needsAddToCategory; |
| } |
| |
| /** |
| * Force a re-layout of entire form. |
| */ |
| public void reflow() { |
| if (reflow) { |
| try { |
| form.setRedraw(false); |
| // help the layout managers: ensure that the form width always matches |
| // the parent client area width. |
| Rectangle parentClientArea = form.getParent().getClientArea(); |
| Point formSize = form.getSize(); |
| if (formSize.x != parentClientArea.width) { |
| ScrollBar verticalBar = form.getVerticalBar(); |
| int verticalBarWidth = verticalBar != null ? verticalBar.getSize().x : 15; |
| form.setSize(parentClientArea.width - verticalBarWidth, formSize.y); |
| } |
| |
| form.layout(true, false); |
| form.reflow(true); |
| } finally { |
| form.setRedraw(true); |
| } |
| } |
| } |
| |
| /** |
| * Updates the editor contents in place. |
| * |
| * @deprecated Use {@link #refresh()} instead |
| */ |
| @Deprecated |
| public void refreshFormContent() { |
| refresh(); |
| } |
| |
| /** |
| * Updates the editor contents in place. |
| */ |
| @Override |
| public void refresh() { |
| if (getManagedForm() == null || getManagedForm().getForm().isDisposed()) { |
| // editor possibly closed or page has not been initialized |
| return; |
| } |
| |
| try { |
| showEditorBusy(true); |
| |
| boolean hasIncoming = false; |
| |
| if (getTask() != null) { |
| hasIncoming = getTask().getSynchronizationState().isIncoming(); |
| } |
| if (model != null) { |
| doSave(new NullProgressMonitor()); |
| refreshInput(); |
| } else { |
| initModel(getTaskEditor().getTaskEditorInput()); |
| } |
| |
| if (taskData != null) { |
| try { |
| setReflow(false); |
| // prevent menu from being disposed when disposing control on the form during refresh |
| Menu menu = editorComposite.getMenu(); |
| CommonUiUtil.setMenu(editorComposite, null); |
| |
| // clear old controls and parts |
| for (Control control : editorComposite.getChildren()) { |
| control.dispose(); |
| } |
| if (focusTracker != null) { |
| focusTracker.reset(); |
| } |
| lastSelection = null; |
| for (IFormPart part : getManagedForm().getParts()) { |
| part.dispose(); |
| getManagedForm().removePart(part); |
| } |
| |
| // restore menu |
| editorComposite.setMenu(menu); |
| |
| createFormContentInternal(); |
| |
| getTaskEditor().setMessage(null, 0); |
| if (hasIncoming) { |
| getTaskEditor().setActivePage(getId()); |
| } |
| |
| setSubmitEnabled(true); |
| } finally { |
| setReflow(true); |
| } |
| } |
| |
| updateOutlinePage(); |
| updateHeaderMessage(); |
| getManagedForm().dirtyStateChanged(); |
| getTaskEditor().updateHeaderToolBar(); |
| } finally { |
| showEditorBusy(false); |
| } |
| reflow(); |
| } |
| |
| private void refreshInput() { |
| try { |
| refreshDisabled = true; |
| model.refresh(null); |
| } catch (CoreException e) { |
| getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Failed_to_read_task_data_ + e.getMessage(), |
| IMessageProvider.ERROR); |
| taskData = null; |
| return; |
| } finally { |
| refreshDisabled = false; |
| } |
| |
| setTaskData(model.getTaskData()); |
| } |
| |
| /** |
| * Registers a drop listener for <code>control</code>. The default implementation registers a listener for attaching |
| * files. Does nothing if the editor is showing a new task. |
| * <p> |
| * Clients may override. |
| * </p> |
| * |
| * @param control |
| * the control to register the listener for |
| */ |
| public void registerDefaultDropListener(final Control control) { |
| if (getModel() == null || getModel().getTaskData().isNew()) { |
| return; |
| } |
| |
| DropTarget target = new DropTarget(control, DND.DROP_COPY | DND.DROP_DEFAULT); |
| LocalSelectionTransfer localSelectionTransfer = LocalSelectionTransfer.getTransfer(); |
| final TextTransfer textTransfer = TextTransfer.getInstance(); |
| final FileTransfer fileTransfer = FileTransfer.getInstance(); |
| Transfer[] types = new Transfer[] { localSelectionTransfer, textTransfer, fileTransfer }; |
| target.setTransfer(types); |
| if (defaultDropListener == null) { |
| defaultDropListener = new TaskAttachmentDropListener(this); |
| } |
| target.addDropListener(defaultDropListener); |
| } |
| |
| public void removeSelectionChangedListener(ISelectionChangedListener listener) { |
| selectionChangedListeners.remove(listener); |
| } |
| |
| public void selectionChanged(Object element) { |
| selectionChanged(new SelectionChangedEvent(this, new StructuredSelection(element))); |
| } |
| |
| public void selectionChanged(SelectionChangedEvent event) { |
| ISelection selection = event.getSelection(); |
| if (selection instanceof TextSelection) { |
| // only update global actions |
| ((TaskEditorActionContributor) getEditorSite().getActionBarContributor()) |
| .updateSelectableActions(event.getSelection()); |
| return; |
| } |
| if (selection.isEmpty()) { |
| // something was unselected, reset to default selection |
| selection = defaultSelection; |
| // XXX a styled text widget has lost focus, re-enable all edit actions |
| ((TaskEditorActionContributor) getEditorSite().getActionBarContributor()).forceActionsEnabled(); |
| } |
| if (!selection.equals(lastSelection)) { |
| this.lastSelection = selection; |
| fireSelectionChanged(lastSelection); |
| getSite().getSelectionProvider().setSelection(selection); |
| } |
| } |
| |
| @Override |
| public void setFocus() { |
| if (focusTracker != null && focusTracker.setFocus()) { |
| return; |
| } else { |
| IFormPart[] parts = getManagedForm().getParts(); |
| if (parts.length > 0) { |
| parts[0].setFocus(); |
| return; |
| } |
| } |
| super.setFocus(); |
| } |
| |
| public void setNeedsAddToCategory(boolean needsAddToCategory) { |
| this.needsAddToCategory = needsAddToCategory; |
| } |
| |
| public void setReflow(boolean reflow) { |
| this.reflow = reflow; |
| form.setRedraw(reflow); |
| } |
| |
| public void setSelection(ISelection selection) { |
| IFormPart[] parts = getManagedForm().getParts(); |
| for (IFormPart formPart : parts) { |
| if (formPart instanceof AbstractTaskEditorPart) { |
| if (((AbstractTaskEditorPart) formPart).setSelection(selection)) { |
| lastSelection = selection; |
| return; |
| } |
| } |
| } |
| } |
| |
| // TODO EDITOR this needs to be tracked somewhere else |
| private void setSubmitEnabled(boolean enabled) { |
| AbstractTaskEditorPart actionPart = getPart(ID_PART_ACTIONS); |
| if (actionPart instanceof TaskEditorActionPart) { |
| ((TaskEditorActionPart) actionPart).setSubmitEnabled(enabled); |
| } |
| if (submitButton != null && !submitButton.isDisposed()) { |
| submitButton.setEnabled(enabled); |
| } |
| submitEnabled = enabled; |
| } |
| |
| private void setTaskData(TaskData taskData) { |
| this.taskData = taskData; |
| } |
| |
| @Override |
| public void showBusy(boolean busy) { |
| if (getManagedForm() != null && !getManagedForm().getForm().isDisposed() && this.busy != busy) { |
| setSubmitEnabled(!busy); |
| CommonUiUtil.setEnabled(editorComposite, !busy); |
| this.busy = busy; |
| } |
| } |
| |
| // TODO m4.0 remove |
| public void showEditorBusy(boolean busy) { |
| getTaskEditor().showBusy(busy); |
| refreshDisabled = busy; |
| } |
| |
| private void updateHeaderMessage() { |
| if (taskData == null) { |
| getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Synchronize_to_retrieve_task_data, |
| IMessageProvider.WARNING, new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| if (synchronizeEditorAction != null) { |
| synchronizeEditorAction.run(); |
| } |
| } |
| }); |
| } |
| if (getTaskEditor().getMessage() == null |
| && TasksUiPlugin.getTaskList().getTask(task.getRepositoryUrl(), task.getTaskId()) == null) { |
| getTaskEditor().setMessage(Messages.AbstractTaskEditorPage_Add_task_to_tasklist, |
| IMessageProvider.INFORMATION, new NotInTaskListListener()); |
| } |
| } |
| |
| @Override |
| public Control getPartControl() { |
| return partControl != null ? partControl : super.getPartControl(); |
| } |
| |
| /** |
| * Returns true, if the page has an always visible footer. |
| * |
| * @see #setNeedsFooter(boolean) |
| */ |
| private boolean needsFooter() { |
| return needsFooter; |
| } |
| |
| /** |
| * Specifies that the page should provide an always visible footer. This flag is not set by default. |
| * |
| * @see #createFooterContent(Composite) |
| * @see #needsFooter() |
| */ |
| @SuppressWarnings("unused") |
| private void setNeedsFooter(boolean needsFooter) { |
| this.needsFooter = needsFooter; |
| } |
| |
| private void createFooterContent(Composite parent) { |
| parent.setLayout(new GridLayout()); |
| } |
| |
| /** |
| * Returns true, if the page supports a submit operation. |
| * |
| * @since 3.2 |
| * @see #setNeedsSubmit(boolean) |
| */ |
| public boolean needsSubmit() { |
| return needsSubmit; |
| } |
| |
| /** |
| * Specifies that the page supports the submit operation. This flag is set to true by default. |
| * |
| * @since 3.2 |
| * @see #needsSubmit() |
| * @see #doSubmit() |
| */ |
| public void setNeedsSubmit(boolean needsSubmit) { |
| this.needsSubmit = needsSubmit; |
| } |
| |
| /** |
| * Returns true, if the page provides a submit button. |
| * |
| * @since 3.2 |
| * @see #setNeedsSubmitButton(boolean) |
| */ |
| public boolean needsSubmitButton() { |
| return needsSubmitButton; |
| } |
| |
| /** |
| * Specifies that the page supports submitting. This flag is set to false by default. |
| * |
| * @since 3.2 |
| * @see #needsSubmitButton() |
| */ |
| public void setNeedsSubmitButton(boolean needsSubmitButton) { |
| this.needsSubmitButton = needsSubmitButton; |
| } |
| |
| /** |
| * Returns true, if the page provides a submit button. |
| * |
| * @since 3.2 |
| * @see #setNeedsPrivateSection(boolean) |
| */ |
| public boolean needsPrivateSection() { |
| return needsPrivateSection; |
| } |
| |
| /** |
| * Specifies that the page should provide the private section. This flag is not set by default. |
| * |
| * @since 3.2 |
| * @see #needsPrivateSection() |
| */ |
| public void setNeedsPrivateSection(boolean needsPrivateSection) { |
| this.needsPrivateSection = needsPrivateSection; |
| } |
| |
| @Override |
| public boolean selectReveal(Object object) { |
| if (object instanceof TaskEditorOutlineNode) { |
| TaskEditorOutlineNode node = (TaskEditorOutlineNode) object; |
| TaskAttribute attribute = node.getData(); |
| if (attribute != null) { |
| super.selectReveal(attribute.getId()); |
| } else { |
| TaskRelation taskRelation = node.getTaskRelation(); |
| TaskRepository taskRepository = node.getTaskRepository(); |
| if (taskRelation != null && taskRepository != null) { |
| String taskID = taskRelation.getTaskId(); |
| TasksUiUtil.openTask(taskRepository, taskID); |
| } else { |
| EditorUtil.reveal(this.getManagedForm().getForm(), node.getLabel()); |
| } |
| return true; |
| } |
| } |
| return super.selectReveal(object); |
| } |
| |
| void expandLastComment() { |
| if (getManagedForm() == null || getManagedForm().getForm().isDisposed()) { |
| // editor possibly closed or page has not been initialized |
| return; |
| } |
| |
| if (taskData == null) { |
| return; |
| } |
| |
| List<TaskAttribute> commentAttributes = taskData.getAttributeMapper().getAttributesByType(taskData, |
| TaskAttribute.TYPE_COMMENT); |
| if (commentAttributes.size() > 0) { |
| selectReveal(commentAttributes.get(commentAttributes.size() - 1).getId()); |
| } |
| } |
| |
| } |