Multilingual Eclipse help using Docbook

June 8th, 2010 by Alexander Nittka

Based on Robert’s blog on creating an Eclipse Help plugin, I want to extend that example to support multiple languages. By default, Eclipse seems already to interpret toc.xml, contexts.xml (for contexts defined using the contexts extension point) and the html-files from an nl/de, nl/fr,… directory within the plugin if the corresponding locale is set in the Eclipse start parameters.

So the idea is to adapt the sample to allow docbook sources for all wanted languages within the plugin. There are other objectives that I want to address here:

  • some support for keeping help in synch for all languages
  • try to support some basic context help

In this post, I will not present many snippets but only briefly describe the intentions of the sample help project (download here).
You’ll have to copy the docbook-xml-4.5- and docbook-xsl-files to the corresponing directories in the project as described in Robert’s blog. Note that you don’t need the modified eclipse33.xsl file. For our purposes, we further extended that file and it is included in the docbook-directory and automatically used. There you also find the build script. I have limited experience with xslt and ant (not to say none), so there is much room for improvement.

The docbook directory should contain one subdirectory per language named corresponding to the intended locale (the sample has one for German and one for English). The script assumes that for each language the root xml file is called manual.xml. After first running the script (which probably fails) a run-configuration is created. In the JRE-tab, choose “run in the same JRE as workspace” as described in Robert’s blog. Afterwards, the build should be successful.

If you don’t change the parameters, a contexts.xml will be generated. It basically contains all ids you define in the docbook sources along with a link to their location in the generated html file. Also a ContextConstants.java is generated which contains java-constants for all those ids. Why that? The idea is to simplify a basic context sensitive help. Usually you don’t write a help plugin for its own purpose but for documenting some other set of plugins.

Assuming, you have defined a view in some plugin (and it implements Adaptable) you can use the following code to get a link to the documentation for that view in the dynamic help (please make getInternalHelpContext in HelpUtil.java a public method…)

public Object getAdapter(Class adapter) {
  if (IContextProvider.class.equals(adapter)) {
    return HelpUtil.getInternalHelpContext(
       APxHelpUtil.ConstantForContextThatShouldBeUsedForTheView);
    }
  ...
}

When the dynamic help view is open this should cause a see-also-link to the documentation to be displayed.

The generated constants make sure that the context you call actually exists (otherwise you get a compile error, as the constant is not known). But they also help you to keep the help for different languages in synch. Ideally, the structure of the help is the same for all languages and only the texts differ. So for every language you would use the same ids for chapters, sections etc. If sturcture and ids are indeed the same, the generated ContextConstants will be identical for all languages. If not, they differ and you have feedback that something is wrong.

There are a couple of things missing. If there are broken links in the docbook files (i.e. pointers to non-existent ids), the build process does not fail. The generated html files will contain ???. It would be cool if there was some automatic report for that. Also, the help will not work if toc.xml or contexts.xml contain a & as in ä. For some characters, there is a replace statement in the build.xml but it would be nice, if the “correct” characters were already generated. Anyway, I have tried to provide helpful comments in the build script so that you should get an idea what is going on and have hints for making your own adaptions. Note that contexts.xml and plugin.xml will “always” be overwritten, so if you want to define your ones, you have to adapt the script and the transformations accordingly.

Eclipse DemoCamp, 08.06.2010, TU Dresden

June 4th, 2010 by Lars Martin

Im Juni ist es wieder soweit: anläßlich des Helios Release ruft die Eclipse Foundation weltweit dazu auf, sogenannte Eclipse DemoCamps zu organisieren. Gemeinsam mit der Java User Group Sachsen lädt die itemis AG am 8. Juni zum Eclipse DemoCamp an die Technische Universität nach Dresden ein. Nein, mit Zelten hat das Ganze nur wenig zu tun. Vielmehr geht es darum, an exemplarischen Beispielen die hoch interessante Eclipse Technologie kennenzulernen. Besonderer Wert wird hierbei auf das Wort Demo gelegt -  Beiträge sollen einen hohen Praxisbezug haben. Reine Powerpoint Vorträge sind nicht gern gesehen.

Datum: 8. Juni 2010
Ort: Techn. Universität, Nöthnitzer Str. 46, 01187 Dresden
Zeit: 17:00 – 20:00 Uhr

Die Agenda sieht nach aktuellem Stand den folgenden Ablauf vor:

  • 16:45 Uhr – 17:10 Uhr Einlass und Registrierung
  • 17:10 Uhr – 17:15 Uhr Eröffnung
  • 17:15 Uhr – 17:35 Uhr Jan Reimann: Refactoring Models
  • 17:40 Uhr – 18:00 Uhr Frank Schwarz: JPA in OSGi-Anwendungen
  • 18:05 Uhr – 18:25 Uhr Christian Kurze: Computer-Aided Warehouse Engineering
  • 18:25 Uhr – 19:00 Uhr Pause
  • 19:00 Uhr – 19:20 Uhr Steffen Stundzig: Modellbasiertes Testen mit Xtext und Fit
  • 19:25 Uhr – 19:45 Uhr Jendrik Johannes: Easy and clean seperation of hand-written methods in EMF models with eJava
  • 19:45 Uhr – 20:00 Uhr Ausklang

Um besser planen zu können, bitten wir alle Interessierten, sich vorab anzumelden.

Weitere Informationen:

http://wiki.eclipse.org/Eclipse_DemoCamps_Helios_2010/Dresden

https://www.xing.com/events/501768

Getting your Xtext model to the Properties View

June 1st, 2010 by Alexander Nittka

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) {}
  }

anno 2008

May 4th, 2010 by Steffen Stundzig

Hallo,

letzten Freitag war unsere SpringCon l.e. 2010. Alle Leipziger Mitarbeiter waren eingeladen, mal wieder auf dem Balkon zu grillen, zu chillen und zu quatschen. Und tatsächlich waren fast alle anwesend. Dies ist selten in letzter Zeit, wo das Büro im Schnitt nur mit 3-4 Personen besetzt ist.

Ich hielt am Anfang einen kurzen Rückblick über das vergangene Jahr 2009 und unsere sehr gute Wirtschaftskrisenbewältigung und beschrieb die aktuellen Situation. Da viele in verschiedenen Projekten bei Kunden vor Ort tätig sind, finde ich solche Updates sehr wichtig. Anschließend gab es natürlich Original Thüringer Rostbratwürste, Steaks, Geflügelspieße, Grillzöpfe, Erdbeeren und Reissalat. Alles einfach organisiert über http://doodle.com.

Außerdem war dieses Datum ziemlich genau der 2. Geburtstag unserer Leipziger itemis Niederlassung. Danke an alle, die bis heute daran mitgearbeitet haben, das es uns gibt.

SpringCon l.e. 2010 (1)

SpringCon l.e. 2010 (1)

SpringCon l.e. 2010 (2)

SpringCon l.e. 2010 (2)