com.bdaum.zoom.ui.internal.widgets.AnimatedGallery.java Source code

Java tutorial

Introduction

Here is the source code for com.bdaum.zoom.ui.internal.widgets.AnimatedGallery.java

Source

/*
 * This file is part of the ZoRa project: http://www.photozora.org.
 *
 * ZoRa is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * ZoRa is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ZoRa; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * (c) 2009-2017 Berthold Daum  
 */
package com.bdaum.zoom.ui.internal.widgets;

import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
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.swt.widgets.Shell;
import org.piccolo2d.PCamera;
import org.piccolo2d.PNode;
import org.piccolo2d.activities.PActivity;
import org.piccolo2d.activities.PTransformActivity;
import org.piccolo2d.event.PBasicInputEventHandler;
import org.piccolo2d.event.PInputEvent;
import org.piccolo2d.extras.swt.PSWTCanvas;
import org.piccolo2d.extras.swt.PSWTImage;
import org.piccolo2d.extras.swt.PSWTPath;
import org.piccolo2d.extras.swt.PSWTText;
import org.piccolo2d.util.PAffineTransform;
import org.piccolo2d.util.PBounds;
import org.piccolo2d.util.PPaintContext;

import com.bdaum.zoom.cat.model.asset.Asset;
import com.bdaum.zoom.cat.model.asset.Region;
import com.bdaum.zoom.cat.model.asset.RegionImpl;
import com.bdaum.zoom.cat.model.group.SmartCollectionImpl;
import com.bdaum.zoom.cat.model.locationShown.LocationShownImpl;
import com.bdaum.zoom.core.Constants;
import com.bdaum.zoom.core.Core;
import com.bdaum.zoom.core.IAssetProvider;
import com.bdaum.zoom.core.IPostProcessor2;
import com.bdaum.zoom.core.IScoreFormatter;
import com.bdaum.zoom.core.IVolumeManager;
import com.bdaum.zoom.core.QueryField;
import com.bdaum.zoom.core.db.IDbManager;
import com.bdaum.zoom.core.internal.CoreActivator;
import com.bdaum.zoom.core.internal.IMediaSupport;
import com.bdaum.zoom.core.internal.db.AssetEnsemble;
import com.bdaum.zoom.css.internal.CssActivator;
import com.bdaum.zoom.css.internal.IExtendedColorModel2;
import com.bdaum.zoom.image.ImageConstants;
import com.bdaum.zoom.image.ImageStore;
import com.bdaum.zoom.ui.AssetSelection;
import com.bdaum.zoom.ui.internal.IPresentationHandler;
import com.bdaum.zoom.ui.internal.Icons;
import com.bdaum.zoom.ui.internal.UiActivator;
import com.bdaum.zoom.ui.internal.UiUtilities;
import com.bdaum.zoom.ui.internal.dialogs.ColorCodeDialog;
import com.bdaum.zoom.ui.internal.dialogs.RatingDialog;
import com.bdaum.zoom.ui.internal.dialogs.StatusDialog;
import com.bdaum.zoom.ui.internal.views.AssetContainer;
import com.bdaum.zoom.ui.internal.views.ImageRegion;
import com.bdaum.zoom.ui.preferences.PreferenceConstants;

@SuppressWarnings("restriction")
public class AnimatedGallery implements IExtendedColorModel2, IPresentationHandler {

    private final class GalleryPBasicInputEventHandler extends PBasicInputEventHandler {
        private final PSWTCanvas pcanvas;
        private int eat;
        private boolean keyDown;
        private boolean textEvent = false;

        private GalleryPBasicInputEventHandler(PSWTCanvas canvas) {
            this.pcanvas = canvas;
        }

        @Override
        public void keyPressed(PInputEvent event) {
            textEvent = textEventHandler == null ? false : textEventHandler.hasFocus();
            if (textEvent)
                textEventHandler.keyPressed(event);
            else {
                if (keyDown)
                    releaseKey(pcanvas, event);
                keyDown = true;
            }
        }

        @Override
        public void keyReleased(PInputEvent event) {
            if (!textEvent) {
                releaseKey(pcanvas, event);
                keyDown = false;
            }
            textEvent = false;
        }

        private void releaseKey(final PSWTCanvas pswtCanvas, PInputEvent event) {
            int f = event.isControlDown() ? 5 : 1;
            switch (event.getKeyCode()) {
            case KeyEvent.VK_SPACE:
                if (slides.length > 0) {
                    int min;
                    if (event.isShiftDown()) {
                        min = 0;
                        for (Asset asset : selectedSlides) {
                            Integer index = galleryMap.get(asset);
                            if (index != null)
                                min = Math.max(min, index);
                        }
                        --min;
                        if (min < 0)
                            min = slides.length - 1;
                    } else {
                        min = Integer.MAX_VALUE;
                        for (Asset asset : selectedSlides) {
                            Integer index = galleryMap.get(asset);
                            if (index != null)
                                min = Math.min(min, index);
                        }
                        ++min;
                        if (min >= slides.length)
                            min = 0;
                    }
                    PGalleryItem slide = slides[min];
                    if (slide != null) {
                        oldAssetSelection = null;
                        goToSlide(slide);
                        lastSingleClick = slide;
                    }
                }
                break;
            case SWT.ARROW_RIGHT:
                if (event.isShiftDown())
                    nav(1, 0);
                else
                    pan(pswtCanvas, -30 * f, 0);
                break;
            case SWT.ARROW_LEFT:
                if (event.isShiftDown())
                    nav(-1, 0);
                else
                    pan(pswtCanvas, 30 * f, 0);
                break;
            case SWT.ARROW_UP:
                if (event.isShiftDown())
                    nav(0, -1);
                else
                    pan(pswtCanvas, 0, 30 * f);
                break;
            case SWT.ARROW_DOWN:
                if (event.isShiftDown())
                    nav(0, 1);
                else
                    pan(pswtCanvas, 0, -30 * f);
                break;
            case SWT.END:
                pan(pswtCanvas, (int) (-pswtCanvas.getCamera().getViewBounds().getWidth() / 2) * f, 0);
                break;
            case SWT.HOME:
                pan(pswtCanvas, (int) (pswtCanvas.getCamera().getViewBounds().getWidth() / 2) * f, 0);
                break;
            case SWT.PAGE_UP:
                pan(pswtCanvas, 0, (int) (pswtCanvas.getCamera().getViewBounds().getHeight() / 2) * f);
                break;
            case SWT.PAGE_DOWN:
                pan(pswtCanvas, 0, (int) (-pswtCanvas.getCamera().getViewBounds().getHeight() / 2) * f);
                break;
            default:
                char c = event.getKeyChar();
                switch (c) {
                case 13:
                case SWT.TAB:
                    fireKeyEvent(c, event.getKeyCode(),
                            (event.isShiftDown() ? SWT.SHIFT : 0) | (event.isControlDown() ? SWT.CTRL : 0));
                    break;
                case '+':
                    zoom(pswtCanvas, 1.05d);
                    break;
                case '-':
                    zoom(pswtCanvas, 0.95d);
                    break;
                case '*':
                    event.getCamera().setViewTransform(new AffineTransform());
                    break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case SWT.DEL:
                    fireKeyEvent(c, event.getKeyCode(), 0);
                    break;
                }
                break;
            }
        }

        @Override
        public void mouseDragged(PInputEvent event) {
            textEventHandler.mouseDragged(event);
        }

        @Override
        public void mouseReleased(PInputEvent event) {
            if (textEventHandler != null)
                textEventHandler.mouseReleased(event);
            if (eat > 0) {
                eat--;
                return;
            }
            int stateMask = event.isControlDown() ? SWT.CTRL : 0;
            if (event.isAltDown())
                stateMask |= SWT.ALT;
            if (event.isShiftDown())
                stateMask |= SWT.SHIFT;
            PNode picked = event.getPickedNode();
            if (picked.getParent() instanceof PGalleryItem) {
                PGalleryItem currentSlide = (PGalleryItem) picked.getParent();
                if (isSelected(currentSlide.getAsset())) {
                    if (currentSlide.isRatingStar(picked)) {
                        Asset asset = currentSlide.getAsset();
                        if (asset != null) {
                            RatingDialog dialog = new RatingDialog(pcanvas.getShell(), asset.getRating(), 0.6d,
                                    true, true);
                            initContextDialog(dialog, pcanvas, event);
                            event.setHandled(true);
                            eat++;
                            int rating = dialog.open();
                            if (rating != RatingDialog.ABORT)
                                fireOperationEvent(picked, stateMask, RATE, rating);
                        }
                    } else if (currentSlide.isColorCodeRectangle(picked)) {
                        Asset asset = currentSlide.getAsset();
                        if (asset != null) {
                            ColorCodeDialog dialog = new ColorCodeDialog(pcanvas.getShell(), asset.getColorCode());
                            initContextDialog(dialog, pcanvas, event);
                            event.setHandled(true);
                            eat++;
                            int code = dialog.open();
                            if (code >= Constants.COLOR_UNDEFINED)
                                fireOperationEvent(picked, stateMask, CODE, code);
                        }
                    } else if (currentSlide.isLocationPin(picked)) {
                        event.setHandled(true);
                        fireOperationEvent(picked, stateMask, SHOWLOCATION, 0);
                    } else if (currentSlide.isDoneMark(picked)) {
                        Asset asset = currentSlide.getAsset();
                        if (asset != null) {
                            StatusDialog dialog = new StatusDialog(pcanvas.getShell(), asset.getStatus());
                            initContextDialog(dialog, pcanvas, event);
                            event.setHandled(true);
                            eat++;
                            int status = dialog.open();
                            if (status > Constants.STATE_UNDEFINED)
                                fireOperationEvent(picked, stateMask, STATUS, status);
                        }
                    } else if (currentSlide.isRotate90(picked)) {
                        event.setHandled(true);
                        fireOperationEvent(picked, stateMask, ROTATE, 90);
                    } else if (currentSlide.isRotate270(picked)) {
                        event.setHandled(true);
                        fireOperationEvent(picked, stateMask, ROTATE, 270);
                    } else if (currentSlide.isVoicenote(picked)) {
                        event.setHandled(true);
                        fireOperationEvent(picked, stateMask, VOICENOTE, 0);
                    }
                }
            } else if (picked.getParent() == slideBar && picked instanceof PGalleryItem && !event.isHandled()
                    && event.getButton() == MouseEvent.BUTTON1 && event.getClickCount() == 1
                    && !event.isAltDown()) {
                picked.raiseToTop();
                oldAssetSelection = null;
                PGalleryItem item = (PGalleryItem) picked;
                if (event.isShiftDown()) {
                    if (lastSingleClick != null) {
                        if (getOrder(item, lastSingleClick))
                            select(item, lastSingleClick);
                        else
                            select(lastSingleClick, item);
                    }
                } else if (item.isSelected()) {
                    if (event.isControlDown())
                        deselect(item, 1000);
                    else {
                        Point2D position = event.getCanvasPosition();
                        ImageRegion imageRegion = getBestFaceRegion((int) position.getX(), (int) position.getY(),
                                false, item.slide);
                        if (imageRegion != null)
                            fireRegionEvent(stateMask, imageRegion);
                        else
                            goToSlide(null);
                        lastSingleClick = null;
                    }
                } else {
                    if (event.isControlDown())
                        select(item, 1000);
                    else {
                        selectedSlides.remove(item.getAsset());
                        goToSlide(item);
                    }
                    lastSingleClick = item;
                }
                fireSelectionEvent(1, picked, stateMask);
            } else if (event.getClickCount() == 2) {
                if (picked.getParent() == slideBar)
                    fireSelectionEvent(2, picked, stateMask);
                else
                    resetPanAndZoom(event.getCamera());
            }
        }

        private void initContextDialog(Dialog dialog, final PSWTCanvas pswtCanvas, PInputEvent event) {
            dialog.create();
            PCamera camera = pswtCanvas.getLayer().getCamera(0);
            PAffineTransform transform = camera.getViewTransform();
            Point2D p2 = transform.transform(event.getPosition(), null);
            dialog.getShell().setLocation(pswtCanvas.toDisplay((int) p2.getX(), (int) p2.getY()));
        }
    }

    @SuppressWarnings("serial")
    public static class PGalleryRegion extends PSWTPath {

        private static final Ellipse2D.Float TEMP_OVAL = new Ellipse2D.Float();
        private static final Rectangle2D.Float TEMP_RECTANGLE = new Rectangle2D.Float();
        private ImageRegion imageRegion;
        private boolean tempPaintChange;

        private PGalleryRegion(Shape shape) {
            super(shape);
        }

        public static PGalleryRegion createRegion(int x, int y, int width, int height, java.awt.Paint regionColor) {
            PGalleryRegion region;
            if (height < 0) {
                TEMP_OVAL.setFrame(x - width / 2, y - width / 2, width, width);
                region = new PGalleryRegion(TEMP_OVAL);
            } else {
                TEMP_RECTANGLE.setFrame(x, y, width, height);
                region = new PGalleryRegion(TEMP_RECTANGLE);
            }
            region.setStrokeColor(regionColor);
            region.setPaint(null);
            region.setPickable(false);
            region.setVisible(true);
            return region;
        }

        public void setImageRegion(ImageRegion imageRegion) {
            this.imageRegion = imageRegion;
        }

        public ImageRegion getImageRegion() {
            return imageRegion;
        }

        /**
         * Force intersection test to return true inspite of paint being null
         * 
         * @param aBounds
         *            bounds being tested for intersection
         * @return true if path visibly crosses bounds
         */
        public boolean intersects(final Rectangle2D aBounds) {
            tempPaintChange = true;
            try {
                setPaint(Color.gray);
                return super.intersects(aBounds);
            } finally {
                setPaint(null);
                tempPaintChange = false;
            }
        }

        /*
         * Don't invalidate for paint changes from intersects()
         */
        @Override
        public void invalidatePaint() {
            if (!tempPaintChange)
                super.invalidatePaint();
        }

        /*
         * Don't report for paint changes from intersects()
         */
        @Override
        protected void firePropertyChange(int propertyCode, String propertyName, Object oldValue, Object newValue) {
            if (!tempPaintChange)
                super.firePropertyChange(propertyCode, propertyName, oldValue, newValue);
        }

        public double getDistanceFromRegionCenter(int x, int y) {
            Point2D pntSrc = new Point(x, y);
            getParent().globalToLocal(pntSrc);
            PBounds bounds = getBounds();
            double dx = bounds.x + bounds.width / 2 - pntSrc.getX();
            double dy = bounds.y + bounds.height / 2 - pntSrc.getY();
            return dx * dx + dy * dy;
        }
    }

    @SuppressWarnings("serial")
    public class PGalleryItem extends PNode implements AssetContainer, PTextHandler, TraverseHandler {

        private static final int LOWER_THUMBNAIL_HMARGINS = 5;
        private static final int UPPER_THUMBNAIL_HMARGINS = 8;

        private PSWTImage shadow;
        private PSWTAssetThumbnail slide;
        private List<PSWTText> frameLabels;
        private PSWTImage colorCodeRectangle;
        private PSWTImage ratingStar;
        private PAffineTransform selectTransform;
        private PAffineTransform deselectTransform;
        private Asset asset;
        private PSWTImage rotate90Arrow;
        private PSWTImage rotate270Arrow;
        private TextField caption;
        private PSWTImage speakerNode;
        private PSWTImage doneMark;
        private final int index;
        private PAffineTransform multiTransform;
        private String captionText;
        private ZPSWTImage mediaIcon;
        private ZPSWTImage locationPin;
        private int defaultFontSize = JFaceResources.getDefaultFont().getFontData()[0].getHeight();

        public PGalleryItem(PSWTCanvas canvas, int index, final Asset asset) {
            this.index = index;
            this.asset = asset;
            captionText = computeCaption(asset);
            if (shadowImage == null)
                shadowImage = Icons.shadow.getImage();
            shadow = new ZPSWTImage(canvas, shadowImage);
            slide = new PSWTAssetThumbnail(canvas, imageSource, asset);
            PBounds bounds = slide.getBoundsReference();
            slide.setPickable(false);
            double x = bounds.getX();
            double y = bounds.getY();
            double width = bounds.getWidth();
            double height = bounds.getHeight();
            shadow.setBounds(x + 15, y + 15, width, height);
            setBounds(x, y, width + 5, height + 5);
            shadow.setPickable(false);
            addChild(shadow);
            addChild(slide);
            // overlay region frames
            if (asset != null) {
                if (showRegions > 0) {
                    String[] regionIds = asset.getPerson();
                    if (regionIds != null && regionIds.length > 0) {
                        String fontFamily = "Arial"; //$NON-NLS-1$
                        int fontsize = 11;
                        frameLabels = new ArrayList<PSWTText>(2 * regionIds.length);
                        IDbManager dbManager = Core.getCore().getDbManager();
                        List<RegionImpl> regionList = dbManager.obtainObjects(RegionImpl.class,
                                "asset_person_parent", //$NON-NLS-1$
                                asset.getStringId(), QueryField.EQUALS);
                        int i = 0;
                        int rotation = asset.getRotation();
                        rectangleList.clear();
                        for (RegionImpl region : regionList) {
                            if (i++ > showRegions)
                                break;
                            String regionid = region.getStringId();
                            Rectangle frame = UiUtilities.computeFrame(regionid, 0, 0, (int) width, (int) height,
                                    rotation);
                            if (frame != null && !UiUtilities.isDoubledRegion(rectangleList, frame)) {
                                rectangleList.add(frame);
                                String type = region.getType();
                                PGalleryRegion frameFill = PGalleryRegion.createRegion(frame.x, frame.y,
                                        frame.width, frame.height, regionColor);
                                slide.addChild(frameFill);
                                Color color = Color.RED;
                                String name = "?"; //$NON-NLS-1$
                                if (type == null || type.isEmpty() || Region.type_face.equals(type)
                                        || Region.type_pet.equals(type)) {
                                    String albumId = region.getAlbum();
                                    SmartCollectionImpl album = albumId == null ? null
                                            : dbManager.obtainById(SmartCollectionImpl.class, albumId);
                                    if (album != null)
                                        name = album.getName();
                                    if (personId == null || personId.equals(albumId))
                                        color = Color.YELLOW;
                                } else if (Region.type_barCode.equals(type)) {
                                    name = region.getAlbum();
                                    color = Color.GREEN;
                                }
                                createFrameLabel(frameFill, frame.x + 5, frame.y + 3, fontFamily, fontsize,
                                        Color.DARK_GRAY, name);
                                createFrameLabel(frameFill, frame.x + 4, frame.y + 2, fontFamily, fontsize, color,
                                        name);
                                frameFill.setImageRegion(new ImageRegion(frame, regionid,
                                        type == null ? Region.type_face : type, name, asset));
                            }
                        }
                    }
                }
            }
            // Decoration
            addMediaIcon(canvas, asset, x, y, width);
            addCaption(canvas, asset, captionText, x, y, height);
            addRatingStar(canvas, asset, x, y, width);
            addDoneMark(canvas, asset, x, y, width);
            double x1 = addColorCode(canvas, asset);
            addLocationPin(canvas, asset, x1, y);
            addRotateButtons(canvas, asset, width, height);
            addVoiceNote(canvas, asset, width, height);
            textEventHandler = new TextEventHandler(UiUtilities.getAwtBackground(canvas, selectionBackgroundColor),
                    this);
        }

        private void createFrameLabel(PSWTPath path, double x, double y, String fontFamily, int fontsize,
                Color penColor, String text) {
            PSWTText label = new PSWTText(text, new Font(fontFamily, Font.PLAIN, Math.max(fontsize - 2, 4)));
            label.setPenColor(penColor);
            label.setOffset(x, y);
            label.setPickable(false);
            label.setGreekThreshold(2);
            label.setTransparent(true);
            label.setVisible(false);
            path.addChild(label);
            frameLabels.add(label);
        }

        private void addCaption(PSWTCanvas pswtCanvas, final Asset anAsset, String text, double x, double y,
                double height) {
            boolean readOnly = anAsset.getFileState() != IVolumeManager.ONLINE
                    || showLabel != Constants.TITLE_LABEL;
            caption = new TextField(text, SWT.DEFAULT,
                    UiUtilities.getAwtFont(pswtCanvas, null, java.awt.Font.BOLD,
                            showLabel == Constants.CUSTOM_LABEL && labelFontSize != 0 ? labelFontSize
                                    : defaultFontSize),
                    UiUtilities.getAwtForeground(pswtCanvas, null), null, true,
                    SWT.SINGLE | (readOnly ? SWT.READ_ONLY : SWT.NONE));
            caption.setSelectedBgColor(UiUtilities.getAwtBackground(pswtCanvas, selectionBackgroundColor));
            caption.setGreekThreshold(6);
            caption.setOffset(x, y + height);
            caption.setPickable(false);
            caption.setVisible(false);
            if (!readOnly) {
                caption.setValidator(new IInputValidator() {
                    public String isValid(String newText) {
                        return QueryField.NAME.isValid(newText, anAsset);
                    }
                });
                caption.addErrorListener(titleVerifyListener);
            }
            addChild(caption);
        }

        private void addDoneMark(PSWTCanvas pswtCanvas, Asset anAsset, double x, double y, double width) {
            if (showDoneMark && anAsset != null) {
                int status = anAsset.getStatus();
                String mimeType = anAsset.getMimeType();
                if (status == Constants.STATE_READY
                        || status == Constants.STATE_CORRECTED && ImageConstants.IMAGE_X_RAW.equals(mimeType)) {
                    Image icon = Icons.done.getImage();
                    addChild(doneMark = new ZPSWTImage(pswtCanvas, icon));
                    doneMark.setOffset(width + x, y - icon.getBounds().height);
                    doneMark.setName(Messages.AnimatedGallery_raw_recipe_applies);
                }
            }
        }

        private void addVoiceNote(PSWTCanvas pswtCanvas, Asset anAsset, double width, double height) {
            if (showVoicenoteButton && anAsset != null) {
                Image speakerIcon = UiUtilities.getAnnotationIcon(anAsset);
                if (speakerIcon != null) {
                    Rectangle ibounds = speakerIcon.getBounds();
                    addChild(speakerNode = new ZPSWTImage(pswtCanvas, speakerIcon));
                    speakerNode.setOffset((width - ibounds.width) / 2, height - ibounds.height);
                    speakerNode
                            .setName(AssetEnsemble.hasVoiceNote(anAsset) ? Messages.AnimatedGallery_play_voicenote
                                    : AssetEnsemble.getNoteText(anAsset));
                }
            }
        }

        private void addRotateButtons(PSWTCanvas pswtCanvas, Asset anAsset, double width, double height) {
            if (showRotateButtons && anAsset != null) {
                Image rotate90icon = Icons.rotate90.getImage();
                Rectangle bounds5 = rotate90icon.getBounds();
                addChild(rotate90Arrow = new ZPSWTImage(pswtCanvas, rotate90icon));
                rotate90Arrow.setOffset(LOWER_THUMBNAIL_HMARGINS,
                        height - bounds5.height - LOWER_THUMBNAIL_HMARGINS);
                rotate90Arrow.setName(Messages.AnimatedGallery_rotate_right);
                Image rotate270icon = Icons.rotate270.getImage();
                Rectangle bounds6 = rotate270icon.getBounds();
                addChild(rotate270Arrow = new ZPSWTImage(pswtCanvas, rotate270icon));
                rotate270Arrow.setOffset(width - bounds6.width - LOWER_THUMBNAIL_HMARGINS,
                        height - bounds6.height - LOWER_THUMBNAIL_HMARGINS);
                rotate270Arrow.setName(Messages.AnimatedGallery_rotate_left);
            }
        }

        private double addColorCode(PSWTCanvas pswtCanvas, Asset anAsset) {
            if (showColorCode && anAsset != null) {
                int colorCode = anAsset.getColorCode();
                if (colorCode == Constants.COLOR_UNDEFINED) {
                    IPostProcessor2[] processors = Core.getCore().getDbFactory().getAutoColoringProcessors();
                    if (processors != null)
                        for (int i = 0; i < processors.length; i++)
                            if (processors[i] != null && processors[i].accept(anAsset)) {
                                colorCode = i;
                                break;
                            }
                }
                addChild(colorCodeRectangle = new ZPSWTImage(pswtCanvas, Icons.toSwtColors(colorCode)));
                colorCodeRectangle.setOffset(UPPER_THUMBNAIL_HMARGINS, UPPER_THUMBNAIL_HMARGINS);
                colorCodeRectangle.setName(Messages.AnimatedGallery_color_code);
                PBounds bounds = colorCodeRectangle.getBoundsReference();
                return bounds.width + bounds.x;
            }
            return 0d;
        }

        private void addLocationPin(PSWTCanvas pswtCanvas, Asset anAsset, double x, double y) {
            if (showLocation && anAsset != null) {
                boolean gps = !(Double.isNaN(asset.getGPSLatitude()) || Double.isNaN(asset.getGPSLatitude()));
                if (!gps)
                    gps = !Core.getCore().getDbManager()
                            .obtainStructForAsset(LocationShownImpl.class, asset.getStringId(), false).isEmpty();
                addChild(locationPin = new ZPSWTImage(pswtCanvas,
                        gps ? Icons.location.getImage() : Icons.nolocation.getImage()));
                locationPin.setOffset(x + UPPER_THUMBNAIL_HMARGINS, y + UPPER_THUMBNAIL_HMARGINS);
                locationPin.setName(
                        gps ? Messages.AnimatedGallery_location_present : Messages.AnimatedGallery_no_location);
            }
        }

        private void addRatingStar(PSWTCanvas pswtCanvas, Asset anAsset, double x, double y, double width) {
            if (RATING_NO != showRating && anAsset != null) {
                int rating = anAsset.getRating();
                double rfactor;
                Image icon;
                if (rating > RatingDialog.UNDEF) {
                    if (RATING_SIZE == showRating) {
                        if (rating > 0) {
                            rfactor = (rating + 0.6d) / 5.6d;
                            icon = Icons.rating61.getImage();
                        } else {
                            rfactor = 1.6d / 5.6d;
                            icon = Icons.rating60.getImage();
                        }
                    } else {
                        switch (rating) {
                        case 0:
                            icon = Icons.rating60.getImage();
                            break;
                        case 1:
                            icon = Icons.rating61.getImage();
                            break;
                        case 2:
                            icon = Icons.rating62.getImage();
                            break;
                        case 3:
                            icon = Icons.rating63.getImage();
                            break;
                        case 4:
                            icon = Icons.rating64.getImage();
                            break;
                        default:
                            icon = Icons.rating65.getImage();
                            break;
                        }
                        rfactor = 2.6d / 5.6d;
                    }
                } else {
                    rfactor = 3.6d / 5.6d;
                    icon = Icons.rating6u.getImage();
                }
                addChild(ratingStar = new ZPSWTImage(pswtCanvas, icon));
                ratingStar.scale(rfactor);
                ratingStar.setOffset(width + x - (icon.getBounds().width * rfactor) - UPPER_THUMBNAIL_HMARGINS, y);
                ratingStar.setName(Messages.AnimatedGallery_rating);
            }
        }

        private void addMediaIcon(PSWTCanvas pswtCanvas, Asset anAsset, double x, double y, double width) {
            IMediaSupport mediaSupport = CoreActivator.getDefault().getMediaSupport(anAsset.getFormat());
            if (mediaSupport != null) {
                Image icon = mediaSupport.getIcon40();
                if (icon != null) {
                    double rfactor = 2.6d / 5.6d;
                    Rectangle bounds4 = icon.getBounds();
                    double h = bounds4.height * rfactor;
                    addChild(mediaIcon = new ZPSWTImage(pswtCanvas, icon));
                    mediaIcon.setPickable(false);
                    mediaIcon.scale(rfactor);
                    mediaIcon.setOffset(width + x - bounds4.width * rfactor - UPPER_THUMBNAIL_HMARGINS,
                            y + 3 * h / 2);
                    mediaIcon.setName(mediaSupport.getName());
                }
            }
        }

        private String computeCaption(Asset anAsset) {
            return (anAsset != null && showLabel != Constants.NO_LABEL) ? UiUtilities.computeImageCaption(anAsset,
                    scoreFormatter, null, null, showLabel == Constants.CUSTOM_LABEL ? labelTemplate : null) : ""; //$NON-NLS-1$
        }

        public void setSelectTransform() {
            selectTransform = getTransform();
            showDecoration(true);
        }

        public void showDecoration(boolean isSelected) {
            if (colorCodeRectangle != null)
                colorCodeRectangle.setVisible(
                        isSelected || asset != null && asset.getColorCode() != Constants.COLOR_UNDEFINED);
            if (locationPin != null)
                locationPin.setVisible(isSelected
                        || !(Double.isNaN(asset.getGPSLatitude()) || Double.isNaN(asset.getGPSLatitude())));
            if (ratingStar != null)
                ratingStar.setVisible(isSelected || asset != null && asset.getRating() > 0);
            if (rotate90Arrow != null)
                rotate90Arrow.setVisible(isSelected);
            if (rotate270Arrow != null)
                rotate270Arrow.setVisible(isSelected);
            shadow.setVisible(isSelected);
            if (frameLabels != null)
                for (PSWTText label : frameLabels)
                    label.setVisible(isSelected);
            caption.setVisible(isSelected);
        }

        public void setMultiTransform() {
            multiTransform = getTransform();
            showDecoration(true);
        }

        public void setDeselectTransform() {
            deselectTransform = getTransform();
            showDecoration(false);
        }

        public void deselect(int msec, boolean hideCover) {
            PTransformActivity animateToTransform = animateToTransform(deselectTransform, msec);
            if (hideCover)
                animateToTransform.setDelegate(new PActivity.PActivityDelegate() {
                    public void activityStepped(PActivity activity) {
                        // do nothing
                    }

                    public void activityStarted(PActivity activity) {
                        // do nothing
                    }

                    public void activityFinished(PActivity activity) {
                        try {
                            slideBar.removeChild(cover);
                        } catch (Exception e) {
                            // do nothing
                        }
                    }
                });
            showDecoration(false);
        }

        public void select(int msec, boolean cutTheTallPoppies) {
            PTransformActivity animateToTransform;
            switch (selectedSlides.size()) {
            case 0:
            case 1:
                animateToTransform = animateToTransform(selectTransform, msec);
                break;
            case 2:
                animateToTransform = animateToTransform(multiTransform, msec);
                if (cutTheTallPoppies)
                    for (Asset anAsset : selectedSlides) {
                        if (anAsset != this.asset) {
                            Integer ix = galleryMap.get(anAsset);
                            if (ix != null)
                                slides[ix].select(msec, false);
                        }
                    }
                break;
            default:
                animateToTransform = animateToTransform(multiTransform, msec);
                break;
            }
            animateToTransform.setDelegate(new PActivity.PActivityDelegate() {

                public void activityStepped(PActivity activity) {
                    // do nothing
                }

                public void activityStarted(PActivity activity) {
                    // do nothing
                }

                public void activityFinished(PActivity activity) {
                    switch (asset.getFileState()) {
                    case IVolumeManager.OFFLINE:
                        if (offlineColor != null)
                            caption.setPenColor(offlineColor);
                        break;
                    case IVolumeManager.REMOTE:
                    case IVolumeManager.PEER:
                        if (remoteColor != null)
                            caption.setPenColor(remoteColor);
                        break;
                    default:
                        caption.setPenColor(UiUtilities.getAwtForeground(canvas, null));
                        break;
                    }
                    caption.setVisible(true);
                    showDecoration(true);
                }
            });
        }

        public Asset getAsset() {
            return asset;
        }

        public boolean isSelected() {
            return selectedSlides.contains(asset);
        }

        public boolean isRatingStar(PNode picked) {
            return picked == ratingStar;
        }

        public boolean isDoneMark(PNode picked) {
            return picked == doneMark;
        }

        public boolean isColorCodeRectangle(PNode picked) {
            return picked == colorCodeRectangle;
        }

        public boolean isLocationPin(PNode picked) {
            return picked == locationPin;
        }

        public boolean isRotate90(PNode picked) {
            return picked == rotate90Arrow;
        }

        public boolean isRotate270(PNode picked) {
            return picked == rotate270Arrow;
        }

        public boolean isVoicenote(PNode picked) {
            return picked == speakerNode;
        }

        public int getIndex() {
            return index;
        }

        public void update(Asset anAsset) {
            removeAllChildren();
            slide = new PSWTAssetThumbnail(canvas, imageSource, anAsset);
            PBounds bounds = slide.getBoundsReference();
            double x = bounds.getX();
            double y = bounds.getY();
            double width = bounds.getWidth();
            double height = bounds.getHeight();
            shadow.setBounds(x + 15, y + 15, width, height);
            setBounds(x, y, width + 5, height + 5);
            addChild(shadow);
            addChild(slide);
            slide.setPickable(false);
            caption.setText(computeCaption(anAsset));
            addChild(caption);
            addRatingStar(canvas, anAsset, x, y, width);
            addDoneMark(canvas, anAsset, x, y, width);
            addColorCode(canvas, anAsset);
            addRotateButtons(canvas, anAsset, width, height);
            addVoiceNote(canvas, anAsset, width, height);
        }

        public void processTextEvent(TextField focus) {
            String text = focus.getText();
            int p = captionText.lastIndexOf('.');
            if (p >= 0 && text.lastIndexOf('.') < 0)
                text += captionText.substring(p);
            if (focus.isValid() && !captionText.equals(text))
                fireModifyEvent(asset, text);
        }

        public boolean traverse(char keyCharacter, int stateMask) {
            if (keyCharacter == SWT.TAB) {
                nav((stateMask & SWT.SHIFT) != 0 ? -1 : 1, 0);
                getControl().getDisplay().asyncExec(() -> {
                    if (!getControl().isDisposed())
                        focusOnTitle();
                });
                return true;
            }
            return false;
        }

        public void setFocusTo(TextField field, boolean focussed) {
            field.setFocus(focussed);
            textEventHandler.setFocus(field);
        }

    }

    public static final int RATE = 1;

    public static final int CODE = 2;

    public static final int ROTATE = 3;

    public static final int VOICENOTE = 4;

    public static final int STATUS = 5;

    public static final int SHOWLOCATION = 6;

    public static final int REGION = 7;

    private static final PGalleryItem[] NOSLIDES = new PGalleryItem[0];

    private static final int RATING_NO = 0;
    private static final int RATING_SIZE = 1;
    private static final int RATING_COUNT = 2;

    private static final double PAN_SENSITIVITY = 0.2d;
    private static final double VACCEL = 5d;
    private static final double HACCEL = 2.5d;

    private Color offlineColor;
    private Color remoteColor;

    PSWTPath slideBar;
    protected PGalleryItem[] slides = NOSLIDES;
    private PSWTCanvas canvas;
    private int columns = 6;
    private int slideSize;
    protected TreeSet<Asset> selectedSlides = new TreeSet<Asset>(new Comparator<Asset>() {
        public int compare(Asset a1, Asset a2) {
            Integer i1 = galleryMap.get(a1);
            Integer i2 = galleryMap.get(a2);
            return (i1 == null || i2 == null) ? 0 : i1.compareTo(i2);
        }
    });
    private List<Asset> collection;
    private int count;
    private PSWTPath surface;
    java.awt.Rectangle surfaceBounds;
    private List<Listener> listeners = new ArrayList<Listener>(3);
    private GalleryZoomEventHandler zoomHandler;
    private InertiaMouseWheelListener wheelListener;
    private PSWTImage cover;
    Image shadowImage;
    protected boolean showRotateButtons;
    protected boolean showColorCode;
    protected int showRating;
    protected boolean showLocation;
    protected boolean showVoicenoteButton;
    protected boolean showDoneMark;
    private int showRegions;
    private GalleryPanEventHandler panHandler;
    private Map<Asset, Integer> galleryMap = new HashMap<Asset, Integer>();
    private ImageStore imageSource;

    private AssetSelection oldAssetSelection;

    private IScoreFormatter scoreFormatter;

    private PGalleryItem focussedSlide;

    protected PGalleryItem lastSingleClick;

    private final int thumbSize;

    protected TextEventHandler textEventHandler;

    private org.eclipse.swt.graphics.Color selectionBackgroundColor;

    private org.eclipse.swt.graphics.Color foregroundColor;

    private org.eclipse.swt.graphics.Color backgroundColor;

    public VerifyListener titleVerifyListener;

    private String personId;

    private final Point2D pntSrc = new Point(0, 0);

    private Color regionColor;

    protected double previousMagnification = 1d;

    protected AffineTransform oldTransform;

    private List<Rectangle> rectangleList = new ArrayList<>();

    private int showLabel;

    private String labelTemplate;

    private int labelFontSize;

    public AnimatedGallery(final PSWTCanvas canvas, int slideSize, int thumbSize, ImageStore imageSource) {
        this.thumbSize = thumbSize;
        this.imageSource = imageSource;
        setPreferences();
        this.canvas = canvas;
        this.slideSize = slideSize;
        RGB rgb = JFaceResources.getColorRegistry().getRGB(Constants.APPCOLOR_REGION_FACE);
        regionColor = new Color(rgb.red, rgb.green, rgb.blue);
        canvas.setInteractingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
        Rectangle bounds = canvas.getDisplay().getPrimaryMonitor().getBounds();
        surfaceBounds = new java.awt.Rectangle(-bounds.width * 10, -bounds.height * 10, bounds.width * 20,
                bounds.height * 20);
        surface = PSWTPath.createRectangle(surfaceBounds.x, surfaceBounds.y, surfaceBounds.width,
                surfaceBounds.height);
        slideBar = PSWTPath.createRectangle(0, 0, columns * slideSize, slideSize);
        cover = new ZPSWTImage(canvas, Icons.transparentCover.getImage());
        cover.setPickable(false);
        themeChanged();
        canvas.getLayer().addChild(surface);
        surface.addChild(slideBar);
        // TODO is this needed?
        // canvas.addGestureListener(new GestureListener() {
        //
        // public void gesture(GestureEvent e) {
        // if ((e.detail & SWT.GESTURE_BEGIN) != 0) {
        // if (e.magnification == 1d) {
        // previousMagnification = e.magnification;
        // wheelListener.pause();
        // }
        // } else if ((e.detail & SWT.GESTURE_MAGNIFY) != 0) {
        // zoom(canvas,
        // Math.sqrt(e.magnification / previousMagnification));
        // previousMagnification = e.magnification;
        // } else if ((e.detail & SWT.GESTURE_PAN) != 0) {
        // pan(canvas, e.xDirection, e.yDirection);
        // } else if ((e.detail & SWT.GESTURE_END) != 0) {
        // wheelListener.restart();
        // }
        // }
        // });

        PBasicInputEventHandler eventHandler = new GalleryPBasicInputEventHandler(canvas);
        canvas.getRoot().getDefaultInputManager().setKeyboardFocus(eventHandler);
        canvas.addInputEventListener(eventHandler);
        setEventHandlers();
        canvas.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                if (collection != null)
                    fillSlidebar(collection);
            }
        });
        titleVerifyListener = new VerifyListener() {
            public void verifyText(VerifyEvent e) {
                fireErrorEvent(e);
            }
        };
    }

    private void setEventHandlers() {
        PNode[] workArea = new PNode[] { slideBar };
        canvas.removeInputEventListener(canvas.getPanEventHandler());
        canvas.addInputEventListener(panHandler = new GalleryPanEventHandler(this, workArea, surfaceBounds.x,
                surfaceBounds.y, surfaceBounds.width + surfaceBounds.x, surfaceBounds.height + surfaceBounds.y,
                GalleryPanEventHandler.BOTH, InputEvent.ALT_MASK | InputEvent.BUTTON1_MASK, -3, PAN_SENSITIVITY,
                HACCEL, VACCEL));
        canvas.removeInputEventListener(canvas.getZoomEventHandler());
        canvas.addInputEventListener(zoomHandler = new GalleryZoomEventHandler(this, workArea, -10));
        canvas.addMouseWheelListener(wheelListener = new InertiaMouseWheelListener());
    }

    protected void select(PGalleryItem item1, PGalleryItem item2) {
        Integer from = galleryMap.get(item1.getAsset());
        Integer to = galleryMap.get(item2.getAsset());
        if (from != null && to != null) {
            Set<Asset> toBeDeselected = new HashSet<Asset>(selectedSlides);
            for (int i = from; i <= to; i++) {
                if (slides[i] == null)
                    makeSlide(columns, i, collection.get(i));
                select(slides[i], 500);
                toBeDeselected.remove(slides[i].getAsset());
            }
            for (Asset asset : toBeDeselected) {
                Integer index = galleryMap.get(asset);
                if (index != null)
                    deselect(slides[index], 500);
            }
        }
    }

    protected boolean getOrder(PGalleryItem item1, PGalleryItem item2) {
        Integer index1 = galleryMap.get(item1.getAsset());
        Integer index2 = galleryMap.get(item2.getAsset());
        if (index1 != null && index2 != null)
            return index1 < index2;
        return false;
    }

    protected void nav(int i, int j) {
        if (focussedSlide != null) {
            Integer index = galleryMap.get(focussedSlide.getAsset());
            if (index != null) {
                int ni = index.intValue();
                ni += i + j * columns;
                if (ni >= 0 && ni < slides.length) {
                    PGalleryItem slide = slides[ni];
                    if (slide == null)
                        slide = makeSlide(columns, ni, collection.get(ni));
                    goToSlide(lastSingleClick = slide);
                }
            }
        }
    }

    protected void setPreferences() {
        IPreferenceStore preferenceStore = applyPreferences();
        preferenceStore.addPropertyChangeListener(new IPropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                String property = event.getProperty();
                if (PreferenceConstants.SHOWROTATEBUTTONS.equals(property)
                        || PreferenceConstants.SHOWCOLORCODE.equals(property)
                        || PreferenceConstants.SHOWRATING.equals(property)
                        || PreferenceConstants.SHOWLOCATION.equals(property)
                        || PreferenceConstants.SHOWDONEMARK.equals(property)
                        || PreferenceConstants.SHOWVOICENOTE.equals(property)
                        || PreferenceConstants.MAXREGIONS.equals(property))
                    applyPreferences();
            }
        });
    }

    protected IPreferenceStore applyPreferences() {
        final IPreferenceStore preferenceStore = UiActivator.getDefault().getPreferenceStore();
        showRotateButtons = preferenceStore.getBoolean(PreferenceConstants.SHOWROTATEBUTTONS);
        showColorCode = !PreferenceConstants.COLORCODE_NO
                .equals(preferenceStore.getString(PreferenceConstants.SHOWCOLORCODE));
        showLocation = preferenceStore.getBoolean(PreferenceConstants.SHOWLOCATION);
        String rating = preferenceStore.getString(PreferenceConstants.SHOWRATING);
        showRating = PreferenceConstants.SHOWRATING_NO.equals(rating) ? RATING_NO
                : PreferenceConstants.SHOWRATING_COUNT.equals(rating) ? RATING_COUNT : RATING_SIZE;
        showDoneMark = preferenceStore.getBoolean(PreferenceConstants.SHOWDONEMARK);
        showVoicenoteButton = preferenceStore.getBoolean(PreferenceConstants.SHOWVOICENOTE);
        showRegions = preferenceStore.getInt(PreferenceConstants.MAXREGIONS);
        return preferenceStore;
    }

    public void setMinScale(double minScale) {
        wheelListener.setMinScale(minScale);
        zoomHandler.setMinScale(minScale);
    }

    public void setMaxScale(double maxScale) {
        wheelListener.setMaxScale(maxScale);
        zoomHandler.setMaxScale(maxScale);
    }

    public double getMinScale() {
        return wheelListener.getMinScale();
    }

    public double getMaxScale() {
        return wheelListener.getMaxScale();
    }

    public void deselect(PGalleryItem slide, int msec) {
        if (selectedSlides.remove(slide.getAsset())) {
            slide.deselect(msec, selectedSlides.isEmpty());
            fireSelectionEvent(1, slide, 0);
        }
    }

    public void select(PGalleryItem slide, int msec) {
        if (!slide.isSelected()) {
            if (getSelectionCount() == 0) {
                PBounds bBounds = slideBar.getBoundsReference();
                slideBar.addChild(cover);
                cover.setBounds(0d, 0d, bBounds.getWidth(), bBounds.getHeight());
            }
            selectedSlides.add(slide.getAsset());
            slide.raiseToTop();
            slide.select(msec, true);
        }
    }

    void goToSlide(PGalleryItem slide) {
        Asset asset = slide == null ? null : slide.getAsset();
        if (getSelectionCount() != 1 || asset == null || !isSelected(asset)) {
            int l = selectedSlides.size();
            if (l > 0) {
                int i = 0;
                for (Asset a : selectedSlides) {
                    Integer index = galleryMap.get(a);
                    if (index != null) {
                        PGalleryItem item = slides[index];
                        if (item != null)
                            item.deselect(1000, (++i) == l && slide == null);
                    }
                }
                selectedSlides.clear();
                focussedSlide = null;
            }
            if (slide != null) {
                selectedSlides.add(asset);
                oldAssetSelection = null;
                if (l == 0) {
                    PBounds bBounds = slideBar.getBoundsReference();
                    slideBar.addChild(cover);
                    cover.setBounds(0d, 0d, bBounds.getWidth(), bBounds.getHeight());
                }
                slide.raiseToTop();
                slide.select(1000, true);
                focussedSlide = slide;
            }
        }
        for (Asset a : selectedSlides) {
            Integer index = galleryMap.get(a);
            if (index != null) {
                PGalleryItem item = slides[index];
                if (item != null) {
                    fireSelectionEvent(1, item, 0);
                    return;
                }
            }
        }
        fireSelectionEvent(1, null, 0);
    }

    public void themeChanged() {
        CssActivator.getDefault().applyStyles(canvas, true);
        Color newPaint = UiUtilities.getAwtBackground(canvas, backgroundColor);
        surface.setPaint(newPaint);
        slideBar.setPaint(newPaint);
        slideBar.setStrokeColor(UiUtilities.getAwtForeground(canvas, foregroundColor));
        CssActivator.getDefault().applyExtendedStyle(canvas, this);
    }

    public void setOfflineColor(org.eclipse.swt.graphics.Color c) {
        // do nothing
    }

    public void setSelectedOfflineColor(org.eclipse.swt.graphics.Color c) {
        offlineColor = UiUtilities.getAwtForeground(canvas, c);
    }

    public void setRemoteColor(org.eclipse.swt.graphics.Color c) {
        // not used
    }

    public void setTitleColor(org.eclipse.swt.graphics.Color c) {
        // not used
    }

    public void setSelectedRemoteColor(org.eclipse.swt.graphics.Color c) {
        remoteColor = UiUtilities.getAwtForeground(canvas, c);
    }

    public boolean applyColorsTo(Object element) {
        return element instanceof PSWTCanvas;
    }

    public void setCollection(List<Asset> list, boolean reset) {
        this.collection = list;
        count = (list == null) ? 0 : list.size();
        clearSlidebar();
        slides = new PGalleryItem[count];
        PBounds bounds = slideBar.getBoundsReference();
        if (count == 0)
            slideBar.setBounds(bounds.getX(), bounds.getY(), columns * slideSize, bounds.getHeight());
        else {
            int rows = (count + columns - 1) / columns;
            slideBar.setBounds(bounds.getX(), bounds.getY(), columns * slideSize, rows * slideSize);
        }
        slideBar.repaint();
        galleryMap.clear();
        if (reset)
            resetPanAndZoom(canvas.getCamera());
        fillSlidebar(list);
    }

    private void fillSlidebar(List<Asset> list) {
        if (list != null) {
            ArrayList<PGalleryItem> selected = new ArrayList<PGalleryItem>(getSelectionCount());
            for (int i = 0; i < list.size(); i++)
                if (slides[i] == null && testSlide(columns, i)) {
                    PGalleryItem selectedSlide = makeSlide(columns, i, list.get(i));
                    if (selectedSlide != null)
                        selected.add(selectedSlide);
                }
            for (PGalleryItem galleryItem : selected)
                galleryItem.raiseToTop();
        }
    }

    public void update(Collection<? extends Asset> assets) {
        for (Asset asset : assets) {
            Integer i = galleryMap.get(asset);
            if (i != null) {
                PGalleryItem item = slides[i];
                if (item != null)
                    item.update(asset);
            }
        }
    }

    public void setColumns(int columns) {
        this.columns = columns;
        PBounds bounds = slideBar.getBoundsReference();
        int cnt = slides.length;
        if (cnt > 0) {
            int rows = (cnt + columns - 1) / columns;
            clearSlidebar();
            slides = new PGalleryItem[cnt];
            slideBar.setBounds(bounds.getX(), bounds.getY(), columns * slideSize, rows * slideSize);
            slideBar.repaint();
            fillSlidebar(collection);
        } else {
            slideBar.setBounds(bounds.getX(), bounds.getY(), columns * slideSize, bounds.getHeight());
            slideBar.repaint();
        }
    }

    private boolean testSlide(int cols, int i) {
        int availableSpace = slideSize * 9 / 10;
        int margins = (slideSize - availableSpace) / 2;
        PCamera camera = canvas.getCamera();
        PBounds viewBounds = camera.getViewBounds();
        int x = i % cols;
        int y = i / cols;
        PBounds itemBounds = new PBounds(margins + x * slideSize, margins + y * slideSize, availableSpace,
                availableSpace);
        return viewBounds.intersects(itemBounds);
    }

    private PGalleryItem makeSlide(int cols, int i, Asset asset) {
        int availableSpace = slideSize * 9 / 10;
        int margins = (slideSize - availableSpace) / 2;
        int x = i % cols;
        int y = i / cols;
        PGalleryItem slide = new PGalleryItem(canvas, i, asset);
        slide.offset(x * slideSize + slideSize / 4, y * slideSize + slideSize / 3);
        double size = Math.max(slide.getHeight(), slide.getWidth());
        double thumbFactor = thumbSize / size;
        slide.scale(thumbFactor);
        slide.setSelectTransform();
        slide.setTransform(new AffineTransform());
        double scaleFactor = availableSpace / size;
        double multiScaleFactor = (scaleFactor + 1d) * 0.5d;
        slide.scale(multiScaleFactor);
        slide.offset(
                margins + x * slideSize + slideSize / 4
                        + (availableSpace - slide.getWidth() * multiScaleFactor) / 2,
                margins + y * slideSize + slideSize / 3
                        + (availableSpace - slide.getHeight() * multiScaleFactor) / 2);
        slide.setMultiTransform();
        slide.setTransform(new AffineTransform());
        slide.scale(scaleFactor);
        slide.offset(margins + x * slideSize + (availableSpace - slide.getWidth() * scaleFactor) / 2,
                margins + y * slideSize + (availableSpace - slide.getHeight() * scaleFactor) / 2);
        slide.setDeselectTransform();
        slideBar.addChild(0, slide);
        if (slides.length <= i) {
            PGalleryItem[] copy = new PGalleryItem[Math.max(i + 1, slides.length * 2)];
            System.arraycopy(slides, 0, copy, 0, slides.length);
            slides = copy;
        }
        slides[i] = slide;
        if (asset != null) {
            galleryMap.put(asset, i);
            if (isSelected(asset))
                select(slide, 100);
        }
        return slide;
    }

    private void clearSlidebar() {
        try {
            slideBar.removeAllChildren();
        } catch (SWTException e) {
            // Ignore
        }
        slides = NOSLIDES;
        selectedSlides.clear();
    }

    public boolean isDisposed() {
        return canvas.isDisposed();
    }

    public void dispose() {
        if (wheelListener != null) {
            wheelListener.dispose();
            wheelListener = null;
        }
        if (panHandler != null) {
            panHandler.dispose();
            panHandler = null;
        }
        if (zoomHandler != null) {
            zoomHandler.dispose();
            zoomHandler = null;
        }
        clearSlidebar();
    }

    public void addListener(Listener listener) {
        if (!listeners.contains(listener))
            listeners.add(listener);
    }

    public void addSelectionListener(SelectionListener e) {
        // do nothing
    }

    public void removeListener(Object o) {
        listeners.remove(o);
    }

    private void fireSelectionEvent(int cnt, PNode picked, int stateMask) {
        Event e = new Event();
        e.type = (cnt == 1) ? SWT.Selection : SWT.MouseDoubleClick;
        e.data = picked;
        e.stateMask = stateMask;
        fireEvent(e);
    }

    private void fireKeyEvent(char character, int keycode, int stateMask) {
        Event e = new Event();
        e.type = SWT.KeyUp;
        e.stateMask = stateMask;
        e.character = character;
        e.keyCode = keycode;
        fireEvent(e);
    }

    private void fireOperationEvent(PNode picked, int stateMask, int type, int value) {
        PGalleryItem parent = (PGalleryItem) picked.getParent();
        Asset asset = parent.getAsset();
        Event e = new Event();
        e.type = SWT.SetData;
        e.data = asset;
        e.stateMask = stateMask;
        e.detail = type;
        e.keyCode = value;
        fireEvent(e);
    }

    private void fireRegionEvent(int stateMask, ImageRegion imageRegion) {
        Event e = new Event();
        e.type = SWT.SetData;
        e.data = imageRegion;
        e.stateMask = stateMask;
        e.detail = REGION;
        e.keyCode = 0;
        fireEvent(e);
    }

    private void fireModifyEvent(Asset asset, String newTitle) {
        Event e = new Event();
        e.type = SWT.Modify;
        e.data = asset;
        e.text = newTitle;
        fireEvent(e);
    }

    private void fireErrorEvent(VerifyEvent e) {
        Event ev = new Event();
        ev.data = e.data;
        ev.text = e.text;
        ev.type = SWT.Verify;
        fireEvent(ev);
    }

    private void fireEvent(Event ev) {
        ev.display = canvas.getDisplay();
        ev.widget = canvas;
        ev.time = (int) System.currentTimeMillis();
        for (Listener listener : listeners)
            listener.handleEvent(ev);
    }

    public AssetSelection getSelection() {
        if (oldAssetSelection != null)
            return oldAssetSelection;
        AssetSelection sel = new AssetSelection(selectedSlides.size());
        for (Asset asset : selectedSlides) {
            Integer index = galleryMap.get(asset);
            if (index != null) {
                PGalleryItem item = slides[index];
                if (item != null) {
                    sel.add(item.getAsset());
                    continue;
                }
            }
            sel.add(asset);
        }
        return sel;
    }

    public void setSelection(AssetSelection assetSelection, IAssetProvider provider) {
        oldAssetSelection = assetSelection;
        if (assetSelection.isPicked()) {
            for (Iterator<Asset> iterator = selectedSlides.iterator(); iterator.hasNext();) {
                Asset asset = iterator.next();
                if (!assetSelection.isSelected(asset)) {
                    Integer index = galleryMap.get(asset);
                    if (index != null) {
                        PGalleryItem item = slides[index];
                        if (item != null) {
                            deselect(item, 200);
                            continue;
                        }
                    }
                    iterator.remove();
                }
            }
            int cnt = 0;
            for (Asset asset : assetSelection) {
                if (!selectedSlides.contains(asset)) {
                    Integer i = galleryMap.get(asset);
                    int index = -1;
                    if (i != null)
                        index = i;
                    else if (provider != null) {
                        index = provider.indexOf(asset);
                        if (index >= 0)
                            makeSlide(columns, index, asset);
                    }
                    if (index >= 0 && slides[index] != null) {
                        if (cnt == 0)
                            revealSlide(asset);
                        select(slides[index], 1000);
                        continue;
                    }
                    selectedSlides.add(asset);
                } else if (cnt == 0)
                    revealSlide(asset);
                ++cnt;
            }
        } else
            for (Asset asset : galleryMap.keySet()) {
                if (!selectedSlides.contains(asset)) {
                    Integer index = galleryMap.get(asset);
                    if (slides[index] != null)
                        select(slides[index], 1000);
                }
            }
    }

    private void revealSlide(Asset asset) {
        Integer i = galleryMap.get(asset);
        if (i != null) {
            PGalleryItem slide = slides[i];
            Point2D r1c = slide.getBounds().getCenter2D();
            slide.localToGlobal(r1c);
            PCamera camera = canvas.getCamera();
            Point2D r2c = camera.getBounds().getCenter2D();
            camera.localToGlobal(r2c);
            pan(canvas, (int) (r2c.getX() - r1c.getX()), (int) (r2c.getY() - r1c.getY()));
            slide.raiseToTop();
        }
    }

    private boolean isSelected(Asset asset) {
        if (oldAssetSelection != null && !oldAssetSelection.isPicked())
            return true;
        return selectedSlides.contains(asset);
    }

    public String getTooltip(int x, int y) {
        ArrayList<PNode> nodes = findMatchingNodes(x, y);
        for (PNode node : nodes) {
            String name = node.getName();
            if (name != null)
                return name;
        }
        return null;
    }

    public Asset getItem(int x, int y) {
        ArrayList<PNode> nodes = findMatchingNodes(x, y);
        for (PNode node : nodes) {
            if (node instanceof PGalleryItem)
                return ((PGalleryItem) node).getAsset();
            if (node instanceof PSWTImage) {
                PNode parent = node.getParent();
                if (parent instanceof PGalleryItem)
                    return ((PGalleryItem) parent).getAsset();
            }
        }
        return null;
    }

    /**
     * @param x
     *            - x-coordinate canvas
     * @param y
     *            - y-coordinate canvas
     * @return list of matching nodes
     */
    private ArrayList<PNode> findMatchingNodes(int x, int y) {
        PCamera camera = canvas.getCamera();
        pntSrc.setLocation(x, y);
        camera.localToView(pntSrc);
        ArrayList<PNode> results = new ArrayList<PNode>();
        slideBar.findIntersectingNodes(new java.awt.Rectangle((int) pntSrc.getX(), (int) pntSrc.getY(), 1, 1),
                results);
        return results;
    }

    /**
     * @param x
     *            - x-coordinate canvas
     * @param y
     *            - y-coordinate canvas
     * @param owner
     *            - owning thumbnail or null
     * @return list of matching regions
     */
    public PGalleryRegion[] getRegions(int x, int y, PSWTAssetThumbnail owner) {
        ArrayList<PNode> nodes = findMatchingNodes(x, y);
        List<PGalleryRegion> result = new ArrayList<>(nodes.size());
        for (PNode node : nodes)
            if (node instanceof PGalleryRegion && (owner == null || node.getParent() == owner))
                result.add(((PGalleryRegion) node));
        return result.toArray(new PGalleryRegion[result.size()]);
    }

    public int getSelectionCount() {
        return (oldAssetSelection != null) ? oldAssetSelection.size() : selectedSlides.size();
    }

    public void setScoreFormatter(IScoreFormatter scoreFormatter) {
        this.scoreFormatter = scoreFormatter;
    }

    public Display getDisplay() {
        return canvas.getDisplay();
    }

    public void setCursor(Cursor cursor) {
        canvas.setCursor(cursor);
    }

    public void addMouseListener(MouseListener listener) {
        canvas.addMouseListener(listener);
    }

    public void addMouseMoveListener(MouseMoveListener listener) {
        canvas.addMouseMoveListener(listener);
    }

    public void addKeyListener(KeyListener listener) {
        canvas.addKeyListener(listener);
    }

    public void removeMouseListener(MouseListener listener) {
        canvas.removeMouseListener(listener);
    }

    public void removeMouseMoveListener(MouseMoveListener listener) {
        canvas.removeMouseMoveListener(listener);
    }

    public void removeKeyListener(KeyListener listener) {
        canvas.removeKeyListener(listener);
    }

    public Control getControl() {
        return canvas;
    }

    public void setFocus() {
        canvas.setFocus();
    }

    public Shell getShell() {
        return canvas.getShell();
    }

    public PGalleryItem[] getItems() {
        return slides;
    }

    public void setForegroundColor(org.eclipse.swt.graphics.Color foregroundColor) {
        this.foregroundColor = foregroundColor;
    }

    public void setSelectionForegroundColor(org.eclipse.swt.graphics.Color selectionForegroundColor) {
        // do nothing
    }

    public void setSelectionBackgroundColor(org.eclipse.swt.graphics.Color selectionBackgroundColor) {
        this.selectionBackgroundColor = selectionBackgroundColor;
    }

    public void setBackgroundColor(org.eclipse.swt.graphics.Color backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    public void setTitleForeground(org.eclipse.swt.graphics.Color color) {
        // do nothing
    }

    public void setTitleBackground(org.eclipse.swt.graphics.Color color) {
        // do nothing
    }

    public void setPersonFilter(String personId) {
        this.personId = personId;
    }

    private void resetPanAndZoom(PCamera camera) {
        wheelListener.cancel();
        if (oldTransform == null) {
            oldTransform = camera.getViewTransform();
            camera.setViewTransform(new AffineTransform());
        } else {
            camera.setViewTransform(oldTransform);
            resetTransform();
        }
    }

    private void pan(final PSWTCanvas pswtCanvas, int dx, int dy) {
        PCamera camera = pswtCanvas.getCamera();
        PBounds viewBounds = camera.getViewBounds();
        double x = viewBounds.getX() - dx;
        if (x < surfaceBounds.x && dx > 0)
            return;
        if (x + viewBounds.getWidth() > surfaceBounds.x + surfaceBounds.width && dx < 0)
            return;
        double y = viewBounds.getY() - dy;
        if (y < surfaceBounds.y && dy > 0)
            return;
        if (y + viewBounds.getHeight() > surfaceBounds.y + surfaceBounds.height && dy < 0)
            return;
        camera.translateView(dx, dy);
        resetTransform();
    }

    private void zoom(final PSWTCanvas pswtCanvas, double scaleDelta) {
        PCamera camera = pswtCanvas.getCamera();
        double newScale = scaleDelta * camera.getViewScale();
        if (newScale >= wheelListener.getMinScale() && newScale <= wheelListener.getMaxScale()) {
            Point2D offset = null;
            for (Asset asset : selectedSlides) {
                Integer index = galleryMap.get(asset);
                if (index != null) {
                    PGalleryItem galleryItem = slides[index];
                    if (galleryItem != null)
                        offset = galleryItem.getOffset();
                }
            }
            if (offset != null)
                camera.scaleViewAboutPoint(scaleDelta, offset.getX(), offset.getY());
            else {
                Rectangle clientArea = pswtCanvas.getClientArea();
                camera.scaleViewAboutPoint(scaleDelta, clientArea.x + clientArea.width / 2,
                        clientArea.y + clientArea.height / 2);
            }
            resetTransform();
        }
    }

    public void resetTransform() {
        oldTransform = null;
    }

    protected void focusOnTitle() {
        if (focussedSlide != null && focussedSlide.caption != null)
            focussedSlide.setFocusTo(focussedSlide.caption, true);
    }

    public ImageRegion getBestFaceRegion(int x, int y, boolean all, PSWTAssetThumbnail owner) {
        PGalleryRegion[] regions = getRegions(x, y, owner);
        if (regions != null) {
            ImageRegion bestRegion = getBestRegion(regions, Region.type_face, true, x, y);
            return (bestRegion == null && all) ? getBestRegion(regions, Region.type_face, false, x, y) : bestRegion;
        }
        return null;
    }

    public ImageRegion getBestRegion(PGalleryRegion[] regions, String type, boolean all, int x, int y) {
        ImageRegion foundRegion = null;
        if (regions != null) {
            double minD = Double.MAX_VALUE;
            for (PGalleryRegion region : regions) {
                ImageRegion imageRegion = region.getImageRegion();
                if ((all || imageRegion.name == null || imageRegion.name.equals("?")) //$NON-NLS-1$
                        && (type == null || type.equals(imageRegion.type))) {
                    double d = region.getDistanceFromRegionCenter(x, y);
                    if (d < minD) {
                        minD = d;
                        foundRegion = imageRegion;
                    }
                }
            }
        }
        return foundRegion;
    }

    public ImageRegion[] findAllRegions(org.eclipse.swt.events.MouseEvent event) {
        PGalleryRegion[] regions = getRegions(event.x, event.y, null);
        ImageRegion[] imageRegions = new ImageRegion[regions.length];
        for (int i = 0; i < regions.length; i++)
            imageRegions[i] = regions[i].getImageRegion();
        return imageRegions;
    }

    public void setAppearance(int showLabel, String labelTemplate, int fontSize) {
        this.showLabel = showLabel;
        this.labelTemplate = labelTemplate;
        this.labelFontSize = fontSize;
    }

}