com.bdaum.zoom.ui.internal.views.SlideshowView.java Source code

Java tutorial

Introduction

Here is the source code for com.bdaum.zoom.ui.internal.views.SlideshowView.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-2019 Berthold Daum  
 */

package com.bdaum.zoom.ui.internal.views;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.InputEvent;
import java.awt.geom.Point2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.AbstractOperation;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.UndoContext;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.piccolo2d.PNode;
import org.piccolo2d.event.PBasicInputEventHandler;
import org.piccolo2d.event.PInputEvent;
import org.piccolo2d.extras.swt.PSWTCanvas;
import org.piccolo2d.extras.swt.PSWTHandle;
import org.piccolo2d.extras.swt.PSWTPath;
import org.piccolo2d.extras.swt.PSWTText;
import org.piccolo2d.extras.util.PLocator;
import org.piccolo2d.util.PBounds;
import org.piccolo2d.util.PDimension;

import com.bdaum.aoModeling.runtime.IIdentifiableObject;
import com.bdaum.aoModeling.runtime.IdentifiableObject;
import com.bdaum.zoom.cat.model.asset.Asset;
import com.bdaum.zoom.cat.model.asset.AssetImpl;
import com.bdaum.zoom.cat.model.group.slideShow.Slide;
import com.bdaum.zoom.cat.model.group.slideShow.SlideImpl;
import com.bdaum.zoom.cat.model.group.slideShow.SlideShow;
import com.bdaum.zoom.cat.model.group.slideShow.SlideShowImpl;
import com.bdaum.zoom.common.CommonUtilities;
import com.bdaum.zoom.core.Constants;
import com.bdaum.zoom.core.Core;
import com.bdaum.zoom.core.ISpellCheckingService;
import com.bdaum.zoom.core.IVolumeManager;
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.css.CSSProperties;
import com.bdaum.zoom.css.internal.CssActivator;
import com.bdaum.zoom.image.ImageUtilities;
import com.bdaum.zoom.ui.AssetSelection;
import com.bdaum.zoom.ui.Ui;
import com.bdaum.zoom.ui.internal.HelpContextIds;
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.EditSlideDialog;
import com.bdaum.zoom.ui.internal.dialogs.SectionBreakDialog;
import com.bdaum.zoom.ui.internal.dialogs.SelectSlideDialog;
import com.bdaum.zoom.ui.internal.dialogs.SlideshowEditDialog;
import com.bdaum.zoom.ui.internal.hover.IHoverContext;
import com.bdaum.zoom.ui.internal.job.DecorateJob;
import com.bdaum.zoom.ui.internal.operations.SlideshowPropertiesOperation;
import com.bdaum.zoom.ui.internal.widgets.AbstractHandle;
import com.bdaum.zoom.ui.internal.widgets.GalleryPanEventHandler;
import com.bdaum.zoom.ui.internal.widgets.GreekedPSWTText;
import com.bdaum.zoom.ui.internal.widgets.IAugmentedTextField;
import com.bdaum.zoom.ui.internal.widgets.PContainer;
import com.bdaum.zoom.ui.internal.widgets.PSWTAssetThumbnail;
import com.bdaum.zoom.ui.internal.widgets.PSWTSectionBreak;
import com.bdaum.zoom.ui.internal.widgets.TextEventHandler;
import com.bdaum.zoom.ui.internal.widgets.TextField;
import com.bdaum.zoom.ui.internal.widgets.ZPSWTImage;

@SuppressWarnings("restriction")
public class SlideshowView extends AbstractPresentationView implements IHoverContext {

    private static final Point DRAGTOLERANCE = new Point(25, 25);

    public class EditCaptionOperation extends AbstractOperation {

        private final SlideImpl slide;
        private final String text;
        private final IAugmentedTextField textField;
        private String oldCaption;

        public EditCaptionOperation(SlideImpl slide, String text, IAugmentedTextField textField) {
            super(Messages.getString("SlideshowView.edit_caption_undo")); //$NON-NLS-1$
            this.slide = slide;
            this.text = text;
            this.textField = textField;
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            oldCaption = slide.getCaption();
            doSetCaption(text);
            return Status.OK_STATUS;
        }

        private void doSetCaption(String t) {
            slide.setCaption(t);
            storeSafelyAndUpdateIndex(null, slide, slide.getAsset());
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            doSetCaption(text);
            doSetTextField(text);
            return Status.OK_STATUS;
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            doSetCaption(oldCaption);
            doSetTextField(oldCaption);
            return Status.OK_STATUS;
        }

        private void doSetTextField(String t) {
            textField.setText(t);
        }
    }

    public class EditSlideOperation extends AbstractOperation {

        public final static int FADEIN = 0;
        public final static int DURATION = 1;
        public final static int FADEOUT = 2;
        public final static int DELAY = 3;
        public static final int ZOOM = 4;
        public static final int ZOOMPOS = 5;
        public static final int EDIT_SLIDE = 100;
        private final PSlide pslide;
        private final SlideImpl slide;
        private SlideImpl backup;
        private SlideImpl redo;
        private final int mode;
        private boolean indexChange;

        public EditSlideOperation(PSlide pslide, SlideImpl slide, SlideImpl backup, int mode) {
            super(Messages.getString("SlideshowView.change_slide_layout_undo")); //$NON-NLS-1$
            switch (mode) {
            case FADEIN:
                setLabel(Messages.getString("SlideshowView.change_fadein")); //$NON-NLS-1$
                break;
            case DURATION:
                setLabel(Messages.getString("SlideshowView.change_dur")); //$NON-NLS-1$
                break;
            case FADEOUT:
                setLabel(Messages.getString("SlideshowView.change_fadeout")); //$NON-NLS-1$
                break;
            case DELAY:
                setLabel(Messages.getString("SlideshowView.change_delay")); //$NON-NLS-1$
                break;
            case ZOOM:
                setLabel(Messages.getString("SlideshowView.change_zoom")); //$NON-NLS-1$
                break;
            case ZOOMPOS:
                setLabel(Messages.getString("SlideshowView.change_zoomdir")); //$NON-NLS-1$
                break;
            case EDIT_SLIDE:
                setLabel(Messages.getString("SlideshowView.edit_slide_undo")); //$NON-NLS-1$
                break;
            }
            this.pslide = pslide;
            this.slide = slide;
            this.backup = backup;
            this.mode = mode;
            indexChange = (backup.getCaption() == null && slide.getCaption() != null
                    && !slide.getCaption().isEmpty())
                    || (backup.getCaption() != null && !backup.getCaption().equals(slide.getCaption()))
                    || (backup.getDescription() == null && slide.getDescription() != null
                            && !slide.getDescription().isEmpty())
                    || (backup.getDescription() != null && !backup.getDescription().equals(slide.getDescription()));
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            updateSlide();
            return Status.OK_STATUS;
        }

        private void updateSlide() {
            String assetId = slide.getAsset();
            storeSafelyAndUpdateIndex(null, slide, indexChange ? assetId : null);
            switch (mode) {
            case FADEIN:
            case DURATION:
            case FADEOUT:
            case DELAY:
            case ZOOM:
            case ZOOMPOS:
                pslide.layout();
                break;
            default:
                pslide.layout();
                pslide.update(slide, assetId == null ? null : Core.getCore().getDbManager().obtainAsset(assetId));
                break;
            }
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            slide.setCaption(redo.getCaption());
            slide.setSequenceNo(redo.getSequenceNo());
            slide.setDescription(redo.getDescription());
            slide.setLayout(redo.getLayout());
            slide.setDelay(redo.getDelay());
            slide.setFadeIn(redo.getFadeIn());
            slide.setDuration(redo.getDuration());
            slide.setFadeOut(redo.getFadeOut());
            slide.setEffect(redo.getEffect());
            slide.setZoom(redo.getZoom());
            slide.setZoomX(redo.getZoomX());
            slide.setZoomY(redo.getZoomY());
            slide.setAsset(redo.getAsset());
            slide.setNoVoice(redo.getNoVoice());
            slide.setSafety(redo.getSafety());
            updateSlide();
            return Status.OK_STATUS;
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            redo = cloneSlide(slide);
            slide.setCaption(backup.getCaption());
            slide.setSequenceNo(backup.getSequenceNo());
            slide.setDescription(backup.getDescription());
            slide.setLayout(backup.getLayout());
            slide.setDelay(backup.getDelay());
            slide.setFadeIn(backup.getFadeIn());
            slide.setDuration(backup.getDuration());
            slide.setFadeOut(backup.getFadeOut());
            slide.setEffect(backup.getEffect());
            slide.setZoom(backup.getZoom());
            slide.setZoomX(backup.getZoomX());
            slide.setZoomY(backup.getZoomY());
            slide.setAsset(backup.getAsset());
            slide.setNoVoice(backup.getNoVoice());
            slide.setSafety(backup.getSafety());
            updateSlide();
            return Status.OK_STATUS;
        }
    }

    public class CreateBreakOperation extends AbstractOperation {

        private final SlideImpl sectionSlide;
        private PSlide added;
        private final Point2D at;

        public CreateBreakOperation(SlideImpl sectionSlide, Point2D at) {
            super(Messages.getString("SlideshowView.create_section_break_undo")); //$NON-NLS-1$
            this.sectionSlide = sectionSlide;
            this.at = at;
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = getSlideShow();
            if (show != null) {
                PNode target = findSlide(show, at, null);
                int index = target == null ? slideBar.getChildrenCount() + 1
                        : target instanceof PSlide ? ((PSlide) target).slide.getSequenceNo() : 0;
                sectionSlide.setFadeIn(slideshow.getFading());
                sectionSlide.setFadeOut(slideshow.getFading());
                sectionSlide.setDelay(slideshow.getFading());
                int dur = CommonUtilities.computeHoverTime(
                        sectionSlide.getCaption().length() + sectionSlide.getDescription().length());
                sectionSlide.setDuration(Math.max(slideshow.getDuration(), dur));
                sectionSlide.setEffect(show.getEffect());
                sectionSlide.setZoom(0);
                sectionSlide.setSequenceNo(index + 1);
                if (index >= show.getEntry().size())
                    show.addEntry(sectionSlide.getStringId());
                else
                    show.getEntry().add(index, sectionSlide.getStringId());
                sectionSlide.setSlideShow_entry_parent(show.getStringId());
                added = makeSlide(sectionSlide);
                updateSequenceNumbers();
                realignSlides();
                List<IIdentifiableObject> toBeStored = new ArrayList<>(2);
                toBeStored.add(sectionSlide);
                toBeStored.add(show);
                storeSafelyAndUpdateIndex(null, toBeStored, null);
                updateActions(false);
                updateStatusLine();
                ++sectionCount;
            }
            return Status.OK_STATUS;
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            added = null;
            return execute(monitor, info);
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            if (added != null) {
                SlideShowImpl show = getSlideShow();
                if (show != null) {
                    removeSlide(show, added.slide.getSequenceNo());
                    updateSequenceNumbers();
                    realignSlides();
                    updateSelectionMarkers();
                    storeSafelyAndUpdateIndex(added.slide, show, added.getAssetId());
                    updateActions(false);
                    updateStatusLine();
                    --sectionCount;
                }
            }
            return Status.OK_STATUS;
        }

    }

    public class SetTimeCursorOperation extends AbstractOperation {

        private final Point2D to;
        private Point2D from;

        public SetTimeCursorOperation(Point2D to) {
            super(Messages.getString("SlideshowView.set_time_cursor_undo")); //$NON-NLS-1$
            this.to = to;
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            from = timeCursor.getOffset();
            timeCursor.setOffset(to.getX(), 0);
            return Status.OK_STATUS;
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            timeCursor.setOffset(to.getX(), 0);
            return Status.OK_STATUS;
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            timeCursor.setOffset(from.getX(), 0);
            return Status.OK_STATUS;
        }

    }

    public class MoveSlideOperation extends AbstractOperation {

        private final PSlide moved;
        private final Point2D newOffset;
        private int oldSeqNo;

        public MoveSlideOperation(PSlide moved, Point2D newOffset) {
            super(Messages.getString("SlideshowView.move_slide_undo")); //$NON-NLS-1$
            this.moved = moved;
            this.newOffset = newOffset;
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            final SlideShowImpl show = getSlideShow();
            if (show != null) {
                PNode target = findSlide(show, newOffset, moved);
                int newSeqNo = target == null ? slideBar.getChildrenCount() + 1
                        : target instanceof PSlide ? ((PSlide) target).slide.getSequenceNo() : 0;
                oldSeqNo = moved.slide.getSequenceNo();
                if (oldSeqNo != newSeqNo) {
                    if (oldSeqNo > newSeqNo)
                        ++newSeqNo;
                    removeSlide(show, oldSeqNo);
                    moved.setSequenceNo(newSeqNo);
                    insertSlide(show, moved);
                    updateSequenceNumbers();
                    newSeqNo = moved.slide.getSequenceNo();
                    realignSlides();
                    Core.getCore().getDbManager().safeTransaction(null, show);
                    updateActions(false);
                    return Status.OK_STATUS;
                }
            }
            return Status.CANCEL_STATUS;
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            return execute(monitor, info);
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = getSlideShow();
            removeSlide(show, moved.slide.getSequenceNo());
            moved.setSequenceNo(oldSeqNo);
            insertSlide(show, moved);
            updateSequenceNumbers();
            realignSlides();
            Core.getCore().getDbManager().safeTransaction(null, show);
            updateActions(false);
            return Status.OK_STATUS;
        }
    }

    public class DeleteSlideOperation extends AbstractOperation {

        private List<PSlide> deleted;
        private boolean cut;

        public DeleteSlideOperation(List<PSlide> deleted, boolean cut) {
            super(Messages.getString("SlideshowView.delete_slide_undo")); //$NON-NLS-1$
            this.deleted = deleted;
            this.cut = cut;
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = getSlideShow();
            if (show != null) {
                if (cut && !deleted.isEmpty())
                    clipboard.clear();
                List<IIdentifiableObject> toBeDeleted = new ArrayList<>(deleted.size());
                List<String> toBeIndexed = new ArrayList<>(deleted.size());
                List<Slide> clipped = null;
                for (int i = deleted.size() - 1; i >= 0; i--) {
                    SlideImpl slide = deleted.get(i).slide;
                    if (cut) {
                        if (clipped == null)
                            clipped = new ArrayList<Slide>(deleted.size());
                        clipped.add(slide);
                    }
                    toBeDeleted.add(slide);
                    if (slide.getAsset() != null)
                        toBeIndexed.add(slide.getAsset());
                    else
                        --sectionCount;
                    removeSlide(show, slide.getSequenceNo());
                }
                updateSequenceNumbers();
                realignSlides();
                updateSelectionMarkers();
                storeSafelyAndUpdateIndex(toBeDeleted, show, toBeIndexed);
                if (clipped != null) {
                    clipboard.clear();
                    clipboard.addAll(clipped);
                }
                updateActions(false);
                updateStatusLine();
                return Status.OK_STATUS;
            }
            return Status.CANCEL_STATUS;
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            return execute(monitor, info);
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = getSlideShow();
            if (show != null) {
                List<IIdentifiableObject> toBeStored = new ArrayList<>(2);
                List<String> toBeIndexed = new ArrayList<>(deleted.size());
                for (PSlide pslide : deleted) {
                    insertSlide(show, pslide);
                    SlideImpl slide = pslide.slide;
                    toBeStored.add(slide);
                    if (slide.getAsset() != null)
                        toBeIndexed.add(slide.getAsset());
                    else
                        ++sectionCount;
                }
                updateSequenceNumbers();
                realignSlides();
                toBeStored.add(show);
                storeSafelyAndUpdateIndex(null, toBeStored, toBeIndexed);
                updateActions(false);
                updateStatusLine();
            }
            return Status.OK_STATUS;
        }
    }

    public class DropAssetOperation extends AbstractOperation {

        private final AssetSelection selection;
        private final Point2D position;
        private List<PSlide> added;
        private final boolean replace;
        private PSlide deleted;

        public DropAssetOperation(AssetSelection selection, Point2D position, boolean replace) {
            super(Messages.getString("SlideshowView.drop_images_undo")); //$NON-NLS-1$
            this.selection = selection;
            this.position = position;
            this.replace = replace;
            added = new ArrayList<PSlide>(selection.size());
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = null;
            for (Asset asset : selection)
                if (accepts(asset)) {
                    show = getSlideShow();
                    break;
                }
            if (show != null) {
                PNode target = findSlide(show, position, null);
                List<String> assetIds = new ArrayList<String>(selection.size());
                int seqNo = target == null ? slideBar.getChildrenCount() + 1
                        : target instanceof PSlide ? ((PSlide) target).slide.getSequenceNo() : 0;
                Slide replaced = null;
                if (replace && target instanceof PSlide) {
                    replaced = ((PSlide) target).slide;
                    if (replaced.getAsset() != null)
                        assetIds.add(replaced.getAsset());
                    deleted = removeSlide(show, seqNo);
                    --seqNo;
                }
                boolean cleared = false;
                List<IIdentifiableObject> toBeStored = new ArrayList<>(selection.size() + 1);
                for (Asset asset : selection) {
                    if (!accepts(asset))
                        continue;
                    if (!cleared) {
                        clearSelection();
                        cleared = true;
                    }
                    int effect = show.getEffect();
                    if (effect == Constants.SLIDE_TRANSITION_RANDOM)
                        effect = (int) (Math.random() * Constants.SLIDE_TRANSITION_N);
                    String assetId = asset.getStringId();
                    SlideImpl slide = replaced != null
                            ? new SlideImpl(UiUtilities.createSlideTitle(asset), seqNo, null, replaced.getLayout(),
                                    replaced.getDelay(), replaced.getFadeIn(), replaced.getDuration(),
                                    replaced.getFadeOut(), replaced.getEffect(), replaced.getZoom(),
                                    replaced.getZoomX(), replaced.getZoomY(), replaced.getNoVoice(),
                                    asset.getSafety(), assetId)
                            : new SlideImpl(UiUtilities.createSlideTitle(asset), seqNo, null,
                                    Constants.SLIDE_NO_THUMBNAILS, show.getFading(), show.getFading(),
                                    show.getDuration(), show.getFading(), effect, show.getZoom(), 0, 0, false,
                                    asset.getSafety(), assetId);
                    replaced = null;
                    insertIntoShow(show, slide, seqNo);
                    PSlide pslide = makeSlide(slide);
                    pslide.setSelected(true);
                    lastSelection = recentSelection = pslide;
                    added.add(pslide);
                    toBeStored.add(slide);
                    assetIds.add(assetId);
                    ++seqNo;
                }
                updateSequenceNumbers();
                realignSlides();
                updateSlidebarBounds();
                toBeStored.add(show);
                storeSafelyAndUpdateIndex(deleted, toBeStored, assetIds);
                updateActions(false);
                updateStatusLine();
                return Status.OK_STATUS;
            }
            return Status.CANCEL_STATUS;
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            added.clear();
            return execute(monitor, info);
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = getSlideShow();
            if (show != null) {
                List<IIdentifiableObject> toBeDeleted = new ArrayList<>(added.size());
                List<String> assetIds = new ArrayList<>(added.size());
                for (int i = added.size() - 1; i >= 0; i--) {
                    PSlide pslide = added.get(i);
                    removeSlide(show, pslide.slide.getSequenceNo());
                    toBeDeleted.add(pslide.slide);
                    assetIds.add(pslide.getAssetId());
                }
                if (deleted != null)
                    insertSlide(show, deleted);
                updateSequenceNumbers();
                realignSlides();
                updateSelectionMarkers();
                storeSafelyAndUpdateIndex(toBeDeleted, show, assetIds);
                updateActions(false);
                updateStatusLine();
            }
            return Status.OK_STATUS;
        }
    }

    public class PasteSlidesOperation extends AbstractOperation {

        private Point2D position;
        private List<PSlide> added;

        public PasteSlidesOperation(Point2D position) {
            super(Messages.getString("SlideshowView.paste_slides")); //$NON-NLS-1$
            this.position = position;
            added = new ArrayList<PSlide>(clipboard.size());
        }

        @Override
        public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = getSlideShow();
            if (show != null && !clipboard.isEmpty()) {
                PNode target = findSlide(show, position, null);
                List<String> assetIds = new ArrayList<String>(clipboard.size());
                int seqNo = target == null ? slideBar.getChildrenCount() + 1
                        : target instanceof PSlide ? ((PSlide) target).slide.getSequenceNo() : 0;
                if (!clipboard.isEmpty())
                    clearSelection();
                List<IIdentifiableObject> toBeStored = new ArrayList<>(clipboard.size() + 1);
                for (IIdentifiableObject slide : clipboard) {
                    SlideImpl copy = cloneSlide((SlideImpl) slide);
                    insertIntoShow(show, copy, seqNo);
                    PSlide pslide = makeSlide(copy);
                    pslide.setSelected(true);
                    lastSelection = recentSelection = pslide;
                    added.add(pslide);
                    toBeStored.add(copy);
                    if (copy.getAsset() != null)
                        assetIds.add(copy.getAsset());
                    else
                        ++sectionCount;
                    ++seqNo;
                }
                updateSequenceNumbers();
                realignSlides();
                updateSlidebarBounds();
                toBeStored.add(show);
                storeSafelyAndUpdateIndex(null, toBeStored, assetIds);
                updateActions(false);
                updateStatusLine();
                return Status.OK_STATUS;
            }
            return Status.CANCEL_STATUS;
        }

        @Override
        public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            added.clear();
            return execute(monitor, info);
        }

        @Override
        public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
            SlideShowImpl show = getSlideShow();
            if (show != null) {
                List<IIdentifiableObject> toBeDeleted = new ArrayList<>(added.size());
                List<String> assetIds = new ArrayList<String>(added.size());
                for (int i = added.size() - 1; i >= 0; i--) {
                    PSlide pslide = added.get(i);
                    SlideImpl slide = pslide.slide;
                    removeSlide(show, slide.getSequenceNo());
                    toBeDeleted.add(slide);
                    if (slide.getAsset() != null)
                        assetIds.add(slide.getAsset());
                    else
                        --sectionCount;
                }
                updateSequenceNumbers();
                realignSlides();
                updateSelectionMarkers();
                storeSafelyAndUpdateIndex(toBeDeleted, show, assetIds);
                updateStatusLine();
                updateActions(false);
            }
            return Status.OK_STATUS;
        }

    }

    public class SlideShowDecorateJob extends DecorateJob {

        private Color penColor;

        public SlideShowDecorateJob() {
            super(Messages.getString("SlideshowView.decorate_slideshow")); //$NON-NLS-1$
        }

        @Override
        protected boolean mayRun() {
            return slideBar != null && slideBar.getChildrenCount() > 0;
        }

        @Override
        protected void doRun(IProgressMonitor monitor) {
            final Display display = canvas.getDisplay();
            IDbManager dbManager = Core.getCore().getDbManager();
            for (Object child : slideBar.getChildrenReference().toArray()) {
                if (child instanceof PSlide && isVisible() && mayRun()) {
                    final PSlide pslide = (PSlide) child;
                    SlideImpl slide = pslide.slide;
                    try {
                        String assetId = slide.getAsset();
                        if (assetId != null) {
                            AssetImpl asset = dbManager.obtainAsset(assetId);
                            if (asset != null && !display.isDisposed()
                                    && !pslide.caption.getPenColor().equals(penColor)) {
                                switch (volumeManager.determineFileState(asset)) {
                                case IVolumeManager.REMOTE:
                                    if (remoteColor != null)
                                        penColor = remoteColor;
                                    break;
                                case IVolumeManager.OFFLINE:
                                    if (offlineColor != null)
                                        penColor = offlineColor;
                                    break;
                                default:
                                    penColor = titleForegroundColor;
                                    break;
                                }
                                display.asyncExec(() -> {
                                    if (!canvas.isDisposed())
                                        pslide.caption.setPenColor(penColor);
                                });
                            }
                        }
                    } catch (Exception e) {
                        // just ignore
                    }
                }
                if (monitor.isCanceled())
                    break;
            }
        }
    }

    class PSlide extends PPresentationItem {

        private static final long serialVersionUID = -2576106874620734403L;

        private final class SlideHandle extends AbstractHandle {

            private static final long serialVersionUID = 744097311042432663L;

            class SlideLocator extends PLocator {

                private static final long serialVersionUID = 1228742743568586994L;

                @Override
                public double locateX() {
                    return SlideHandle.this.locateX();
                }

                @Override
                public double locateY() {
                    return SlideHandle.this.locateY();
                }
            }

            private int style;
            private double oldScale;
            private double shiftX;
            private double shiftY;
            private int size;
            private SlideImpl backup;

            private SlideHandle(int style, int size) {
                super(style == EditSlideOperation.ZOOMPOS ? SWT.CURSOR_SIZEALL : SWT.CURSOR_SIZEWE, null);
                setLocator(new SlideLocator());
                this.style = style;
                this.size = size;
                switch (style) {
                case EditSlideOperation.FADEIN:
                    setTooltip(Messages.getString("SlideshowView.fade_in_tooltip")); //$NON-NLS-1$
                    setPathToPolyline(new float[] { 0f, size, size / 2 }, new float[] { 0f, 0f, -size });
                    setPaint(FADEINHANDLECOLOR);
                    break;
                case EditSlideOperation.DELAY:
                    setTooltip(Messages.getString("SlideshowView.delay_tooltip")); //$NON-NLS-1$
                    setPathToPolyline(new float[] { size, 0, size }, new float[] { -size / 2, 0f, size / 2 });
                    setPaint(DELAYHANDLECOLOR);
                    break;
                case EditSlideOperation.DURATION:
                    setTooltip(Messages.getString("SlideshowView.duration_tooltip")); //$NON-NLS-1$
                    setPathToPolyline(new float[] { 0f, size, 0 }, new float[] { -size / 2, 0f, size / 2 });
                    setPaint(DURATIONHANDLECOLOR);
                    break;
                case EditSlideOperation.ZOOM:
                    setTooltip(Messages.getString("SlideshowView.zoom_amount")); //$NON-NLS-1$
                    setPathToPolyline(new float[] { 0f, size, size / 2 },
                            new float[] { size / 2f, size / 2f, size + size / 2f });
                    setPaint(ZOOMHANDLECOLOR);
                    break;
                case EditSlideOperation.ZOOMPOS:
                    setTooltip(Messages.getString("SlideshowView.zoom_dir")); //$NON-NLS-1$
                    setPathToEllipse(size / 2, size / 2, size, size);
                    setPaint(ZOOMHANDLECOLOR);
                    break;
                default:
                    setTooltip(Messages.getString("SlideshowView.fade_out_tooltip")); //$NON-NLS-1$
                    setPathToPolyline(new float[] { 0f, size, size / 2 },
                            new float[] { size / 2f, size / 2f, size + size / 2f });
                    setPaint(FADEOUTHANDLECOLOR);
                    break;
                }
                addInputEventListener(new PBasicInputEventHandler() {
                    @Override
                    public void mouseEntered(PInputEvent event) {
                        PSlide.this.raiseToTop();
                    }
                });
            }

            @Override
            public void startHandleDrag(Point2D localPoint, PInputEvent event) {
                backup = cloneSlide(slide);
                oldScale = pImage.getScale();
                shiftX = shiftY = 0;
                PSlide.this.raiseToTop();
            }

            @Override
            public void dragHandle(PDimension localDimension, PInputEvent event) {
                double width = localDimension.getWidth();
                double height = localDimension.getHeight();
                shiftX += width;
                shiftY += height;
                double shift = width / factor;
                switch (style) {
                case EditSlideOperation.FADEIN:
                    int fadein = (int) (slide.getFadeIn() - shift);
                    if (fadein >= 0) {
                        slide.setFadeIn(fadein);
                        startLine.setPathToPolyline(new float[] { (float) (-fadein * factor), 0f },
                                new float[] { slideSize, 0f });
                    }
                    break;
                case EditSlideOperation.DELAY:
                    int delay = (int) (slide.getDelay() + shift);
                    if (delay >= 0) {
                        slide.setDelay(delay);
                        PSlide.this.offset(width, 0);
                    }
                    break;
                case EditSlideOperation.DURATION:
                    int duration = (int) (slide.getDuration() + shift);
                    if (duration >= 0) {
                        slide.setDuration(duration);
                        double x = pImage.getOffset().getX();
                        pImage.setScale(oldScale * duration / backup.getDuration());
                        pImage.setOffset(x, slideSize - pImage.getFullBoundsReference().getHeight());
                    }
                    break;
                case EditSlideOperation.ZOOM:
                    slide.setZoom(
                            Math.max(0, Math.min(100, (int) (slide.getZoom() + width * 100d / pImage.getWidth()))));
                    break;
                case EditSlideOperation.ZOOMPOS:
                    slide.setZoomX(Math.max(-100,
                            Math.min(100, (int) (slide.getZoomX() + width * 200d / pImage.getWidth()))));
                    slide.setZoomY(Math.max(-100,
                            Math.min(100, (int) (slide.getZoomY() + height * 200d / pImage.getHeight()))));
                    break;
                default:
                    int fadeout = (int) (slide.getFadeOut() + shift);
                    if (fadeout >= 0) {
                        slide.setFadeOut(fadeout);
                        endLine.setPathToPolyline(new float[] { 0f, (float) (fadeout * factor) },
                                new float[] { 0f, slideSize });
                    }
                    break;
                }
                legend.setText(createLegendText(slide));
                invalidatePaint();
                relocateHandle();
                if (style == EditSlideOperation.ZOOM || style == EditSlideOperation.ZOOMPOS)
                    connectZoomPoints();
            }

            @Override
            public void endHandleDrag(Point2D localPoint, PInputEvent event) {
                double shift = shiftX / factor;
                switch (style) {
                case EditSlideOperation.FADEIN:
                    int fadein = (int) (backup.getFadeIn() - shift);
                    if (fadein >= 0) {
                        int maxFadein = slide.getDelay();
                        int seqNo = slide.getSequenceNo();
                        if (seqNo > 1)
                            maxFadein += ((PSlide) slideBar.getChild(seqNo - 2)).slide.getDuration();
                        slide.setFadeIn(Math.min(fadein, maxFadein));
                    }
                    break;
                case EditSlideOperation.DELAY:
                    int delay = (int) (backup.getDelay() + shift);
                    if (delay >= 0) {
                        slide.setDelay(delay);
                        PSlide.this.offset(shiftX, 0);
                    }
                    break;
                case EditSlideOperation.DURATION:
                    int duration = (int) (backup.getDuration() + shift);
                    if (duration >= 0)
                        slide.setDuration(duration);
                    break;
                case EditSlideOperation.ZOOM:
                    slide.setZoom(Math.max(0,
                            Math.min(100, (int) (backup.getZoom() + shiftX * 100d / pImage.getWidth()))));
                    break;
                case EditSlideOperation.ZOOMPOS:
                    slide.setZoomX(Math.max(-100,
                            Math.min(100, (int) (backup.getZoomX() + shiftX * 100d / pImage.getWidth()))));
                    slide.setZoomY(Math.max(-100,
                            Math.min(100, (int) (backup.getZoomY() + shiftY * 100d / pImage.getHeight()))));
                    break;
                default:
                    int maxFadeout = 60000;
                    int nxtIndex = slide.getSequenceNo();
                    if (slideshow != null && nxtIndex < slideshow.getEntry().size()) {
                        Slide next = slideMap.get(slideshow.getEntry(nxtIndex)).slide;
                        maxFadeout = next.getDelay() + Math.min(maxFadeout / 3, next.getDuration() / 2);
                    }
                    int fadeout = (int) (backup.getFadeOut() + shift);
                    if (fadeout >= 0)
                        slide.setFadeOut(Math.min(fadeout, maxFadeout));
                    break;
                }
                performOperation(new EditSlideOperation(PSlide.this, slide, backup, style));
                setPickedNode(this);
                updateActions(false);
                updateHandles();
            }

            public double locateX() {
                switch (style) {
                case EditSlideOperation.FADEIN:
                    return -slide.getFadeIn() * factor;
                case EditSlideOperation.DELAY:
                    return -size / 2d;
                case EditSlideOperation.DURATION:
                    return pImage.getFullBoundsReference().getWidth() + size / 2d;
                case EditSlideOperation.ZOOM:
                    return slide.getZoom() / 100d * pImage.getWidth();
                case EditSlideOperation.ZOOMPOS:
                    return (100 + slide.getZoomX()) / 200d * pImage.getWidth();
                default:
                    return pImage.getFullBoundsReference().getWidth() + slide.getFadeOut() * factor;
                }
            }

            public double locateY() {
                switch (style) {
                case EditSlideOperation.DELAY:
                case EditSlideOperation.DURATION:
                    return (pImage.getOffset().getY() + slideSize) / 2;
                case EditSlideOperation.ZOOM:
                    return pImage.getY() - size;
                case EditSlideOperation.ZOOMPOS:
                    return (100 + slide.getZoomY()) / 200d * pImage.getHeight();
                default:
                    return slideSize;
                }
            }
        }

        private SlideImpl slide;
        private PSWTHandle startPoint;
        private PSWTHandle endPoint;
        private PSWTPath startLine;
        private PSWTPath endLine;
        private PSWTHandle midPoint;
        private PSWTText legend;
        private PSWTText seqNo;
        private double totalWidth;
        private SlideHandle delayPoint;
        private final int csize = 12;
        private SlideHandle zoomPoint;
        private SlideHandle zoomDirPoint;
        private PSWTPath zoomLine;

        public PSlide(final PSWTCanvas canvas, SlideImpl slide) {
            this.slide = slide;
            final IDbManager dbManager = Core.getCore().getDbManager();
            totalWidth = (slide.getDuration() + slide.getFadeIn() + slide.getFadeOut()) * factor;
            double fadein = slide.getFadeIn() * factor;
            double fadeout = slide.getFadeOut() * factor;
            setPickable(false);
            // Image
            String assetId = slide.getAsset();
            pImage = assetId == null ? new PSWTSectionBreak(canvas, slide)
                    : new PSWTAssetThumbnail(canvas, SlideshowView.this, dbManager.obtainAsset(assetId));
            PBounds bounds = pImage.getBoundsReference();
            double scale = setSizeAndPos(slide, bounds);
            addChild(pImage);
            pImage.setPickable(true);
            setBounds(-fadein, 0, totalWidth, slideSize);
            // select frame
            pImage.addChild(createSelectionFrame(bounds));
            // fadein
            startPoint = new SlideHandle(EditSlideOperation.FADEIN, csize);
            startPoint.setPickable(true);
            addChild(startPoint);
            startLine = new PSWTPath();
            startLine.setPathToPolyline(new float[] { (float) -fadein, 0f }, new float[] { slideSize, 0f });
            startLine.setStrokeColor(FADEINSTROKECOLOR);
            startLine.setPickable(false);
            addChild(startLine);
            // delay
            delayPoint = new SlideHandle(EditSlideOperation.DELAY, csize);
            delayPoint.setPickable(true);
            addChild(delayPoint);
            // duration
            midPoint = new SlideHandle(EditSlideOperation.DURATION, csize);
            midPoint.setPickable(true);
            addChild(midPoint);
            // zoom
            if (assetId != null) {
                zoomPoint = new SlideHandle(EditSlideOperation.ZOOM, csize);
                zoomPoint.setPickable(true);
                pImage.addChild(zoomPoint);
                zoomDirPoint = new SlideHandle(EditSlideOperation.ZOOMPOS, csize);
                zoomDirPoint.setTransparency(0.5f);
                zoomDirPoint.setPickable(true);
                pImage.addChild(zoomDirPoint);
                zoomLine = new PSWTPath();
                zoomLine.setTransparency(0.5f);
                connectZoomPoints();
                zoomLine.setStrokeColor(ZOOMHANDLECOLOR);
                zoomLine.setPickable(false);
                pImage.addChild(zoomLine);
            }
            // fadeout
            endPoint = new SlideHandle(EditSlideOperation.FADEOUT, csize);
            endPoint.setPickable(true);
            addChild(endPoint);
            endLine = new PSWTPath();
            endLine.setPathToPolyline(new float[] { 0f, (float) fadeout }, new float[] { 0f, slideSize });
            double w = bounds.getWidth() * scale;
            endLine.setOffset(w, 0);
            endLine.setStrokeColor(FADEOUTSTROKCOLOR);
            endLine.setPickable(false);
            addChild(endLine);
            // caption
            RGB rgb = canvas.getForeground().getRGB();
            Color penColor = new Color(rgb.red, rgb.green, rgb.blue);
            seqNo = new GreekedPSWTText(createSequenceNo(slide), SEQNOFONT);
            seqNo.setGreekThreshold(5);
            seqNo.setTransparent(true);
            seqNo.setPenColor(penColor);
            seqNo.setOffset(csize, slideSize + csize);
            seqNo.setPickable(true);
            addChild(seqNo);
            caption = createTextLine(this, slide.getCaption(), 5, (int) (w - 2 * csize - seqNo.getWidth()),
                    (int) (seqNo.getWidth() + csize), slideSize + csize, titleForegroundColor, null, CAPTIONFONT,
                    ISpellCheckingService.TITLEOPTIONS, SWT.SINGLE);
            // legend
            legend = new GreekedPSWTText(createLegendText(slide), LEGENDFONT);
            legend.setGreekThreshold(5);
            legend.setTransparent(true);
            legend.setPenColor(penColor);
            legend.setOffset(csize, slideSize + 3 * csize);
            legend.setPickable(true);
            addChild(legend);
            // handlers
            installHandleEventHandlers(pImage, false, false, this);
            addInputEventListener(new PBasicInputEventHandler() {
                @Override
                public void mousePressed(PInputEvent event) {
                    setPickedNode(event.getPickedNode());
                    if (getAdapter(IPresentationItem.class) instanceof PSlide)
                        event.getPickedNode().raiseToTop();
                }
            });
            pImage.addInputEventListener(new PBasicInputEventHandler() {
                public void mousePressed(PInputEvent event) {
                    PNode pickedNode = event.getPickedNode();
                    setPickedNode(pickedNode);
                    if (event.isLeftMouseButton() && !event.isShiftDown() && !event.isAltDown()
                            && (pickedNode instanceof PSWTAssetThumbnail || pickedNode instanceof PSWTSectionBreak))
                        selectSlide((PSlide) pickedNode.getParent(), event.getModifiers(), event.getClickCount());
                }

                @Override
                public void mouseReleased(PInputEvent event) {
                    if (event.getClickCount() == 1 && event.isAltDown()) {
                        editLegend((PSlide) pImage.getParent());
                        canvas.repaint();
                    }
                }
            });
            updateHandles();
        }

        public void updateHandles() {
            boolean visible = slide.getZoom() > 0;
            if (zoomDirPoint != null)
                zoomDirPoint.setVisible(visible);
            if (zoomLine != null)
                zoomLine.setVisible(visible);
        }

        protected void connectZoomPoints() {
            if (zoomPoint != null) {
                PBounds bounds = zoomPoint.getBoundsReference();
                double x1 = bounds.getCenterX();
                double y1 = bounds.getCenterY();
                bounds = zoomDirPoint.getBoundsReference();
                double x2 = bounds.getCenterX();
                double y2 = bounds.getCenterY();
                zoomLine.setPathToPolyline(new float[] { (float) x1, (float) x2 },
                        new float[] { (float) y1, (float) y2 });
            }
        }

        public void update(SlideImpl slideImpl, Asset asset) {
            PNode removedImage = removeChild(pImage);
            pImage.getImage().dispose();
            images.remove(pImage.getImage());
            Display display = canvas.getDisplay();
            if (asset != null) {
                pImage = new ZPSWTImage(canvas,
                        ImageUtilities.loadThumbnail(display, asset == null ? null : asset.getJpegThumbnail(),
                                Ui.getUi().getDisplayCMS(), SWT.IMAGE_JPEG, true));
                while (removedImage.getChildrenCount() > 0)
                    pImage.addChild(removedImage.removeChild(0));
            } else
                pImage = new PSWTSectionBreak(canvas, slideImpl);
            images.add(pImage.getImage());
            setSizeAndPos(slideImpl, pImage.getBoundsReference());
            addChild(0, pImage);
            pImage.setPickable(true);
            pImage.lowerToBottom();
            caption.setText(slideImpl.getCaption());
        }

        private double setSizeAndPos(SlideImpl slide, PBounds bounds) {
            double scale = (slideSize / bounds.getWidth()) * slide.getDuration() / slideshow.getDuration();
            pImage.scale(scale);
            pImage.setOffset(0, slideSize - bounds.getHeight() * scale);
            return scale;
        }

        private void layout() {
            legend.setText(createLegendText(slide));
            totalWidth = (slide.getDuration() + slide.getFadeIn() + slide.getFadeOut()) * factor;
            PBounds bounds = pImage.getGlobalBounds();
            setSizeAndPos(slide, bounds);
            startLine.setPathToPolyline(new float[] { (float) -(slide.getFadeIn() * factor), 0f },
                    new float[] { slideSize, 0 });
            endLine.setPathToPolyline(new float[] { 0, (float) (slide.getFadeOut() * factor) },
                    new float[] { 0, slideSize });
            endLine.setOffset(bounds.width, 0);
            connectZoomPoints();
            caption.setOffset(seqNo.getWidth() + csize, slideSize + csize);
            legend.setOffset(csize, slideSize + 3 * csize);
            realignSlides();
            invalidatePaint();
            relocateAllHandles();
        }

        private String createLegendText(SlideImpl slide) {
            af.setMaximumFractionDigits(1);
            StringBuilder sb = new StringBuilder();
            sb.append(Messages.getString("SlideshowView.delay")).append(af.format(slide.getDelay() / 1000d)) //$NON-NLS-1$
                    .append(" sec").append('\n'); //$NON-NLS-1$
            sb.append(Messages.getString("SlideshowView.duration")) //$NON-NLS-1$
                    .append(af.format(slide.getDuration() / 1000d)).append(" sec") //$NON-NLS-1$
                    .append('\n');
            sb.append(Messages.getString("SlideshowView.fade_in")).append(af.format(slide.getFadeIn() / 1000d)) //$NON-NLS-1$
                    .append(" sec").append('\n'); //$NON-NLS-1$
            sb.append(Messages.getString("SlideshowView.fade_out")).append( //$NON-NLS-1$
                    af.format(slide.getFadeOut() / 1000d)).append(" sec").append('\n'); //$NON-NLS-1$
            sb.append(Messages.getString("SlideshowView.effect")) //$NON-NLS-1$
                    .append(SlideshowEditDialog.EFFECTS[slide.getEffect()]).append('\n');
            sb.append(slide.getZoom() > 0 ? NLS.bind(Messages.getString("SlideshowView.zoom_legend"), //$NON-NLS-1$
                    new Object[] { slide.getZoom(), slide.getZoomX(), slide.getZoomY() })
                    : Messages.getString("SlideshowView.no_zoom")); //$NON-NLS-1$
            return sb.toString();
        }

        void relocateAllHandles() {
            startPoint.relocateHandle();
            delayPoint.relocateHandle();
            midPoint.relocateHandle();
            zoomPoint.relocateHandle();
            zoomDirPoint.relocateHandle();
            endPoint.relocateHandle();
            connectZoomPoints();
        }

        public void processTextEvent(TextField focus) {
            String text = focus.getText();
            String capt = slide.getCaption();
            if (capt == null || !capt.equals(text))
                performOperation(new EditCaptionOperation(slide, text, focus));
        }

        public void setSequenceNo(int i) {
            slide.setSequenceNo(i);
            seqNo.setText(createSequenceNo(slide));
            caption.setOffset(seqNo.getWidth() + csize, caption.getOffset().getY());
        }

        private String createSequenceNo(Slide s) {
            return s.getSequenceNo() + " - "; //$NON-NLS-1$
        }

        public void setPenColor(Color color) {
            seqNo.setPenColor(color);
            legend.setPenColor(color);
        }

        public void setTitleColor(Color color) {
            caption.setPenColor(color);
            caption.setSelectedBgColor(selectionBackgroundColor);
        }

        public String getAssetId() {
            return slide.getAsset();
        }
    }

    public static final String SLIDES_PERSPECTIVE = "com.bdaum.zoom.PresentationPerspective"; //$NON-NLS-1$
    public static final String ID = "com.bdaum.zoom.ui.SlideshowView"; //$NON-NLS-1$
    private static final String LAST_SLIDESHOW = "com.bdaum.zoom.lastSlideshow"; //$NON-NLS-1$
    protected static final Color DURATIONHANDLECOLOR = new Color(224, 224, 0);
    protected static final Color DELAYHANDLECOLOR = new Color(224, 224, 224);
    protected static final Color FADEINHANDLECOLOR = new Color(0, 224, 0);
    protected static final Color FADEOUTHANDLECOLOR = new Color(224, 0, 0);
    protected static final Color FADEOUTSTROKCOLOR = new Color(192, 0, 0);
    protected static final Color FADEINSTROKECOLOR = new Color(0, 192, 0);
    protected static final Color ZOOMHANDLECOLOR = new Color(0, 168, 224);

    protected static final Font TIMEBARFONT = new Font("Arial", Font.PLAIN, 9); //$NON-NLS-1$
    protected static final Font LEGENDFONT = TIMEBARFONT;
    protected static final Font SEQNOFONT = new Font("Arial", Font.BOLD, 9); //$NON-NLS-1$
    public static final Font CAPTIONFONT = SEQNOFONT;

    final static NumberFormat af = (NumberFormat.getNumberInstance());

    private SlideShowImpl slideshow;
    private Map<String, PSlide> slideMap = new HashMap<>(521);
    private int slideSize = 200;
    private int defaultDuration = 7000;
    private double factor = (double) slideSize / defaultDuration;
    private PSWTPath timeBar, timeCursor;
    private PContainer slideBar;
    private IAction playAction, setTimerCursorAction, createBreakAction, gotoSectionAction, gotoTimeCursorAction;
    private int sectionCount;

    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        if (memento != null) {
            SlideShowImpl obj = Core.getCore().getDbManager().obtainById(SlideShowImpl.class,
                    memento.getString(LAST_SLIDESHOW));
            if (obj != null)
                factor = ((double) slideSize) / (defaultDuration = obj.getDuration());
        }
    }

    @Override
    public void saveState(IMemento memento) {
        if (memento != null && slideshow != null)
            memento.putString(LAST_SLIDESHOW, slideshow.getStringId());
        super.saveState(memento);
    }

    @Override
    protected String createHoverText(PNode node) {
        while (node != null && !(node instanceof PSlide))
            node = node.getParent();
        if (node instanceof PSlide)
            return UiActivator.getDefault().getHoverManager().getHoverText(
                    "com.bdaum.zoom.ui.hover.slideshow.slide", //$NON-NLS-1$
                    ((PSlide) node).slide, this);
        return ""; //$NON-NLS-1$
    }

    @Override
    protected String createHoverTitle(PNode node) {
        while (node != null && !(node instanceof PSlide))
            node = node.getParent();
        if (node instanceof PSlide)
            return UiActivator.getDefault().getHoverManager().getHoverTitle(
                    "com.bdaum.zoom.ui.hover.slideshow.slide", //$NON-NLS-1$
                    ((PSlide) node).slide, this);
        return null;
    }

    @Override
    public String getValue(String tv, Object object) {
        if (Constants.HV_TOTAL.equals(tv))
            return String.valueOf(slideBar.getChildrenCount());
        return null;
    }

    @Override
    public void createPartControl(Composite parent) {
        super.createPartControl(parent);
        canvas.setData(CSSProperties.ID, CSSProperties.SLIDESHOW);
        undoContext = new UndoContext() {
            @Override
            public String getLabel() {
                return Messages.getString("SlideshowView.slideshow_undo_context"); //$NON-NLS-1$
            }
        };
        slideBar = new PContainer(1000 * slideSize, slideSize);
        slideBar.setOffset(0d, slideSize / 2d);
        surface.addChild(slideBar);
        timeBar = PSWTPath.createRectangle(0, 0, 1000 * slideSize, 25);
        timeBar.setOffset(0d, 2.2d * slideSize);
        timeBar.setPickable(true);
        double tensec = slideSize * 10000d / defaultDuration;
        double off = tensec;
        double limit = 1000 * slideSize;
        int sec = 10;
        while (off < limit) {
            int minute = sec / 60;
            String secs = String.valueOf((sec % 60));
            if (secs.length() < 2)
                secs = "0" + secs; //$NON-NLS-1$
            String text = minute + "'" + secs + "\""; //$NON-NLS-1$ //$NON-NLS-2$
            GreekedPSWTText legend = new GreekedPSWTText(text, TIMEBARFONT);
            legend.setGreekThreshold(5);
            legend.setTransparent(true);
            legend.setOffset(off, 5);
            legend.setPickable(false);
            timeBar.addChild(legend);
            sec += 10;
            off += tensec;
        }
        timeCursor = PSWTPath.createRectangle(0, 1, 3, 23);
        timeCursor.setPickable(false);
        timeCursor.setStrokeColor(Color.RED);
        timeCursor.setPaint(Color.RED);
        timeBar.addChild(timeCursor);
        surface.addChild(timeBar);
        setColor(canvas);
        addWheelListener(0.2d, 3d);
        textEventHandler = new TextEventHandler(selectionBackgroundColor);
        PBasicInputEventHandler eventHandler = new PBasicInputEventHandler() {

            private Point2D mousePos;
            private boolean dragged;

            @Override
            public void keyPressed(PInputEvent event) {
                if (textEventHandler.hasFocus())
                    textEventHandler.keyPressed(event);
                else
                    pan(event);
            }

            @Override
            public void mouseMoved(PInputEvent event) {
                currentMousePosition = event.getPosition();
            }

            @Override
            public void mousePressed(PInputEvent event) {
                dragged = false;
                mousePos = event.getPosition();
                setPickedNode(event.getPickedNode());
            }

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

            @Override
            public void mouseReleased(PInputEvent event) {
                if (!dragged) {
                    positionX = null;
                    textEventHandler.mouseReleased(event);
                    PNode pickedNode = getPickedNode();
                    if (event.getClickCount() == 2) {
                        if (!(pickedNode.getParent() instanceof PSlide))
                            toggleTransform(event.getCamera());
                    } else if (event.getClickCount() == 1) {
                        if (event.isLeftMouseButton()) {
                            if (event.isControlDown() && pickedNode == surface)
                                propertiesAction.run();
                            else if (mousePos != null && pickedNode == timeBar) {
                                double x = event.getPosition().getX();
                                if (Math.abs(x - mousePos.getX()) < 5)
                                    timeCursor.setOffset(x, 0);
                            } else if (pickedNode instanceof GreekedPSWTText
                                    && editLegend((PSlide) pickedNode.getParent()))
                                canvas.repaint();
                        } else if (event.isRightMouseButton()) {
                            positionX = event.getPosition();
                            positionRelativeToCamera = event.getPositionRelativeTo(canvas.getCamera());
                        }
                    }
                }
                mousePos = null;
            }
        };
        canvas.getRoot().getDefaultInputManager().setKeyboardFocus(eventHandler);
        canvas.addInputEventListener(eventHandler);
        setPanAndZoomHandlers();
        // Create the help context id for the viewer's control
        PlatformUI.getWorkbench().getHelpSystem().setHelp(canvas, HelpContextIds.SLIDESHOW_VIEW);
        // Drop-Untersttzung
        addDropListener(canvas);
        // Hover
        installHoveringController();
        // Actions
        makeActions();
        installListeners();
        hookContextMenu();
        contributeToActionBars();
        if (slideshow != null)
            setInput(slideshow);
        addCatalogListener();
        setDecorator(canvas, new SlideShowDecorateJob());
        updateActions(false);
    }

    private void setPanAndZoomHandlers() {
        setPanAndZoomHandlers(-4, -14);
    }

    protected boolean editLegend(PSlide pslide) {
        if (pslide != null) {
            SlideImpl slide = pslide.slide;
            SlideImpl backup = cloneSlide(slide);
            EditSlideDialog dialog = new EditSlideDialog(getSite().getShell(), slide, getSlideShow());
            if (dialog.open() == Window.OK && performOperation(
                    new EditSlideOperation(pslide, slide, backup, EditSlideOperation.EDIT_SLIDE)).isOK()) {
                pslide.invalidatePaint();
                pslide.relocateAllHandles();
                pslide.updateHandles();
                return true;
            }
        }
        return false;
    }

    protected void selectSlide(PSlide pslide, int modifiers, int clicks) {
        if ((modifiers & InputEvent.CTRL_MASK) != 0) {
            pslide.setSelected(!pslide.isSelected());
            if (pslide.isSelected())
                recentSelection = pslide;
            else
                updateSelectionMarkers();
        } else if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
            List<String> entries = slideshow.getEntry();
            int index = pslide.slide.getSequenceNo() - 1;
            if (pslide.isSelected()) {
                int lastIndex = -1;
                for (int i = 0; i < entries.size(); i++)
                    if (slideMap.get(entries.get(i)) == lastSelection) {
                        lastIndex = i;
                        break;
                    }
                if (lastIndex < 0) {
                    int lower = 0;
                    int upper = entries.size() - 1;
                    for (int i = index - 1; i >= 0; i--) {
                        if (!slideMap.get(entries.get(i)).isSelected())
                            break;
                        lower = i;
                    }
                    for (int i = index + 1; i < entries.size(); i++) {
                        if (!slideMap.get(entries.get(i)).isSelected())
                            break;
                        upper = i;
                    }
                    if (index - lower < upper - index)
                        lastIndex = lower;
                    else
                        lastIndex = upper;
                }
                if (lastIndex < index)
                    for (int i = index - 1; i >= lastIndex; i--)
                        slideMap.get(entries.get(i)).setSelected(false);
                else
                    for (int i = index + 1; i <= lastIndex; i++)
                        slideMap.get(entries.get(i)).setSelected(false);
            } else {
                recentSelection = pslide;
                for (int i = index - 1; i >= 0; i--)
                    if (slideMap.get(entries.get(i)).isSelected()) {
                        for (int j = i + 1; j <= index; j++)
                            slideMap.get(entries.get(j)).setSelected(true);
                        lastSelection = pslide;
                        return;
                    }
                for (int i = index + 1; i < entries.size(); i++)
                    if (slideMap.get(entries.get(i)).isSelected()) {
                        for (int j = i - 1; j >= index; j--)
                            slideMap.get(entries.get(j)).setSelected(true);
                    }
            }
            lastSelection = pslide;
        } else {
            clearSelection();
            recentSelection = pslide;
            if (clicks == 2 && sectionCount > 0) {
                List<String> entries = slideshow.getEntry();
                int index = pslide.slide.getSequenceNo() - 1;
                if (pslide.slide.getAsset() == null) {
                    pslide.setSelected(true);
                    for (int i = index + 1; i < entries.size(); i++) {
                        PSlide ps = slideMap.get(entries.get(i));
                        if (ps.slide.getAsset() == null)
                            break;
                        ps.setSelected(true);
                    }
                } else {
                    for (int i = index; i >= 0; i--) {
                        PSlide ps = slideMap.get(entries.get(i));
                        ps.setSelected(true);
                        if (ps.slide.getAsset() == null)
                            break;
                    }
                    for (int i = index + 1; i < entries.size(); i++) {
                        PSlide ps = slideMap.get(entries.get(i));
                        if (ps.slide.getAsset() == null)
                            break;
                        ps.setSelected(true);
                    }
                }
            } else
                pslide.setSelected(true);
        }
    }

    private void clearSelection() {
        for (ListIterator<?> childrenIterator = slideBar.getChildrenIterator(); childrenIterator.hasNext();)
            ((PSlide) childrenIterator.next()).setSelected(false);
        lastSelection = recentSelection = null;
    }

    public void updateSelectionMarkers() {
        for (ListIterator<?> childrenIterator = slideBar.getChildrenIterator(); childrenIterator.hasNext();)
            if (((PSlide) childrenIterator.next()).isSelected())
                return;
        lastSelection = recentSelection = null;
    }

    protected List<PSlide> getSelectedSlides(PSlide picked, boolean multiple) {
        boolean hit = false;
        List<PSlide> slides = new ArrayList<>();
        if (slideshow != null) {
            if (multiple)
                for (String id : slideshow.getEntry()) {
                    PSlide pSlide = slideMap.get(id);
                    if (pSlide.isSelected()) {
                        slides.add(pSlide);
                        hit |= picked == pSlide;
                    }
                }
            if (!hit) {
                slides.clear();
                slides.add(picked);
                selectSlide(picked, 0, 1);
            }
        }
        return slides;
    }

    @Override
    protected void updatePresentation(Collection<? extends Asset> assets) {
        Map<String, Asset> amap = new HashMap<String, Asset>(assets.size());
        for (Asset a : assets)
            amap.put(a.getStringId(), a);
        for (ListIterator<?> it = slideBar.getChildrenIterator(); it.hasNext();) {
            PSlide pslide = (PSlide) it.next();
            Asset asset = amap.get(pslide.getAssetId());
            if (asset != null) {
                pslide.update(pslide.slide, asset);
                break;
            }
        }
    }

    protected PSlide makeSlide(SlideImpl slide) {
        PSlide pslide = new PSlide(canvas, slide);
        pslide.setPenColor(foregroundColor);
        pslide.setTitleColor(titleForegroundColor);
        slideBar.addChild(pslide);
        slideMap.put(slide.getStringId(), pslide);
        return pslide;
    }

    @Override
    protected void deletePresentationItem(IPresentationItem picked, boolean cut, boolean multiple) {
        if (getSlideShow() != null)
            performOperation(new DeleteSlideOperation(getSelectedSlides((PSlide) picked, multiple), cut));
    }

    @Override
    protected void pastePresentationItems() {
        if (getSlideShow() != null)
            performOperation(new PasteSlidesOperation(positionX));
    }

    protected void updateSequenceNumbers() {
        int i = 1;
        for (String id : slideshow.getEntry()) {
            PSlide pslide = slideMap.get(id);
            if (pslide.slide.getSequenceNo() != i)
                pslide.setSequenceNo(i);
            ++i;
        }
    }

    protected void moveSlide(PSlide moved, Point2D newOffset) {
        performOperation(new MoveSlideOperation(moved, newOffset));
    }

    private void realignSlides() {
        int pos = 0;
        for (String id : slideshow.getEntry()) {
            PSlide pslide = slideMap.get(id);
            Slide slide = pslide.slide;
            pos += slide.getDelay();
            double x = pos * factor;
            if (pslide.getOffset().getX() != x)
                pslide.setOffset(x, 0);
            pos += slide.getDuration();
        }
    }

    protected SlideShowImpl getSlideShow() {
        if (slideshow == null && !dbIsReadonly()) {
            SlideshowEditDialog dialog = new SlideshowEditDialog(getSite().getShell(), null, null,
                    Messages.getString("SlideshowView.create_slideshow"), false, false); //$NON-NLS-1$
            if (dialog.open() == Window.OK)
                slideshow = dialog.getResult();
        }
        return slideshow;
    }

    @Override
    public void updateActions(boolean force) {
        if (isVisible() || force) {
            if (playAction != null) {
                super.updateActions(force);
                playAction.setEnabled(slideBar.getChildrenCount() > 0);
                gotoSectionAction.setEnabled(sectionCount > 0);
            }
        }
    }

    @Override
    protected void fillLocalToolBar(IToolBarManager manager) {
        super.fillLocalToolBar(manager);
        manager.add(playAction);
    }

    @Override
    protected void fillLocalPullDown(IMenuManager manager) {
        manager.add(playAction);
        super.fillLocalPullDown(manager);
    }

    @Override
    protected void fillContextMenu(IMenuManager manager) {
        updateActions(true);
        boolean writable = !dbIsReadonly();
        Object item = getAdapter(IPresentationItem.class);
        PSlide pslide = null;
        if (item instanceof PSlide)
            pslide = (PSlide) item;
        if (writable) {
            manager.add(cutAction);
            manager.add(pasteAction);
            manager.add(new Separator());
        }
        manager.add(setTimerCursorAction);
        if (writable)
            manager.add(createBreakAction);
        manager.add(new Separator());
        manager.add(gotoExhibitAction);
        if (gotoSectionAction.isEnabled())
            manager.add(gotoSectionAction);
        manager.add(gotoTimeCursorAction);
        if (gotoLastSelectedAction.isEnabled())
            manager.add(gotoLastSelectedAction);
        if (pslide != null)
            addCommonContextActions(manager);
        super.fillContextMenu(manager);
    }

    @Override
    protected void makeActions() {
        super.makeActions();
        playAction = new Action(Messages.getString("SlideshowView.play"), Icons.play.getDescriptor()) { //$NON-NLS-1$
            @Override
            public void run() {
                SlideShowPlayer player = new SlideShowPlayer();
                player.init(getSite().getWorkbenchWindow(), slideshow, getSlides(false), false);
                player.open((int) (timeCursor.getOffset().getX() / slideSize * defaultDuration));
            }
        };
        playAction.setToolTipText(Messages.getString("SlideshowView.play_slideshow")); //$NON-NLS-1$
        propertiesAction = new Action(Messages.getString("SlideshowView.properties"), //$NON-NLS-1$
                Icons.slideshow.getDescriptor()) {
            @Override
            public void run() {
                SlideShowImpl backup = SlideshowPropertiesOperation.cloneSlideshow(slideshow);
                SlideshowEditDialog dialog = new SlideshowEditDialog(getSite().getShell(), null, slideshow,
                        slideshow.getName(), false, true);
                if (dialog.open() == Window.OK) {
                    slideshow = dialog.getResult();
                    performOperation(new SlideshowPropertiesOperation(backup, slideshow));
                    setInput(slideshow);
                }
            }
        };
        propertiesAction.setToolTipText(Messages.getString("SlideshowView.edit_slideshow_properties")); //$NON-NLS-1$
        setTimerCursorAction = new Action(Messages.getString("SlideshowView.set_time_cursor")) { //$NON-NLS-1$
            @Override
            public void run() {
                performOperation(new SetTimeCursorOperation(positionX));
            }
        };
        setTimerCursorAction.setToolTipText(Messages.getString("SlideshowView.set_time_cursor_tooltip")); //$NON-NLS-1$
        gotoExhibitAction = new Action(Messages.getString("SlideshowView.goto_slide")) { //$NON-NLS-1$
            @Override
            public void run() {
                gotoSlide(false);
            }

        };
        gotoExhibitAction.setToolTipText(Messages.getString("SlideshowView.goto_slide_tooltip")); //$NON-NLS-1$
        gotoSectionAction = new Action(Messages.getString("SlideshowView.goto_section")) { //$NON-NLS-1$
            @Override
            public void run() {
                gotoSlide(true);
            }
        };
        gotoSectionAction.setToolTipText(Messages.getString("SlideshowView.goto_section_tooltip")); //$NON-NLS-1$
        gotoTimeCursorAction = new Action(Messages.getString("SlideshowView.goto_timemark")) { //$NON-NLS-1$
            @Override
            public void run() {
                if (slideshow != null) {
                    Point2D offset = timeCursor.getOffset();
                    PNode pslide = findSlide(slideshow, offset, null);
                    if (pslide instanceof PSlide)
                        revealSlide(((PSlide) pslide).slide);
                }
            }
        };
        gotoTimeCursorAction.setToolTipText(Messages.getString("SlideshowView.goto_timemark_tooltip")); //$NON-NLS-1$
        createBreakAction = new Action(Messages.getString("SlideshowView.create_section_break")) { //$NON-NLS-1$
            @Override
            public void run() {
                SectionBreakDialog dialog = new SectionBreakDialog(getSite().getShell(), null);
                if (dialog.open() == Window.OK)
                    performOperation(new CreateBreakOperation(dialog.getResult(), positionX));
            }
        };
        createBreakAction.setToolTipText(Messages.getString("SlideshowView.create_section_break_tooltip")); //$NON-NLS-1$
        exhibitPropertiesAction = new Action(Messages.getString("SlideshowView.slide_properties")) { //$NON-NLS-1$
            @Override
            public void run() {
                editLegend((PSlide) getAdapter(IPresentationItem.class));
            }
        };
        exhibitPropertiesAction.setToolTipText(Messages.getString("SlideshowView.properties_tooltip")); //$NON-NLS-1$
    }

    protected void gotoSlide(boolean sections) {
        SelectSlideDialog dialog = new SelectSlideDialog(getSite().getShell(), getSlides(sections), sections);
        dialog.create();
        org.eclipse.swt.graphics.Point pos = canvas.toDisplay((int) positionRelativeToCamera.getX(),
                (int) positionRelativeToCamera.getY());
        pos.x += 10;
        pos.y += 10;
        dialog.getShell().setLocation(pos);
        if (dialog.open() == Window.OK)
            revealSlide(dialog.getResult());
    }

    private void revealSlide(SlideImpl selectedSlide) {
        revealItem(slideMap.get(selectedSlide.getStringId()));
    }

    protected List<SlideImpl> getSlides(boolean sections) {
        List<SlideImpl> slides = new ArrayList<>(slideshow.getEntry().size());
        for (String id : slideshow.getEntry()) {
            SlideImpl slide = slideMap.get(id).slide;
            if (slide.getAsset() == null || !sections)
                slides.add(slide);
        }
        return slides;
    }

    public static SlideImpl cloneSlide(SlideImpl slide) {
        return new SlideImpl(slide.getCaption(), slide.getSequenceNo(), slide.getDescription(), slide.getLayout(),
                slide.getDelay(), slide.getFadeIn(), slide.getDuration(), slide.getFadeOut(), slide.getEffect(),
                slide.getZoom(), slide.getZoomX(), slide.getZoomY(), slide.getNoVoice(), slide.getSafety(),
                slide.getAsset());
    }

    @Override
    protected void setColor(Control control) {
        CssActivator.getDefault().applyExtendedStyle(control, this);
        Color newPaint = UiUtilities.getAwtBackground(control, null);
        surface.setPaint(newPaint);
        slideBar.setPaint(newPaint);
        timeBar.setPaint(newPaint);
        slideBar.setStrokeColor(selectionBackgroundColor);
        for (ListIterator<?> it = slideBar.getChildrenIterator(); it.hasNext();) {
            Object child = it.next();
            if (child instanceof PSWTText)
                ((PSWTText) child).setPenColor(selectionBackgroundColor);
            else if (child instanceof TextField)
                ((TextField) child).setPenColor(selectionBackgroundColor);
            else if (child instanceof PSlide) {
                ((PPresentationItem) child).setPenColor(foregroundColor);
                ((PPresentationItem) child).setTitleColor(titleForegroundColor);
            }
        }
        timeBar.setStrokeColor(selectionBackgroundColor);
        for (ListIterator<?> it = timeBar.getChildrenIterator(); it.hasNext();) {
            Object child = it.next();
            if (child instanceof PSWTText)
                ((PSWTText) child).setPenColor(selectionBackgroundColor);
        }
    }

    @Override
    public boolean selectionChanged() {
        return true;
    }

    @Override
    public void setInput(IdentifiableObject presentation) {
        super.setInput(presentation);
        sectionCount = 0;
        if (presentation instanceof SlideShowImpl) {
            this.slideshow = (SlideShowImpl) presentation;
            setPartName(slideshow.getName());
            beginTask(slideshow.getEntry().size());
            defaultDuration = slideshow.getDuration();
            factor = ((double) slideSize) / defaultDuration;
            List<String> ids = new ArrayList<String>(slideshow.getEntry().size());
            IDbManager dbManager = Core.getCore().getDbManager();
            for (String id : slideshow.getEntry()) {
                SlideImpl slide = dbManager.obtainById(SlideImpl.class, id);
                if (slide != null) {
                    if (!slideMap.containsKey(id)) {
                        ids.add(id);
                        makeSlide(slide).setSequenceNo(slideBar.getChildrenCount());
                        if (slide.getAsset() == null)
                            ++sectionCount;
                    }
                }
                progressBar.worked(1);
            }
            slideshow.setEntry(ids);
            realignSlides();
            updateSlidebarBounds();
            updateActions(false);
            updateStatusLine();
            endTask();
        } else
            setPartName(Messages.getString("SlideshowView.slide_show")); //$NON-NLS-1$
    }

    protected PNode findSlide(SlideShow show, Point2D loc, PSlide exclude) {
        PSlide found = null;
        double x = loc.getX();
        for (String id : show.getEntry()) {
            PSlide pslide = slideMap.get(id);
            if (pslide == exclude)
                continue;
            if (pslide.getOffset().getX() >= x)
                return found != null ? found : pslide.getParent();
            found = pslide;
        }
        return null;
    }

    protected PSlide removeSlide(SlideShow show, int seqNo) {
        String id = show.getEntry().remove(seqNo - 1);
        PPresentationItem pslide = slideMap.remove(id);
        return (PSlide) slideBar.removeChild(pslide);
    }

    protected void insertSlide(SlideShow show, PSlide pslide) {
        int seqNo = pslide.slide.getSequenceNo();
        slideBar.addChild(pslide);
        SlideImpl slide = pslide.slide;
        slideMap.put(slide.getStringId(), pslide);
        insertIntoShow(show, slide, seqNo - 1);
    }

    private static void insertIntoShow(SlideShow show, Slide slide, int index) {
        if (index >= show.getEntry().size())
            show.addEntry(slide.getStringId());
        else
            show.getEntry().add(index, slide.getStringId());
        slide.setSlideShow_entry_parent(show.getStringId());
    }

    private void updateSlidebarBounds() {
        double w = slideBar.getBoundsReference().getWidth();
        for (int i = 0; i < slideBar.getChildrenCount(); i++) {
            PPresentationItem pslide = (PPresentationItem) slideBar.getChild(i);
            double x2 = pslide.getOffset().getX() + pslide.getBoundsReference().getWidth();
            if (x2 > w)
                w = x2;
        }
        if (w > slideBar.getBoundsReference().getWidth())
            slideBar.setWidth(w + 2 * slideSize);
        updateSurfaceBounds(w, -1);
    }

    @Override
    protected void cleanUp() {
        slideMap.clear();
        try {
            slideBar.removeAllChildren();
        } catch (SWTException e) {
            // do nothing
        }
        super.cleanUp();
    }

    @Override
    protected void dropAssets(ISelection selection, org.eclipse.swt.graphics.Point point, Point2D position,
            boolean replace) {
        performOperation(new DropAssetOperation((AssetSelection) selection, position, replace));
        getSite().getPage().activate(this);
    }

    @Override
    protected PNode[] getWorkArea() {
        return new PNode[] { slideBar };
    }

    @Override
    protected int getPanDirection() {
        return GalleryPanEventHandler.HOR;
    }

    @Override
    protected int getHoriztontalMargins() {
        return slideSize;
    }

    public void removeSlides(List<SlideImpl> slideList) {
        if (getSlideShow() != null) {
            List<PSlide> pslides = new ArrayList<>(slideList.size());
            for (Slide slide : slideList) {
                PSlide pslide = slideMap.get(slide.getStringId());
                if (pslide != null)
                    pslides.add(pslide);
            }
            performOperation(new DeleteSlideOperation(pslides, false));
        }
    }

    @Override
    public Object getContent() {
        return slideshow;
    }

    @Override
    protected AbstractHandleDragSequenceEventHandler createDragSequenceEventHandler(PNode node, boolean relative,
            boolean restricted, PNode presentationObject) {
        return new AbstractPresentationView.AbstractHandleDragSequenceEventHandler(node, relative, restricted,
                presentationObject) {

            @Override
            protected void moveToFront(PNode object) {
                updateActions(false);
            }

            @Override
            protected void drop(PInputEvent event, PNode pnode) {
                PSlide pslide = (PSlide) pnode.getParent();
                Point2D newOffset = pslide.getOffset();
                PBounds ibounds = pnode.getFullBoundsReference();
                Point2D point = new Point2D.Double(newOffset.getX() + ibounds.x + ibounds.width / 2,
                        newOffset.getY() + ibounds.y + ibounds.height / 2);
                double x = point.getX();
                double y = point.getY();
                pnode.localToGlobal(point);
                Point2D canvasPosition = event.getCanvasPosition();
                if (canvasPosition.getY() < 0)
                    performOperation(new DeleteSlideOperation(Collections.singletonList(pslide), false));
                else {
                    surface.parentToLocal(point);
                    if (slideBar.getBoundsReference().contains(x, y))
                        moveSlide(pslide, newOffset);
                    else
                        pslide.setOffset(oldOffset);
                }
            }

            @Override
            public Point getDragTolerance() {
                return DRAGTOLERANCE;
            }
        };
    }

    @Override
    protected PBounds getClientAreaReference() {
        return surface.getBoundsReference();
    }

    public static boolean accepts(Asset asset) {
        if (asset == null)
            return false;
        IMediaSupport mediaSupport = CoreActivator.getDefault().getMediaSupport(asset.getFormat());
        return mediaSupport == null || mediaSupport.testProperty(IMediaSupport.SLIDESHOW);
    }

    @Override
    protected void refreshAfterHistoryEvent(IUndoableOperation operation) {
        if (operation instanceof SlideshowPropertiesOperation)
            setInput(slideshow);
    }

    protected SlideImpl cloneSlide(Slide slide) {
        return new SlideImpl(slide.getCaption(), slide.getSequenceNo(), slide.getDescription(), slide.getLayout(),
                slide.getDelay(), slide.getFadeIn(), slide.getDuration(), slide.getFadeOut(), slide.getEffect(),
                slide.getZoom(), slide.getZoomX(), slide.getZoomY(), slide.getNoVoice(), slide.getSafety(),
                slide.getAsset());
    }

    @Override
    public String getId() {
        return ID;
    }

    @Override
    protected String getStatusMessage() {
        SlideShow show = getSlideShow();
        if (show != null)
            return NLS.bind(sectionCount == 1 ? Messages.getString("SlideshowView.n_slides_one_section") //$NON-NLS-1$
                    : Messages.getString("SlideshowView.n_slides_m_sections"), //$NON-NLS-1$
                    show.getEntry().size(), sectionCount);
        return null;
    }
}