Friday, June 12, 2009

The Code For Two Margins


OK, two posts ago I showed that I could make a double-margin in an editor using Patch Fragments. One post ago I used a screencast to demonstrate the basics of writing your own patch fragment.

In this post I will show the code for the double margin.

While in the screencast I messed with org.eclipse.core.resources, for this case, I wrote a patch fragment for org.eclipse.ui.workbench.texteditor. It's my expectation that you have read those last two posts, since I'm only a specific subset here.

First I imported the org.eclipse.ui.workbench.texteditor plug-in, and made a bundle fragment that lived off it.

In the fragment, I created the java package org.eclipse.ui.texteditor, and copied the entire source file for org.eclipse.ui.texteditor.SourceViewerDecoratorSupport from the source project to the new fragment package.

After much searching, I found the thing in SourceViewerDecoratorSupport responsible for painting the margin. It's MarginPainter. Because MarginPainter implements IPainter and PaintListener I hoped writing a delegate that implemented those interfaces would work. e.g.

class TwoColumnMarginPainter implements IPainter, PaintListener {
  private final MarginPainter delegate;

  public TwoColumnMarginPainter(...) {
    this.delegate = new MarginPainter(...);
  }

  public void initializer() {
    delegate.initialize();
  }

  ...
}

Any method defined by the interfaces would be implemented just like initialize: delegate the call to the delegate MarginPainter.

Next, I replaced all uses of MarginPainter with TwoColumnMarginPainter, a grand total of two locations:

From To
private MarginPainter fMarginPainter; private TwoColumnMarginPainter fMarginPainter;        
private void showMargin() {
...
  if (fMarginPainter == null) {
    if (fSourceViewer instanceof ITextViewerExtension2) {
     fMarginPainter= new MarginPainter(fSourceViewer);
private void showMargin() {
...
  if (fMarginPainter == null) {
    if (fSourceViewer instanceof ITextViewerExtension2) {
      System.err.println("Black Magic!");
      fMarginPainter= new
TwoColumnMarginPainter(fSourceViewer, 20)
;

The part where it prints "Black Magic!" was my cue during testing that the patch fragment was taking control of SourceViewerDecoratorSupport.

Once that worked, it was a matter of doubling up in my delegate. Here's the the final implementation of the painter, in full.

package org.eclipse.ui.texteditor;


import org.eclipse.jface.text.IPaintPositionManager;
import org.eclipse.jface.text.IPainter;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.MarginPainter;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;


public class TwoColumnMarginPainter implements IPainter, PaintListener {
  private final MarginPainter delegate;
  private final MarginPainter sibling;
  private final int offset;


  public TwoColumnMarginPainter(ITextViewer textViewer, int offset) {
    delegate = new MarginPainter(textViewer);
    sibling = new MarginPainter(textViewer);
    this.offset = offset;
  }


  // @Override
  public void setMarginRulerColumn(int width) {
    delegate.setMarginRulerColumn(width);
    sibling.setMarginRulerColumn(width + offset);
  }


  // @Override
  public void setMarginRulerStyle(int lineStyle) {
    delegate.setMarginRulerStyle(lineStyle);
    sibling.setMarginRulerStyle(lineStyle);
  }


  // @Override
  public void setMarginRulerWidth(int lineWidth) {
    delegate.setMarginRulerWidth(lineWidth);
    sibling.setMarginRulerWidth(lineWidth);
  }


  // @Override
  public void setMarginRulerColor(Color color) {
    delegate.setMarginRulerColor(color);
    sibling.setMarginRulerColor(color);
  }


  // @Override
  public void initialize() {
    delegate.initialize();
    sibling.initialize();
  }


  // @Override
  public void deactivate(boolean redraw) {
    delegate.deactivate(redraw);
    sibling.deactivate(redraw);
  }


  // @Override
  public void dispose() {
    delegate.dispose();
    sibling.dispose();
  }


  // @Override
  public void paint(int reason) {
    delegate.paint(reason);
    sibling.paint(reason);
  }


  // @Override
  public void paintControl(PaintEvent e) {
    delegate.paintControl(e);
    sibling.paintControl(e);
  }


  // @Override
  public void setPositionManager(IPaintPositionManager manager) {
    delegate.setPositionManager(manager);
    sibling.setPositionManager(manager);
  }
}

Between this post and the last one you should be able to reproduce the double-margin effect yourself. Let me know if you have any problems or interesting ideas.

2 comments:

David Plass said...

A less hacky, more permanent, but admittedly slower way to do this would be to introduce a "MarginPainterFactory" that can be specified by an extension point in the appropriate plugin. Then your plugin fragment could specify an alternate factory and wouldn't have to be a patch fragment.

konberg said...

Sorry for the late reply. I think you would still need the patch fragment because something has to replace the class SourceViewerDecoratorSupport