com.nokia.tools.variant.media.ui.preview.ImagePreviewController.java Source code

Java tutorial

Introduction

Here is the source code for com.nokia.tools.variant.media.ui.preview.ImagePreviewController.java

Source

/*
 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 * 
 * Initial Contributors:
 * Nokia Corporation - Initial contribution
 * 
 * Contributors:
 * 
 * Description: This file is part of com.nokia.tools.variant.media.ui component.
 */

package com.nokia.tools.variant.media.ui.preview;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;

import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Manager;
import javax.media.Player;
import javax.media.RealizeCompleteEvent;
import javax.media.StartEvent;
import javax.media.StopEvent;
import javax.media.Time;

import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IActionBars;

import com.nokia.tools.variant.common.core.utils.ExceptionUtils;
import com.nokia.tools.variant.common.core.utils.FileUtils;
import com.nokia.tools.variant.common.core.utils.PlatformUtil;
import com.nokia.tools.variant.media.ui.MediaUIPlugin;
import com.nokia.tools.variant.preview.IPreviewDescriptor;
import com.nokia.tools.variant.preview.PluginImages;
import com.nokia.tools.variant.preview.PreviewController;

/**
 * Controller that can preview frame animation plus audio channel.
 */
public class ImagePreviewController extends PreviewController {

    private Point imageSize;
    private int scaleFactor;
    private Point canvasSize;
    private ScrolledComposite topComposite;
    private Composite imageComposite;
    private Canvas canvas;

    private int frame;
    private PlayTask playTask;
    private static final Timer timer = new Timer("Frame animation player", true);

    private IImagePreviewDescriptor descriptor;

    private final PlayAction playAction = new PlayAction();
    private final LoopAction loopAction = new LoopAction();
    private final FrameNextAction nextFrameAction = new FrameNextAction();
    private final FramePrevAction prevFrameAction = new FramePrevAction();
    private final Separator zoomSeparator = new Separator("GROUP.ZOOM");
    private final Separator playSeparator = new Separator("GROUP.PLAY");

    private Player player;
    private IAudioPreviewDescriptor audio;
    private File tempAudioFile;
    private Image firstBuffer;
    private GC firstBufferGC;

    /** Disable zoom in action when current image width exceeds MAX_IMAGE_WIDTH */
    private static final int MAX_IMAGE_WIDTH = 2280;

    /**
     * Disable zoom in action when current image height exceeds MAX_IMAGE_HEIGHT
     */
    private static final int MAX_IMAGE_HEIGHT = 2024;

    private ListenerList resolutionChangeListeners = new ListenerList();

    /**
     * 
     * @param path
     */
    public ImagePreviewController() {
    }

    /**
     * 
     */
    public void createControl(Composite parent) {
        imageSize = new Point(0, 0);
        canvasSize = new Point(0, 0);
        topComposite = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
        topComposite.setExpandHorizontal(true);
        topComposite.setExpandVertical(true);
        topComposite.setBackground(ColorConstants.white);
        topComposite.setLayout(new FillLayout());

        imageComposite = new Composite(topComposite, SWT.NONE);
        imageComposite.setBackground(topComposite.getBackground());
        topComposite.setContent(imageComposite);
        imageComposite.setLayout(new GridLayout(1, false));

        Composite border = new Composite(imageComposite, SWT.NONE);
        border.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
        border.setLayout(new GridLayout());
        canvas = new Canvas(border, SWT.DOUBLE_BUFFERED) {
            @Override
            public Point computeSize(int hHint, int wHint, boolean changed) {
                Point im = new Point(canvasSize.x, canvasSize.y);

                if (hHint != SWT.DEFAULT) {
                    im.x = hHint;
                }
                if (wHint != SWT.DEFAULT) {
                    im.y = hHint;
                }

                return im;
            }
        };
        topComposite.addListener(SWT.Resize, new Listener() {
            public void handleEvent(Event event) {
                Point size = imageComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
                Rectangle ca = topComposite.getClientArea();
                size.x = Math.max(size.x, ca.width);
                size.y = Math.max(size.y, ca.height);
                imageComposite.setSize(size);
                imageComposite.layout(true);
                topComposite.setMinSize(size);
            }
        });

        GridData gd = new GridData(SWT.CENTER, SWT.CENTER, true, true);
        gd.minimumHeight = canvasSize.y;
        gd.minimumWidth = canvasSize.x;
        canvas.setLayoutData(gd);
        canvas.setBackground(parent.getBackground());

        canvas.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                drawFrame(e, frame);
            }
        });
    }

    private ImageData prevId;

    protected void drawFrame(PaintEvent e, int frameId) {
        if (descriptor.getFrameCount() <= frameId) {
            // no frame to draw
            return;
        }

        Rectangle target = canvas.getClientArea();
        ImageData id = descriptor.getFrame(frameId);
        int w = 0;
        int h = 0;
        ImageLoader imageLoader = descriptor.getImageLoader();
        if (imageLoader != null) {
            w = imageLoader.logicalScreenWidth;
            h = imageLoader.logicalScreenHeight;
        }
        if (prevId != null) {
            if (prevId.disposalMethod == SWT.DM_FILL_BACKGROUND) {
                // Fill with the background color before drawing.
                Color bgColor = null;
                int backgroundPixel = -1;
                if (imageLoader != null) {
                    backgroundPixel = imageLoader.backgroundPixel;
                }
                if (backgroundPixel != -1) {
                    // Fill with the background color.
                    RGB backgroundRGB = prevId.palette.getRGB(backgroundPixel);
                    bgColor = new Color(null, backgroundRGB);
                }
                try {
                    firstBuffer.setBackground(bgColor != null ? bgColor : firstBuffer.getBackground());
                    firstBufferGC.fillRectangle(prevId.x, prevId.y, prevId.width, prevId.height);
                } finally {
                    if (bgColor != null)
                        bgColor.dispose();
                }
            } else if (prevId.disposalMethod == SWT.DM_FILL_PREVIOUS) {
                // Restore the previous image before drawing.
                firstBufferGC.drawImage(firstBuffer, 0, 0, prevId.width, prevId.height, prevId.x, prevId.y,
                        prevId.width, prevId.height);
            }
        }
        // store previous image data
        prevId = id;

        Image nextFrame = new Image(Display.getDefault(), id);
        firstBufferGC.drawImage(nextFrame, 0, 0, id.width, id.height, id.x, id.y, id.width, id.height);
        nextFrame.dispose();
        Rectangle b = canvas.getBounds();
        int ch = b.height;
        int cw = b.width;
        if (h == 0) {
            h = id.height;
        }
        if (w == 0) {
            w = id.width;
        }

        e.gc.drawImage(firstBuffer, 0, 0, w, h, 0, 0, cw, ch);
    }

    /**
     * Send information about current size of displayed image. This information
     * is used to enable/disable further zooming in, in case that current image
     * is already too large.
     * 
     * @param width
     *            current width
     * @param height
     *            current height
     */
    private void fireNewImageSize(final int width, final int height) {
        for (final Object listener : resolutionChangeListeners.getListeners()) {
            SafeRunner.run(new SafeRunnable() {
                public void run() throws Exception {
                    ResolutionChangeListener rcl = (ResolutionChangeListener) listener;
                    rcl.currentResolutionChanged(width, height);
                }
            });
        }
    }

    protected void updateCanvasBounds() {
        Point newCanvasSize = new Point(imageSize.x, imageSize.y);
        if (scaleFactor > 0) {
            for (int i = 0; i < scaleFactor; i++) {
                newCanvasSize.x *= 2;
                newCanvasSize.y *= 2;
            }
        } else if (scaleFactor < 0) {
            for (int i = scaleFactor; i < 0; i++) {
                newCanvasSize.x /= 2;
                newCanvasSize.y /= 2;
            }
        }
        Point oldCanvasSize = this.canvasSize;
        this.canvasSize = newCanvasSize;

        if (canvas != null && !canvas.isDisposed()) {
            GridData gd = (GridData) canvas.getLayoutData();
            gd.minimumHeight = newCanvasSize.y;
            gd.minimumWidth = newCanvasSize.x;

            Point size = imageComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
            Rectangle ca = topComposite.getClientArea();
            size.x = Math.max(size.x, ca.width);
            size.y = Math.max(size.y, ca.height);
            imageComposite.setSize(size);
            imageComposite.layout(new Control[] { canvas });
            imageComposite.redraw();
            topComposite.setMinSize(size);
        }
        if (!newCanvasSize.equals(oldCanvasSize)) {
            fireNewImageSize(newCanvasSize.x, newCanvasSize.y);
        }
    }

    @Override
    public Control getControl() {
        return topComposite;
    }

    @Override
    public void setInput(IPreviewDescriptor descriptor) {
        prevId = null;
        stopPlaying();
        disposeImage();
        frame = 0;

        this.descriptor = (IImagePreviewDescriptor) descriptor;

        if (this.descriptor.getFrameCount() > 0) {
            ImageData id = this.descriptor.getFrame(0);
            if (this.descriptor.getImageLoader() == null) {
                imageSize = new Point(0, 0);
            } else {
                imageSize = new Point(this.descriptor.getImageLoader().logicalScreenWidth,
                        this.descriptor.getImageLoader().logicalScreenHeight);
            }
            if (imageSize.x == 0 && imageSize.y == 0) {
                imageSize = new Point(id.width, id.height);
            }
            firstBuffer = new Image(Display.getDefault(), imageSize.x, imageSize.y);
            firstBufferGC = new GC(firstBuffer);
            firstBufferGC.setBackground(topComposite.getBackground());
            firstBufferGC.fillRectangle(0, 0, imageSize.x, imageSize.y);

            // initialize first frame
            Image image = new Image(Display.getDefault(), id);
            firstBufferGC.drawImage(image, 0, 0, id.width, id.height, id.x, id.y, id.width, id.height);
            image.dispose();
        } else {
            imageSize = new Point(0, 0);
        }

        if (descriptor instanceof IAudioPreviewDescriptor) {
            this.audio = (IAudioPreviewDescriptor) descriptor;
        } else {
            this.audio = null;
        }

        setActionBars(getActionBars());
        updateCanvasBounds();
    }

    @Override
    public void dispose() {
        disposeImage();
        cleanupAudioResources();
        resolutionChangeListeners.clear();
        resolutionChangeListeners = null;
        if (firstBuffer != null) {
            firstBuffer.dispose();
            firstBuffer = null;
        }
        if (canvas != null) {
            canvas.dispose();
            canvas = null;
        }
        if (topComposite != null) {
            if (!topComposite.isDisposed()) {
                for (Control child : topComposite.getChildren()) {
                    if (child != null) {
                        child.dispose();
                    }
                }
            }
            topComposite.dispose();
            topComposite = null;
        }
        if (getActionBars() != null) {
            IMenuManager menu = getActionBars().getMenuManager();
            menu.removeAll();
            IToolBarManager toolBar = getActionBars().getToolBarManager();
            toolBar.removeAll();
            getActionBars().updateActionBars();
        }
    }

    private void disposeImage() {
        imageSize = new Point(0, 0);
        scaleFactor = 0;
        frame = 0;
        if (firstBuffer != null) {
            firstBuffer.dispose();
            firstBuffer = null;
        }
        // Stop loop
        if (loopAction.isChecked() == true) {
            loopAction.setChecked(false);
        }
    }

    @Override
    public void setActionBars(IActionBars actionBars) {
        unregisterActions(getActionBars());
        super.setActionBars(actionBars);
        registerActions(getActionBars());
    }

    protected void registerActions(IActionBars actionBars) {
        if (actionBars != null) {
            IMenuManager menu = actionBars.getMenuManager();
            menu.add(zoomSeparator);
            menu.add(new ZoomInAction());
            menu.add(new ZoomOutAction());
            menu.add(new Zoom11Action());

            IToolBarManager toolBar = actionBars.getToolBarManager();
            toolBar.add(zoomSeparator);
            toolBar.add(new ZoomInAction());
            toolBar.add(new ZoomOutAction());
            toolBar.add(new Zoom11Action());

            if (descriptor != null && descriptor.getFrameCount() > 1) {
                menu.add(playSeparator);
                menu.add(prevFrameAction);
                menu.add(nextFrameAction);
                menu.add(playAction);
                menu.add(loopAction);
                toolBar.add(playSeparator);
                toolBar.add(prevFrameAction);
                toolBar.add(nextFrameAction);
                toolBar.add(playAction);
                toolBar.add(loopAction);

                prevFrameAction.update();
                nextFrameAction.update();
                playAction.update();
            }

            actionBars.updateActionBars();
        }
    }

    protected void unregisterActions(IActionBars actionBars) {
        if (actionBars != null) {
            IMenuManager menu = actionBars.getMenuManager();
            menu.removeAll();
            IToolBarManager toolBar = actionBars.getToolBarManager();
            toolBar.removeAll();
            actionBars.updateActionBars();
        }
    }

    protected void zoomIn() {
        scaleFactor = Math.min(3, scaleFactor + 1);
        updateCanvasBounds();
    }

    protected void zoomOut() {
        scaleFactor = Math.max(-3, scaleFactor - 1);
        updateCanvasBounds();
    }

    protected void zoom11() {
        scaleFactor = 0;
        updateCanvasBounds();
    }

    protected void nextFrame() {
        // stopPlaying();
        if (frame < descriptor.getFrameCount()) {
            frame++;
            canvas.redraw();
        }
        updateActions();
    }

    protected void prevFrame() {
        // stopPlaying();
        if (frame > 0) {
            frame--;
            canvas.redraw();
        }
        updateActions();
    }

    /**
     * Delay in
     * 
     * @return delay in miliseconds
     */
    long getDelay() {
        ImageData frameData = descriptor.getFrame(frame);
        long delay = (long) frameData.delayTime;
        if (delay == 0) {
            delay = 20; // 0.2 ms
            return delay;
        } else {
            return delay * 10;
        }
    }

    class PlayTask extends TimerTask {
        @Override
        public void run() {
            if (frame < descriptor.getFrameCount() - 1) {
                frame++;
                playTask = new PlayTask();
                timer.schedule(playTask, getDelay());
            } else if (loopAction.isChecked()) {
                frame = 0;
                playTask = new PlayTask();
                timer.schedule(playTask, getDelay());
            } else {
                // done
                frame = 0;
                stopPlaying();
            }
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    if (canvas != null) {
                        canvas.redraw();
                        canvas.update();
                    }
                }
            });
        }

        public void go() {
            if (frame < descriptor.getFrameCount() - 1 || loopAction.isChecked()) {
                playTask = new PlayTask();
                timer.schedule(playTask, getDelay());
            } else if (loopAction.isChecked()) {

            } else {
                frame = 0;
                stopPlaying();
            }
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    canvas.redraw();
                    canvas.update();
                }
            });
        }

    };

    protected void updateActions() {
        prevFrameAction.update();
        nextFrameAction.update();
        playAction.update();
    }

    synchronized void stopPlaying() {
        if (playTask != null && loopAction.isChecked() == false) {
            playTask.cancel();
            playTask = null;
            cleanupAudioResources();
        }
        updateActions();
    }

    private void createTempAudioFile() throws Exception {
        if (audio.getAudioContent() != null) {
            String ext = FileUtils.getExtension(audio.getAudioContent().getName());

            String tempExt;
            if (ext == null) {
                tempExt = ".mp3";
            } else {
                tempExt = "." + ext;
            }
            tempAudioFile = File.createTempFile("temp", tempExt);
            IStorage input = audio.getAudioContent();

            IWorkspaceRunnable copyOperation = new IWorkspaceRunnable() {
                public void run(IProgressMonitor monitor) throws CoreException {
                    monitor.beginTask("Copy content", 1);
                    InputStream content = audio.getAudioContent().getContents();
                    try {
                        FileOutputStream out = new FileOutputStream(tempAudioFile);
                        if (content != null) {
                            FileUtils.copyStream(content, out);
                            FileUtils.closeStream(out);
                            tempAudioFile.deleteOnExit();
                        }
                    } catch (IOException ex) {
                        cleanupAudioResources();
                        throw ExceptionUtils.wrapCoreException(ex, MediaUIPlugin.PLUGIN_ID, "Internal Error");
                    } finally {
                        FileUtils.closeStream(content);
                    }

                    monitor.done();
                }
            };
            ISchedulingRule rule = PlatformUtil.getAdapter(input, ISchedulingRule.class, true);
            ResourcesPlugin.getWorkspace().run(copyOperation, rule, IWorkspace.AVOID_UPDATE,
                    new NullProgressMonitor());
        }
    }

    protected synchronized void cleanupAudioResources() {
        if (player != null) {
            player.stop();
            player.deallocate();
            player = null;
        }
        if (tempAudioFile != null) {
            tempAudioFile.delete();
            tempAudioFile = null;
        }
    }

    synchronized void togglePlay() {
        if (playTask != null) {
            loopAction.setChecked(false);
            stopPlaying();
        } else {
            playTask = new PlayTask();
            updateActions();

            if (this.audio != null && this.audio.getAudioContent() != null) {
                // if we have audio channel we synchronize frame playback start
                // with audio player.
                try {
                    createTempAudioFile();
                    player = Manager.createPlayer(tempAudioFile.toURL());

                    long startTime = 0;
                    for (int i = 0; i < frame; i++) {
                        int delay = descriptor.getFrame(i).delayTime;
                        startTime += delay == 0 ? 100 : delay;
                    }
                    final Time mediaTime = new Time(startTime * 1000000);

                    player.addControllerListener(new ControllerListener() {
                        boolean isPlaying = false;

                        public void controllerUpdate(ControllerEvent event) {
                            if (event instanceof RealizeCompleteEvent) {
                                if (isPlaying == false) {
                                    player.setMediaTime(mediaTime);
                                }
                            } else if (event instanceof StartEvent) {
                                if (isPlaying == false) {
                                    playTask.go();
                                }
                            } else if (event instanceof StopEvent) {
                                if (loopAction.isChecked()) {
                                    isPlaying = true;
                                    player.setMediaTime(new Time(0));
                                    player.start();
                                } else {
                                    isPlaying = false;
                                    cleanupAudioResources();
                                }
                            }
                        }
                    });
                    player.realize();
                    player.start();

                } catch (Exception ex) {
                    ex.printStackTrace();
                    cleanupAudioResources();
                    // continue without audio
                    playTask.go();
                }
            } else {
                playTask.go();
            }
        }
    }

    class ZoomInAction extends Action implements ResolutionChangeListener {
        static final String ID = "ImagePreview.ZoomIn";

        public ZoomInAction() {
            super("Zoom In", PluginImages.ZOOM_IN);
            setId(ID);
            resolutionChangeListeners.add(this);
        }

        @Override
        public void run() {
            zoomIn();
        }

        public void currentResolutionChanged(int newWidth, int newHeight) {
            if (newWidth > MAX_IMAGE_WIDTH) {
                setEnabled(false);
            } else if (newHeight > MAX_IMAGE_HEIGHT) {
                setEnabled(false);
            } else {
                setEnabled(true);
            }
        }
    }

    class ZoomOutAction extends Action {
        static final String ID = "ImagePreview.ZoomOut";

        public ZoomOutAction() {
            super("Zoom Out", PluginImages.ZOOM_OUT);
            setId(ID);
        }

        @Override
        public void run() {
            zoomOut();
        }

    }

    class Zoom11Action extends Action {
        static final String ID = "ImagePreview.Zoom11";

        public Zoom11Action() {
            super("Zoom To Original", PluginImages.ZOOM_ORIGINAL);
            setId(ID);
        }

        @Override
        public void run() {
            zoom11();
        }

    }

    class FrameNextAction extends Action {
        static final String ID = "ImagePreview.NextFrame";

        public FrameNextAction() {
            super("Next Frame", PluginImages.FORWARD);
            setId(ID);
        }

        @Override
        public void run() {
            nextFrame();
        }

        public void update() {
            boolean enabled = playTask == null && descriptor != null && frame < descriptor.getFrameCount() - 1;
            setEnabled(enabled);
        }
    }

    class FramePrevAction extends Action {
        static final String ID = "ImagePreview.NextFrame";

        public FramePrevAction() {
            super("Previous Frame", PluginImages.REWIND);
            setId(ID);
        }

        @Override
        public void run() {
            prevFrame();
        }

        public void update() {
            boolean enabled = playTask == null && descriptor != null && frame > 0;
            setEnabled(enabled);
        }
    }

    class PlayAction extends Action {
        static final String ID = "ImagePreview.Play";

        public PlayAction() {
            super("Start Animation", PluginImages.PLAY);
            setId(ID);
        }

        @Override
        public void run() {
            togglePlay();
        }

        void update() {
            boolean enabled = descriptor != null && descriptor.getFrameCount() > 0;
            setEnabled(enabled);
            if (playTask != null) {
                setText("Stop Animation");
                setImageDescriptor(PluginImages.STOP);
            } else {
                setText("Start Animation");
                setImageDescriptor(PluginImages.PLAY);
            }
        }
    }

    class LoopAction extends Action {
        static final String ID = "ImagePreview.Loop";

        public LoopAction() {
            super("Loop", Action.AS_CHECK_BOX);
            setImageDescriptor(PluginImages.LOOP);
            setId(ID);
        }

        @Override
        public void run() {
            playAction.run();
        }
    }

}

interface ResolutionChangeListener {
    public void currentResolutionChanged(int newWidth, int newHeight);
}