views:

826

answers:

2

http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/DisplayananimatedGIF.htm describes how to display an animated GIF in SWT - in general. While the code works and is easily comprehensible I'm facing serious issues displaying an animated GIF in a SWT/JFace table/tree viewer cell with that technique. -> all code below

Essentially, I implemented my own OwnerDrawLabelProvider which creates an ImageLoader in paint(Event, Object) and starts an animation thread. The problem seems to be that this animation thread is not the UI thread and I don't know which GC or Display instance to use in its run() method.

I tried creating a separate GC instance in the thread's constructor - derived from event.gc - but the thread fails writing to that GC as soon as I step out of the debugger...

Sat Jan  9 22:11:57 192.168.1.6.local.home java[25387] : CGContextConcatCTM: invalid context 0x0
2010-01-09 22:12:18.356 java[25387:17b03] It does not make sense to draw an image when [NSGraphicsContext currentContext] is nil.  This is a programming error. Break on _NSWarnForDrawingImageWithNoCurrentContext to debug.  This will be logged only once.  This may break in the future.
Sat Jan  9 22:12:41 192.168.1.6.local.home java[25387] : CGContextConcatCTM: invalid context 0x0 

How do I need to handle this situation?
Below are the relevant code sections:

/* Called by paint(Event, Object). */
private void paintAnimated(final Event event, final ImageLoader imageLoader) {
    if (imageLoader == null || ArrayUtils.isEmpty(imageLoader.data)) {
      return;
    }
    final Thread animateThread = new AnimationThread(event, imageLoader);
    animateThread.setDaemon(true);
    animateThread.start();
  }

  private class AnimationThread extends Thread {

    private Display display;

    private GC gc;

    private ImageLoader imageLoader;

    private Color background;

    public AnimationThread(final Event event, final ImageLoader imageLoader) {
      super("Animation");
      this.display = event.display;
      /*
       * If we were to simply reference event.gc it would be reset/empty by the time it's being used
       * in run().
       */
      this.gc = new GC(event.gc.getDevice());
      this.imageLoader = imageLoader;
      this.background = getBackground(event.item, event.index);
    }

    @Override
    public void run() {
      /*
       * Create an off-screen image to draw on, and fill it with the shell background.
       */
      final Image offScreenImage =
          new Image(this.display, this.imageLoader.logicalScreenWidth,
              this.imageLoader.logicalScreenHeight);
      final GC offScreenImageGC = new GC(offScreenImage);
      offScreenImageGC.setBackground(this.background);
      offScreenImageGC.fillRectangle(0, 0, this.imageLoader.logicalScreenWidth,
          this.imageLoader.logicalScreenHeight);
      Image image = null;
      try {
        /* Create the first image and draw it on the off-screen image. */
        int imageDataIndex = 0;
        ImageData imageData = this.imageLoader.data[imageDataIndex];
        image = new Image(this.display, imageData);
        offScreenImageGC.drawImage(image, 0, 0, imageData.width, imageData.height, imageData.x,
            imageData.y, imageData.width, imageData.height);

        /*
         * Now loop through the images, creating and drawing each one on the off-screen image before
         * drawing it on the shell.
         */
        int repeatCount = this.imageLoader.repeatCount;
        while (this.imageLoader.repeatCount == 0 || repeatCount > 0) {
          switch (imageData.disposalMethod) {
            case SWT.DM_FILL_BACKGROUND:
              /* Fill with the background color before drawing. */
              offScreenImageGC.setBackground(this.background);
              offScreenImageGC.fillRectangle(imageData.x, imageData.y, imageData.width,
                  imageData.height);
              break;
            case SWT.DM_FILL_PREVIOUS:
              // Restore the previous image before drawing.
              offScreenImageGC.drawImage(image, 0, 0, imageData.width, imageData.height,
                  imageData.x, imageData.y, imageData.width, imageData.height);
              break;
          }

          imageDataIndex = (imageDataIndex + 1) % this.imageLoader.data.length;
          imageData = this.imageLoader.data[imageDataIndex];
          image.dispose();
          image = new Image(this.display, imageData);
          offScreenImageGC.drawImage(image, 0, 0, imageData.width, imageData.height, imageData.x,
              imageData.y, imageData.width, imageData.height);

          // Draw the off-screen image.
          this.gc.drawImage(offScreenImage, 0, 0);

          /*
           * Sleeps for the specified delay time (adding commonly-used slow-down fudge factors).
           */
          try {
            int ms = imageData.delayTime * 10;
            if (ms 

I posted the same problem to the SWT newsgroup http://www.eclipse.org/forums/index.php?t=tree&th=160398

A: 

Can't you let a LabelProvider return different images and then call viewer.update(...) on the elements you want to animate. You can use Display.timerExec to get a callback instead of having a separate thread.

See my answer here for how you can change colors. You should be able to do something similar with images.

Kire Haglin
Thanks for the pointer, I'll have a look into it.Just to be clear... you propose to implement a "regular" LabelProvider (i.e. not OwnerDraw) to return static images where appropriate - on rows with static images - and separately call viewer.update(...) for all the rows with animated images.
Marcel
Hhhmmm, that's well and good but I still need to find the correct Display and GC instances to paint on...
Marcel
You can just get the Display from the control. GC gc = new GC(myTable.getDisplay()).
Kire Haglin
Or if you don't need to resize them.ImageData[] imageDatas = new ImageLoader().load(new FileInputStream("myAnimated.gif"));Image[] images = new Image[imageDatas.length];for(int n=0;n<imageDatas.length;n++){ images[n] = new Image(myTable.getDislay(), imageDatas[n]);}
Kire Haglin
A: 

After many hours of frustrating trial-and-error a co-worker came up with a feasible solution. My initial approaches to have this implemented in a totally self-contained LabelProvider failed miserably.

One approach that didn't work was to override LabelProvider#update() and to call timerExec(100, new Runnable() {...viewer.update()... from within that method. The "life"-cycle of that is hard to control and it uses too many CPU cycles (10% on my MacBook).

One of the colleague's ideas was to implement a custom TableEditor: a label with an image (one frame of the animated GIF) but no text. Each TableEditor instance would start its own thread in which it updates the label's image. This works quite well, but there's a separate "animation" thread for each animated icon. Also, this was a performance killer, consumed 25% CPU on my MacBook.

The final approach has three building blocks

  • an OwnerDrawLabelProvider which paints either a static image or the frame of an animated GIF
  • an animation thread (the pace maker), it calls redraw() for the column which contains the animated GIFs and it also calls update()
  • and the viewer's content provider that controls the animation thread.

Details in my blog http://www.frightanic.com/2010/02/09/animated-gif-in-swt-tabletree-viewer-cell/.

Marcel