wiki:Entry_MS_Migration

Version 9 (modified by sena, 4 years ago) (diff)

--

Migrating the multi-page viewer

Each of the MS plugins have a multi-page viewer that extends from CancelableMultiPageEditor (core plugin). For this specific plugin the class is MassSpecMultiPageViewer. One of the major changes for this editor and all the pages (tabs) of the editor is the removal of the reference to ViewInput (core plugin - uses IEditorInput). All pages to be added to a multi-page editor now implement an interface IEntryEditorPart which will give them access to the Entry.

public interface IEntryEditorPart {
	Entry getEntry();
	void setEntry (Entry entry);
	
	void createPartControl(Composite parent);

	void setDirty(boolean b);
	boolean isDirty();
}

Multi-page editor requires a CTabFolder to hold the pages and each page needs to be added as a CTabItem to this folder. CancelableMultiPageEditor handles the interactions such as adding pages so MassSpecMultiPageViewer does not need to change much to handle the addition of pages (tabs). One major change is that all the pages need to be created with ContextInjectionFactory.make(...) method to be able access injected objects later. The following code shows the required changes:

        protected MassSpecScansView getNewScansView( Entry entry, MassSpecEntityProperty entityProperty) {
		MassSpecEntityProperty msProp = (MassSpecEntityProperty) entityProperty.clone();
		msProp.setParentScanNum( entityProperty.getScanNum() );
		msProp.setScanNum(null);
		getPart().getContext().set(MIN_MS_LEVEL_CONTEXT, getMinMSLevel());
		getPart().getContext().set(Property.class, msProp);
		getPart().getContext().set(Entry.class, entry);
		MassSpecScansView view = ContextInjectionFactory.make(MassSpecScansView.class, getPart().getContext());
		
		return view;
	}

Previously, MassSpecScansView object was being created by calling its constructor directly (see below). However, now all the necessary objects for the MassSpecScanView need to be set into the context before initializing it through ContextInjectionFactory.

       protected MassSpecScansView getNewScansView( Entry entry, MassSpecEntityProperty entityProperty) {
		MassSpecEntityProperty msProp = (MassSpecEntityProperty) entityProperty.clone();
		msProp.setParentScanNum( entityProperty.getScanNum() );
		msProp.setScanNum(null);
                return new MassSpecScansView(this.getContainer(), entry, msProp, getMinMSLevel());
       }

The required constructors for any of the multi-page viewers are shown below:

public class MassSpecMultiPageViewer extends CancelableMultiPageEditor implements IPropertyChangeListener {
 
        ...

        @Inject
	public MassSpecMultiPageViewer(Entry entry) {
		super();
		this.entry = entry;
                ...
		
	}
	
	@Inject
	public MassSpecMultiPageViewer (MPart part) {
		super();
		this.entry = (Entry) part.getTransientData().get(IGritsUIService.TRANSIENT_DATA_KEY_PART_ENTRY);
		...
	}
        
        ...
}

The second constructor which gets the entry from the part's transient data is necessary since we also create these editors through commands (not only with "double-click" from the project explorer). "Double-click" from the project explorer handles setting the transient data to make it possible to match the part with the given input (Entry). However, there is no notion of active Entry selection when we open the part with a double-click on the tables. In such a case, we have to get the related Entry from the part's transient data.

Another special case for the ms plugins is that the multi-page viewers have a parent/child relationship. The top-level viewer is matched with an Entry with a certain property (which is defined as a "category" for the part descriptor in fragment.4xmi) and the child multi-page viewers display Entries with a child property of the parent viewer. In order to handle this case, we define the same multi-page viewer twice as a part descriptor in fragment.4xmi with a different id. For the child multi-page viewer, the "category" should list the child property's id. An example (fragment.4xmi) from "entry.ms" plugin is shown below:

<fragments xsi:type="fragment:StringModelFragment" xmi:id="_tcuOcJrFEeaQyJVS-36HMg" featurename="descriptors" parentElementId="org.grits.toolbox.core.application">
    <elements xsi:type="basic:PartDescriptor" xmi:id="_wCsLIJrFEeaQyJVS-36HMg" elementId="plugin.ms.annotation.views.MassSpecMultiPageViewer" 
                label="Mass Spec Overview" iconURI="platform:/plugin/org.grits.toolbox.entry.ms/icons/massspectrum.png" 
                bindingContexts="_hdfcUJrFEeaQyJVS-36HMg" allowMultiple="true" 
                category="org.grits.toolbox.property.ms" closeable="true" dirtyable="true" 
                contributionURI="bundleclass://org.grits.toolbox.entry.ms/org.grits.toolbox.entry.ms.views.tabbed.MassSpecMultiPageViewer">
      <tags>default</tags>
      <tags>removeOnHide</tags>
    </elements>
    <elements xsi:type="basic:PartDescriptor" xmi:id="_A10RgJxwEeaQyJVS-36HMg" elementId="plugin.ms.annotation.views.MassSpecMultiPageViewer2" 
                label="Mass Spec Scan View" iconURI="platform:/plugin/org.grits.toolbox.entry.ms/icons/massspectrum.png" 
                bindingContexts="_hdfcUJrFEeaQyJVS-36HMg" allowMultiple="true" 
                category="org.grits.toolbox.entry.ms.property.MassSpecProperty" closeable="true" dirtyable="true" 
                contributionURI="bundleclass://org.grits.toolbox.entry.ms/org.grits.toolbox.entry.ms.views.tabbed.MassSpecMultiPageViewer">
      <tags>default</tags>
      <tags>removeOnHide</tags>
    </elements>
  </fragments>

Migrating the pages

Pages (tabs) of the multi-page viewers in MS plugins extend from either EntryEditorPart or ScrollableEntryEditorPart or implement IEntryEditorPart interface. In all cases, their input is the Entry object and it needs to be injected (through the constructor) for most of these pages. For the most part, the Entry is the minimum that needs to be injected but many of the pages require several other objects to be set in their constructor. As mentioned above, these objects should be set in the context before creation (with ContextInjectionFactory.make) of such pages.

        @Inject
	public MassSpecSpectraView(@Optional Entry entry) {
		this.entry = entry;
	}

The other common part for the pages is the "postConstruct" method to set the part. Part is later used to access the context, change the label and so on as necessary.

        @PostConstruct 
	public void postConstruct(MPart part) {
		this.part = part;
	}

Normally the saving of the pages' contents is handled by overwriting "updateProjectProperty" method but if the page overwrites "doSave" method (from EntryEditorPart) itself, it should be annotated with "@Persist":

        @Persist
	public void doSave(IProgressMonitor monitor) {
		this.viewBase.doSave(monitor);
		setDirty(false);
	}

Similarly, any setFocus() methods should be annotated with "@Focus".

Updating the pages

One important aspect of the multi-page editor is to react to the changes in any of the pages (tabs) so that the editor gets marked dirty. In order to handle this behavior, the MassSpecMultiPageViewer needs to subscribe to the changes in the tabs. This is done by publishing EntryEditorPart.EVENT_TOPIC_CONTENT_MODIFIED event every time a page gets dirty as shown below:

public abstract class EntryEditorPart implements IEntryEditorPart {
	
	public static final String EVENT_TOPIC_CONTENT_MODIFIED = "content_modified_in_a_tab";
	
	protected Entry entry = null;
	protected Font boldFont = JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT);
	protected String errMsg = null;
	
	boolean dirty = false;
	@Inject protected IEclipseContext context;
	@Inject IEventBroker eventBroker;
	
	public IEclipseContext getContext() {
		return context;
	}
	
	@Override
	public Entry getEntry() {
		return entry;
	}

	@Override
	public void setEntry(Entry entry) {
		this.entry = entry;
	}
	
	@Override
	public void setDirty(boolean d) {
		this.dirty = d;
		eventBroker.post(EVENT_TOPIC_CONTENT_MODIFIED, this);
	}
...

The multi-page editor then listens to this event.

public class MassSpecMultiPageViewer extends CancelableMultiPageEditor implements IPropertyChangeListener {

     ...

        /** 
	 * this method is called whenever a page (tab) is updated 
	 * However we have to check to make sure the modified page is one of the pages of this
	 * multi-page editor
	 *  
	 * @param the part that gets dirty
	 */
	@Optional @Inject
	public void tabContentModified (@UIEventTopic
			(EntryEditorPart.EVENT_TOPIC_CONTENT_MODIFIED) IEntryEditorPart part) {
		if (part.equals(propertyView) || part.equals(scansView) || this.alPeaksViews.contains(part))
			setDirty(part.isDirty());
	} 
    ...
}

Each specific multi-page viewer needs to react to the changes in its own pages, not any other component that gets modified.