de.eckhartarnold.client.Filmstrip.java Source code

Java tutorial

Introduction

Here is the source code for de.eckhartarnold.client.Filmstrip.java

Source

/*
 * Copyright 2008 Eckhart Arnold (eckhart_arnold@hotmail.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package de.eckhartarnold.client;

import java.util.HashSet;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Displays a row of thumbnail images as if 
 * on a film strip.
 * 
 * @author ecki
 *
 */
public class Filmstrip extends Composite implements ResizeListener {

    /**
     * An interface that encapsulates a callback method which is called when
     * the user clicks on an image of the film strip. 
     */
    public interface IPickImage {
        /**
         * Called when the user clicks on a certain image on the film strip.
         * @param imageNr  the number of the image that has been clicked on by
         *                 the user.
         */
        void onPickImage(int imageNr);
    }

    private class Sliding extends Animation {
        private int displacement, current;

        public boolean isComplete() {
            return current == 0;
        }

        public void onComplete() {
            if (current != 0) {
                current = 0;
                redraw(0);
            }
            thumbnails.get(cursor).setStylePrimaryName("filmstripHighlighted");
        }

        public void onUpdate(double progress) {
            int pos = (int) ((1.0 - progress) * displacement);
            if (Math.abs(pos - current) >= 10) { // do not take every small step!
                current = pos;
                redraw(current);
            }
        }

        public void setDisplacement(int displacement) {
            cancel();
            current = -1;
            this.displacement = displacement;
        }
    }

    private int width, height = -1;
    private int borderSize = 2; // should be the same as specified in the css-file!!!
    private int cursor = 0;
    private int filmstripBorderSize = 0; // should be the same as specified in the css-file!!!
    private boolean isLoaded = false; // true, if the filmstrip is visible
    private int slidingDuration = 200;
    private HashSet<Image> visible;
    private IPickImage pickImageCallback;

    private final SimplePanel envelope;
    private final AbsolutePanel panel;
    private final Sliding sliding = new Sliding();
    private final Thumbnails thumbnails;

    /**
     * The constructor of class <code>Filmstrip</code>
     * @param collection  the image collection info from which to construct the
     *                    film strip
     */
    public Filmstrip(ImageCollectionInfo collection) {
        this(new Thumbnails(collection));
    }

    /**
     * The constructor of class <code>Filmstrip</code>
     * @param thumbnailImages  the thumb nail images to be displayed on the
     *                         film strip
     */
    public Filmstrip(Thumbnails thumbnailImages) {
        this.thumbnails = thumbnailImages;
        envelope = new SimplePanel();
        envelope.addStyleName("filmstripEnvelope");
        panel = new AbsolutePanel();
        panel.addStyleName("filmstripPanel");
        envelope.setWidget(panel);
        initWidget(envelope);
        // sinkEvents(Event.ONMOUSEWHEEL);

        ClickHandler imageClickHandler = new ClickHandler() {
            public void onClick(ClickEvent event) {
                Widget sender = (Widget) event.getSource();
                if (pickImageCallback != null) {
                    pickImageCallback.onPickImage(thumbnails.indexOf((Image) sender));
                }
            }
        };

        MouseDownHandler imageMouseDownHandler = new MouseDownHandler() {
            public void onMouseDown(MouseDownEvent event) {
                Widget sender = (Widget) event.getSource();
                if (pickImageCallback != null && sender != thumbnails.get(cursor)) {
                    sender.addStyleName("filmstripPressed");
                }
            }
        };

        MouseOverHandler imageMouseOverHandler = new MouseOverHandler() {
            public void onMouseOver(MouseOverEvent event) {
                Widget sender = (Widget) event.getSource();
                if (pickImageCallback != null && sender != thumbnails.get(cursor)) {
                    sender.addStyleName("filmstripTouched");
                }
            }
        };

        MouseOutHandler imageMouseOutHandler = new MouseOutHandler() {
            public void onMouseOut(MouseOutEvent event) {
                Widget sender = (Widget) event.getSource();
                if (pickImageCallback != null && sender != thumbnails.get(cursor)) {
                    sender.removeStyleName("filmstripTouched");
                    sender.removeStyleName("filmstripPressed");
                }
            }
        };

        MouseUpHandler imageMouseUpHandler = new MouseUpHandler() {
            public void onMouseUp(MouseUpEvent event) {
                Widget sender = (Widget) event.getSource();
                if (pickImageCallback != null && sender != thumbnails.get(cursor)) {
                    sender.removeStyleName("filmstripPressed");
                }
            }
        };

        for (int i = 0; i < thumbnails.size(); i++) {
            Image img = thumbnails.get(i);
            if (i == cursor)
                img.setStyleName("filmstripHighlighted");
            else
                img.setStyleName("filmstrip");
            img.addClickHandler(imageClickHandler);
            img.addMouseDownHandler(imageMouseDownHandler);
            img.addMouseOverHandler(imageMouseOverHandler);
            img.addMouseOutHandler(imageMouseOutHandler);
            img.addMouseUpHandler(imageMouseUpHandler);
        }

        visible = new HashSet<Image>();
    }

    /**
     * Returns the duration in milliseconds for sliding from one image
     * to the next.
     * @return Duration in milliseconds
     */
    public int getSlidingDuration() {
        return slidingDuration;
    }

    /* (non-Javadoc)
      * @see com.google.gwt.user.client.ui.Composite#onBrowserEvent(com.google.gwt.user.client.Event)
      */
    /*  @Override
      public void onBrowserEvent(Event event) {
        switch (event.getTypeInt()) {
          case Event.ONMOUSEWHEEL:
        }
      }*/

    /* (non-Javadoc)
     * @see de.eckhartarnold.client.ResizeListener#onResized()
     */
    public void onResized() {
        //if (!loaded) return;
        // envelope.clear();
        assert envelope.getWidget() == null : "prepareResized() must be called before onResized()!";

        int height = Toolbox.getOffsetHeight(envelope);
        int width = envelope.getOffsetWidth();
        envelope.setWidget(panel);
        if (width == this.width && height == this.height)
            return;
        panel.setPixelSize(width - filmstripBorderSize * 2, height);
        if (width != this.width)
            this.width = width;
        if (height != this.height && height != 0) {
            this.height = height;
            thumbnails.adjustToHeight(height - 2 * borderSize);
        }
        redraw(0);
    }

    /**
     * Removes the {@link AbsolutePanel} so that the image panels size
     * can be determined correctly by the browser before calling 
     * <code>onResized</code>. The <code>AbsolutePanel</code>
     * is restored by method <code>onResized</code>. This method therefore 
     * should always be used in conjunction with the method 
     * <code>onResized</code>. 
     */
    public void prepareResized() {
        panel.setSize("100%", "100%"); // required for Internet Explorer 7 compatibility!
        envelope.clear();
    }

    /**
     * Puts the focus on the given image, i.e. the film strip will be
     * scrolled so that the focused image appears in the center. It's
     * frame will be highlighted.
     * 
     * @param imageNr
     */
    public void focusImage(int imageNr) {
        assert imageNr >= 0 && imageNr < thumbnails.size();
        if (imageNr != cursor || !sliding.isComplete()) {
            sliding.cancel();
            thumbnails.get(cursor).setStylePrimaryName("filmstrip");
            if (slidingDuration > 0 && isLoaded) {
                sliding.setDisplacement(displacement(imageNr));
                int duration = slidingDuration * Math.min(10, Math.abs(imageNr - cursor));
                cursor = imageNr;
                sliding.run(duration);
            } else {
                cursor = imageNr;
                thumbnails.get(cursor).setStylePrimaryName("filmstripHighlighted");
                if (height > 0 && isLoaded)
                    redraw(0);
            }
        }
    }

    /** 
     * Sets the callback that is called when the user selects an image by 
     * clicking on a thumbnail or by turning the mouse wheel while the mouse
     * pointer is located over the preview filmstrip.
     * 
     * @param callback an Interface of the respective "callback" interface
     *                 or <code>null</code>. 
     */
    public void setPickImageCallback(IPickImage callback) {
        pickImageCallback = callback;
        if (callback != null) {
            for (int i = 0; i < thumbnails.size(); i++) {
                Image img = thumbnails.get(i);
                img.addStyleDependentName("selectable");
            }
        } else {
            for (int i = 0; i < thumbnails.size(); i++) {
                Image img = thumbnails.get(i);
                img.removeStyleDependentName("selectable");
            }
        }
    }

    /**
     * Sets the duration in milliseconds for sliding from one image
     * to the next.
     * @param duration  Duration in milliseconds
     */
    public void setSlidingDuration(int duration) {
        slidingDuration = duration;
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.Widget#onLoad()
     */
    @Override
    protected void onLoad() {
        if (envelope.getWidget() != null) {
            prepareResized();
            onResized();
        } // else prepare resize has already been called!
        isLoaded = true;
        redraw(0);
    }

    /* (non-Javadoc)
     * @see com.google.gwt.user.client.ui.Widget#onUnload()
     */
    @Override
    protected void onUnload() {
        isLoaded = false;
    }

    /**
     * Moves a thumbnail image to the given position within the widget's 
     * absolute panel. Automatically adds an image to the widget's panel
     * if it was not visible before, or removes it, if it is not visible
     * any more (due to its position lying outside the panel's boundaries). 
     * 
     * @param imgIndex the index number of the thumbnail image
     * @param left     the position of the image on the absolute panel
     */
    private void move(int imgIndex, int left) {
        Image img = thumbnails.get(imgIndex);
        int imgWidth = thumbnails.imageSize(imgIndex)[0];
        if (visible.contains(img)) {
            if (left < this.width && left + imgWidth > 0) {
                panel.setWidgetPosition(img, left, 0);
            } else {
                panel.remove(img);
                visible.remove(img);
            }
            img.removeStyleName("filmstripTouched");
            img.removeStyleName("filmstripPressed");
        } else {
            if (left < this.width && left + imgWidth > 0) {
                panel.add(img, left, 0);
                visible.add(img);
            }
        }
    }

    /**
     * Determines the displacement of the currently focused slide to the
     * given slide. 
     * @param slideNr the number of the slide in relation to which the 
     *                displacement shall be calculated, if the slide
     *                <code>slideNr</code> were focused and the focused
     *                slide somewhere either to the left or to the 
     *                right of it.
     * @return the displacement
     */
    private int displacement(int slideNr) {
        if (slideNr == cursor)
            return 0;
        int delta = 0;
        delta += thumbnails.imageSize(cursor)[0] / 2;
        delta += thumbnails.imageSize(slideNr)[0] / 2;
        if (slideNr > cursor) {
            for (int i = cursor + 1; i < slideNr; i++) {
                delta += thumbnails.imageSize(i)[0];
            }
            return delta;
        } else {
            for (int i = slideNr + 1; i < cursor; i++) {
                delta += thumbnails.imageSize(i)[0];
            }
            return -delta;
        }
    }

    /**
     * Redraws the film strip. if parameter <code>displacement</code> is
     * unequal zero, the row of images will be displaced by a certain number of
     * pixels. This is used to implement an animated change of the selected 
     * image changes.
     * 
     * @param displacement  the number of pixels by which the thumbnails shall be 
     *                      displaced. (0 means no displacement, a negative 
     *                      number results in a displacement to the left a 
     *                      positive number in a displacement to the right.)
     */
    private void redraw(int displacement) {
        int c1, c2;
        int left, right;
        int center = width / 2 + displacement;

        int w = thumbnails.imageSize(cursor)[0];
        move(cursor, center - w / 2);

        c1 = cursor - 1;
        c2 = cursor + 1;
        left = center - w / 2 - borderSize;
        right = center + w / 2 + borderSize;

        //    while ((right < width && c2 < thumbnails.size()) ||  
        //        (left >= 0 && c1 >= 0)) {
        while (c1 >= 0 || c2 < thumbnails.size()) {
            if (c1 >= 0) {
                left -= thumbnails.imageSize(c1)[0] + 2 * borderSize;
                move(c1, left + borderSize);
                c1--;
            }
            if (c2 < thumbnails.size()) {
                move(c2, right + borderSize);
                right += thumbnails.imageSize(c2)[0] + 2 * borderSize;
                c2++;
            }
        }
    }
}