Getting your Xtext model to the Properties View

The default eclipse properties view processes structured selections, but in a text editor you usually get a TextSelection. One way of still getting information displayed is to push a customised structured selection to the view. In order to do that, we adapt code from Xtext’s outline view implementation for registering a selection listener that will trigger the update whenever the text selection changes. I am well aware that I am presenting a very crude implementation… it is just a proof of concept.

The first step is extending the XtextEditor, creating and using an additional Listener, …

public class MyXtextEditor extends XtextEditor {
  private MySelectionChangeListener mylistener;

  @Override
  public void createPartControl(Composite parent) {
    super.createPartControl(parent);
    mylistener = new MySelectionChangeListener(this);
    mylistener.install(getSelectionProvider());
  }

  @Override
  public void dispose() {
    mylistener.uninstall(getSelectionProvider());
    super.dispose();
  }
}

…and binding it in the UiModule.

  public Class<? extends XtextEditor> bindEditor() {
    return MyXtextEditor.class;
  }

Next, we make sure that the Listener triggers the information of the properties view.

public class MySelectionChangeListener implements ISelectionChangedListener {

  private XtextEditor editor;

  public MySelectionChangeListener(XtextEditor editor) {
    this.editor=editor;
  }

  public void selectionChanged(SelectionChangedEvent event) {
    try {
      ISelection selection = event.getSelection();
      if (!selection.isEmpty() && selection instanceof ITextSelection) {
        final ITextSelection textSel = (ITextSelection) selection;
        //determine the Model element at the offset and invoke the
        //invoke the information of the properties view

        editor.getDocument().readOnly(new IUnitOfWork.Void<XtextResource>() {
          public void process(XtextResource resource) throws Exception {
            IParseResult parseResult = resource.getParseResult();
            if(parseResult==null)return;
            CompositeNode rootNode = parseResult.getRootNode();
            int offset = textSel.getOffset();
            AbstractNode node =
              ParseTreeUtil.getCurrentOrFollowingNodeByOffset(rootNode, offset);
            EObject object=NodeUtil.getNearestSemanticObject(node);

            PropertyPageInformer.informPropertyView(editor, object);
          }
        });

      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  //install and uninstall is basically copied form
  //AbstractSelectionChangedListener
  public void install(ISelectionProvider selectionProvider) {
    if (selectionProvider == null)
      return;

    if (selectionProvider instanceof IPostSelectionProvider) {
      IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
      provider.addPostSelectionChangedListener(this);
    }
    else {
      selectionProvider.addSelectionChangedListener(this);
    }
  }

  public void uninstall(ISelectionProvider selectionProvider) {
    if (selectionProvider == null)
      return;

    if (selectionProvider instanceof IPostSelectionProvider) {
      IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
      provider.removePostSelectionChangedListener(this);
    }
    else {
      selectionProvider.removeSelectionChangedListener(this);
    }
  }
}

The PropertyPageInformer is responsible for sending our customised structured selection to the properties view.

public class PropertyPageInformer {

  public static void informPropertyView(XtextEditor editor, EObject object) {
    PropertySheet propertyView=null;

    //fetch the properties view
    try{
      propertyView = (PropertySheet)PlatformUI.getWorkbench().
        getActiveWorkbenchWindow().getActivePage().
        findView(IPageLayout.ID_PROP_SHEET);
    } catch (Exception e) {
    }

    if(propertyView!=null){
      //make sure our editor is marked as active part
      //otherwise the selectionChanged-call will be ignored
      propertyView.partActivated(editor);
      //feed the view with our custom selection
      propertyView.selectionChanged(editor, constructSelection(object));
    }
  }

  private static ISelection constructSelection(final EObject object) {
    return new StructuredSelection(){
      public Object[] toArray() {
        return new Object[]{new MyPropertyInfo(object)};
      }
    };
  }
}

MyPropertyInfo finally contains the information that can be processed by the view.

class MyPropertyInfo implements IAdaptable, IPropertySource{
    List<String> ids=new ArrayList<String>();
    Map<String, String> idToLabelMap=new HashMap<String, String>();
    Map<String, String> idToCategoryMap=new HashMap<String, String>();
    Map<String, String> idToValueMap=new HashMap<String, String>();

    public MyPropertyInfo(EObject object) {
      String id="type";
      ids.add(id);
      idToCategoryMap.put(id, "ObjectInfo");
      idToLabelMap.put(id, "type of the current object");
      idToValueMap.put(id, object.getClass().getSimpleName());

      id="someID";
      ids.add(id);
      idToLabelMap.put(id, "some String");
      idToValueMap.put(id, ""+this.hashCode());
    }

    @SuppressWarnings("rawtypes")
    public Object getAdapter(Class adapter) {
      if(adapter.equals(IPropertySource.class)){
        return this;
      }
      return null;
    }

    public boolean isPropertySet(Object id) {
      return false;
    }

    public Object getPropertyValue(Object id) {
      return idToValueMap.get(id);
    }

    public IPropertyDescriptor[] getPropertyDescriptors() {
      IPropertyDescriptor[] descs=new IPropertyDescriptor[ids.size()];
      for (int i=0;i<ids.size();i++) {
        final String id=ids.get(i);
        descs[i]=new PropertyDescriptor(id, idToLabelMap.get(id)){
          @Override
          public String getCategory() {
            return idToCategoryMap.get(id);
          };
        };
      }
      return descs;
    }

    public Object getEditableValue() {
      return null;
    }
    public void setPropertyValue(Object id, Object value) {}
    public void resetPropertyValue(Object id) {}
  }

Leave a Reply