The problem:
I want to contribute new context menu action to Mylyn Task List and Task Editor. To make it not so simple the action should be available (enabled or visible) only for tasks which come from repository of type JIRA.
This is quite simple to do for Task List. Just add viewer contribution to you plugin.xml, handle selectionChanged in your action and that's it.
Here is the plugin.xml example:
<extension point="org.eclipse.ui.popupMenus">
<viewerContribution
id="org.eclipse.mylyn.tasks.ui.views.tasks"
targetID="org.eclipse.mylyn.tasks.ui.views.tasks">
<action
class="com.atlassian.connector.eclipse.internal.jira.ui.actions.WatchAction"
id="com.atlassian.connector.eclipse.jira.ui.actions.WatchAction"
label="%Action.Watch.label"
menubarPath="additions"/>
<visibility>
<systemProperty name="com.atlassian.connector.eclipse.jira.ui.issue.selected" value="true"/>
</visibility>
</viewerContribution>
</extension>
and WatchAction example:
public abstract class AbstractJiraAction extends BaseSelectionListenerAction implements IViewActionDelegate {
...
public void selectionChanged(IAction action, ISelection selection) {
// code goes here
updateVisibility(selection);
}
private void updateVisibility(ISelection selection) {
Iterator<?> iter = this.getStructuredSelection().iterator();
while (iter.hasNext()) {
Object sel = iter.next();
if (sel instanceof ITask) {
ITask task = (ITask) sel;
if (task.getConnectorKind().equals(JiraCorePlugin.CONNECTOR_KIND)) {
System.setProperty(JiraConstants.ISSUE_SELECTED_SYSTEM_PROPERTY, "true"); //$NON-NLS-1$
return;
}
}
}
System.setProperty(JiraConstants.ISSUE_SELECTED_SYSTEM_PROPERTY, "false"); //$NON-NLS-1$
}
Unfortunately there is no simple way to do the same for editor. The problem is that we have no selectionChanged notification when we go to another editor so we have no chance to disable our action or make it invisible.
The bypass here could be to register as a workbench|site|page listener and get notifications when we switch between editors.
But what is the right solution here?
The solution:
The solution is to use objectContribution instead of viewerContribution. Unfortunately in the current case we can contribute only to ITask object which means the menu will appear for all tasks. This is not what we want to achieve as we want to show menu only for Jira tasks.
To solve the problem just register in the plugin.xml your own IAdapterFactory impelentaton which can translate ITask to your IJiraTask and contribute to the IJiraTask.
plugin.xml:
<extension point="org.eclipse.ui.popupMenus">
<objectContribution
id="com.atlassian.connector.eclipse.internal.jira.ui.IJiraTaskObjectContribution"
objectClass="com.atlassian.connector.eclipse.internal.jira.ui.IJiraTask"
adaptable="true">
<action
class="com.atlassian.connector.eclipse.internal.jira.ui.actions.WatchAction"
id="com.atlassian.connector.eclipse.jira.ui.actions.WatchAction"
label="%Action.Watch.label"
menubarPath="additions"/>
</objectContribution>
</extension>
<extension
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="org.eclipse.core.runtime.IAdaptable"
class="com.atlassian.connector.eclipse.internal.jira.ui.JiraTaskAdapterFactory">
<adapter
type="com.atlassian.connector.eclipse.internal.jira.ui.IJiraTask">
</adapter>
</factory>
</extension>
Please note that objectClass in objectContribution points to IJiraTask which means that we contribute the action to objects of type IJiraTask.
We also register JiraTaskAdapterFactory which can try to adapt any object of type IAdaptable (adaptableType) to IJiraTask (adapter)
Let's look at the JiraTaskAdapterFactory:
public class JiraTaskAdapterFactory implements IAdapterFactory {
private static final class JiraTask implements IJiraTask {
...
}
public Object getAdapter(Object adaptableObject, Class adapterType) {
if (!adapterType.equals(IJiraTask.class)) {
return null;
}
if (adaptableObject instanceof ITask) {
ITask task = (ITask) adaptableObject;
if (task.getConnectorKind().equals(JiraCorePlugin.CONNECTOR_KIND)) {
return new JiraTask((ITask) adaptableObject);
}
}
return null;
}
public Class[] getAdapterList() {
return new Class[] { IJiraTask.class };
}
}
* We have IJiraTask implementation here.
* getAdapterList() method returns IJiraTask.class which means we can adapt (translate) only to IJiraTask.
* getAdapter tries to translate adaptableObject to adapterType. In our case we translate ITask to IJiraTask.
The result is that our menu will appear only for Mylyn tasks which come from JIRA repository.