10.3. Updating the UIIn Chapter 5, "Starting the Hyperbola Prototype," we saw that TReeViewers use content and label providers to determine what elements to show and how to show them. The setup there was quite straightforward:
Now that we have moved to Smack model objects such as RosterEntry and RosterGroup, we no longer have IAdaptables. These classes cannot be modified to implement IAdaptable or extend PlatformObject as we did in the chat model. While it is still possible to map Smack model objects to IWorkbenchAdapter, the standard TReeViewer and provider infrastructure expect IAdaptables instances. In the next section, we replace the standard providers with something more flexible. Before doing that, let's update the adaptor factory from Chapter 5, "Starting the Hyperbola Prototype," to adapt instances of RosterEntry and RosterGroup to IWorkbenchAdapter. While we're at it, we'll change the adapter factory to be registered declaratively using extensions rather than registering it programmatically as we did before. The snippet below shows the XML markup that identifies the HyperbolaAdapterFactory class as being able to supply IWorkbenchAdapters for the new Smack RosterGroup and RosterEntry model objects. This also highlights the fact that the factories and types being adapted need not be in the same plug-in. org.eclipsercp.hyperbola/plugin.xml
<extension
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="org.jivesoftware.smack.RosterGroup"
class="org.eclipsercp.hyperbola.HyperbolaAdapterFactory">
<adapter type="org.eclipse.ui.model.IWorkbenchAdapter"/>
</factory>
<factory
adaptableType="org.jivesoftware.smack.RosterEntry"
class="org.eclipsercp.hyperbola.HyperbolaAdapterFactory">
<adapter type="org.eclipse.ui.model.IWorkbenchAdapter"/>
</factory>
</extension>10.3.1. The Content ProviderNow that we have Smack model objects adapted to be IWorkbenchAdapters, we should be able to use the default providers to populate the Contacts treeViewer with RosterGroups and RosterEntrys. Unfortunately, the Workbench's built-in content and label provider classes require instances of IAdaptable, not just objects that can be adapted to IWorkbenchAdapter. This is a quirk of the implementation, not a design point. To work around this, we have to implement our own providers. Luckily this is reasonably easy. The required content provider simply implements four methods that map almost directly onto the IWorkbenchAdapter methods. Go ahead and create the content provider as shown below. Notice that this content provider is completely generic and does not mention anything about Smack, chats, or Hyperbola. It just interprets IWorkbenchAdapters as needed by treeViewers. org.eclipsercp.hyperbola/HyperbolaContentProvider public class HyperbolaContentProvider implements ITreeContentProvider { protected IWorkbenchAdapter getAdapter(Object element) { IWorkbenchAdapter adapter = null; if (element instanceof IAdaptable) adapter = (IWorkbenchAdapter) ((IAdaptable) element) .getAdapter(IWorkbenchAdapter.class); if (element != null && adapter == null) adapter = (IWorkbenchAdapter) Platform.getAdapterManager() .loadAdapter(element, IWorkbenchAdapter.class.getName()); return adapter; } public Object[] getChildren(Object element) { IWorkbenchAdapter adapter = getAdapter(element); if (adapter != null) return adapter.getChildren(element); return new Object[0]; } public Object[] getElements(Object element) { return getChildren(element); } public Object getParent(Object element) { IWorkbenchAdapter adapter = getAdapter(element); if (adapter != null) return adapter.getParent(element); return null; } public boolean hasChildren(Object element) { return getChildren(element).length > 0; } } 10.3.2. The Label ProviderThe label provider is a little more complex because it has to manage images, but as you can see from the snippet below, it otherwise follows exactly the same pattern as the content provider. Notice again that this provider is generic. org.eclipsercp.hyperbola/HyperbolaLabelProvider public class HyperbolaLabelProvider extends LabelProvider { private Map imageTable = new HashMap(7); protected IWorkbenchAdapter getAdapter(Object element) { IWorkbenchAdapter adapter = null; if (element instanceof IAdaptable) adapter = (IWorkbenchAdapter) ((IAdaptable) element) .getAdapter(IWorkbenchAdapter.class); if (element != null && adapter == null) adapter = (IWorkbenchAdapter) Platform.getAdapterManager() .loadAdapter(element, IWorkbenchAdapter.class.getName()); return adapter; } public final Image getImage(Object element) { IWorkbenchAdapter adapter = getAdapter(element); if (adapter == null) return null; ImageDescriptor descriptor = adapter.getImageDescriptor(element); if (descriptor == null) return null; Image image = (Image) imageTable.get(descriptor); if (image == null) { image = descriptor.createImage(); imageTable.put(descriptor, image); } return image; } public final String getText(Object element) { IWorkbenchAdapter adapter = getAdapter(element); if (adapter == null) return ""; return adapter.getLabel(element); } public void dispose() { if (imageTable != null) { for (Iterator i = imageTable.values().iterator(); i.hasNext();) ((Image) i.next()).dispose(); imageTable = null; } } } |